mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-07 23:25:51 -08:00
Compare commits
14 Commits
NewSoupVi-
...
0.6.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9b35de7ee | ||
|
|
125d053b61 | ||
|
|
585cbf95a6 | ||
|
|
909565e5d9 | ||
|
|
a79423534c | ||
|
|
7a6fb5e35b | ||
|
|
6af34b66fb | ||
|
|
2974f7d11f | ||
|
|
edc0c89753 | ||
|
|
b1ff55dd06 | ||
|
|
f4b5422f66 | ||
|
|
d4ebace99f | ||
|
|
95e09c8e2a | ||
|
|
4623d59206 |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
||||
4
Main.py
4
Main.py
@@ -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:
|
||||
|
||||
21
NetUtils.py
21
NetUtils.py
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
1
Utils.py
1
Utils.py
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
```
|
||||
|
||||
1
setup.py
1
setup.py
@@ -63,7 +63,6 @@ non_apworlds: set[str] = {
|
||||
"Adventure",
|
||||
"ArchipIDLE",
|
||||
"Archipelago",
|
||||
"Clique",
|
||||
"Lufia II Ancient Cave",
|
||||
"Meritous",
|
||||
"Ocarina of Time",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}",
|
||||
}))
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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
|
||||
@@ -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(),
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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*~~
|
||||
@@ -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*~~
|
||||
@@ -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))
|
||||
|
||||
@@ -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]]
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
@@ -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"),
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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"))
|
||||
@@ -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).
|
||||
@@ -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!
|
||||
@@ -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
|
||||
@@ -1,5 +0,0 @@
|
||||
from test.bases import WorldTestBase
|
||||
|
||||
|
||||
class RLTestBase(WorldTestBase):
|
||||
game = "Rogue Legacy"
|
||||
@@ -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)
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user