Compare commits

..

14 Commits

Author SHA1 Message Date
Justus Lind
a9b35de7ee Muse Dash: Update song list to Rotaeno Update/7th Anniversary (#5066) 2025-07-12 09:02:49 -04:00
Scipio Wright
125d053b61 TUNIC: Fix missing line for UT stuff #5185 2025-07-12 13:52:02 +02:00
Scipio Wright
585cbf95a6 TUNIC: Add UT Support for Breakables (#5182) 2025-07-12 07:14:34 -04:00
Jérémie Bolduc
909565e5d9 Stardew Valley: Remove Rarecrow Locations from Night Market when Museumsanity is Disabled (#5146) 2025-07-12 07:12:04 -04:00
Exempt-Medic
a79423534c LADX: Update marin.txt (#5178) 2025-07-11 18:44:26 -04:00
NewSoupVi
7a6fb5e35b Revert "Core: Take Counter back out of RestrictedUnpickler" (#5184)
* Revert "Core: Take Counter back out of RestrictedUnpickler #5169"

This reverts commit 95e09c8e2a.

* Update Utils.py
2025-07-11 23:28:18 +02:00
Zach “Phar” Parks
6af34b66fb Various: Remove Rogue Legacy and Clique (#5177)
* Various: Remove Rogue Legacy and Clique

* Remove Clique from setup.py and revert network diagram.md change.

* Try again.

* Update network diagram.md

---------

Co-authored-by: Zach “Phar” Parks <phar@pharware.com>
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2025-07-11 19:34:46 +02:00
NewSoupVi
2974f7d11f Core: Replace Clique with V6 in unit tests (#5181)
* replace Clique with V6 in unit tests

* no hard mode in V6

* modify regex in copy_world to allow : str

* oops

* I see now

* work around all typing

* there actually needs to be something
2025-07-11 19:27:28 +02:00
Carter Hesterman
edc0c89753 CIV 6: Remove Erroneous Boost Prereqs for Computers Boost (#5134) 2025-07-10 09:10:56 -04:00
axe-y
b1ff55dd06 DLCQ: Fix/Refactor LFoD Start Inventory (#5176) 2025-07-10 08:33:52 -04:00
Remy Jette
f4b5422f66 Factorio: Fix link to world_gen documentation (#5171) 2025-07-07 22:57:55 +02:00
massimilianodelliubaldini
d4ebace99f [Jak and Daxter] Auto Detect Install Path after Game Launcher Update #5152 2025-07-07 19:15:37 +02:00
NewSoupVi
95e09c8e2a Core: Take Counter back out of RestrictedUnpickler #5169 2025-07-07 16:24:35 +02:00
Fabian Dill
4623d59206 Core: ensure slot_data and er_hint_info are only base data types (#5144)
---------

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
2025-07-07 15:51:39 +02:00
44 changed files with 552 additions and 1716 deletions

View File

@@ -98,7 +98,7 @@ jobs:
shell: bash
run: |
cd build/exe*
cp Players/Templates/Clique.yaml Players/
cp Players/Templates/VVVVVV.yaml Players/
timeout 30 ./ArchipelagoGenerate
- name: Store 7z
uses: actions/upload-artifact@v4
@@ -189,7 +189,7 @@ jobs:
shell: bash
run: |
cd build/exe*
cp Players/Templates/Clique.yaml Players/
cp Players/Templates/VVVVVV.yaml Players/
timeout 30 ./ArchipelagoGenerate
- name: Store AppImage
uses: actions/upload-artifact@v4

View File

@@ -12,6 +12,7 @@ import worlds
from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld
from Fill import FillError, balance_multiworld_progression, distribute_items_restrictive, flood_items, \
parse_planned_blocks, distribute_planned_blocks, resolve_early_locations_for_planned
from NetUtils import convert_to_base_types
from Options import StartInventoryPool
from Utils import __version__, output_path, version_tuple
from settings import get_settings
@@ -334,6 +335,9 @@ def main(args, seed=None, baked_server_options: dict[str, object] | None = None)
}
AutoWorld.call_all(multiworld, "modify_multidata", multidata)
for key in ("slot_data", "er_hint_data"):
multidata[key] = convert_to_base_types(multidata[key])
multidata = zlib.compress(pickle.dumps(multidata), 9)
with open(os.path.join(temp_dir, f'{outfilebase}.archipelago'), 'wb') as f:

View File

@@ -106,6 +106,27 @@ def _scan_for_TypedTuples(obj: typing.Any) -> typing.Any:
return obj
_base_types = str | int | bool | float | None | tuple["_base_types", ...] | dict["_base_types", "base_types"]
def convert_to_base_types(obj: typing.Any) -> _base_types:
if isinstance(obj, (tuple, list, set, frozenset)):
return tuple(convert_to_base_types(o) for o in obj)
elif isinstance(obj, dict):
return {convert_to_base_types(key): convert_to_base_types(value) for key, value in obj.items()}
elif obj is None or type(obj) in (str, int, float, bool):
return obj
# unwrap simple types to their base, such as StrEnum
elif isinstance(obj, str):
return str(obj)
elif isinstance(obj, int):
return int(obj)
elif isinstance(obj, float):
return float(obj)
else:
raise Exception(f"Cannot handle {type(obj)}")
_encode = JSONEncoder(
ensure_ascii=False,
check_circular=False,

View File

@@ -14,7 +14,6 @@ Currently, the following games are supported:
* Super Metroid
* Secret of Evermore
* Final Fantasy
* Rogue Legacy
* VVVVVV
* Raft
* Super Mario 64
@@ -41,7 +40,6 @@ Currently, the following games are supported:
* The Messenger
* Kingdom Hearts 2
* The Legend of Zelda: Link's Awakening DX
* Clique
* Adventure
* DLC Quest
* Noita

View File

@@ -442,6 +442,7 @@ class RestrictedUnpickler(pickle.Unpickler):
if module == "builtins" and name in safe_builtins:
return getattr(builtins, name)
# used by OptionCounter
# necessary because the actual Options class instances are pickled when transfered to WebHost generation pool
if module == "collections" and name == "Counter":
return collections.Counter
# used by MultiServer -> savegame/multidata

View File

@@ -48,9 +48,6 @@
# Civilization VI
/worlds/civ6/ @hesto2
# Clique
/worlds/clique/ @ThePhar
# Dark Souls III
/worlds/dark_souls_3/ @Marechal-L @nex3
@@ -148,9 +145,6 @@
# Raft
/worlds/raft/ @SunnyBat
# Rogue Legacy
/worlds/rogue_legacy/ @ThePhar
# Risk of Rain 2
/worlds/ror2/ @kindasneaki

View File

@@ -125,10 +125,8 @@ flowchart LR
NM[Mod with Archipelago.MultiClient.Net]
subgraph FNA/XNA
TS[Timespinner]
RL[Rogue Legacy]
end
NM <-- TsRandomizer --> TS
NM <-- RogueLegacyRandomizer --> RL
subgraph Unity
ROR[Risk of Rain 2]
SN[Subnautica]
@@ -177,4 +175,4 @@ flowchart LR
FMOD <--> FMAPI
end
CC <-- Integrated --> FC
```
```

View File

@@ -63,7 +63,6 @@ non_apworlds: set[str] = {
"Adventure",
"ArchipIDLE",
"Archipelago",
"Clique",
"Lufia II Ancient Cave",
"Meritous",
"Ocarina of Time",

View File

@@ -1,7 +1,7 @@
import unittest
from Fill import distribute_items_restrictive
from NetUtils import encode
from NetUtils import convert_to_base_types
from worlds.AutoWorld import AutoWorldRegister, call_all
from worlds import failed_world_loads
from . import setup_solo_multiworld
@@ -47,7 +47,7 @@ class TestImplemented(unittest.TestCase):
call_all(multiworld, "post_fill")
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.")
convert_to_base_types(data) # only put base data types into slot data
def test_no_failed_world_loads(self):
if failed_world_loads:

View File

@@ -63,12 +63,12 @@ if __name__ == "__main__":
spacer = '=' * 80
with TemporaryDirectory() as tempdir:
multis = [["Clique"], ["Temp World"], ["Clique", "Temp World"]]
multis = [["VVVVVV"], ["Temp World"], ["VVVVVV", "Temp World"]]
p1_games = []
data_paths = []
rooms = []
copy_world("Clique", "Temp World")
copy_world("VVVVVV", "Temp World")
try:
for n, games in enumerate(multis, 1):
print(f"Generating [{n}] {', '.join(games)}")
@@ -101,7 +101,7 @@ if __name__ == "__main__":
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
if collected_items < 2: # Don't collect anything on the last iteration
client.collect_any()
# TODO: Ctrl+C test here as well
@@ -125,7 +125,7 @@ if __name__ == "__main__":
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
if collected_items < 2: # Don't collect anything on the last iteration
client.collect_any()
if collected_items == 1:
sleep(1) # wait for the server to collect the item

View File

@@ -34,7 +34,7 @@ def _generate_local_inner(games: Iterable[str],
f.write(json.dumps({
"name": f"Player{n}",
"game": game,
game: {"hard_mode": "true"},
game: {},
"description": f"generate_local slot {n} ('Player{n}'): {game}",
}))

View File

@@ -30,7 +30,7 @@ def copy(src: str, dst: str) -> None:
_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)
contents = re.sub(r'game\s*(:\s*[a-zA-Z\[\]]+)?\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)

View File

@@ -295,7 +295,7 @@ class World(metaclass=AutoWorldRegister):
future. Protocol level compatibility check moved to MultiServer.min_client_version.
"""
required_server_version: Tuple[int, int, int] = (0, 6, 2)
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()

View File

@@ -78,8 +78,8 @@ boosts: List[CivVIBoostData] = [
CivVIBoostData(
"BOOST_TECH_IRON_WORKING",
"ERA_CLASSICAL",
["TECH_MINING"],
1,
["TECH_MINING", "TECH_BRONZE_WORKING"],
2,
"DEFAULT",
),
CivVIBoostData(
@@ -165,15 +165,9 @@ boosts: List[CivVIBoostData] = [
"BOOST_TECH_CASTLES",
"ERA_MEDIEVAL",
[
"CIVIC_DIVINE_RIGHT",
"CIVIC_EXPLORATION",
"CIVIC_REFORMED_CHURCH",
"CIVIC_SUFFRAGE",
"CIVIC_TOTALITARIANISM",
"CIVIC_CLASS_STRUGGLE",
"CIVIC_DIGITAL_DEMOCRACY",
"CIVIC_CORPORATE_LIBERTARIANISM",
"CIVIC_SYNTHETIC_TECHNOCRACY",
],
1,
"DEFAULT",
@@ -393,9 +387,6 @@ boosts: List[CivVIBoostData] = [
"CIVIC_SUFFRAGE",
"CIVIC_TOTALITARIANISM",
"CIVIC_CLASS_STRUGGLE",
"CIVIC_DIGITAL_DEMOCRACY",
"CIVIC_CORPORATE_LIBERTARIANISM",
"CIVIC_SYNTHETIC_TECHNOCRACY",
],
1,
"DEFAULT",

View File

@@ -1,38 +0,0 @@
from typing import Callable, Dict, NamedTuple, Optional, TYPE_CHECKING
from BaseClasses import Item, ItemClassification
if TYPE_CHECKING:
from . import CliqueWorld
class CliqueItem(Item):
game = "Clique"
class CliqueItemData(NamedTuple):
code: Optional[int] = None
type: ItemClassification = ItemClassification.filler
can_create: Callable[["CliqueWorld"], bool] = lambda world: True
item_data_table: Dict[str, CliqueItemData] = {
"Feeling of Satisfaction": CliqueItemData(
code=69696969,
type=ItemClassification.progression,
),
"Button Activation": CliqueItemData(
code=69696968,
type=ItemClassification.progression,
can_create=lambda world: world.options.hard_mode,
),
"A Cool Filler Item (No Satisfaction Guaranteed)": CliqueItemData(
code=69696967,
can_create=lambda world: False # Only created from `get_filler_item_name`.
),
"The Urge to Push": CliqueItemData(
type=ItemClassification.progression,
),
}
item_table = {name: data.code for name, data in item_data_table.items() if data.code is not None}

View File

@@ -1,37 +0,0 @@
from typing import Callable, Dict, NamedTuple, Optional, TYPE_CHECKING
from BaseClasses import Location
if TYPE_CHECKING:
from . import CliqueWorld
class CliqueLocation(Location):
game = "Clique"
class CliqueLocationData(NamedTuple):
region: str
address: Optional[int] = None
can_create: Callable[["CliqueWorld"], bool] = lambda world: True
locked_item: Optional[str] = None
location_data_table: Dict[str, CliqueLocationData] = {
"The Big Red Button": CliqueLocationData(
region="The Button Realm",
address=69696969,
),
"The Item on the Desk": CliqueLocationData(
region="The Button Realm",
address=69696968,
can_create=lambda world: world.options.hard_mode,
),
"In the Player's Mind": CliqueLocationData(
region="The Button Realm",
locked_item="The Urge to Push",
),
}
location_table = {name: data.address for name, data in location_data_table.items() if data.address is not None}
locked_locations = {name: data for name, data in location_data_table.items() if data.locked_item}

View File

@@ -1,34 +0,0 @@
from dataclasses import dataclass
from Options import Choice, Toggle, PerGameCommonOptions, StartInventoryPool
class HardMode(Toggle):
"""Only for the most masochistically inclined... Requires button activation!"""
display_name = "Hard Mode"
class ButtonColor(Choice):
"""Customize your button! Now available in 12 unique colors."""
display_name = "Button Color"
option_red = 0
option_orange = 1
option_yellow = 2
option_green = 3
option_cyan = 4
option_blue = 5
option_magenta = 6
option_purple = 7
option_pink = 8
option_brown = 9
option_white = 10
option_black = 11
@dataclass
class CliqueOptions(PerGameCommonOptions):
color: ButtonColor
hard_mode: HardMode
start_inventory_from_pool: StartInventoryPool
# DeathLink is always on. Always.
# death_link: DeathLink

View File

@@ -1,11 +0,0 @@
from typing import Dict, List, NamedTuple
class CliqueRegionData(NamedTuple):
connecting_regions: List[str] = []
region_data_table: Dict[str, CliqueRegionData] = {
"Menu": CliqueRegionData(["The Button Realm"]),
"The Button Realm": CliqueRegionData(),
}

View File

@@ -1,13 +0,0 @@
from typing import Callable, TYPE_CHECKING
from BaseClasses import CollectionState
if TYPE_CHECKING:
from . import CliqueWorld
def get_button_rule(world: "CliqueWorld") -> Callable[[CollectionState], bool]:
if world.options.hard_mode:
return lambda state: state.has("Button Activation", world.player)
return lambda state: True

View File

@@ -1,102 +0,0 @@
from typing import List, Dict, Any
from BaseClasses import Region, Tutorial
from worlds.AutoWorld import WebWorld, World
from .Items import CliqueItem, item_data_table, item_table
from .Locations import CliqueLocation, location_data_table, location_table, locked_locations
from .Options import CliqueOptions
from .Regions import region_data_table
from .Rules import get_button_rule
class CliqueWebWorld(WebWorld):
theme = "partyTime"
setup_en = Tutorial(
tutorial_name="Start Guide",
description="A guide to playing Clique.",
language="English",
file_name="guide_en.md",
link="guide/en",
authors=["Phar"]
)
setup_de = Tutorial(
tutorial_name="Anleitung zum Anfangen",
description="Eine Anleitung um Clique zu spielen.",
language="Deutsch",
file_name="guide_de.md",
link="guide/de",
authors=["Held_der_Zeit"]
)
tutorials = [setup_en, setup_de]
game_info_languages = ["en", "de"]
class CliqueWorld(World):
"""The greatest game of all time."""
game = "Clique"
web = CliqueWebWorld()
options: CliqueOptions
options_dataclass = CliqueOptions
location_name_to_id = location_table
item_name_to_id = item_table
def create_item(self, name: str) -> CliqueItem:
return CliqueItem(name, item_data_table[name].type, item_data_table[name].code, self.player)
def create_items(self) -> None:
item_pool: List[CliqueItem] = []
for name, item in item_data_table.items():
if item.code and item.can_create(self):
item_pool.append(self.create_item(name))
self.multiworld.itempool += item_pool
def create_regions(self) -> None:
# Create regions.
for region_name in region_data_table.keys():
region = Region(region_name, self.player, self.multiworld)
self.multiworld.regions.append(region)
# Create locations.
for region_name, region_data in region_data_table.items():
region = self.get_region(region_name)
region.add_locations({
location_name: location_data.address for location_name, location_data in location_data_table.items()
if location_data.region == region_name and location_data.can_create(self)
}, CliqueLocation)
region.add_exits(region_data_table[region_name].connecting_regions)
# Place locked locations.
for location_name, location_data in locked_locations.items():
# Ignore locations we never created.
if not location_data.can_create(self):
continue
locked_item = self.create_item(location_data_table[location_name].locked_item)
self.get_location(location_name).place_locked_item(locked_item)
# Set priority location for the Big Red Button!
self.options.priority_locations.value.add("The Big Red Button")
def get_filler_item_name(self) -> str:
return "A Cool Filler Item (No Satisfaction Guaranteed)"
def set_rules(self) -> None:
button_rule = get_button_rule(self)
self.get_location("The Big Red Button").access_rule = button_rule
self.get_location("In the Player's Mind").access_rule = button_rule
# Do not allow button activations on buttons.
self.get_location("The Big Red Button").item_rule = lambda item: item.name != "Button Activation"
# Completion condition.
self.multiworld.completion_condition[self.player] = lambda state: state.has("The Urge to Push", self.player)
def fill_slot_data(self) -> Dict[str, Any]:
return {
"color": self.options.color.current_key
}

View File

@@ -1,18 +0,0 @@
# Clique
## Was ist das für ein Spiel?
~~Clique ist ein psychologisches Überlebens-Horror Spiel, in dem der Spieler der Versuchung wiederstehen muss große~~
~~(rote) Knöpfe zu drücken.~~
Clique ist ein scherzhaftes Spiel, welches für Archipelago im März 2023 entwickelt wurde, um zu zeigen, wie einfach
es sein kann eine Welt für Archipelago zu entwicklen. Das Ziel des Spiels ist es den großen (standardmäßig) roten
Knopf zu drücken. Wenn ein Spieler auf dem `hard_mode` (schwieriger Modus) spielt, muss dieser warten bis jemand
anderes in der Multiworld den Knopf aktiviert, damit er gedrückt werden kann.
Clique kann auf den meisten modernen, HTML5-fähigen Browsern gespielt werden.
## Wo ist die Seite für die Einstellungen?
Die [Seite für die Spielereinstellungen dieses Spiels](../player-options) enthält alle Optionen die man benötigt um
eine YAML-Datei zu konfigurieren und zu exportieren.

View File

@@ -1,16 +0,0 @@
# Clique
## What is this game?
~~Clique is a psychological survival horror game where a player must survive the temptation to press red buttons.~~
Clique is a joke game developed for Archipelago in March 2023 to showcase how easy it can be to develop a world for
Archipelago. The objective of the game is to press the big red button. If a player is playing on `hard_mode`, they must
wait for someone else in the multiworld to "activate" their button before they can press it.
Clique can be played on most modern HTML5-capable browsers.
## 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.

View File

@@ -1,25 +0,0 @@
# Clique Anleitung
Nachdem dein Seed generiert wurde, gehe auf die Website von [Clique dem Spiel](http://clique.pharware.com/) und gib
Server-Daten, deinen Slot-Namen und ein Passwort (falls vorhanden) ein. Klicke dann auf "Connect" (Verbinden).
Wenn du auf "Einfach" spielst, kannst du unbedenklich den Knopf drücken und deine "Befriedigung" erhalten.
Wenn du auf "Schwer" spielst, ist es sehr wahrscheinlich, dass du warten musst bevor du dein Ziel erreichen kannst.
Glücklicherweise läuft Click auf den meißten großen Browsern, die HTML5 unterstützen. Das heißt du kannst Clique auf
deinem Handy starten und produktiv sein während du wartest!
Falls du einige Ideen brauchst was du tun kannst, während du wartest bis der Knopf aktiviert wurde, versuche
(mindestens) eins der Folgenden:
- Dein Zimmer aufräumen.
- Die Wäsche machen.
- Etwas Essen von einem X-Belieben Fast Food Restaruant holen.
- Das tägliche Wordle machen.
- ~~Deine Seele an **Phar** verkaufen.~~
- Deine Hausaufgaben erledigen.
- Deine Post abholen.
~~Solltest du auf irgendwelche Probleme in diesem Spiel stoßen, solltest du keinesfalls nicht **thephar** auf~~
~~Discord kontaktieren. *zwinker* *zwinker*~~

View File

@@ -1,22 +0,0 @@
# Clique Start Guide
After rolling your seed, go to the [Clique Game](http://clique.pharware.com/) site and enter the server details, your
slot name, and a room password if one is required. Then click "Connect".
If you're playing on "easy mode", just click the button and receive "Satisfaction".
If you're playing on "hard mode", you may need to wait for activation before you can complete your objective. Luckily,
Clique runs in most major browsers that support HTML5, so you can load Clique on your phone and be productive while
you wait!
If you need some ideas for what to do while waiting for button activation, give the following a try:
- Clean your room.
- Wash the dishes.
- Get some food from a non-descript fast food restaurant.
- Do the daily Wordle.
- ~~Sell your soul to Phar.~~
- Do your school work.
~~If you run into any issues with this game, definitely do not contact **thephar** on discord. *wink* *wink*~~

View File

@@ -30,7 +30,6 @@ class Group(enum.Enum):
Deprecated = enum.auto()
@dataclass(frozen=True)
class ItemData:
code_without_offset: offset
@@ -98,14 +97,15 @@ def create_trap_items(world, world_options: Options.DLCQuestOptions, trap_needed
return traps
def create_items(world, world_options: Options.DLCQuestOptions, locations_count: int, excluded_items: list[str], random: Random):
def create_items(world, world_options: Options.DLCQuestOptions, locations_count: int, excluded_items: list[str],
random: Random):
created_items = []
if world_options.campaign == Options.Campaign.option_basic or world_options.campaign == Options.Campaign.option_both:
create_items_basic(world_options, created_items, world, excluded_items)
create_items_campaign(world_options, created_items, world, excluded_items, Group.DLCQuest, 825, 250)
if (world_options.campaign == Options.Campaign.option_live_freemium_or_die or
world_options.campaign == Options.Campaign.option_both):
create_items_lfod(world_options, created_items, world, excluded_items)
create_items_campaign(world_options, created_items, world, excluded_items, Group.Freemium, 889, 200)
trap_items = create_trap_items(world, world_options, locations_count - len(created_items), random)
created_items += trap_items
@@ -113,27 +113,8 @@ def create_items(world, world_options: Options.DLCQuestOptions, locations_count:
return created_items
def create_items_lfod(world_options, created_items, world, excluded_items):
for item in items_by_group[Group.Freemium]:
if item.name in excluded_items:
excluded_items.remove(item)
continue
if item.has_any_group(Group.DLC):
created_items.append(world.create_item(item))
if item.has_any_group(Group.Item) and world_options.item_shuffle == Options.ItemShuffle.option_shuffled:
created_items.append(world.create_item(item))
if item.has_any_group(Group.Twice):
created_items.append(world.create_item(item))
if world_options.coinsanity == Options.CoinSanity.option_coin:
if world_options.coinbundlequantity == -1:
create_coin_piece(created_items, world, 889, 200, Group.Freemium)
return
create_coin(world_options, created_items, world, 889, 200, Group.Freemium)
def create_items_basic(world_options, created_items, world, excluded_items):
for item in items_by_group[Group.DLCQuest]:
def create_items_campaign(world_options: Options.DLCQuestOptions, created_items: list[DLCQuestItem], world, excluded_items: list[str], group: Group, total_coins: int, required_coins: int):
for item in items_by_group[group]:
if item.name in excluded_items:
excluded_items.remove(item.name)
continue
@@ -146,14 +127,15 @@ def create_items_basic(world_options, created_items, world, excluded_items):
created_items.append(world.create_item(item))
if world_options.coinsanity == Options.CoinSanity.option_coin:
if world_options.coinbundlequantity == -1:
create_coin_piece(created_items, world, 825, 250, Group.DLCQuest)
create_coin_piece(created_items, world, total_coins, required_coins, group)
return
create_coin(world_options, created_items, world, 825, 250, Group.DLCQuest)
create_coin(world_options, created_items, world, total_coins, required_coins, group)
def create_coin(world_options, created_items, world, total_coins, required_coins, group):
coin_bundle_required = math.ceil(required_coins / world_options.coinbundlequantity)
coin_bundle_useful = math.ceil((total_coins - coin_bundle_required * world_options.coinbundlequantity) / world_options.coinbundlequantity)
coin_bundle_useful = math.ceil(
(total_coins - coin_bundle_required * world_options.coinbundlequantity) / world_options.coinbundlequantity)
for item in items_by_group[group]:
if item.has_any_group(Group.Coin):
for i in range(coin_bundle_required):
@@ -165,7 +147,7 @@ def create_coin(world_options, created_items, world, total_coins, required_coins
def create_coin_piece(created_items, world, total_coins, required_coins, group):
for item in items_by_group[group]:
if item.has_any_group(Group.Piece):
for i in range(required_coins*10):
for i in range(required_coins * 10):
created_items.append(world.create_item(item))
for i in range((total_coins - required_coins) * 10):
created_items.append(world.create_item(item, ItemClassification.useful))

View File

@@ -321,7 +321,7 @@ class InventorySpillTrapCount(TrapCount):
class FactorioWorldGen(OptionDict):
"""World Generation settings. Overview of options at https://wiki.factorio.com/Map_generator,
with in-depth documentation at https://lua-api.factorio.com/latest/Concepts.html#MapGenSettings"""
with in-depth documentation at https://lua-api.factorio.com/latest/concepts/MapGenSettings.html"""
display_name = "World Generation"
# FIXME: do we want default be a rando-optimized default or in-game DS?
value: dict[str, dict[str, typing.Any]]

View File

@@ -367,7 +367,7 @@ def find_root_directory(ctx: JakAndDaxterContext):
f" Close all launchers, games, clients, and console windows, then restart Archipelago.")
if not os.path.exists(settings_path):
msg = (f"{err_title}: the OpenGOAL settings file does not exist.\n"
msg = (f"{err_title}: The OpenGOAL settings file does not exist.\n"
f"{alt_instructions}")
ctx.on_log_error(logger, msg)
return
@@ -375,14 +375,44 @@ def find_root_directory(ctx: JakAndDaxterContext):
with open(settings_path, "r") as f:
load = json.load(f)
jak1_installed = load["games"]["Jak 1"]["isInstalled"]
# This settings file has changed format once before, and may do so again in the future.
# Guard against future incompatibilities by checking the file version first, and use that to determine
# what JSON keys to look for next.
try:
settings_version = load["version"]
logger.debug(f"OpenGOAL settings file version: {settings_version}")
except KeyError:
msg = (f"{err_title}: The OpenGOAL settings file has no version number!\n"
f"{alt_instructions}")
ctx.on_log_error(logger, msg)
return
try:
if settings_version == "2.0":
jak1_installed = load["games"]["Jak 1"]["isInstalled"]
mod_sources = load["games"]["Jak 1"]["modsInstalledVersion"]
elif settings_version == "3.0":
jak1_installed = load["games"]["jak1"]["isInstalled"]
mod_sources = load["games"]["jak1"]["mods"]
else:
msg = (f"{err_title}: The OpenGOAL settings file has an unknown version number ({settings_version}).\n"
f"{alt_instructions}")
ctx.on_log_error(logger, msg)
return
except KeyError as e:
msg = (f"{err_title}: The OpenGOAL settings file does not contain key entry {e}!\n"
f"{alt_instructions}")
ctx.on_log_error(logger, msg)
return
if not jak1_installed:
msg = (f"{err_title}: The OpenGOAL Launcher is missing a normal install of Jak 1!\n"
f"{alt_instructions}")
ctx.on_log_error(logger, msg)
return
mod_sources = load["games"]["Jak 1"]["modsInstalledVersion"]
if mod_sources is None:
msg = (f"{err_title}: No mod sources have been configured in the OpenGOAL Launcher!\n"
f"{alt_instructions}")

View File

@@ -255,7 +255,6 @@ Try Bumper Stickers!
Try Castlevania 64!
Try Celeste 64!
Try ChecksFinder!
Try Clique!
Try Dark Souls III!
Try DLCQuest!
Try Donkey Kong Country 3!
@@ -268,6 +267,7 @@ Try A Hat in Time!
Try Heretic!
Try Hollow Knight!
Try Hylics 2!
Try Jak and Daxter: The Precursor Legacy!
Try Kingdom Hearts 2!
Try Kirby's Dream Land 3!
Try Landstalker - The Treasures of King Nole!
@@ -288,11 +288,10 @@ Try Pokemon Emerald!
Try Pokemon Red and Blue!
Try Raft!
Try Risk of Rain 2!
Try Rogue Legacy!
Try Secret of Evermore!
Try shapez!
Try Shivers!
Try A Short Hike!
Try Slay the Spire!
Try SMZ3!
Try Sonic Adventure 2 Battle!
Try Starcraft 2!
@@ -300,6 +299,7 @@ Try Stardew Valley!
Try Subnautica!
Try Sudoku!
Try Super Mario 64!
Try Super Mario Land 2: 6 Golden Coins!
Try Super Mario World!
Try Super Metroid!
Try Terraria!
@@ -312,7 +312,6 @@ Try The Witness!
Try Yoshi's Island!
Try Yu-Gi-Oh! 2006!
Try Zillion!
Try Zork Grand Inquisitor!
Try Old School Runescape!
Try Kingdom Hearts!
Try Mega Man 2!
@@ -369,7 +368,6 @@ Have they added Among Us to AP yet?
Every copy of LADX is personalized, David.
Looks like you're going on A Short Hike. Bring back feathers please?
Functioning Brain is at...\nWait. This isn't Witness. Wrong game, sorry.
Don't forget to check your Clique!\nIf, y'know, you have one. No pressure...
:3
Sorry ######, but your progression item is in another world.
&newgames\n&oldgames

View File

@@ -641,4 +641,17 @@ SONG_DATA: Dict[str, SongData] = {
"Save Yourself": SongData(2900765, "85-3", "Happy Otaku Pack Vol.20", True, 5, 7, 10),
"Menace": SongData(2900766, "85-4", "Happy Otaku Pack Vol.20", True, 7, 9, 11),
"Dangling": SongData(2900767, "85-5", "Happy Otaku Pack Vol.20", True, 6, 8, 10),
"Inverted World": SongData(2900768, "86-0", "Aquaria Cruising Guide", True, 4, 6, 8),
"Suito": SongData(2900769, "86-1", "Aquaria Cruising Guide", True, 6, 8, 11),
"The Promised Land": SongData(2900770, "86-2", "Aquaria Cruising Guide", True, 4, 6, 9),
"Alfheim's faith": SongData(2900771, "86-3", "Aquaria Cruising Guide", True, 6, 8, 11),
"Heaven's Cage": SongData(2900772, "86-4", "Aquaria Cruising Guide", True, 5, 7, 10),
"Broomstick adventure!": SongData(2900773, "86-5", "Aquaria Cruising Guide", True, 7, 9, 11),
"Strong Nurse Buro-chan!": SongData(2900774, "43-61", "MD Plus Project", True, 5, 7, 9),
"Cubism": SongData(2900775, "43-62", "MD Plus Project", False, 5, 7, 9),
"Cubibibibism": SongData(2900776, "43-63", "MD Plus Project", False, 6, 8, 10),
"LET'S TOAST!!": SongData(2900777, "43-64", "MD Plus Project", False, 6, 8, 10),
"#YamiKawa": SongData(2900778, "43-65", "MD Plus Project", False, 5, 7, 10),
"Rainy Step": SongData(2900779, "43-66", "MD Plus Project", False, 2, 5, 8),
"OHOSHIKATSU": SongData(2900780, "43-67", "MD Plus Project", False, 5, 7, 10),
}

View File

@@ -1,111 +0,0 @@
from typing import Dict, NamedTuple, Optional
from BaseClasses import Item, ItemClassification
class RLItem(Item):
game: str = "Rogue Legacy"
class RLItemData(NamedTuple):
category: str
code: Optional[int] = None
classification: ItemClassification = ItemClassification.filler
max_quantity: int = 1
weight: int = 1
def get_items_by_category(category: str) -> Dict[str, RLItemData]:
item_dict: Dict[str, RLItemData] = {}
for name, data in item_table.items():
if data.category == category:
item_dict.setdefault(name, data)
return item_dict
item_table: Dict[str, RLItemData] = {
# Vendors
"Blacksmith": RLItemData("Vendors", 90_000, ItemClassification.progression),
"Enchantress": RLItemData("Vendors", 90_001, ItemClassification.progression),
"Architect": RLItemData("Vendors", 90_002, ItemClassification.useful),
# Classes
"Progressive Knights": RLItemData("Classes", 90_003, ItemClassification.useful, 2),
"Progressive Mages": RLItemData("Classes", 90_004, ItemClassification.useful, 2),
"Progressive Barbarians": RLItemData("Classes", 90_005, ItemClassification.useful, 2),
"Progressive Knaves": RLItemData("Classes", 90_006, ItemClassification.useful, 2),
"Progressive Shinobis": RLItemData("Classes", 90_007, ItemClassification.useful, 2),
"Progressive Miners": RLItemData("Classes", 90_008, ItemClassification.useful, 2),
"Progressive Liches": RLItemData("Classes", 90_009, ItemClassification.useful, 2),
"Progressive Spellthieves": RLItemData("Classes", 90_010, ItemClassification.useful, 2),
"Dragons": RLItemData("Classes", 90_096, ItemClassification.progression),
"Traitors": RLItemData("Classes", 90_097, ItemClassification.useful),
# Skills
"Health Up": RLItemData("Skills", 90_013, ItemClassification.progression_skip_balancing, 15),
"Mana Up": RLItemData("Skills", 90_014, ItemClassification.progression_skip_balancing, 15),
"Attack Up": RLItemData("Skills", 90_015, ItemClassification.progression_skip_balancing, 15),
"Magic Damage Up": RLItemData("Skills", 90_016, ItemClassification.progression_skip_balancing, 15),
"Armor Up": RLItemData("Skills", 90_017, ItemClassification.useful, 15),
"Equip Up": RLItemData("Skills", 90_018, ItemClassification.useful, 5),
"Crit Chance Up": RLItemData("Skills", 90_019, ItemClassification.useful, 5),
"Crit Damage Up": RLItemData("Skills", 90_020, ItemClassification.useful, 5),
"Down Strike Up": RLItemData("Skills", 90_021),
"Gold Gain Up": RLItemData("Skills", 90_022),
"Potion Efficiency Up": RLItemData("Skills", 90_023),
"Invulnerability Time Up": RLItemData("Skills", 90_024),
"Mana Cost Down": RLItemData("Skills", 90_025),
"Death Defiance": RLItemData("Skills", 90_026, ItemClassification.useful),
"Haggling": RLItemData("Skills", 90_027, ItemClassification.useful),
"Randomize Children": RLItemData("Skills", 90_028, ItemClassification.useful),
# Blueprints
"Progressive Blueprints": RLItemData("Blueprints", 90_055, ItemClassification.useful, 15),
"Squire Blueprints": RLItemData("Blueprints", 90_040, ItemClassification.useful),
"Silver Blueprints": RLItemData("Blueprints", 90_041, ItemClassification.useful),
"Guardian Blueprints": RLItemData("Blueprints", 90_042, ItemClassification.useful),
"Imperial Blueprints": RLItemData("Blueprints", 90_043, ItemClassification.useful),
"Royal Blueprints": RLItemData("Blueprints", 90_044, ItemClassification.useful),
"Knight Blueprints": RLItemData("Blueprints", 90_045, ItemClassification.useful),
"Ranger Blueprints": RLItemData("Blueprints", 90_046, ItemClassification.useful),
"Sky Blueprints": RLItemData("Blueprints", 90_047, ItemClassification.useful),
"Dragon Blueprints": RLItemData("Blueprints", 90_048, ItemClassification.useful),
"Slayer Blueprints": RLItemData("Blueprints", 90_049, ItemClassification.useful),
"Blood Blueprints": RLItemData("Blueprints", 90_050, ItemClassification.useful),
"Sage Blueprints": RLItemData("Blueprints", 90_051, ItemClassification.useful),
"Retribution Blueprints": RLItemData("Blueprints", 90_052, ItemClassification.useful),
"Holy Blueprints": RLItemData("Blueprints", 90_053, ItemClassification.useful),
"Dark Blueprints": RLItemData("Blueprints", 90_054, ItemClassification.useful),
# Runes
"Vault Runes": RLItemData("Runes", 90_060, ItemClassification.progression),
"Sprint Runes": RLItemData("Runes", 90_061, ItemClassification.progression),
"Vampire Runes": RLItemData("Runes", 90_062, ItemClassification.useful),
"Sky Runes": RLItemData("Runes", 90_063, ItemClassification.progression),
"Siphon Runes": RLItemData("Runes", 90_064, ItemClassification.useful),
"Retaliation Runes": RLItemData("Runes", 90_065),
"Bounty Runes": RLItemData("Runes", 90_066),
"Haste Runes": RLItemData("Runes", 90_067),
"Curse Runes": RLItemData("Runes", 90_068),
"Grace Runes": RLItemData("Runes", 90_069),
"Balance Runes": RLItemData("Runes", 90_070, ItemClassification.useful),
# Junk
"Triple Stat Increase": RLItemData("Filler", 90_030, weight=6),
"1000 Gold": RLItemData("Filler", 90_031, weight=3),
"3000 Gold": RLItemData("Filler", 90_032, weight=2),
"5000 Gold": RLItemData("Filler", 90_033, weight=1),
}
event_item_table: Dict[str, RLItemData] = {
"Defeat Khidr": RLItemData("Event", classification=ItemClassification.progression),
"Defeat Alexander": RLItemData("Event", classification=ItemClassification.progression),
"Defeat Ponce de Leon": RLItemData("Event", classification=ItemClassification.progression),
"Defeat Herodotus": RLItemData("Event", classification=ItemClassification.progression),
"Defeat Neo Khidr": RLItemData("Event", classification=ItemClassification.progression),
"Defeat Alexander IV": RLItemData("Event", classification=ItemClassification.progression),
"Defeat Ponce de Freon": RLItemData("Event", classification=ItemClassification.progression),
"Defeat Astrodotus": RLItemData("Event", classification=ItemClassification.progression),
"Defeat The Fountain": RLItemData("Event", classification=ItemClassification.progression),
}

View File

@@ -1,94 +0,0 @@
from typing import Dict, NamedTuple, Optional
from BaseClasses import Location
class RLLocation(Location):
game: str = "Rogue Legacy"
class RLLocationData(NamedTuple):
category: str
code: Optional[int] = None
def get_locations_by_category(category: str) -> Dict[str, RLLocationData]:
location_dict: Dict[str, RLLocationData] = {}
for name, data in location_table.items():
if data.category == category:
location_dict.setdefault(name, data)
return location_dict
location_table: Dict[str, RLLocationData] = {
# Manor Renovation
"Manor - Ground Road": RLLocationData("Manor", 91_000),
"Manor - Main Base": RLLocationData("Manor", 91_001),
"Manor - Main Bottom Window": RLLocationData("Manor", 91_002),
"Manor - Main Top Window": RLLocationData("Manor", 91_003),
"Manor - Main Rooftop": RLLocationData("Manor", 91_004),
"Manor - Left Wing Base": RLLocationData("Manor", 91_005),
"Manor - Left Wing Window": RLLocationData("Manor", 91_006),
"Manor - Left Wing Rooftop": RLLocationData("Manor", 91_007),
"Manor - Left Big Base": RLLocationData("Manor", 91_008),
"Manor - Left Big Upper 1": RLLocationData("Manor", 91_009),
"Manor - Left Big Upper 2": RLLocationData("Manor", 91_010),
"Manor - Left Big Windows": RLLocationData("Manor", 91_011),
"Manor - Left Big Rooftop": RLLocationData("Manor", 91_012),
"Manor - Left Far Base": RLLocationData("Manor", 91_013),
"Manor - Left Far Roof": RLLocationData("Manor", 91_014),
"Manor - Left Extension": RLLocationData("Manor", 91_015),
"Manor - Left Tree 1": RLLocationData("Manor", 91_016),
"Manor - Left Tree 2": RLLocationData("Manor", 91_017),
"Manor - Right Wing Base": RLLocationData("Manor", 91_018),
"Manor - Right Wing Window": RLLocationData("Manor", 91_019),
"Manor - Right Wing Rooftop": RLLocationData("Manor", 91_020),
"Manor - Right Big Base": RLLocationData("Manor", 91_021),
"Manor - Right Big Upper": RLLocationData("Manor", 91_022),
"Manor - Right Big Rooftop": RLLocationData("Manor", 91_023),
"Manor - Right High Base": RLLocationData("Manor", 91_024),
"Manor - Right High Upper": RLLocationData("Manor", 91_025),
"Manor - Right High Tower": RLLocationData("Manor", 91_026),
"Manor - Right Extension": RLLocationData("Manor", 91_027),
"Manor - Right Tree": RLLocationData("Manor", 91_028),
"Manor - Observatory Base": RLLocationData("Manor", 91_029),
"Manor - Observatory Telescope": RLLocationData("Manor", 91_030),
# Boss Rewards
"Castle Hamson Boss Reward": RLLocationData("Boss", 91_100),
"Forest Abkhazia Boss Reward": RLLocationData("Boss", 91_102),
"The Maya Boss Reward": RLLocationData("Boss", 91_104),
"Land of Darkness Boss Reward": RLLocationData("Boss", 91_106),
# Special Locations
"Jukebox": RLLocationData("Special", 91_200),
"Painting": RLLocationData("Special", 91_201),
"Cheapskate Elf's Game": RLLocationData("Special", 91_202),
"Carnival": RLLocationData("Special", 91_203),
# Diaries
**{f"Diary {i+1}": RLLocationData("Diary", 91_300 + i) for i in range(0, 25)},
# Chests
**{f"Castle Hamson - Chest {i+1}": RLLocationData("Chests", 91_600 + i) for i in range(0, 50)},
**{f"Forest Abkhazia - Chest {i+1}": RLLocationData("Chests", 91_700 + i) for i in range(0, 50)},
**{f"The Maya - Chest {i+1}": RLLocationData("Chests", 91_800 + i) for i in range(0, 50)},
**{f"Land of Darkness - Chest {i+1}": RLLocationData("Chests", 91_900 + i) for i in range(0, 50)},
**{f"Chest {i+1}": RLLocationData("Chests", 92_000 + i) for i in range(0, 200)},
# Fairy Chests
**{f"Castle Hamson - Fairy Chest {i+1}": RLLocationData("Fairies", 91_400 + i) for i in range(0, 15)},
**{f"Forest Abkhazia - Fairy Chest {i+1}": RLLocationData("Fairies", 91_450 + i) for i in range(0, 15)},
**{f"The Maya - Fairy Chest {i+1}": RLLocationData("Fairies", 91_500 + i) for i in range(0, 15)},
**{f"Land of Darkness - Fairy Chest {i+1}": RLLocationData("Fairies", 91_550 + i) for i in range(0, 15)},
**{f"Fairy Chest {i+1}": RLLocationData("Fairies", 92_200 + i) for i in range(0, 60)},
}
event_location_table: Dict[str, RLLocationData] = {
"Castle Hamson Boss Room": RLLocationData("Event"),
"Forest Abkhazia Boss Room": RLLocationData("Event"),
"The Maya Boss Room": RLLocationData("Event"),
"Land of Darkness Boss Room": RLLocationData("Event"),
"Fountain Room": RLLocationData("Event"),
}

View File

@@ -1,387 +0,0 @@
from Options import Choice, Range, Toggle, DeathLink, DefaultOnToggle, OptionSet, PerGameCommonOptions
from dataclasses import dataclass
class StartingGender(Choice):
"""
Determines the gender of your initial 'Sir Lee' character.
"""
display_name = "Starting Gender"
option_sir = 0
option_lady = 1
alias_male = 0
alias_female = 1
default = "random"
class StartingClass(Choice):
"""
Determines the starting class of your initial 'Sir Lee' character.
"""
display_name = "Starting Class"
option_knight = 0
option_mage = 1
option_barbarian = 2
option_knave = 3
option_shinobi = 4
option_miner = 5
option_spellthief = 6
option_lich = 7
default = 0
class NewGamePlus(Choice):
"""
Puts the castle in new game plus mode which vastly increases enemy level, but increases gold gain by 50%. Not
recommended for those inexperienced to Rogue Legacy!
"""
display_name = "New Game Plus"
option_normal = 0
option_new_game_plus = 1
option_new_game_plus_2 = 2
alias_hard = 1
alias_brutal = 2
default = 0
class LevelScaling(Range):
"""
A percentage modifier for scaling enemy level as you continue throughout the castle. 100 means enemies will have
100% level scaling (normal). Setting this too high will result in enemies with absurdly high levels, you have been
warned.
"""
display_name = "Enemy Level Scaling Percentage"
range_start = 1
range_end = 300
default = 100
class FairyChestsPerZone(Range):
"""
Determines the number of Fairy Chests in a given zone that contain items. After these have been checked, only stat
bonuses can be found in Fairy Chests.
"""
display_name = "Fairy Chests Per Zone"
range_start = 0
range_end = 15
default = 1
class ChestsPerZone(Range):
"""
Determines the number of Non-Fairy Chests in a given zone that contain items. After these have been checked, only
gold or stat bonuses can be found in Chests.
"""
display_name = "Chests Per Zone"
range_start = 20
range_end = 50
default = 20
class UniversalFairyChests(Toggle):
"""
Determines if fairy chests should be combined into one pool instead of per zone, similar to Risk of Rain 2.
"""
display_name = "Universal Fairy Chests"
class UniversalChests(Toggle):
"""
Determines if non-fairy chests should be combined into one pool instead of per zone, similar to Risk of Rain 2.
"""
display_name = "Universal Non-Fairy Chests"
class Vendors(Choice):
"""
Determines where to place the Blacksmith and Enchantress unlocks in logic (or start with them unlocked).
"""
display_name = "Vendors"
option_start_unlocked = 0
option_early = 1
option_normal = 2
option_anywhere = 3
default = 1
class Architect(Choice):
"""
Determines where the Architect sits in the item pool.
"""
display_name = "Architect"
option_start_unlocked = 0
option_early = 1
option_anywhere = 2
option_disabled = 3
alias_normal = 2
default = 2
class ArchitectFee(Range):
"""
Determines how large of a percentage the architect takes from the player when utilizing his services. 100 means he
takes all your gold. 0 means his services are free.
"""
display_name = "Architect Fee Percentage"
range_start = 0
range_end = 100
default = 40
class DisableCharon(Toggle):
"""
Prevents Charon from taking your money when you re-enter the castle. Also removes Haggling from the Item Pool.
"""
display_name = "Disable Charon"
class RequirePurchasing(DefaultOnToggle):
"""
Determines where you will be required to purchase equipment and runes from the Blacksmith and Enchantress before
equipping them. If you disable require purchasing, Manor Renovations are scaled to take this into account.
"""
display_name = "Require Purchasing"
class ProgressiveBlueprints(Toggle):
"""
Instead of shuffling blueprints randomly into the pool, blueprint unlocks are progressively unlocked. You would get
Squire first, then Knight, etc., until finally Dark.
"""
display_name = "Progressive Blueprints"
class GoldGainMultiplier(Choice):
"""
Adjusts the multiplier for gaining gold from all sources.
"""
display_name = "Gold Gain Multiplier"
option_normal = 0
option_quarter = 1
option_half = 2
option_double = 3
option_quadruple = 4
default = 0
class NumberOfChildren(Range):
"""
Determines the number of offspring you can choose from on the lineage screen after a death.
"""
display_name = "Number of Children"
range_start = 1
range_end = 5
default = 3
class AdditionalLadyNames(OptionSet):
"""
Set of additional names your potential offspring can have. If Allow Default Names is disabled, this is the only list
of names your children can have. The first value will also be your initial character's name depending on Starting
Gender.
"""
display_name = "Additional Lady Names"
class AdditionalSirNames(OptionSet):
"""
Set of additional names your potential offspring can have. If Allow Default Names is disabled, this is the only list
of names your children can have. The first value will also be your initial character's name depending on Starting
Gender.
"""
display_name = "Additional Sir Names"
class AllowDefaultNames(DefaultOnToggle):
"""
Determines if the default names defined in the vanilla game are allowed to be used. Warning: Your world will not
generate if the number of Additional Names defined is less than the Number of Children value.
"""
display_name = "Allow Default Names"
class CastleScaling(Range):
"""
Adjusts the scaling factor for how big a castle can be. Larger castles scale enemies quicker and also take longer
to generate. 100 means normal castle size.
"""
display_name = "Castle Size Scaling Percentage"
range_start = 50
range_end = 300
default = 100
class ChallengeBossKhidr(Choice):
"""
Determines if Neo Khidr replaces Khidr in their boss room.
"""
display_name = "Khidr"
option_vanilla = 0
option_challenge = 1
default = 0
class ChallengeBossAlexander(Choice):
"""
Determines if Alexander the IV replaces Alexander in their boss room.
"""
display_name = "Alexander"
option_vanilla = 0
option_challenge = 1
default = 0
class ChallengeBossLeon(Choice):
"""
Determines if Ponce de Freon replaces Ponce de Leon in their boss room.
"""
display_name = "Ponce de Leon"
option_vanilla = 0
option_challenge = 1
default = 0
class ChallengeBossHerodotus(Choice):
"""
Determines if Astrodotus replaces Herodotus in their boss room.
"""
display_name = "Herodotus"
option_vanilla = 0
option_challenge = 1
default = 0
class HealthUpPool(Range):
"""
Determines the number of Health Ups in the item pool.
"""
display_name = "Health Up Pool"
range_start = 0
range_end = 15
default = 15
class ManaUpPool(Range):
"""
Determines the number of Mana Ups in the item pool.
"""
display_name = "Mana Up Pool"
range_start = 0
range_end = 15
default = 15
class AttackUpPool(Range):
"""
Determines the number of Attack Ups in the item pool.
"""
display_name = "Attack Up Pool"
range_start = 0
range_end = 15
default = 15
class MagicDamageUpPool(Range):
"""
Determines the number of Magic Damage Ups in the item pool.
"""
display_name = "Magic Damage Up Pool"
range_start = 0
range_end = 15
default = 15
class ArmorUpPool(Range):
"""
Determines the number of Armor Ups in the item pool.
"""
display_name = "Armor Up Pool"
range_start = 0
range_end = 10
default = 10
class EquipUpPool(Range):
"""
Determines the number of Equip Ups in the item pool.
"""
display_name = "Equip Up Pool"
range_start = 0
range_end = 10
default = 10
class CritChanceUpPool(Range):
"""
Determines the number of Crit Chance Ups in the item pool.
"""
display_name = "Crit Chance Up Pool"
range_start = 0
range_end = 5
default = 5
class CritDamageUpPool(Range):
"""
Determines the number of Crit Damage Ups in the item pool.
"""
display_name = "Crit Damage Up Pool"
range_start = 0
range_end = 5
default = 5
class FreeDiaryOnGeneration(DefaultOnToggle):
"""
Allows the player to get a free diary check every time they regenerate the castle in the starting room.
"""
display_name = "Free Diary On Generation"
class AvailableClasses(OptionSet):
"""
List of classes that will be in the item pool to find. The upgraded form of the class will be added with it.
The upgraded form of your starting class will be available regardless.
"""
display_name = "Available Classes"
default = frozenset(
{"Knight", "Mage", "Barbarian", "Knave", "Shinobi", "Miner", "Spellthief", "Lich", "Dragon", "Traitor"}
)
valid_keys = {"Knight", "Mage", "Barbarian", "Knave", "Shinobi", "Miner", "Spellthief", "Lich", "Dragon", "Traitor"}
@dataclass
class RLOptions(PerGameCommonOptions):
starting_gender: StartingGender
starting_class: StartingClass
available_classes: AvailableClasses
new_game_plus: NewGamePlus
fairy_chests_per_zone: FairyChestsPerZone
chests_per_zone: ChestsPerZone
universal_fairy_chests: UniversalFairyChests
universal_chests: UniversalChests
vendors: Vendors
architect: Architect
architect_fee: ArchitectFee
disable_charon: DisableCharon
require_purchasing: RequirePurchasing
progressive_blueprints: ProgressiveBlueprints
gold_gain_multiplier: GoldGainMultiplier
number_of_children: NumberOfChildren
free_diary_on_generation: FreeDiaryOnGeneration
khidr: ChallengeBossKhidr
alexander: ChallengeBossAlexander
leon: ChallengeBossLeon
herodotus: ChallengeBossHerodotus
health_pool: HealthUpPool
mana_pool: ManaUpPool
attack_pool: AttackUpPool
magic_damage_pool: MagicDamageUpPool
armor_pool: ArmorUpPool
equip_pool: EquipUpPool
crit_chance_pool: CritChanceUpPool
crit_damage_pool: CritDamageUpPool
allow_default_names: AllowDefaultNames
additional_lady_names: AdditionalLadyNames
additional_sir_names: AdditionalSirNames
death_link: DeathLink

View File

@@ -1,61 +0,0 @@
from typing import Any, Dict
from .Options import Architect, GoldGainMultiplier, Vendors
rl_options_presets: Dict[str, Dict[str, Any]] = {
# Example preset using only literal values.
"Unknown Fate": {
"progression_balancing": "random",
"accessibility": "random",
"starting_gender": "random",
"starting_class": "random",
"new_game_plus": "random",
"fairy_chests_per_zone": "random",
"chests_per_zone": "random",
"universal_fairy_chests": "random",
"universal_chests": "random",
"vendors": "random",
"architect": "random",
"architect_fee": "random",
"disable_charon": "random",
"require_purchasing": "random",
"progressive_blueprints": "random",
"gold_gain_multiplier": "random",
"number_of_children": "random",
"free_diary_on_generation": "random",
"khidr": "random",
"alexander": "random",
"leon": "random",
"herodotus": "random",
"health_pool": "random",
"mana_pool": "random",
"attack_pool": "random",
"magic_damage_pool": "random",
"armor_pool": "random",
"equip_pool": "random",
"crit_chance_pool": "random",
"crit_damage_pool": "random",
"allow_default_names": True,
"death_link": "random",
},
# A preset I actually use, using some literal values and some from the option itself.
"Limited Potential": {
"progression_balancing": "disabled",
"fairy_chests_per_zone": 2,
"starting_class": "random",
"chests_per_zone": 30,
"vendors": Vendors.option_normal,
"architect": Architect.option_disabled,
"gold_gain_multiplier": GoldGainMultiplier.option_half,
"number_of_children": 2,
"free_diary_on_generation": False,
"health_pool": 10,
"mana_pool": 10,
"attack_pool": 10,
"magic_damage_pool": 10,
"armor_pool": 5,
"equip_pool": 10,
"crit_chance_pool": 5,
"crit_damage_pool": 5,
}
}

View File

@@ -1,114 +0,0 @@
from typing import Dict, List, NamedTuple, Optional, TYPE_CHECKING
from BaseClasses import MultiWorld, Region, Entrance
from .Locations import RLLocation, location_table, get_locations_by_category
if TYPE_CHECKING:
from . import RLWorld
class RLRegionData(NamedTuple):
locations: Optional[List[str]]
region_exits: Optional[List[str]]
def create_regions(world: "RLWorld"):
regions: Dict[str, RLRegionData] = {
"Menu": RLRegionData(None, ["Castle Hamson"]),
"The Manor": RLRegionData([], []),
"Castle Hamson": RLRegionData([], ["Forest Abkhazia", "The Maya", "Land of Darkness",
"The Fountain Room", "The Manor"]),
"Forest Abkhazia": RLRegionData([], []),
"The Maya": RLRegionData([], []),
"Land of Darkness": RLRegionData([], []),
"The Fountain Room": RLRegionData([], None),
}
# Artificially stagger diary spheres for progression.
for diary in range(0, 25):
region: str
if 0 <= diary < 6:
region = "Castle Hamson"
elif 6 <= diary < 12:
region = "Forest Abkhazia"
elif 12 <= diary < 18:
region = "The Maya"
elif 18 <= diary < 24:
region = "Land of Darkness"
else:
region = "The Fountain Room"
regions[region].locations.append(f"Diary {diary + 1}")
# Manor & Special
for manor in get_locations_by_category("Manor").keys():
regions["The Manor"].locations.append(manor)
for special in get_locations_by_category("Special").keys():
regions["Castle Hamson"].locations.append(special)
# Boss Rewards
regions["Castle Hamson"].locations.append("Castle Hamson Boss Reward")
regions["Forest Abkhazia"].locations.append("Forest Abkhazia Boss Reward")
regions["The Maya"].locations.append("The Maya Boss Reward")
regions["Land of Darkness"].locations.append("Land of Darkness Boss Reward")
# Events
regions["Castle Hamson"].locations.append("Castle Hamson Boss Room")
regions["Forest Abkhazia"].locations.append("Forest Abkhazia Boss Room")
regions["The Maya"].locations.append("The Maya Boss Room")
regions["Land of Darkness"].locations.append("Land of Darkness Boss Room")
regions["The Fountain Room"].locations.append("Fountain Room")
# Chests
chests = int(world.options.chests_per_zone)
for i in range(0, chests):
if world.options.universal_chests:
regions["Castle Hamson"].locations.append(f"Chest {i + 1}")
regions["Forest Abkhazia"].locations.append(f"Chest {i + 1 + chests}")
regions["The Maya"].locations.append(f"Chest {i + 1 + (chests * 2)}")
regions["Land of Darkness"].locations.append(f"Chest {i + 1 + (chests * 3)}")
else:
regions["Castle Hamson"].locations.append(f"Castle Hamson - Chest {i + 1}")
regions["Forest Abkhazia"].locations.append(f"Forest Abkhazia - Chest {i + 1}")
regions["The Maya"].locations.append(f"The Maya - Chest {i + 1}")
regions["Land of Darkness"].locations.append(f"Land of Darkness - Chest {i + 1}")
# Fairy Chests
chests = int(world.options.fairy_chests_per_zone)
for i in range(0, chests):
if world.options.universal_fairy_chests:
regions["Castle Hamson"].locations.append(f"Fairy Chest {i + 1}")
regions["Forest Abkhazia"].locations.append(f"Fairy Chest {i + 1 + chests}")
regions["The Maya"].locations.append(f"Fairy Chest {i + 1 + (chests * 2)}")
regions["Land of Darkness"].locations.append(f"Fairy Chest {i + 1 + (chests * 3)}")
else:
regions["Castle Hamson"].locations.append(f"Castle Hamson - Fairy Chest {i + 1}")
regions["Forest Abkhazia"].locations.append(f"Forest Abkhazia - Fairy Chest {i + 1}")
regions["The Maya"].locations.append(f"The Maya - Fairy Chest {i + 1}")
regions["Land of Darkness"].locations.append(f"Land of Darkness - Fairy Chest {i + 1}")
# Set up the regions correctly.
for name, data in regions.items():
world.multiworld.regions.append(create_region(world.multiworld, world.player, name, data))
world.get_entrance("Castle Hamson").connect(world.get_region("Castle Hamson"))
world.get_entrance("The Manor").connect(world.get_region("The Manor"))
world.get_entrance("Forest Abkhazia").connect(world.get_region("Forest Abkhazia"))
world.get_entrance("The Maya").connect(world.get_region("The Maya"))
world.get_entrance("Land of Darkness").connect(world.get_region("Land of Darkness"))
world.get_entrance("The Fountain Room").connect(world.get_region("The Fountain Room"))
def create_region(multiworld: MultiWorld, player: int, name: str, data: RLRegionData):
region = Region(name, player, multiworld)
if data.locations:
for loc_name in data.locations:
loc_data = location_table.get(loc_name)
location = RLLocation(player, loc_name, loc_data.code if loc_data else None, region)
region.locations.append(location)
if data.region_exits:
for exit in data.region_exits:
entrance = Entrance(player, exit, region)
region.exits.append(entrance)
return region

View File

@@ -1,117 +0,0 @@
from BaseClasses import CollectionState
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from . import RLWorld
def get_upgrade_total(world: "RLWorld") -> int:
return int(world.options.health_pool) + int(world.options.mana_pool) + \
int(world.options.attack_pool) + int(world.options.magic_damage_pool)
def get_upgrade_count(state: CollectionState, player: int) -> int:
return state.count("Health Up", player) + state.count("Mana Up", player) + \
state.count("Attack Up", player) + state.count("Magic Damage Up", player)
def has_vendors(state: CollectionState, player: int) -> bool:
return state.has_all({"Blacksmith", "Enchantress"}, player)
def has_upgrade_amount(state: CollectionState, player: int, amount: int) -> bool:
return get_upgrade_count(state, player) >= amount
def has_upgrades_percentage(state: CollectionState, world: "RLWorld", percentage: float) -> bool:
return has_upgrade_amount(state, world.player, round(get_upgrade_total(world) * (percentage / 100)))
def has_movement_rune(state: CollectionState, player: int) -> bool:
return state.has("Vault Runes", player) or state.has("Sprint Runes", player) or state.has("Sky Runes", player)
def has_fairy_progression(state: CollectionState, player: int) -> bool:
return state.has("Dragons", player) or (state.has("Enchantress", player) and has_movement_rune(state, player))
def has_defeated_castle(state: CollectionState, player: int) -> bool:
return state.has("Defeat Khidr", player) or state.has("Defeat Neo Khidr", player)
def has_defeated_forest(state: CollectionState, player: int) -> bool:
return state.has("Defeat Alexander", player) or state.has("Defeat Alexander IV", player)
def has_defeated_tower(state: CollectionState, player: int) -> bool:
return state.has("Defeat Ponce de Leon", player) or state.has("Defeat Ponce de Freon", player)
def has_defeated_dungeon(state: CollectionState, player: int) -> bool:
return state.has("Defeat Herodotus", player) or state.has("Defeat Astrodotus", player)
def set_rules(world: "RLWorld", player: int):
# If 'vendors' are 'normal', then expect it to show up in the first half(ish) of the spheres.
if world.options.vendors == "normal":
world.get_location("Forest Abkhazia Boss Reward").access_rule = \
lambda state: has_vendors(state, player)
# Gate each manor location so everything isn't dumped into sphere 1.
manor_rules = {
"Defeat Khidr" if world.options.khidr == "vanilla" else "Defeat Neo Khidr": [
"Manor - Left Wing Window",
"Manor - Left Wing Rooftop",
"Manor - Right Wing Window",
"Manor - Right Wing Rooftop",
"Manor - Left Big Base",
"Manor - Right Big Base",
"Manor - Left Tree 1",
"Manor - Left Tree 2",
"Manor - Right Tree",
],
"Defeat Alexander" if world.options.alexander == "vanilla" else "Defeat Alexander IV": [
"Manor - Left Big Upper 1",
"Manor - Left Big Upper 2",
"Manor - Left Big Windows",
"Manor - Left Big Rooftop",
"Manor - Left Far Base",
"Manor - Left Far Roof",
"Manor - Left Extension",
"Manor - Right Big Upper",
"Manor - Right Big Rooftop",
"Manor - Right Extension",
],
"Defeat Ponce de Leon" if world.options.leon == "vanilla" else "Defeat Ponce de Freon": [
"Manor - Right High Base",
"Manor - Right High Upper",
"Manor - Right High Tower",
"Manor - Observatory Base",
"Manor - Observatory Telescope",
]
}
# Set rules for manor locations.
for event, locations in manor_rules.items():
for location in locations:
world.get_location(location).access_rule = lambda state: state.has(event, player)
# Set rules for fairy chests to decrease headache of expectation to find non-movement fairy chests.
for fairy_location in [location for location in world.multiworld.get_locations(player) if "Fairy" in location.name]:
fairy_location.access_rule = lambda state: has_fairy_progression(state, player)
# Region rules.
world.get_entrance("Forest Abkhazia").access_rule = \
lambda state: has_upgrades_percentage(state, world, 12.5) and has_defeated_castle(state, player)
world.get_entrance("The Maya").access_rule = \
lambda state: has_upgrades_percentage(state, world, 25) and has_defeated_forest(state, player)
world.get_entrance("Land of Darkness").access_rule = \
lambda state: has_upgrades_percentage(state, world, 37.5) and has_defeated_tower(state, player)
world.get_entrance("The Fountain Room").access_rule = \
lambda state: has_upgrades_percentage(state, world, 50) and has_defeated_dungeon(state, player)
# Win condition.
world.multiworld.completion_condition[player] = lambda state: state.has("Defeat The Fountain", player)

View File

@@ -1,243 +0,0 @@
from typing import List
from BaseClasses import Tutorial
from worlds.AutoWorld import WebWorld, World
from .Items import RLItem, RLItemData, event_item_table, get_items_by_category, item_table
from .Locations import RLLocation, location_table
from .Options import RLOptions
from .Presets import rl_options_presets
from .Regions import create_regions
from .Rules import set_rules
class RLWeb(WebWorld):
theme = "stone"
tutorials = [Tutorial(
"Multiworld Setup Guide",
"A guide to setting up the Rogue Legacy Randomizer software on your computer. This guide covers single-player, "
"multiworld, and related software.",
"English",
"rogue-legacy_en.md",
"rogue-legacy/en",
["Phar"]
)]
bug_report_page = "https://github.com/ThePhar/RogueLegacyRandomizer/issues/new?assignees=&labels=bug&template=" \
"report-an-issue---.md&title=%5BIssue%5D"
options_presets = rl_options_presets
class RLWorld(World):
"""
Rogue Legacy is a genealogical rogue-"LITE" where anyone can be a hero. Each time you die, your child will succeed
you. Every child is unique. One child might be colorblind, another might have vertigo-- they could even be a dwarf.
But that's OK, because no one is perfect, and you don't have to be to succeed.
"""
game = "Rogue Legacy"
options_dataclass = RLOptions
options: RLOptions
topology_present = True
required_client_version = (0, 3, 5)
web = RLWeb()
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: data.code for name, data in location_table.items() if data.code is not None}
def fill_slot_data(self) -> dict:
return self.options.as_dict(*[name for name in self.options_dataclass.type_hints.keys()])
def generate_early(self):
# Check validation of names.
additional_lady_names = len(self.options.additional_lady_names.value)
additional_sir_names = len(self.options.additional_sir_names.value)
if not self.options.allow_default_names:
if additional_lady_names < int(self.options.number_of_children):
raise Exception(
f"allow_default_names is off, but not enough names are defined in additional_lady_names. "
f"Expected {int(self.options.number_of_children)}, Got {additional_lady_names}")
if additional_sir_names < int(self.options.number_of_children):
raise Exception(
f"allow_default_names is off, but not enough names are defined in additional_sir_names. "
f"Expected {int(self.options.number_of_children)}, Got {additional_sir_names}")
def create_items(self):
item_pool: List[RLItem] = []
total_locations = len(self.multiworld.get_unfilled_locations(self.player))
for name, data in item_table.items():
quantity = data.max_quantity
# Architect
if name == "Architect":
if self.options.architect == "disabled":
continue
if self.options.architect == "start_unlocked":
self.multiworld.push_precollected(self.create_item(name))
continue
if self.options.architect == "early":
self.multiworld.local_early_items[self.player]["Architect"] = 1
# Blacksmith and Enchantress
if name == "Blacksmith" or name == "Enchantress":
if self.options.vendors == "start_unlocked":
self.multiworld.push_precollected(self.create_item(name))
continue
if self.options.vendors == "early":
self.multiworld.local_early_items[self.player]["Blacksmith"] = 1
self.multiworld.local_early_items[self.player]["Enchantress"] = 1
# Haggling
if name == "Haggling" and self.options.disable_charon:
continue
# Blueprints
if data.category == "Blueprints":
# No progressive blueprints if progressive_blueprints are disabled.
if name == "Progressive Blueprints" and not self.options.progressive_blueprints:
continue
# No distinct blueprints if progressive_blueprints are enabled.
elif name != "Progressive Blueprints" and self.options.progressive_blueprints:
continue
# Classes
if data.category == "Classes":
if name == "Progressive Knights":
if "Knight" not in self.options.available_classes:
continue
if self.options.starting_class == "knight":
quantity = 1
if name == "Progressive Mages":
if "Mage" not in self.options.available_classes:
continue
if self.options.starting_class == "mage":
quantity = 1
if name == "Progressive Barbarians":
if "Barbarian" not in self.options.available_classes:
continue
if self.options.starting_class == "barbarian":
quantity = 1
if name == "Progressive Knaves":
if "Knave" not in self.options.available_classes:
continue
if self.options.starting_class == "knave":
quantity = 1
if name == "Progressive Miners":
if "Miner" not in self.options.available_classes:
continue
if self.options.starting_class == "miner":
quantity = 1
if name == "Progressive Shinobis":
if "Shinobi" not in self.options.available_classes:
continue
if self.options.starting_class == "shinobi":
quantity = 1
if name == "Progressive Liches":
if "Lich" not in self.options.available_classes:
continue
if self.options.starting_class == "lich":
quantity = 1
if name == "Progressive Spellthieves":
if "Spellthief" not in self.options.available_classes:
continue
if self.options.starting_class == "spellthief":
quantity = 1
if name == "Dragons":
if "Dragon" not in self.options.available_classes:
continue
if name == "Traitors":
if "Traitor" not in self.options.available_classes:
continue
# Skills
if name == "Health Up":
quantity = self.options.health_pool.value
elif name == "Mana Up":
quantity = self.options.mana_pool.value
elif name == "Attack Up":
quantity = self.options.attack_pool.value
elif name == "Magic Damage Up":
quantity = self.options.magic_damage_pool.value
elif name == "Armor Up":
quantity = self.options.armor_pool.value
elif name == "Equip Up":
quantity = self.options.equip_pool.value
elif name == "Crit Chance Up":
quantity = self.options.crit_chance_pool.value
elif name == "Crit Damage Up":
quantity = self.options.crit_damage_pool.value
# Ignore filler, it will be added in a later stage.
if data.category == "Filler":
continue
item_pool += [self.create_item(name) for _ in range(0, quantity)]
# Fill any empty locations with filler items.
while len(item_pool) < total_locations:
item_pool.append(self.create_item(self.get_filler_item_name()))
self.multiworld.itempool += item_pool
def get_filler_item_name(self) -> str:
fillers = get_items_by_category("Filler")
weights = [data.weight for data in fillers.values()]
return self.random.choices([filler for filler in fillers.keys()], weights, k=1)[0]
def create_item(self, name: str) -> RLItem:
data = item_table[name]
return RLItem(name, data.classification, data.code, self.player)
def create_event(self, name: str) -> RLItem:
data = event_item_table[name]
return RLItem(name, data.classification, data.code, self.player)
def set_rules(self):
set_rules(self, self.player)
def create_regions(self):
create_regions(self)
self._place_events()
def _place_events(self):
# Fountain
self.multiworld.get_location("Fountain Room", self.player).place_locked_item(
self.create_event("Defeat The Fountain"))
# Khidr / Neo Khidr
if self.options.khidr == "vanilla":
self.multiworld.get_location("Castle Hamson Boss Room", self.player).place_locked_item(
self.create_event("Defeat Khidr"))
else:
self.multiworld.get_location("Castle Hamson Boss Room", self.player).place_locked_item(
self.create_event("Defeat Neo Khidr"))
# Alexander / Alexander IV
if self.options.alexander == "vanilla":
self.multiworld.get_location("Forest Abkhazia Boss Room", self.player).place_locked_item(
self.create_event("Defeat Alexander"))
else:
self.multiworld.get_location("Forest Abkhazia Boss Room", self.player).place_locked_item(
self.create_event("Defeat Alexander IV"))
# Ponce de Leon / Ponce de Freon
if self.options.leon == "vanilla":
self.multiworld.get_location("The Maya Boss Room", self.player).place_locked_item(
self.create_event("Defeat Ponce de Leon"))
else:
self.multiworld.get_location("The Maya Boss Room", self.player).place_locked_item(
self.create_event("Defeat Ponce de Freon"))
# Herodotus / Astrodotus
if self.options.herodotus == "vanilla":
self.multiworld.get_location("Land of Darkness Boss Room", self.player).place_locked_item(
self.create_event("Defeat Herodotus"))
else:
self.multiworld.get_location("Land of Darkness Boss Room", self.player).place_locked_item(
self.create_event("Defeat Astrodotus"))

View File

@@ -1,34 +0,0 @@
# Rogue Legacy (PC)
## Where is the options page?
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?
Rogue Legacy Randomizer takes all the classes, skills, runes, and blueprints and spreads them out into chests, the manor
upgrade screen, bosses, and some special individual locations. The goal is to become powerful enough to defeat the four
zone bosses and then defeat The Fountain.
## What items and locations get shuffled?
All the skill upgrades, class upgrades, runes packs, and equipment packs are shuffled in the manor upgrade screen, diary
checks, chests and fairy chests, and boss rewards. Skill upgrades are also grouped in packs of 5 to make the finding of
stats less of a chore. Runes and Equipment are also grouped together.
Some additional locations that can contain items are the Jukebox, the Portraits, and the mini-game rewards.
## 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.
## When the player receives an item, what happens?
When the player receives an item, your character will hold the item above their head and display it to the world. It's
good for business!
## What do I do if I encounter a bug with the game?
Please reach out to Phar#4444 on Discord or you can drop a bug report on the
[GitHub page for Rogue Legacy Randomizer](https://github.com/ThePhar/RogueLegacyRandomizer/issues/new?assignees=&labels=bug&template=report-an-issue---.md&title=%5BIssue%5D).

View File

@@ -1,35 +0,0 @@
# Rogue Legacy Randomizer Setup Guide
## Required Software
- Rogue Legacy Randomizer from the
[Rogue Legacy Randomizer Releases Page](https://github.com/ThePhar/RogueLegacyRandomizer/releases)
## Recommended Installation Instructions
Please read the README file on the
[Rogue Legacy Randomizer GitHub](https://github.com/ThePhar/RogueLegacyRandomizer/blob/master/README.md) page for
up-to-date installation instructions.
## 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 [Rogue Legacy Options Page](/games/Rogue%20Legacy/player-options).
### Connect to the MultiServer
Once in game, press the start button and the AP connection screen should appear. You will fill out the hostname, port,
slot name, and password (if applicable). You should only need to fill out hostname, port, and password if the server
provides an alternative one to the default values.
### Play the game
Once you have entered the required values, you go to Connect and then select Confirm on the "Ready to Start" screen. Now
you're off to start your legacy!

View File

@@ -1,23 +0,0 @@
from typing import Dict
from . import RLTestBase
from ..Items import item_table
from ..Locations import location_table
class UniqueTest(RLTestBase):
@staticmethod
def test_item_ids_are_all_unique():
item_ids: Dict[int, str] = {}
for name, data in item_table.items():
assert data.code not in item_ids.keys(), f"'{name}': {data.code}, is not unique. " \
f"'{item_ids[data.code]}' also has this identifier."
item_ids[data.code] = name
@staticmethod
def test_location_ids_are_all_unique():
location_ids: Dict[int, str] = {}
for name, data in location_table.items():
assert data.code not in location_ids.keys(), f"'{name}': {data.code}, is not unique. " \
f"'{location_ids[data.code]}' also has this identifier."
location_ids[data.code] = name

View File

@@ -1,5 +0,0 @@
from test.bases import WorldTestBase
class RLTestBase(WorldTestBase):
game = "Rogue Legacy"

View File

@@ -279,6 +279,9 @@ def extend_festival_locations(randomized_locations: List[LocationData], options:
return
festival_locations = locations_by_tag[LocationTags.FESTIVAL]
if not options.museumsanity:
festival_locations = [location for location in festival_locations if location.name not in ("Rarecrow #7 (Tanuki)", "Rarecrow #8 (Tribal Mask)")]
randomized_locations.extend(festival_locations)
extend_hard_festival_locations(randomized_locations, options)
extend_desert_festival_chef_locations(randomized_locations, options, random)

View File

@@ -1,13 +1,5 @@
from typing import Union
from Utils import cache_self1
from .action_logic import ActionLogicMixin
from .base_logic import BaseLogic, BaseLogicMixin
from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .time_logic import TimeLogicMixin
from .tool_logic import ToolLogicMixin
from .. import options
from ..data.museum_data import MuseumItem, all_museum_items, all_museum_artifacts, all_museum_minerals
from ..stardew_rule import StardewRule, False_

View File

@@ -1,12 +1,16 @@
from collections import Counter
from unittest.mock import patch
from ..bases import SVTestBase
from ...options import Museumsanity
from ..options import presets
from ... import options, StardewLogic, StardewRule
from ...logic.museum_logic import MuseumLogic
from ...stardew_rule import true_, LiteralStardewRule
class TestMuseumMilestones(SVTestBase):
options = {
Museumsanity.internal_name: Museumsanity.option_milestones
options.Museumsanity: options.Museumsanity.option_milestones
}
def test_50_milestone(self):
@@ -14,3 +18,45 @@ class TestMuseumMilestones(SVTestBase):
milestone_rule = self.world.logic.museum.can_find_museum_items(50)
self.assert_rule_false(milestone_rule, self.multiworld.state)
class DisabledMuseumRule(LiteralStardewRule):
value = False
def __or__(self, other) -> StardewRule:
return other
def __and__(self, other) -> StardewRule:
return self
def __repr__(self):
return "Disabled Museum Rule"
class TestMuseumsanityDisabledExcludesMuseumDonationsFromOtherLocations(SVTestBase):
options = {
**presets.allsanity_mods_6_x_x(),
options.Museumsanity.internal_name: options.Museumsanity.option_none
}
def test_museum_donations_are_never_required_in_any_locations(self):
with patch("worlds.stardew_valley.logic.museum_logic.MuseumLogic") as MockMuseumLogic:
museum_logic: MuseumLogic = MockMuseumLogic.return_value
museum_logic.can_donate_museum_items.return_value = DisabledMuseumRule()
museum_logic.can_donate_museum_artifacts.return_value = DisabledMuseumRule()
museum_logic.can_find_museum_artifacts.return_value = DisabledMuseumRule()
museum_logic.can_find_museum_minerals.return_value = DisabledMuseumRule()
museum_logic.can_find_museum_items.return_value = DisabledMuseumRule()
museum_logic.can_complete_museum.return_value = DisabledMuseumRule()
museum_logic.can_donate.return_value = DisabledMuseumRule()
# Allowing calls to museum rules since a lot of other logic depends on it, for minerals for instance.
museum_logic.can_find_museum_item.return_value = true_
regions = {region.name for region in self.multiworld.regions}
self.world.logic = StardewLogic(self.player, self.world.options, self.world.content, regions)
self.world.set_rules()
self.collect_everything()
for location in self.get_real_locations():
with self.subTest(location.name):
self.assert_can_reach_location(location)

View File

@@ -67,99 +67,6 @@ def map_page_index(data: Any) -> int:
# mapping of everything after the second to last slash and the location id
# lua used for the name: string.match(full_name, "[^/]*/[^/]*$")
poptracker_data: dict[str, int] = {
"[Powered Secret Room] Chest/Follow the Purple Energy Road": 509342400,
"[Entryway] Chest/Mind the Slorms": 509342401,
"[Third Room] Beneath Platform Chest/Run from the tentacles!": 509342402,
"[Third Room] Tentacle Chest/Water Sucks": 509342403,
"[Entryway] Obscured Behind Waterfall/You can just go in there": 509342404,
"[Save Room] Upper Floor Chest 1/Through the Power of Prayer": 509342405,
"[Save Room] Upper Floor Chest 2/Above the Fox Shrine": 509342406,
"[Second Room] Underwater Chest/Hidden Passage": 509342407,
"[Back Corridor] Right Secret/Hidden Path": 509342408,
"[Back Corridor] Left Secret/Behind the Slorms": 509342409,
"[Second Room] Obscured Behind Waterfall/Just go in there": 509342410,
"[Side Room] Chest By Pots/Just Climb up There": 509342411,
"[Side Room] Chest By Phrends/So Many Phrends!": 509342412,
"[Second Room] Page/Ruined Atoll Map": 509342413,
"[Passage To Dark Tomb] Page Pickup/Siege Engine": 509342414,
"[1F] Guarded By Lasers/Beside 3 Miasma Seekers": 509342415,
"[1F] Near Spikes/Mind the Miasma Seeker": 509342416,
"Birdcage Room/[2F] Bird Room": 509342417,
"[2F] Entryway Upper Walkway/Overlooking Miasma": 509342418,
"[1F] Library/By the Books": 509342419,
"[2F] Library/Behind the Ladder": 509342420,
"[2F] Guarded By Lasers/Before the big reveal...": 509342421,
"Birdcage Room/[2F] Bird Room Secret": 509342422,
"[1F] Library Secret/Pray to the Wallman": 509342423,
"Spike Maze Near Exit/Watch out!": 509342424,
"2nd Laser Room/Can you roll?": 509342425,
"1st Laser Room/Use a bomb?": 509342426,
"Spike Maze Upper Walkway/Just walk right!": 509342427,
"Skulls Chest/Move the Grave": 509342428,
"Spike Maze Near Stairs/In the Corner": 509342429,
"1st Laser Room Obscured/Follow the red laser of death": 509342430,
"Guardhouse 2 - Upper Floor/In the Mound": 509342431,
"Guardhouse 2 - Bottom Floor Secret/Hidden Hallway": 509342432,
"Guardhouse 1 Obscured/Upper Floor Obscured": 509342433,
"Guardhouse 1/Upper Floor": 509342434,
"Guardhouse 1 Ledge HC/Dancing Fox Spirit Holy Cross": 509342435,
"Golden Obelisk Holy Cross/Use the Holy Cross": 509342436,
"Ice Rod Grapple Chest/Freeze the Blob and ascend With Orb": 509342437,
"Above Save Point/Chest": 509342438,
"Above Save Point Obscured/Hidden Path": 509342439,
"Guardhouse 1 Ledge/From Guardhouse 1 Chest": 509342440,
"Near Save Point/Chest": 509342441,
"Ambushed by Spiders/Beneath Spider Chest": 509342442,
"Near Telescope/Up on the Wall": 509342443,
"Ambushed by Spiders/Spider Chest": 509342444,
"Lower Dash Chest/Dash Across": 509342445,
"Lower Grapple Chest/Grapple Across": 509342446,
"Bombable Wall/Follow the Flowers": 509342447,
"Page On Teleporter/Page": 509342448,
"Forest Belltower Save Point/Near Save Point": 509342449,
"Forest Belltower - After Guard Captain/Chest": 509342450,
"East Bell/Forest Belltower - Obscured Near Bell Top Floor": 509342451,
"Forest Belltower Obscured/Obscured Beneath Bell Bottom Floor": 509342452,
"Forest Belltower Page/Page Pickup": 509342453,
"Forest Grave Path - Holy Cross Code by Grave/Single Money Chest": 509342454,
"Forest Grave Path - Above Gate/Chest": 509342455,
"Forest Grave Path - Obscured Chest/Behind the Trees": 509342456,
"Forest Grave Path - Upper Walkway/From the top of the Guardhouse": 509342457,
"The Hero's Sword/Forest Grave Path - Sword Pickup": 509342458,
"The Hero's Sword/Hero's Grave - Tooth Relic": 509342459,
"Fortress Courtyard - From East Belltower/Crack in the Wall": 509342460,
"Fortress Leaf Piles - Secret Chest/Dusty": 509342461,
"Fortress Arena/Hexagon Red": 509342462,
"Fortress Arena/Siege Engine|Vault Key Pickup": 509342463,
"Fortress East Shortcut - Chest Near Slimes/Mind the Custodians": 509342464,
"[West Wing] Candles Holy Cross/Use the Holy Cross": 509342465,
"Westmost Upper Room/[West Wing] Dark Room Chest 1": 509342466,
"Westmost Upper Room/[West Wing] Dark Room Chest 2": 509342467,
"[East Wing] Bombable Wall/Bomb the Wall": 509342468,
"[West Wing] Page Pickup/He will never visit the Far Shore": 509342469,
"Fortress Grave Path - Upper Walkway/Go Around the East Wing": 509342470,
"Vault Hero's Grave/Fortress Grave Path - Chest Right of Grave": 509342471,
"Vault Hero's Grave/Fortress Grave Path - Obscured Chest Left of Grave": 509342472,
"Vault Hero's Grave/Hero's Grave - Flowers Relic": 509342473,
"Bridge/Chest": 509342474,
"Cell Chest 1/Drop the Shortcut Rope": 509342475,
"Obscured Behind Waterfall/Muffling Bell": 509342476,
"Back Room Chest/Lose the Lure or take 2 Damage": 509342477,
"Cell Chest 2/Mind the Custodian": 509342478,
"Near Vault/Already Stolen": 509342479,
"Slorm Room/Tobias was Trapped Here Once...": 509342480,
"Escape Chest/Don't Kick Fimbleton!": 509342481,
"Grapple Above Hot Tub/Look Up": 509342482,
"Above Vault/Obscured Doorway Ledge": 509342483,
"Main Room Top Floor/Mind the Adult Frog": 509342484,
"Main Room Bottom Floor/Altar Chest": 509342485,
"Side Room Secret Passage/Upper Right Corner": 509342486,
"Side Room Chest/Oh No! Our Frogs! They're Dead!": 509342487,
"Side Room Grapple Secret/Grapple on Over": 509342488,
"Magic Orb Pickup/Frult Meeting": 509342489,
"The Librarian/Hexagon Green": 509342490,
"Library Hall/Holy Cross Chest": 509342491,
"Library Lab Chest by Shrine 2/Chest": 509342492,
"Library Lab Chest by Shrine 1/Chest": 509342493,
"Library Lab Chest by Shrine 3/Chest": 509342494,
"Library Lab by Fuse/Behind Chalkboard": 509342495,
@@ -369,13 +276,412 @@ poptracker_data: dict[str, int] = {
"[North] Page Pickup/Survival Tips": 509342699,
"[Southeast Lowlands] Ice Dagger Pickup/Ice Dagger Cave": 509342700,
"Hero's Grave/Effigy Relic": 509342701,
"[East] Bombable Wall/Break Bombable Wall": 509350705,
"[West] Upper Area Bombable Wall/Break Bombable Wall": 509350704,
"[East Wing] Bombable Wall/Break Bombable Wall": 509350703,
"Bombable Wall/Break Bombable Wall": 509350702,
"[Northwest] Bombable Wall/Break Bombable Wall": 509350701,
"[Southwest] Bombable Wall Near Fountain/Break Bombable Wall": 509350700,
"Cube Cave/Break Bombable Wall": 509350699,
"[Central] Bombable Wall/Break Bombable Wall": 509350698,
"Purgatory Pots/Pot 33": 509350697,
"Purgatory Pots/Pot 32": 509350696,
"Purgatory Pots/Pot 31": 509350695,
"Purgatory Pots/Pot 30": 509350694,
"Purgatory Pots/Pot 29": 509350693,
"Purgatory Pots/Pot 28": 509350692,
"Purgatory Pots/Pot 27": 509350691,
"Purgatory Pots/Pot 26": 509350690,
"Purgatory Pots/Pot 25": 509350689,
"Purgatory Pots/Pot 24": 509350688,
"Purgatory Pots/Pot 23": 509350687,
"Purgatory Pots/Pot 22": 509350686,
"Purgatory Pots/Pot 21": 509350685,
"Purgatory Pots/Pot 20": 509350684,
"Purgatory Pots/Pot 19": 509350683,
"Purgatory Pots/Pot 18": 509350682,
"Purgatory Pots/Pot 17": 509350681,
"Purgatory Pots/Pot 16": 509350680,
"Purgatory Pots/Pot 15": 509350679,
"Purgatory Pots/Pot 14": 509350678,
"Purgatory Pots/Pot 13": 509350677,
"Purgatory Pots/Pot 12": 509350676,
"Purgatory Pots/Pot 11": 509350675,
"Purgatory Pots/Pot 10": 509350674,
"Purgatory Pots/Pot 9": 509350673,
"Purgatory Pots/Pot 8": 509350672,
"Purgatory Pots/Pot 7": 509350671,
"Purgatory Pots/Pot 6": 509350670,
"Purgatory Pots/Pot 5": 509350669,
"Purgatory Pots/Pot 4": 509350668,
"Purgatory Pots/Pot 3": 509350667,
"Purgatory Pots/Pot 2": 509350666,
"Purgatory Pots/Pot 1": 509350665,
"[1F] Pots by Stairs/Pot 2": 509350664,
"[1F] Pots by Stairs/Pot 1": 509350663,
"Crates/Crate 9": 509350662,
"Crates/Crate 8": 509350661,
"Crates/Crate 7": 509350660,
"Crates/Crate 6": 509350659,
"Crates/Crate 5": 509350658,
"Crates/Crate 4": 509350657,
"Crates/Crate 3": 509350656,
"Crates/Crate 2": 509350655,
"Crates/Crate 1": 509350654,
"[Lowlands] Crates/Crate 2": 509350653,
"[Lowlands] Crates/Crate 1": 509350652,
"[West] Near Isolated Chest/Crate 5": 509350651,
"[West] Near Isolated Chest/Crate 4": 509350650,
"[West] Near Isolated Chest/Crate 3": 509350649,
"[West] Near Isolated Chest/Crate 2": 509350648,
"[West] Near Isolated Chest/Crate 1": 509350647,
"[West] Crates by Shooting Range/Crate 5": 509350646,
"[West] Crates by Shooting Range/Crate 4": 509350645,
"[West] Crates by Shooting Range/Crate 3": 509350644,
"[West] Crates by Shooting Range/Crate 2": 509350643,
"[West] Crates by Shooting Range/Crate 1": 509350642,
"[West] Near Isolated Chest/Explosive Pot 2": 509350641,
"[West] Near Isolated Chest/Explosive Pot 1": 509350640,
"[West] Explosive Pot above Shooting Range/Explosive Pot": 509350639,
"[West] Explosive Pots near Bombable Wall/Explosive Pot 2": 509350638,
"[West] Explosive Pots near Bombable Wall/Explosive Pot 1": 509350637,
"[Central] Crates near Shortcut Ladder/Crate 5": 509350636,
"[Central] Crates near Shortcut Ladder/Crate 4": 509350635,
"[Central] Crates near Shortcut Ladder/Crate 3": 509350634,
"[Central] Crates near Shortcut Ladder/Crate 2": 509350633,
"[Central] Crates near Shortcut Ladder/Crate 1": 509350632,
"[Central] Explosive Pots near Shortcut Ladder/Explosive Pot 2": 509350631,
"[Central] Explosive Pots near Shortcut Ladder/Explosive Pot 1": 509350630,
"[Back Entrance] Pots/Pot 5": 509350629,
"[Back Entrance] Pots/Pot 4": 509350628,
"[Back Entrance] Pots/Pot 3": 509350627,
"[Back Entrance] Pots/Pot 2": 509350626,
"[Back Entrance] Pots/Pot 1": 509350625,
"[Central] Explosive Pots near Monastery/Explosive Pot 2": 509350624,
"[Central] Explosive Pots near Monastery/Explosive Pot 1": 509350623,
"[East] Explosive Pot beneath Scaffolding/Explosive Pot": 509350622,
"[East] Explosive Pots/Explosive Pot 3": 509350621,
"[East] Explosive Pots/Explosive Pot 2": 509350620,
"[East] Explosive Pots/Explosive Pot 1": 509350619,
"Display Cases/Display Case 3": 509350618,
"Display Cases/Display Case 2": 509350617,
"Display Cases/Display Case 1": 509350616,
"Orb Room Explosive Pots/Explosive Pot 2": 509350615,
"Orb Room Explosive Pots/Explosive Pot 1": 509350614,
"Pots after Gate/Pot 2": 509350613,
"Pots after Gate/Pot 1": 509350612,
"Slorm Room/Pot": 509350611,
"Main Room Pots/Pot 2": 509350610,
"Main Room Pots/Pot 1": 509350609,
"Side Room Pots/Pot 3": 509350608,
"Side Room Pots/Pot 2": 509350607,
"Side Room Pots/Pot 1": 509350606,
"Pots above Orb Altar/Pot 2": 509350605,
"Pots above Orb Altar/Pot 1": 509350604,
"[Upper] Pots/Pot 6": 509350603,
"[Upper] Pots/Pot 5": 509350602,
"[Upper] Pots/Pot 4": 509350601,
"[Upper] Pots/Pot 3": 509350600,
"[Upper] Pots/Pot 2": 509350599,
"[Upper] Pots/Pot 1": 509350598,
"[South] Explosive Pot near Birds/Explosive Pot": 509350597,
"[West] Broken House/Table": 509350596,
"[West] Broken House/Pot 2": 509350595,
"[West] Broken House/Pot 1": 509350594,
"Fortress Arena/Pot 2": 509350593,
"Fortress Arena/Pot 1": 509350592,
"Fortress Leaf Piles - Secret Chest/Leaf Pile 4": 509350591,
"Fortress Leaf Piles - Secret Chest/Leaf Pile 3": 509350590,
"Fortress Leaf Piles - Secret Chest/Leaf Pile 2": 509350589,
"Fortress Leaf Piles - Secret Chest/Leaf Pile 1": 509350588,
"Barrels/Back Room Barrel 7": 509350587,
"Barrels/Back Room Barrel 6": 509350586,
"Barrels/Back Room Barrel 5": 509350585,
"[Northwest] Sign by Quarry Gate/Sign": 509350400,
"[Central] Sign South of Checkpoint/Sign": 509350401,
"[Central] Sign by Ruined Passage/Sign": 509350402,
"[East] Pots near Slimes/Pot 1": 509350403,
"[East] Pots near Slimes/Pot 2": 509350404,
"[East] Pots near Slimes/Pot 3": 509350405,
"[East] Pots near Slimes/Pot 4": 509350406,
"[East] Pots near Slimes/Pot 5": 509350407,
"[East] Forest Sign/Sign": 509350408,
"[East] Fortress Sign/Sign": 509350409,
"[North] Pots/Pot 1": 509350410,
"[North] Pots/Pot 2": 509350411,
"[North] Pots/Pot 3": 509350412,
"[North] Pots/Pot 4": 509350413,
"[West] Sign Near West Garden Entrance/Sign": 509350414,
"Stick House/Pot 1": 509350415,
"Stick House/Pot 2": 509350416,
"Stick House/Pot 3": 509350417,
"Stick House/Pot 4": 509350418,
"Stick House/Pot 5": 509350419,
"Stick House/Pot 6": 509350420,
"Stick House/Pot 7": 509350421,
"Ruined Shop/Pot 1": 509350422,
"Ruined Shop/Pot 2": 509350423,
"Ruined Shop/Pot 3": 509350424,
"Ruined Shop/Pot 4": 509350425,
"Ruined Shop/Pot 5": 509350426,
"Inside Hourglass Cave/Sign": 509350427,
"Pots by Slimes/Pot 1": 509350428,
"Pots by Slimes/Pot 2": 509350429,
"Pots by Slimes/Pot 3": 509350430,
"Pots by Slimes/Pot 4": 509350431,
"Pots by Slimes/Pot 5": 509350432,
"Pots by Slimes/Pot 6": 509350433,
"[Upper] Barrels/Barrel 1": 509350434,
"[Upper] Barrels/Barrel 2": 509350435,
"[Upper] Barrels/Barrel 3": 509350436,
"Pots after Guard Captain/Pot 1": 509350437,
"Pots after Guard Captain/Pot 2": 509350438,
"Pots after Guard Captain/Pot 3": 509350439,
"Pots after Guard Captain/Pot 4": 509350440,
"Pots after Guard Captain/Pot 5": 509350441,
"Pots after Guard Captain/Pot 6": 509350442,
"Pots after Guard Captain/Pot 7": 509350443,
"Pots after Guard Captain/Pot 8": 509350444,
"Pots after Guard Captain/Pot 9": 509350445,
"Pots/Pot 1": 509350446,
"Pots/Pot 2": 509350447,
"Pots/Pot 3": 509350448,
"Pots/Pot 4": 509350449,
"Pots/Pot 5": 509350450,
"Sign by Grave Path/Sign": 509350451,
"Sign by Guardhouse 1/Sign": 509350452,
"Pots by Grave Path/Pot 1": 509350453,
"Pots by Grave Path/Pot 2": 509350454,
"Pots by Grave Path/Pot 3": 509350455,
"Pots by Envoy/Pot 1": 509350456,
"Pots by Envoy/Pot 2": 509350457,
"Pots by Envoy/Pot 3": 509350458,
"Bottom Floor Pots/Pot 1": 509350459,
"Bottom Floor Pots/Pot 2": 509350460,
"Bottom Floor Pots/Pot 3": 509350461,
"Bottom Floor Pots/Pot 4": 509350462,
"Bottom Floor Pots/Pot 5": 509350463,
"[Side Room] Pots by Chest/Pot 1": 509350464,
"[Side Room] Pots by Chest/Pot 2": 509350465,
"[Side Room] Pots by Chest/Pot 3": 509350466,
"[Third Room] Barrels by Bridge/Barrel 1": 509350467,
"[Third Room] Barrels by Bridge/Barrel 2": 509350468,
"[Third Room] Barrels by Bridge/Barrel 3": 509350469,
"[Third Room] Barrels after Back Corridor/Barrel 1": 509350470,
"[Third Room] Barrels after Back Corridor/Barrel 2": 509350471,
"[Third Room] Barrels after Back Corridor/Barrel 3": 509350472,
"[Third Room] Barrels after Back Corridor/Barrel 4": 509350473,
"[Third Room] Barrels after Back Corridor/Barrel 5": 509350474,
"[Third Room] Barrels by West Turret/Barrel 1": 509350475,
"[Third Room] Barrels by West Turret/Barrel 2": 509350476,
"[Third Room] Barrels by West Turret/Barrel 3": 509350477,
"[Third Room] Pots by East Turret/Pot 1": 509350478,
"[Third Room] Pots by East Turret/Pot 2": 509350479,
"[Third Room] Pots by East Turret/Pot 3": 509350480,
"[Third Room] Pots by East Turret/Pot 4": 509350481,
"[Third Room] Pots by East Turret/Pot 5": 509350482,
"[Third Room] Pots by East Turret/Pot 6": 509350483,
"[Third Room] Pots by East Turret/Pot 7": 509350484,
"Barrels/Barrel 1": 509350485,
"Barrels/Barrel 2": 509350486,
"Pot Hallway Pots/Pot 1": 509350487,
"Pot Hallway Pots/Pot 2": 509350488,
"Pot Hallway Pots/Pot 3": 509350489,
"Pot Hallway Pots/Pot 4": 509350490,
"Pot Hallway Pots/Pot 5": 509350491,
"Pot Hallway Pots/Pot 6": 509350492,
"Pot Hallway Pots/Pot 7": 509350493,
"Pot Hallway Pots/Pot 8": 509350494,
"Pot Hallway Pots/Pot 9": 509350495,
"Pot Hallway Pots/Pot 10": 509350496,
"Pot Hallway Pots/Pot 11": 509350497,
"Pot Hallway Pots/Pot 12": 509350498,
"Pot Hallway Pots/Pot 13": 509350499,
"Pot Hallway Pots/Pot 14": 509350500,
"2nd Laser Room Pots/Pot 1": 509350501,
"2nd Laser Room Pots/Pot 2": 509350502,
"2nd Laser Room Pots/Pot 3": 509350503,
"2nd Laser Room Pots/Pot 4": 509350504,
"2nd Laser Room Pots/Pot 5": 509350505,
"[Southeast Lowlands] Ice Dagger Pickup/Pot 1": 509350506,
"[Southeast Lowlands] Ice Dagger Pickup/Pot 2": 509350507,
"[Southeast Lowlands] Ice Dagger Pickup/Pot 3": 509350508,
"Fire Pots/Fire Pot 1": 509350509,
"Fire Pots/Fire Pot 2": 509350510,
"Fire Pots/Fire Pot 3": 509350511,
"Fire Pots/Fire Pot 4": 509350512,
"Fire Pots/Fire Pot 5": 509350513,
"Fire Pots/Fire Pot 6": 509350514,
"Fire Pots/Fire Pot 7": 509350515,
"Fire Pots/Fire Pot 8": 509350516,
"Upper Fire Pot/Fire Pot": 509350517,
"[Entry] Pots/Pot 1": 509350518,
"[Entry] Pots/Pot 2": 509350519,
"[By Grave] Pots/Pot 1": 509350520,
"[By Grave] Pots/Pot 2": 509350521,
"[By Grave] Pots/Pot 3": 509350522,
"[By Grave] Pots/Pot 4": 509350523,
"[By Grave] Pots/Pot 5": 509350524,
"[By Grave] Pots/Pot 6": 509350525,
"[Central] Fire Pots/Fire Pot 1": 509350526,
"[Central] Fire Pots/Fire Pot 2": 509350527,
"[Central] Pots by Door/Pot 1": 509350528,
"[Central] Pots by Door/Pot 2": 509350529,
"[Central] Pots by Door/Pot 3": 509350530,
"[Central] Pots by Door/Pot 4": 509350531,
"[Central] Pots by Door/Pot 5": 509350532,
"[Central] Pots by Door/Pot 6": 509350533,
"[Central] Pots by Door/Pot 7": 509350534,
"[Central] Pots by Door/Pot 8": 509350535,
"[Central] Pots by Door/Pot 9": 509350536,
"[Central] Pots by Door/Pot 10": 509350537,
"[Central] Pots by Door/Pot 11": 509350538,
"[East Wing] Pots by Broken Checkpoint/Pot 1": 509350539,
"[East Wing] Pots by Broken Checkpoint/Pot 2": 509350540,
"[East Wing] Pots by Broken Checkpoint/Pot 3": 509350541,
"[West Wing] Pots by Checkpoint/Pot 1": 509350542,
"[West Wing] Pots by Checkpoint/Pot 2": 509350543,
"[West Wing] Pots by Checkpoint/Pot 3": 509350544,
"[West Wing] Pots by Overlook/Pot 1": 509350545,
"[West Wing] Pots by Overlook/Pot 2": 509350546,
"[West Wing] Slorm Room Pots/Pot 1": 509350547,
"[West Wing] Slorm Room Pots/Pot 2": 509350548,
"[West Wing] Slorm Room Pots/Pot 3": 509350549,
"[West Wing] Chest Room Pots/Pot 1": 509350550,
"[West Wing] Chest Room Pots/Pot 2": 509350551,
"[West Wing] Pots by Stairs to Basement/Pot 1": 509350552,
"[West Wing] Pots by Stairs to Basement/Pot 2": 509350553,
"[West Wing] Pots by Stairs to Basement/Pot 3": 509350554,
"Entry Spot/Pot 1": 509350555,
"Entry Spot/Pot 2": 509350556,
"Entry Spot/Crate 1": 509350557,
"Entry Spot/Crate 2": 509350558,
"Entry Spot/Crate 3": 509350559,
"Entry Spot/Crate 4": 509350560,
"Entry Spot/Crate 5": 509350561,
"Entry Spot/Crate 6": 509350562,
"Entry Spot/Crate 7": 509350563,
"Slorm Room Crates/Crate 1": 509350564,
"Slorm Room Crates/Crate 2": 509350565,
"Crates under Rope/Crate 1": 509350566,
"Crates under Rope/Crate 2": 509350567,
"Crates under Rope/Crate 3": 509350568,
"Crates under Rope/Crate 4": 509350569,
"Crates under Rope/Crate 5": 509350570,
"Crates under Rope/Crate 6": 509350571,
"Fuse Room Fire Pots/Fire Pot 1": 509350572,
"Fuse Room Fire Pots/Fire Pot 2": 509350573,
"Fuse Room Fire Pots/Fire Pot 3": 509350574,
"Barrels/Barrel by Back Room 1": 509350575,
"Barrels/Barrel by Back Room 2": 509350576,
"Barrels/Barrel by Back Room 3": 509350577,
"Barrels/Barrel by Back Room 4": 509350578,
"Barrels/Barrel by Back Room 5": 509350579,
"Barrels/Barrel by Back Room 6": 509350580,
"Barrels/Back Room Barrel 1": 509350581,
"Barrels/Back Room Barrel 2": 509350582,
"Barrels/Back Room Barrel 3": 509350583,
"[Powered Secret Room] Chest/Follow the Purple Energy Road": 509342400,
"[Entryway] Chest/Mind the Slorms": 509342401,
"[Third Room] Beneath Platform Chest/Run from the tentacles!": 509342402,
"[Third Room] Tentacle Chest/Water Sucks": 509342403,
"[Entryway] Obscured Behind Waterfall/You can just go in there": 509342404,
"[Save Room] Upper Floor Chest 1/Through the Power of Prayer": 509342405,
"[Save Room] Upper Floor Chest 2/Above the Fox Shrine": 509342406,
"[Second Room] Underwater Chest/Hidden Passage": 509342407,
"[Back Corridor] Right Secret/Hidden Path": 509342408,
"[Back Corridor] Left Secret/Behind the Slorms": 509342409,
"[Second Room] Obscured Behind Waterfall/Just go in there": 509342410,
"[Side Room] Chest By Pots/Just Climb up There": 509342411,
"[Side Room] Chest By Phrends/So Many Phrends!": 509342412,
"[Second Room] Page/Ruined Atoll Map": 509342413,
"[Passage To Dark Tomb] Page Pickup/Siege Engine": 509342414,
"[1F] Guarded By Lasers/Beside 3 Miasma Seekers": 509342415,
"[1F] Near Spikes/Mind the Miasma Seeker": 509342416,
"Birdcage Room/[2F] Bird Room": 509342417,
"[2F] Entryway Upper Walkway/Overlooking Miasma": 509342418,
"[1F] Library/By the Books": 509342419,
"[2F] Library/Behind the Ladder": 509342420,
"[2F] Guarded By Lasers/Before the big reveal...": 509342421,
"Birdcage Room/[2F] Bird Room Secret": 509342422,
"[1F] Library Secret/Pray to the Wallman": 509342423,
"Spike Maze Near Exit/Watch out!": 509342424,
"2nd Laser Room/Can you roll?": 509342425,
"1st Laser Room/Use a bomb?": 509342426,
"Spike Maze Upper Walkway/Just walk right!": 509342427,
"Skulls Chest/Move the Grave": 509342428,
"Spike Maze Near Stairs/In the Corner": 509342429,
"1st Laser Room Obscured/Follow the red laser of death": 509342430,
"Guardhouse 2 - Upper Floor/In the Mound": 509342431,
"Guardhouse 2 - Bottom Floor Secret/Hidden Hallway": 509342432,
"Guardhouse 1 Obscured/Upper Floor Obscured": 509342433,
"Guardhouse 1/Upper Floor": 509342434,
"Guardhouse 1 Ledge HC/Dancing Fox Spirit Holy Cross": 509342435,
"Golden Obelisk Holy Cross/Use the Holy Cross": 509342436,
"Ice Rod Grapple Chest/Freeze the Blob and ascend With Orb": 509342437,
"Above Save Point/Chest": 509342438,
"Above Save Point Obscured/Hidden Path": 509342439,
"Guardhouse 1 Ledge/From Guardhouse 1 Chest": 509342440,
"Near Save Point/Chest": 509342441,
"Ambushed by Spiders/Beneath Spider Chest": 509342442,
"Near Telescope/Up on the Wall": 509342443,
"Ambushed by Spiders/Spider Chest": 509342444,
"Lower Dash Chest/Dash Across": 509342445,
"Lower Grapple Chest/Grapple Across": 509342446,
"Bombable Wall/Follow the Flowers": 509342447,
"Page On Teleporter/Page": 509342448,
"Forest Belltower Save Point/Near Save Point": 509342449,
"Forest Belltower - After Guard Captain/Chest": 509342450,
"East Bell/Forest Belltower - Obscured Near Bell Top Floor": 509342451,
"Forest Belltower Obscured/Obscured Beneath Bell Bottom Floor": 509342452,
"Forest Belltower Page/Page Pickup": 509342453,
"Forest Grave Path - Holy Cross Code by Grave/Single Money Chest": 509342454,
"Forest Grave Path - Above Gate/Chest": 509342455,
"Forest Grave Path - Obscured Chest/Behind the Trees": 509342456,
"Forest Grave Path - Upper Walkway/From the top of the Guardhouse": 509342457,
"The Hero's Sword/Forest Grave Path - Sword Pickup": 509342458,
"The Hero's Sword/Hero's Grave - Tooth Relic": 509342459,
"Fortress Courtyard - From East Belltower/Crack in the Wall": 509342460,
"Fortress Leaf Piles - Secret Chest/Dusty": 509342461,
"Fortress Arena/Hexagon Red": 509342462,
"Fortress Arena/Siege Engine|Vault Key Pickup": 509342463,
"Fortress East Shortcut - Chest Near Slimes/Mind the Custodians": 509342464,
"[West Wing] Candles Holy Cross/Use the Holy Cross": 509342465,
"Westmost Upper Room/[West Wing] Dark Room Chest 1": 509342466,
"Westmost Upper Room/[West Wing] Dark Room Chest 2": 509342467,
"[East Wing] Bombable Wall/Bomb the Wall": 509342468,
"[West Wing] Page Pickup/He will never visit the Far Shore": 509342469,
"Fortress Grave Path - Upper Walkway/Go Around the East Wing": 509342470,
"Vault Hero's Grave/Fortress Grave Path - Chest Right of Grave": 509342471,
"Vault Hero's Grave/Fortress Grave Path - Obscured Chest Left of Grave": 509342472,
"Vault Hero's Grave/Hero's Grave - Flowers Relic": 509342473,
"Bridge/Chest": 509342474,
"Cell Chest 1/Drop the Shortcut Rope": 509342475,
"Obscured Behind Waterfall/Muffling Bell": 509342476,
"Back Room Chest/Lose the Lure or take 2 Damage": 509342477,
"Cell Chest 2/Mind the Custodian": 509342478,
"Near Vault/Already Stolen": 509342479,
"Slorm Room/Tobias was Trapped Here Once...": 509342480,
"Escape Chest/Don't Kick Fimbleton!": 509342481,
"Grapple Above Hot Tub/Look Up": 509342482,
"Above Vault/Obscured Doorway Ledge": 509342483,
"Main Room Top Floor/Mind the Adult Frog": 509342484,
"Main Room Bottom Floor/Altar Chest": 509342485,
"Side Room Secret Passage/Upper Right Corner": 509342486,
"Side Room Chest/Oh No! Our Frogs! They're Dead!": 509342487,
"Side Room Grapple Secret/Grapple on Over": 509342488,
"Magic Orb Pickup/Frult Meeting": 509342489,
"The Librarian/Hexagon Green": 509342490,
"Library Hall/Holy Cross Chest": 509342491,
"Library Lab Chest by Shrine 2/Chest": 509342492,
"Barrels/Back Room Barrel 4": 509350584,
}
# for setting up the poptracker integration
tracker_world = {
"map_page_maps": ["maps/maps_pop.json"],
"map_page_locations": ["locations/locations_pop_er.json"],
"map_page_locations": ["locations/locations_pop_er.json", "locations/locations_breakables.json"],
"map_page_setting_key": "Slot:{player}:Current Map",
"map_page_index": map_page_index,
"external_pack_key": "ut_poptracker_path",