mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-07-20 21:09:43 -07:00
Merge branch 'ArchipelagoMW:main' into main
This commit is contained in:
+2
-2
@@ -123,8 +123,8 @@ class WebWorldRegister(type):
|
||||
assert group.options, "A custom defined Option Group must contain at least one Option."
|
||||
# catch incorrectly titled versions of the prebuilt groups so they don't create extra groups
|
||||
title_name = group.name.title()
|
||||
if title_name in prebuilt_options:
|
||||
group.name = title_name
|
||||
assert title_name not in prebuilt_options or title_name == group.name, \
|
||||
f"Prebuilt group name \"{group.name}\" must be \"{title_name}\""
|
||||
|
||||
if group.name == "Item & Location Options":
|
||||
assert not any(option in item_and_loc_options for option in group.options), \
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import bisect
|
||||
import logging
|
||||
import pathlib
|
||||
import weakref
|
||||
from enum import Enum, auto
|
||||
from typing import Optional, Callable, List, Iterable
|
||||
from typing import Optional, Callable, List, Iterable, Tuple
|
||||
|
||||
from Utils import local_path
|
||||
from Utils import local_path, open_filename
|
||||
|
||||
|
||||
class Type(Enum):
|
||||
@@ -49,8 +52,10 @@ class Component:
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.display_name})"
|
||||
|
||||
|
||||
processes = weakref.WeakSet()
|
||||
|
||||
|
||||
def launch_subprocess(func: Callable, name: str = None):
|
||||
global processes
|
||||
import multiprocessing
|
||||
@@ -58,6 +63,7 @@ def launch_subprocess(func: Callable, name: str = None):
|
||||
process.start()
|
||||
processes.add(process)
|
||||
|
||||
|
||||
class SuffixIdentifier:
|
||||
suffixes: Iterable[str]
|
||||
|
||||
@@ -77,6 +83,80 @@ def launch_textclient():
|
||||
launch_subprocess(CommonClient.run_as_textclient, name="TextClient")
|
||||
|
||||
|
||||
def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, pathlib.Path]]:
|
||||
if not apworld_src:
|
||||
apworld_src = open_filename('Select APWorld file to install', (('APWorld', ('.apworld',)),))
|
||||
if not apworld_src:
|
||||
# user closed menu
|
||||
return
|
||||
|
||||
if not apworld_src.endswith(".apworld"):
|
||||
raise Exception(f"Wrong file format, looking for .apworld. File identified: {apworld_src}")
|
||||
|
||||
apworld_path = pathlib.Path(apworld_src)
|
||||
|
||||
module_name = pathlib.Path(apworld_path.name).stem
|
||||
try:
|
||||
import zipfile
|
||||
zipfile.ZipFile(apworld_path).open(module_name + "/__init__.py")
|
||||
except ValueError as e:
|
||||
raise Exception("Archive appears invalid or damaged.") from e
|
||||
except KeyError as e:
|
||||
raise Exception("Archive appears to not be an apworld. (missing __init__.py)") from e
|
||||
|
||||
import worlds
|
||||
if worlds.user_folder is None:
|
||||
raise Exception("Custom Worlds directory appears to not be writable.")
|
||||
for world_source in worlds.world_sources:
|
||||
if apworld_path.samefile(world_source.resolved_path):
|
||||
# Note that this doesn't check if the same world is already installed.
|
||||
# It only checks if the user is trying to install the apworld file
|
||||
# that comes from the installation location (worlds or custom_worlds)
|
||||
raise Exception(f"APWorld is already installed at {world_source.resolved_path}.")
|
||||
|
||||
# TODO: run generic test suite over the apworld.
|
||||
# TODO: have some kind of version system to tell from metadata if the apworld should be compatible.
|
||||
|
||||
target = pathlib.Path(worlds.user_folder) / apworld_path.name
|
||||
import shutil
|
||||
shutil.copyfile(apworld_path, target)
|
||||
|
||||
# If a module with this name is already loaded, then we can't load it now.
|
||||
# TODO: We need to be able to unload a world module,
|
||||
# so the user can update a world without restarting the application.
|
||||
found_already_loaded = False
|
||||
for loaded_world in worlds.world_sources:
|
||||
loaded_name = pathlib.Path(loaded_world.path).stem
|
||||
if module_name == loaded_name:
|
||||
found_already_loaded = True
|
||||
break
|
||||
if found_already_loaded:
|
||||
raise Exception(f"Installed APWorld successfully, but '{module_name}' is already loaded,\n"
|
||||
"so a Launcher restart is required to use the new installation.")
|
||||
world_source = worlds.WorldSource(str(target), is_zip=True)
|
||||
bisect.insort(worlds.world_sources, world_source)
|
||||
world_source.load()
|
||||
|
||||
return apworld_path, target
|
||||
|
||||
|
||||
def install_apworld(apworld_path: str = "") -> None:
|
||||
try:
|
||||
res = _install_apworld(apworld_path)
|
||||
if res is None:
|
||||
logging.info("Aborting APWorld installation.")
|
||||
return
|
||||
source, target = res
|
||||
except Exception as e:
|
||||
import Utils
|
||||
Utils.messagebox(e.__class__.__name__, str(e), error=True)
|
||||
logging.exception(e)
|
||||
else:
|
||||
import Utils
|
||||
logging.info(f"Installed APWorld successfully, copied {source} to {target}.")
|
||||
Utils.messagebox("Install complete.", f"Installed APWorld from {source}.")
|
||||
|
||||
|
||||
components: List[Component] = [
|
||||
# Launcher
|
||||
Component('Launcher', 'Launcher', component_type=Type.HIDDEN),
|
||||
@@ -84,6 +164,7 @@ components: List[Component] = [
|
||||
Component('Host', 'MultiServer', 'ArchipelagoServer', cli=True,
|
||||
file_identifier=SuffixIdentifier('.archipelago', '.zip')),
|
||||
Component('Generate', 'Generate', cli=True),
|
||||
Component("Install APWorld", func=install_apworld, file_identifier=SuffixIdentifier(".apworld")),
|
||||
Component('Text Client', 'CommonClient', 'ArchipelagoTextClient', func=launch_textclient),
|
||||
Component('Links Awakening DX Client', 'LinksAwakeningClient',
|
||||
file_identifier=SuffixIdentifier('.apladx')),
|
||||
|
||||
+15
-5
@@ -1,16 +1,22 @@
|
||||
import importlib
|
||||
import importlib.util
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
import zipimport
|
||||
import time
|
||||
import dataclasses
|
||||
from typing import Dict, List, TypedDict, Optional
|
||||
from typing import Dict, List, TypedDict
|
||||
|
||||
from Utils import local_path, user_path
|
||||
|
||||
local_folder = os.path.dirname(__file__)
|
||||
user_folder = user_path("worlds") if user_path() != local_path() else None
|
||||
user_folder = user_path("worlds") if user_path() != local_path() else user_path("custom_worlds")
|
||||
try:
|
||||
os.makedirs(user_folder, exist_ok=True)
|
||||
except OSError: # can't access/write?
|
||||
user_folder = None
|
||||
|
||||
__all__ = {
|
||||
"network_data_package",
|
||||
@@ -44,7 +50,7 @@ class WorldSource:
|
||||
path: str # typically relative path from this module
|
||||
is_zip: bool = False
|
||||
relative: bool = True # relative to regular world import folder
|
||||
time_taken: Optional[float] = None
|
||||
time_taken: float = -1.0
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({self.path}, is_zip={self.is_zip}, relative={self.relative})"
|
||||
@@ -88,7 +94,6 @@ class WorldSource:
|
||||
print(f"Could not load world {self}:", file=file_like)
|
||||
traceback.print_exc(file=file_like)
|
||||
file_like.seek(0)
|
||||
import logging
|
||||
logging.exception(file_like.read())
|
||||
failed_world_loads.append(os.path.basename(self.path).rsplit(".", 1)[0])
|
||||
return False
|
||||
@@ -103,7 +108,12 @@ for folder in (folder for folder in (user_folder, local_folder) if folder):
|
||||
if not entry.name.startswith(("_", ".")):
|
||||
file_name = entry.name if relative else os.path.join(folder, entry.name)
|
||||
if entry.is_dir():
|
||||
world_sources.append(WorldSource(file_name, relative=relative))
|
||||
if os.path.isfile(os.path.join(entry.path, '__init__.py')):
|
||||
world_sources.append(WorldSource(file_name, relative=relative))
|
||||
elif os.path.isfile(os.path.join(entry.path, '__init__.pyc')):
|
||||
world_sources.append(WorldSource(file_name, relative=relative))
|
||||
else:
|
||||
logging.warning(f"excluding {entry.name} from world sources because it has no __init__.py")
|
||||
elif entry.is_file() and entry.name.endswith(".apworld"):
|
||||
world_sources.append(WorldSource(file_name, is_zip=True, relative=relative))
|
||||
|
||||
|
||||
@@ -168,6 +168,7 @@ async def _game_watcher(ctx: BizHawkClientContext):
|
||||
ctx.auth = None
|
||||
ctx.username = None
|
||||
ctx.client_handler = None
|
||||
ctx.finished_game = False
|
||||
await ctx.disconnect(False)
|
||||
ctx.rom_hash = rom_hash
|
||||
|
||||
|
||||
@@ -28,6 +28,11 @@ class kill_switch:
|
||||
logger.debug("kill_switch: Add switch")
|
||||
cls._to_kill.append(value)
|
||||
|
||||
@classmethod
|
||||
def kill(cls, value):
|
||||
logger.info(f"kill_switch: Process cleanup for 1 process")
|
||||
value._clean(verbose=False)
|
||||
|
||||
@classmethod
|
||||
def kill_all(cls):
|
||||
logger.info(f"kill_switch: Process cleanup for {len(cls._to_kill)} processes")
|
||||
@@ -116,7 +121,7 @@ class SC2Process:
|
||||
async def __aexit__(self, *args):
|
||||
logger.exception("async exit")
|
||||
await self._close_connection()
|
||||
kill_switch.kill_all()
|
||||
kill_switch.kill(self)
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
|
||||
@property
|
||||
|
||||
+19
-18
@@ -2,7 +2,8 @@ from __future__ import annotations
|
||||
|
||||
from typing import Dict
|
||||
|
||||
from Options import Choice, Option, DefaultOnToggle, DeathLink, Range, Toggle
|
||||
from dataclasses import dataclass
|
||||
from Options import Choice, Option, DefaultOnToggle, DeathLink, Range, Toggle, PerGameCommonOptions
|
||||
|
||||
|
||||
class FreeincarnateMax(Range):
|
||||
@@ -223,22 +224,22 @@ class StartCastle(Choice):
|
||||
option_white = 2
|
||||
default = option_yellow
|
||||
|
||||
@dataclass
|
||||
class AdventureOptions(PerGameCommonOptions):
|
||||
dragon_slay_check: DragonSlayCheck
|
||||
death_link: DeathLink
|
||||
bat_logic: BatLogic
|
||||
freeincarnate_max: FreeincarnateMax
|
||||
dragon_rando_type: DragonRandoType
|
||||
connector_multi_slot: ConnectorMultiSlot
|
||||
yorgle_speed: YorgleStartingSpeed
|
||||
yorgle_min_speed: YorgleMinimumSpeed
|
||||
grundle_speed: GrundleStartingSpeed
|
||||
grundle_min_speed: GrundleMinimumSpeed
|
||||
rhindle_speed: RhindleStartingSpeed
|
||||
rhindle_min_speed: RhindleMinimumSpeed
|
||||
difficulty_switch_a: DifficultySwitchA
|
||||
difficulty_switch_b: DifficultySwitchB
|
||||
start_castle: StartCastle
|
||||
|
||||
adventure_option_definitions: Dict[str, type(Option)] = {
|
||||
"dragon_slay_check": DragonSlayCheck,
|
||||
"death_link": DeathLink,
|
||||
"bat_logic": BatLogic,
|
||||
"freeincarnate_max": FreeincarnateMax,
|
||||
"dragon_rando_type": DragonRandoType,
|
||||
"connector_multi_slot": ConnectorMultiSlot,
|
||||
"yorgle_speed": YorgleStartingSpeed,
|
||||
"yorgle_min_speed": YorgleMinimumSpeed,
|
||||
"grundle_speed": GrundleStartingSpeed,
|
||||
"grundle_min_speed": GrundleMinimumSpeed,
|
||||
"rhindle_speed": RhindleStartingSpeed,
|
||||
"rhindle_min_speed": RhindleMinimumSpeed,
|
||||
"difficulty_switch_a": DifficultySwitchA,
|
||||
"difficulty_switch_b": DifficultySwitchB,
|
||||
"start_castle": StartCastle,
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from BaseClasses import MultiWorld, Region, Entrance, LocationProgressType
|
||||
from Options import PerGameCommonOptions
|
||||
from .Locations import location_table, LocationData, AdventureLocation, dragon_room_to_region
|
||||
|
||||
|
||||
@@ -24,7 +25,7 @@ def connect(world: MultiWorld, player: int, source: str, target: str, rule: call
|
||||
connect(world, player, target, source, rule, True)
|
||||
|
||||
|
||||
def create_regions(multiworld: MultiWorld, player: int, dragon_rooms: []) -> None:
|
||||
def create_regions(options: PerGameCommonOptions, multiworld: MultiWorld, player: int, dragon_rooms: []) -> None:
|
||||
|
||||
menu = Region("Menu", player, multiworld)
|
||||
|
||||
@@ -74,7 +75,7 @@ def create_regions(multiworld: MultiWorld, player: int, dragon_rooms: []) -> Non
|
||||
credits_room_far_side.exits.append(Entrance(player, "CreditsFromFarSide", credits_room_far_side))
|
||||
multiworld.regions.append(credits_room_far_side)
|
||||
|
||||
dragon_slay_check = multiworld.dragon_slay_check[player].value
|
||||
dragon_slay_check = options.dragon_slay_check.value
|
||||
priority_locations = determine_priority_locations(multiworld, dragon_slay_check)
|
||||
|
||||
for name, location_data in location_table.items():
|
||||
|
||||
@@ -6,7 +6,7 @@ from BaseClasses import LocationProgressType
|
||||
|
||||
def set_rules(self) -> None:
|
||||
world = self.multiworld
|
||||
use_bat_logic = world.bat_logic[self.player].value == BatLogic.option_use_logic
|
||||
use_bat_logic = self.options.bat_logic.value == BatLogic.option_use_logic
|
||||
|
||||
set_rule(world.get_entrance("YellowCastlePort", self.player),
|
||||
lambda state: state.has("Yellow Key", self.player))
|
||||
@@ -28,7 +28,7 @@ def set_rules(self) -> None:
|
||||
lambda state: state.has("Bridge", self.player) or
|
||||
state.has("Magnet", self.player))
|
||||
|
||||
dragon_slay_check = world.dragon_slay_check[self.player].value
|
||||
dragon_slay_check = self.options.dragon_slay_check.value
|
||||
if dragon_slay_check:
|
||||
if self.difficulty_switch_b == DifficultySwitchB.option_hard_with_unlock_item:
|
||||
set_rule(world.get_location("Slay Yorgle", self.player),
|
||||
|
||||
@@ -15,7 +15,8 @@ from Options import AssembleOptions
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from Fill import fill_restrictive
|
||||
from worlds.generic.Rules import add_rule, set_rule
|
||||
from .Options import adventure_option_definitions, DragonRandoType, DifficultySwitchA, DifficultySwitchB
|
||||
from .Options import DragonRandoType, DifficultySwitchA, DifficultySwitchB, \
|
||||
AdventureOptions
|
||||
from .Rom import get_base_rom_bytes, get_base_rom_path, AdventureDeltaPatch, apply_basepatch, \
|
||||
AdventureAutoCollectLocation
|
||||
from .Items import item_table, ItemData, nothing_item_id, event_table, AdventureItem, standard_item_max
|
||||
@@ -109,7 +110,7 @@ class AdventureWorld(World):
|
||||
game: ClassVar[str] = "Adventure"
|
||||
web: ClassVar[WebWorld] = AdventureWeb()
|
||||
|
||||
option_definitions: ClassVar[Dict[str, AssembleOptions]] = adventure_option_definitions
|
||||
options_dataclass = AdventureOptions
|
||||
settings: ClassVar[AdventureSettings]
|
||||
item_name_to_id: ClassVar[Dict[str, int]] = {name: data.id for name, data in item_table.items()}
|
||||
location_name_to_id: ClassVar[Dict[str, int]] = {name: data.location_id for name, data in location_table.items()}
|
||||
@@ -149,18 +150,18 @@ class AdventureWorld(World):
|
||||
bytearray(f"ADVENTURE{__version__.replace('.', '')[:3]}_{self.player}_{self.multiworld.seed}", "utf8")[:21]
|
||||
self.rom_name.extend([0] * (21 - len(self.rom_name)))
|
||||
|
||||
self.dragon_rando_type = self.multiworld.dragon_rando_type[self.player].value
|
||||
self.dragon_slay_check = self.multiworld.dragon_slay_check[self.player].value
|
||||
self.connector_multi_slot = self.multiworld.connector_multi_slot[self.player].value
|
||||
self.yorgle_speed = self.multiworld.yorgle_speed[self.player].value
|
||||
self.yorgle_min_speed = self.multiworld.yorgle_min_speed[self.player].value
|
||||
self.grundle_speed = self.multiworld.grundle_speed[self.player].value
|
||||
self.grundle_min_speed = self.multiworld.grundle_min_speed[self.player].value
|
||||
self.rhindle_speed = self.multiworld.rhindle_speed[self.player].value
|
||||
self.rhindle_min_speed = self.multiworld.rhindle_min_speed[self.player].value
|
||||
self.difficulty_switch_a = self.multiworld.difficulty_switch_a[self.player].value
|
||||
self.difficulty_switch_b = self.multiworld.difficulty_switch_b[self.player].value
|
||||
self.start_castle = self.multiworld.start_castle[self.player].value
|
||||
self.dragon_rando_type = self.options.dragon_rando_type.value
|
||||
self.dragon_slay_check = self.options.dragon_slay_check.value
|
||||
self.connector_multi_slot = self.options.connector_multi_slot.value
|
||||
self.yorgle_speed = self.options.yorgle_speed.value
|
||||
self.yorgle_min_speed = self.options.yorgle_min_speed.value
|
||||
self.grundle_speed = self.options.grundle_speed.value
|
||||
self.grundle_min_speed = self.options.grundle_min_speed.value
|
||||
self.rhindle_speed = self.options.rhindle_speed.value
|
||||
self.rhindle_min_speed = self.options.rhindle_min_speed.value
|
||||
self.difficulty_switch_a = self.options.difficulty_switch_a.value
|
||||
self.difficulty_switch_b = self.options.difficulty_switch_b.value
|
||||
self.start_castle = self.options.start_castle.value
|
||||
self.created_items = 0
|
||||
|
||||
if self.dragon_slay_check == 0:
|
||||
@@ -227,7 +228,7 @@ class AdventureWorld(World):
|
||||
extra_filler_count = num_locations - self.created_items
|
||||
|
||||
# traps would probably go here, if enabled
|
||||
freeincarnate_max = self.multiworld.freeincarnate_max[self.player].value
|
||||
freeincarnate_max = self.options.freeincarnate_max.value
|
||||
actual_freeincarnates = min(extra_filler_count, freeincarnate_max)
|
||||
self.multiworld.itempool += [self.create_item("Freeincarnate") for _ in range(actual_freeincarnates)]
|
||||
self.created_items += actual_freeincarnates
|
||||
@@ -247,7 +248,7 @@ class AdventureWorld(World):
|
||||
self.created_items += 1
|
||||
|
||||
def create_regions(self) -> None:
|
||||
create_regions(self.multiworld, self.player, self.dragon_rooms)
|
||||
create_regions(self.options, self.multiworld, self.player, self.dragon_rooms)
|
||||
|
||||
set_rules = set_rules
|
||||
|
||||
@@ -354,7 +355,7 @@ class AdventureWorld(World):
|
||||
auto_collect_locations: [AdventureAutoCollectLocation] = []
|
||||
local_item_to_location: {int, int} = {}
|
||||
bat_no_touch_locs: [LocationData] = []
|
||||
bat_logic: int = self.multiworld.bat_logic[self.player].value
|
||||
bat_logic: int = self.options.bat_logic.value
|
||||
try:
|
||||
rom_deltas: { int, int } = {}
|
||||
self.place_dragons(rom_deltas)
|
||||
@@ -421,7 +422,7 @@ class AdventureWorld(World):
|
||||
item_position_data_start = get_item_position_data_start(unplaced_item.table_index)
|
||||
rom_deltas[item_position_data_start] = 0xff
|
||||
|
||||
if self.multiworld.connector_multi_slot[self.player].value:
|
||||
if self.options.connector_multi_slot.value:
|
||||
rom_deltas[connector_port_offset] = (self.player & 0xff)
|
||||
else:
|
||||
rom_deltas[connector_port_offset] = 0
|
||||
|
||||
@@ -185,7 +185,7 @@ class AquariaLocations:
|
||||
"Mithalas City, second bulb at the end of the top path": 698040,
|
||||
"Mithalas City, bulb in the top path": 698036,
|
||||
"Mithalas City, Mithalas Pot": 698174,
|
||||
"Mithalas City, urn in the Cathedral flower tube entrance": 698128,
|
||||
"Mithalas City, urn in the Castle flower tube entrance": 698128,
|
||||
}
|
||||
|
||||
locations_mithalas_city_fishpass = {
|
||||
@@ -246,7 +246,7 @@ class AquariaLocations:
|
||||
"Kelp Forest top left area, bulb in the bottom left clearing": 698044,
|
||||
"Kelp Forest top left area, bulb in the path down from the top left clearing": 698045,
|
||||
"Kelp Forest top left area, bulb in the top left clearing": 698046,
|
||||
"Kelp Forest top left, Jelly Egg": 698185,
|
||||
"Kelp Forest top left area, Jelly Egg": 698185,
|
||||
}
|
||||
|
||||
locations_forest_tl_fp = {
|
||||
@@ -332,7 +332,7 @@ class AquariaLocations:
|
||||
}
|
||||
|
||||
locations_veil_tr_l = {
|
||||
"The Veil top right area, bulb in the top of the waterfall": 698080,
|
||||
"The Veil top right area, bulb at the top of the waterfall": 698080,
|
||||
"The Veil top right area, Transturtle": 698210,
|
||||
}
|
||||
|
||||
|
||||
@@ -771,6 +771,7 @@ class AquariaRegions:
|
||||
self.__connect_regions("Sunken City left area", "Sunken City boss area",
|
||||
self.sunken_city_l, self.sunken_city_boss,
|
||||
lambda state: _has_beast_form(state, self.player) and
|
||||
_has_sun_form(state, self.player) and
|
||||
_has_energy_form(state, self.player) and
|
||||
_has_bind_song(state, self.player))
|
||||
|
||||
@@ -983,7 +984,7 @@ class AquariaRegions:
|
||||
lambda state: _has_damaging_item(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Mithalas City, third urn in the city reserve", self.player),
|
||||
lambda state: _has_damaging_item(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Mithalas City, urn in the Cathedral flower tube entrance", self.player),
|
||||
add_rule(self.multiworld.get_location("Mithalas City, urn in the Castle flower tube entrance", self.player),
|
||||
lambda state: _has_damaging_item(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Mithalas City Castle, urn in the bedroom", self.player),
|
||||
lambda state: _has_damaging_item(state, self.player))
|
||||
@@ -1023,7 +1024,7 @@ class AquariaRegions:
|
||||
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Sun Worm path, second cliff bulb", self.player),
|
||||
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
|
||||
add_rule(self.multiworld.get_location("The Veil top right area, bulb in the top of the waterfall", self.player),
|
||||
add_rule(self.multiworld.get_location("The Veil top right area, bulb at the top of the waterfall", self.player),
|
||||
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
|
||||
|
||||
def __adjusting_under_rock_location(self) -> None:
|
||||
@@ -1175,7 +1176,7 @@ class AquariaRegions:
|
||||
self.multiworld.get_location("Sun Worm path, second cliff bulb",
|
||||
self.player).item_rule =\
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("The Veil top right area, bulb in the top of the waterfall",
|
||||
self.multiworld.get_location("The Veil top right area, bulb at the top of the waterfall",
|
||||
self.player).item_rule =\
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Bubble Cave, bulb in the left cave wall",
|
||||
|
||||
@@ -167,14 +167,10 @@ class AquariaWorld(World):
|
||||
self.__pre_fill_item("Transturtle Simon Says", "Arnassi Ruins, Transturtle", precollected)
|
||||
self.__pre_fill_item("Transturtle Arnassi Ruins", "Simon Says area, Transturtle", precollected)
|
||||
for name, data in item_table.items():
|
||||
if name in precollected:
|
||||
precollected.remove(name)
|
||||
self.multiworld.itempool.append(self.create_item(self.get_filler_item_name()))
|
||||
else:
|
||||
if name not in self.exclude:
|
||||
for i in range(data.count):
|
||||
item = self.create_item(name)
|
||||
self.multiworld.itempool.append(item)
|
||||
if name not in self.exclude:
|
||||
for i in range(data.count):
|
||||
item = self.create_item(name)
|
||||
self.multiworld.itempool.append(item)
|
||||
|
||||
def set_rules(self) -> None:
|
||||
"""
|
||||
|
||||
@@ -56,7 +56,7 @@ after_home_water_locations = [
|
||||
"Mithalas City, second bulb at the end of the top path",
|
||||
"Mithalas City, bulb in the top path",
|
||||
"Mithalas City, Mithalas Pot",
|
||||
"Mithalas City, urn in the Cathedral flower tube entrance",
|
||||
"Mithalas City, urn in the Castle flower tube entrance",
|
||||
"Mithalas City, Doll",
|
||||
"Mithalas City, urn inside a home fish pass",
|
||||
"Mithalas City Castle, bulb in the flesh hole",
|
||||
@@ -93,7 +93,7 @@ after_home_water_locations = [
|
||||
"Kelp Forest top left area, bulb in the bottom left clearing",
|
||||
"Kelp Forest top left area, bulb in the path down from the top left clearing",
|
||||
"Kelp Forest top left area, bulb in the top left clearing",
|
||||
"Kelp Forest top left, Jelly Egg",
|
||||
"Kelp Forest top left area, Jelly Egg",
|
||||
"Kelp Forest top left area, bulb close to the Verse Egg",
|
||||
"Kelp Forest top left area, Verse Egg",
|
||||
"Kelp Forest top right area, bulb under the rock in the right path",
|
||||
@@ -125,7 +125,7 @@ after_home_water_locations = [
|
||||
"Turtle cave, Urchin Costume",
|
||||
"The Veil top right area, bulb in the middle of the wall jump cliff",
|
||||
"The Veil top right area, Golden Starfish",
|
||||
"The Veil top right area, bulb in the top of the waterfall",
|
||||
"The Veil top right area, bulb at the top of the waterfall",
|
||||
"The Veil top right area, Transturtle",
|
||||
"The Veil bottom area, bulb in the left path",
|
||||
"The Veil bottom area, bulb in the spirit path",
|
||||
|
||||
@@ -20,14 +20,14 @@ class BeastFormAccessTest(AquariaTestBase):
|
||||
"Mithalas City, second bulb at the end of the top path",
|
||||
"Mithalas City, bulb in the top path",
|
||||
"Mithalas City, Mithalas Pot",
|
||||
"Mithalas City, urn in the Cathedral flower tube entrance",
|
||||
"Mithalas City, urn in the Castle flower tube entrance",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"Mithalas Cathedral, Mithalan Dress",
|
||||
"Turtle cave, bulb in Bubble Cliff",
|
||||
"Turtle cave, Urchin Costume",
|
||||
"Sun Worm path, first cliff bulb",
|
||||
"Sun Worm path, second cliff bulb",
|
||||
"The Veil top right area, bulb in the top of the waterfall",
|
||||
"The Veil top right area, bulb at the top of the waterfall",
|
||||
"Bubble Cave, bulb in the left cave wall",
|
||||
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
|
||||
"Bubble Cave, Verse Egg",
|
||||
|
||||
@@ -30,7 +30,7 @@ class UNoProgressionHardHiddenTest(AquariaTestBase):
|
||||
"Final Boss area, bulb in the boss third form room",
|
||||
"Sun Worm path, first cliff bulb",
|
||||
"Sun Worm path, second cliff bulb",
|
||||
"The Veil top right area, bulb in the top of the waterfall",
|
||||
"The Veil top right area, bulb at the top of the waterfall",
|
||||
"Bubble Cave, bulb in the left cave wall",
|
||||
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
|
||||
"Bubble Cave, Verse Egg",
|
||||
|
||||
@@ -30,7 +30,7 @@ class UNoProgressionHardHiddenTest(AquariaTestBase):
|
||||
"Final Boss area, bulb in the boss third form room",
|
||||
"Sun Worm path, first cliff bulb",
|
||||
"Sun Worm path, second cliff bulb",
|
||||
"The Veil top right area, bulb in the top of the waterfall",
|
||||
"The Veil top right area, bulb at the top of the waterfall",
|
||||
"Bubble Cave, bulb in the left cave wall",
|
||||
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
|
||||
"Bubble Cave, Verse Egg",
|
||||
|
||||
@@ -18,6 +18,9 @@ class SunFormAccessTest(AquariaTestBase):
|
||||
"Abyss right area, bulb behind the rock in the whale room",
|
||||
"Octopus Cave, Dumbo Egg",
|
||||
"Beating Octopus Prime",
|
||||
"Sunken City, bulb on top of the boss area",
|
||||
"Beating the Golem",
|
||||
"Sunken City cleared",
|
||||
"Final Boss area, bulb in the boss third form room",
|
||||
"Objective complete"
|
||||
]
|
||||
|
||||
@@ -109,7 +109,7 @@ class BombRushCyberfunkWorld(World):
|
||||
def create_items(self):
|
||||
rep_locations: int = 87
|
||||
if self.options.skip_polo_photos:
|
||||
rep_locations -= 18
|
||||
rep_locations -= 17
|
||||
|
||||
self.options.total_rep.round_to_nearest_step()
|
||||
rep_counts = self.options.total_rep.get_rep_item_counts(self.random, rep_locations)
|
||||
@@ -157,7 +157,7 @@ class BombRushCyberfunkWorld(World):
|
||||
self.get_region(n).add_exits(region_exits[n])
|
||||
|
||||
for index, loc in enumerate(location_table):
|
||||
if self.options.skip_polo_photos and "Polo" in loc["name"]:
|
||||
if self.options.skip_polo_photos and "Polo" in loc["game_id"]:
|
||||
continue
|
||||
stage: Region = self.get_region(loc["stage"])
|
||||
stage.add_locations({loc["name"]: base_id + index})
|
||||
|
||||
@@ -64,3 +64,4 @@ item_name_groups = ({
|
||||
})
|
||||
item_name_groups['Horizontal'] = item_name_groups['Cloak'] | item_name_groups['CDash']
|
||||
item_name_groups['Vertical'] = item_name_groups['Claw'] | {'Monarch_Wings'}
|
||||
item_name_groups['Skills'] |= item_name_groups['Vertical'] | item_name_groups['Horizontal']
|
||||
|
||||
@@ -2,6 +2,7 @@ import typing
|
||||
import re
|
||||
from .ExtractedData import logic_options, starts, pool_options
|
||||
from .Rules import cost_terms
|
||||
from schema import And, Schema, Optional
|
||||
|
||||
from Options import Option, DefaultOnToggle, Toggle, Choice, Range, OptionDict, NamedRange, DeathLink
|
||||
from .Charms import vanilla_costs, names as charm_names
|
||||
@@ -212,7 +213,7 @@ class MinimumEggPrice(Range):
|
||||
Only takes effect if the EggSlotShops option is greater than 0."""
|
||||
display_name = "Minimum Egg Price"
|
||||
range_start = 1
|
||||
range_end = 21
|
||||
range_end = 20
|
||||
default = 1
|
||||
|
||||
|
||||
@@ -296,6 +297,9 @@ class PlandoCharmCosts(OptionDict):
|
||||
This is set after any random Charm Notch costs, if applicable."""
|
||||
display_name = "Charm Notch Cost Plando"
|
||||
valid_keys = frozenset(charm_names)
|
||||
schema = Schema({
|
||||
Optional(name): And(int, lambda n: 6 >= n >= 0) for name in charm_names
|
||||
})
|
||||
|
||||
def get_costs(self, charm_costs: typing.List[int]) -> typing.List[int]:
|
||||
for name, cost in self.value.items():
|
||||
|
||||
@@ -308,14 +308,14 @@ class CorSkipToggle(Toggle):
|
||||
|
||||
Full Cor Skip is also affected by this Toggle.
|
||||
"""
|
||||
display_name = "CoR Skip Toggle."
|
||||
display_name = "CoR Skip Toggle"
|
||||
default = False
|
||||
|
||||
|
||||
class CustomItemPoolQuantity(ItemDict):
|
||||
"""Add more of an item into the itempool. Note: You cannot take out items from the pool."""
|
||||
display_name = "Custom Item Pool"
|
||||
verify_item_name = True
|
||||
valid_keys = default_itempool_option.keys()
|
||||
default = default_itempool_option
|
||||
|
||||
|
||||
|
||||
@@ -430,13 +430,13 @@ class KH2World(World):
|
||||
"""
|
||||
for item, value in self.options.start_inventory.value.items():
|
||||
if item in ActionAbility_Table \
|
||||
or item in SupportAbility_Table or exclusion_item_table["StatUps"] \
|
||||
or item in SupportAbility_Table or item in exclusion_item_table["StatUps"] \
|
||||
or item in DonaldAbility_Table or item in GoofyAbility_Table:
|
||||
# cannot have more than the quantity for abilties
|
||||
if value > item_dictionary_table[item].quantity:
|
||||
logging.info(
|
||||
f"{self.multiworld.get_file_safe_player_name(self.player)} cannot have more than {item_dictionary_table[item].quantity} of {item}"
|
||||
f"Changing the amount to the max amount")
|
||||
f"{self.multiworld.get_file_safe_player_name(self.player)} cannot have more than {item_dictionary_table[item].quantity} of {item}."
|
||||
f" Changing the amount to the max amount")
|
||||
value = item_dictionary_table[item].quantity
|
||||
self.item_quantity_dict[item] -= value
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from schema import And, Optional, Or, Schema
|
||||
|
||||
from Options import Accessibility, Choice, DeathLinkMixin, DefaultOnToggle, OptionDict, PerGameCommonOptions, \
|
||||
PlandoConnections, Range, StartInventoryPool, Toggle, Visibility
|
||||
from worlds.messenger.portals import CHECKPOINTS, PORTALS, SHOP_POINTS
|
||||
from .portals import CHECKPOINTS, PORTALS, SHOP_POINTS
|
||||
|
||||
|
||||
class MessengerAccessibility(Accessibility):
|
||||
|
||||
+1
-2
@@ -306,8 +306,7 @@ def write_tokens(world: "MLSSWorld", patch: MLSSProcedurePatch) -> None:
|
||||
if world.options.scale_stats:
|
||||
patch.write_token(APTokenTypes.WRITE, 0xD00002, bytes([0x1]))
|
||||
|
||||
if world.options.xp_multiplier:
|
||||
patch.write_token(APTokenTypes.WRITE, 0xD00003, bytes([world.options.xp_multiplier.value]))
|
||||
patch.write_token(APTokenTypes.WRITE, 0xD00003, bytes([world.options.xp_multiplier.value]))
|
||||
|
||||
if world.options.tattle_hp:
|
||||
patch.write_token(APTokenTypes.WRITE, 0xD00000, bytes([0x1]))
|
||||
|
||||
@@ -22,12 +22,15 @@ class MuseDashCollections:
|
||||
]
|
||||
|
||||
MUSE_PLUS_DLC: str = "Muse Plus"
|
||||
|
||||
# Ordering matters for webhost. Order goes: Muse Plus, Time Limited Muse Plus Dlcs, Paid Dlcs
|
||||
DLC: List[str] = [
|
||||
# MUSE_PLUS_DLC, # To be included when OptionSets are rendered as part of basic settings.
|
||||
# "maimai DX Limited-time Suite", # Part of Muse Plus. Goes away 31st Jan 2026.
|
||||
"Miku in Museland", # Paid DLC not included in Muse Plus
|
||||
"Rin Len's Mirrorland", # Paid DLC not included in Muse Plus
|
||||
"MSR Anthology", # Now no longer available.
|
||||
MUSE_PLUS_DLC,
|
||||
"CHUNITHM COURSE MUSE", # Part of Muse Plus. Goes away 22nd May 2027.
|
||||
"maimai DX Limited-time Suite", # Part of Muse Plus. Goes away 31st Jan 2026.
|
||||
"MSR Anthology", # Now no longer available.
|
||||
"Miku in Museland", # Paid DLC not included in Muse Plus
|
||||
"Rin Len's Mirrorland", # Paid DLC not included in Muse Plus
|
||||
]
|
||||
|
||||
DIFF_OVERRIDES: List[str] = [
|
||||
@@ -50,7 +53,7 @@ class MuseDashCollections:
|
||||
song_items: Dict[str, SongData] = {}
|
||||
song_locations: Dict[str, int] = {}
|
||||
|
||||
vfx_trap_items: Dict[str, int] = {
|
||||
trap_items: Dict[str, int] = {
|
||||
"Bad Apple Trap": STARTING_CODE + 1,
|
||||
"Pixelate Trap": STARTING_CODE + 2,
|
||||
"Ripple Trap": STARTING_CODE + 3,
|
||||
@@ -58,14 +61,16 @@ class MuseDashCollections:
|
||||
"Chromatic Aberration Trap": STARTING_CODE + 5,
|
||||
"Background Freeze Trap": STARTING_CODE + 6,
|
||||
"Gray Scale Trap": STARTING_CODE + 7,
|
||||
"Focus Line Trap": STARTING_CODE + 10,
|
||||
}
|
||||
|
||||
sfx_trap_items: Dict[str, int] = {
|
||||
"Nyaa SFX Trap": STARTING_CODE + 8,
|
||||
"Error SFX Trap": STARTING_CODE + 9,
|
||||
"Focus Line Trap": STARTING_CODE + 10,
|
||||
}
|
||||
|
||||
sfx_trap_items: List[str] = [
|
||||
"Nyaa SFX Trap",
|
||||
"Error SFX Trap",
|
||||
]
|
||||
|
||||
filler_items: Dict[str, int] = {
|
||||
"Great To Perfect (10 Pack)": STARTING_CODE + 30,
|
||||
"Miss To Great (5 Pack)": STARTING_CODE + 31,
|
||||
@@ -78,7 +83,7 @@ class MuseDashCollections:
|
||||
"Extra Life": 1,
|
||||
}
|
||||
|
||||
item_names_to_id: ChainMap = ChainMap({}, filler_items, sfx_trap_items, vfx_trap_items)
|
||||
item_names_to_id: ChainMap = ChainMap({}, filler_items, trap_items)
|
||||
location_names_to_id: ChainMap = ChainMap(song_locations, album_locations)
|
||||
|
||||
def __init__(self) -> None:
|
||||
@@ -171,6 +176,9 @@ class MuseDashCollections:
|
||||
|
||||
return filtered_list
|
||||
|
||||
def filter_songs_to_dlc(self, song_list: List[str], dlc_songs: Set[str]) -> List[str]:
|
||||
return [song for song in song_list if self.song_matches_dlc_filter(self.song_items[song], dlc_songs)]
|
||||
|
||||
def song_matches_dlc_filter(self, song: SongData, dlc_songs: Set[str]) -> bool:
|
||||
if song.album in self.FREE_ALBUMS:
|
||||
return True
|
||||
|
||||
+74
-35
@@ -1,26 +1,26 @@
|
||||
from typing import Dict
|
||||
from Options import Toggle, Option, Range, Choice, DeathLink, ItemSet, OptionSet, PerGameCommonOptions
|
||||
from Options import Toggle, Range, Choice, DeathLink, ItemSet, OptionSet, PerGameCommonOptions, OptionGroup, Removed
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .MuseDashCollection import MuseDashCollections
|
||||
|
||||
|
||||
class AllowJustAsPlannedDLCSongs(Toggle):
|
||||
"""Whether [Muse Plus] DLC Songs, and all the albums included in it, can be chosen as randomised songs.
|
||||
Note: The [Just As Planned] DLC contains all [Muse Plus] songs."""
|
||||
display_name = "Allow [Muse Plus] DLC Songs"
|
||||
|
||||
|
||||
class DLCMusicPacks(OptionSet):
|
||||
"""Which non-[Muse Plus] DLC packs can be chosen as randomised songs."""
|
||||
"""
|
||||
Choose which DLC Packs will be included in the pool of chooseable songs.
|
||||
|
||||
Note: The [Just As Planned] DLC contains all [Muse Plus] songs.
|
||||
"""
|
||||
display_name = "DLC Packs"
|
||||
default = {}
|
||||
valid_keys = [dlc for dlc in MuseDashCollections.DLC]
|
||||
|
||||
|
||||
class StreamerModeEnabled(Toggle):
|
||||
"""In Muse Dash, an option named 'Streamer Mode' removes songs which may trigger copyright issues when streaming.
|
||||
If this is enabled, only songs available under Streamer Mode will be available for randomization."""
|
||||
"""
|
||||
In Muse Dash, an option named 'Streamer Mode' removes songs which may trigger copyright issues when streaming.
|
||||
|
||||
If this is enabled, only songs available under Streamer Mode will be available for randomization.
|
||||
"""
|
||||
display_name = "Streamer Mode Only Songs"
|
||||
|
||||
|
||||
@@ -33,7 +33,8 @@ class StartingSongs(Range):
|
||||
|
||||
|
||||
class AdditionalSongs(Range):
|
||||
"""The total number of songs that will be placed in the randomization pool.
|
||||
"""
|
||||
The total number of songs that will be placed in the randomization pool.
|
||||
- This does not count any starting songs or the goal song.
|
||||
- The final song count may be lower due to other settings.
|
||||
"""
|
||||
@@ -44,7 +45,8 @@ class AdditionalSongs(Range):
|
||||
|
||||
|
||||
class DifficultyMode(Choice):
|
||||
"""Ensures that at any chosen song has at least 1 value falling within these values.
|
||||
"""
|
||||
Ensures that at any chosen song has at least 1 value falling within these values.
|
||||
- Any: All songs are available
|
||||
- Easy: 1, 2 or 3
|
||||
- Medium: 4, 5
|
||||
@@ -66,8 +68,11 @@ class DifficultyMode(Choice):
|
||||
|
||||
# Todo: Investigate options to make this non randomizable
|
||||
class DifficultyModeOverrideMin(Range):
|
||||
"""Ensures that 1 difficulty has at least 1 this value or higher per song.
|
||||
- Difficulty Mode must be set to Manual."""
|
||||
"""
|
||||
Ensures that 1 difficulty has at least 1 this value or higher per song.
|
||||
|
||||
Note: Difficulty Mode must be set to Manual.
|
||||
"""
|
||||
display_name = "Manual Difficulty Min"
|
||||
range_start = 1
|
||||
range_end = 11
|
||||
@@ -76,8 +81,11 @@ class DifficultyModeOverrideMin(Range):
|
||||
|
||||
# Todo: Investigate options to make this non randomizable
|
||||
class DifficultyModeOverrideMax(Range):
|
||||
"""Ensures that 1 difficulty has at least 1 this value or lower per song.
|
||||
- Difficulty Mode must be set to Manual."""
|
||||
"""
|
||||
Ensures that 1 difficulty has at least 1 this value or lower per song.
|
||||
|
||||
Note: Difficulty Mode must be set to Manual.
|
||||
"""
|
||||
display_name = "Manual Difficulty Max"
|
||||
range_start = 1
|
||||
range_end = 11
|
||||
@@ -85,7 +93,8 @@ class DifficultyModeOverrideMax(Range):
|
||||
|
||||
|
||||
class GradeNeeded(Choice):
|
||||
"""Completing a song will require a grade of this value or higher in order to unlock items.
|
||||
"""
|
||||
Completing a song will require a grade of this value or higher in order to unlock items.
|
||||
The grades are as follows:
|
||||
- Silver S (SS): >= 95% accuracy
|
||||
- Pink S (S): >= 90% accuracy
|
||||
@@ -104,7 +113,9 @@ class GradeNeeded(Choice):
|
||||
|
||||
|
||||
class MusicSheetCountPercentage(Range):
|
||||
"""Controls how many music sheets are added to the pool based on the number of songs, including starting songs.
|
||||
"""
|
||||
Controls how many music sheets are added to the pool based on the number of songs, including starting songs.
|
||||
|
||||
Higher numbers leads to more consistent game lengths, but will cause individual music sheets to be less important.
|
||||
"""
|
||||
range_start = 10
|
||||
@@ -121,19 +132,18 @@ class MusicSheetWinCountPercentage(Range):
|
||||
display_name = "Music Sheets Needed to Win"
|
||||
|
||||
|
||||
class TrapTypes(Choice):
|
||||
"""This controls the types of traps that can be added to the pool.
|
||||
class ChosenTraps(OptionSet):
|
||||
"""
|
||||
This controls the types of traps that can be added to the pool.
|
||||
- Traps last the length of a song, or until you die.
|
||||
- VFX Traps consist of visual effects that play over the song. (i.e. Grayscale.)
|
||||
- SFX Traps consist of changing your sfx setting to one possibly more annoying sfx.
|
||||
Traps last the length of a song, or until you die.
|
||||
|
||||
Note: SFX traps are only available if [Just as Planned] DLC songs are enabled.
|
||||
"""
|
||||
display_name = "Available Trap Types"
|
||||
option_None = 0
|
||||
option_VFX = 1
|
||||
option_SFX = 2
|
||||
option_All = 3
|
||||
default = 3
|
||||
display_name = "Chosen Traps"
|
||||
default = {}
|
||||
valid_keys = {trap for trap in MuseDashCollections.trap_items.keys()}
|
||||
|
||||
|
||||
class TrapCountPercentage(Range):
|
||||
@@ -145,24 +155,49 @@ class TrapCountPercentage(Range):
|
||||
|
||||
|
||||
class IncludeSongs(ItemSet):
|
||||
"""Any song listed here will be guaranteed to be included as part of the seed.
|
||||
- Difficulty options will be skipped for these songs.
|
||||
- If there being too many included songs, songs will be randomly chosen without regard for difficulty.
|
||||
- If you want these songs immediately, use start_inventory instead.
|
||||
"""
|
||||
These songs will be guaranteed to show up within the seed.
|
||||
- You must have the DLC enabled to play these songs.
|
||||
- Difficulty options will not affect these songs.
|
||||
- If there are too many included songs, this will act as a whitelist ignoring song difficulty.
|
||||
"""
|
||||
verify_item_name = True
|
||||
display_name = "Include Songs"
|
||||
|
||||
|
||||
class ExcludeSongs(ItemSet):
|
||||
"""Any song listed here will be excluded from being a part of the seed."""
|
||||
"""
|
||||
These songs will be guaranteed to not show up within the seed.
|
||||
|
||||
Note: Does not affect songs within the "Include Songs" list.
|
||||
"""
|
||||
verify_item_name = True
|
||||
display_name = "Exclude Songs"
|
||||
|
||||
|
||||
md_option_groups = [
|
||||
OptionGroup("Song Choice", [
|
||||
DLCMusicPacks,
|
||||
StreamerModeEnabled,
|
||||
IncludeSongs,
|
||||
ExcludeSongs,
|
||||
]),
|
||||
OptionGroup("Difficulty", [
|
||||
GradeNeeded,
|
||||
DifficultyMode,
|
||||
DifficultyModeOverrideMin,
|
||||
DifficultyModeOverrideMax,
|
||||
DeathLink,
|
||||
]),
|
||||
OptionGroup("Traps", [
|
||||
ChosenTraps,
|
||||
TrapCountPercentage,
|
||||
]),
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class MuseDashOptions(PerGameCommonOptions):
|
||||
allow_just_as_planned_dlc_songs: AllowJustAsPlannedDLCSongs
|
||||
dlc_packs: DLCMusicPacks
|
||||
streamer_mode_enabled: StreamerModeEnabled
|
||||
starting_song_count: StartingSongs
|
||||
@@ -173,8 +208,12 @@ class MuseDashOptions(PerGameCommonOptions):
|
||||
grade_needed: GradeNeeded
|
||||
music_sheet_count_percentage: MusicSheetCountPercentage
|
||||
music_sheet_win_count_percentage: MusicSheetWinCountPercentage
|
||||
available_trap_types: TrapTypes
|
||||
chosen_traps: ChosenTraps
|
||||
trap_count_percentage: TrapCountPercentage
|
||||
death_link: DeathLink
|
||||
include_songs: IncludeSongs
|
||||
exclude_songs: ExcludeSongs
|
||||
|
||||
# Removed
|
||||
allow_just_as_planned_dlc_songs: Removed
|
||||
available_trap_types: Removed
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
MuseDashPresets: Dict[str, Dict[str, Any]] = {
|
||||
# An option to support Short Sync games. 40 songs.
|
||||
"No DLC - Short": {
|
||||
"allow_just_as_planned_dlc_songs": False,
|
||||
"dlc_packs": [],
|
||||
"starting_song_count": 5,
|
||||
"additional_song_count": 34,
|
||||
"music_sheet_count_percentage": 20,
|
||||
@@ -11,7 +11,7 @@ MuseDashPresets: Dict[str, Dict[str, Any]] = {
|
||||
},
|
||||
# An option to support Short Sync games but adds variety. 40 songs.
|
||||
"DLC - Short": {
|
||||
"allow_just_as_planned_dlc_songs": True,
|
||||
"dlc_packs": ["Muse Plus"],
|
||||
"starting_song_count": 5,
|
||||
"additional_song_count": 34,
|
||||
"music_sheet_count_percentage": 20,
|
||||
@@ -19,7 +19,7 @@ MuseDashPresets: Dict[str, Dict[str, Any]] = {
|
||||
},
|
||||
# An option to support Longer Sync/Async games. 100 songs.
|
||||
"DLC - Long": {
|
||||
"allow_just_as_planned_dlc_songs": True,
|
||||
"dlc_packs": ["Muse Plus"],
|
||||
"starting_song_count": 8,
|
||||
"additional_song_count": 91,
|
||||
"music_sheet_count_percentage": 20,
|
||||
|
||||
+23
-36
@@ -1,10 +1,10 @@
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from BaseClasses import Region, Item, ItemClassification, Entrance, Tutorial
|
||||
from typing import List, ClassVar, Type
|
||||
from BaseClasses import Region, Item, ItemClassification, Tutorial
|
||||
from typing import List, ClassVar, Type, Set
|
||||
from math import floor
|
||||
from Options import PerGameCommonOptions
|
||||
|
||||
from .Options import MuseDashOptions
|
||||
from .Options import MuseDashOptions, md_option_groups
|
||||
from .Items import MuseDashSongItem, MuseDashFixedItem
|
||||
from .Locations import MuseDashLocation
|
||||
from .MuseDashCollection import MuseDashCollections
|
||||
@@ -35,6 +35,7 @@ class MuseDashWebWorld(WebWorld):
|
||||
|
||||
tutorials = [setup_en, setup_es]
|
||||
options_presets = MuseDashPresets
|
||||
option_groups = md_option_groups
|
||||
|
||||
|
||||
class MuseDashWorld(World):
|
||||
@@ -72,8 +73,6 @@ class MuseDashWorld(World):
|
||||
|
||||
def generate_early(self):
|
||||
dlc_songs = {key for key in self.options.dlc_packs.value}
|
||||
if self.options.allow_just_as_planned_dlc_songs.value:
|
||||
dlc_songs.add(self.md_collection.MUSE_PLUS_DLC)
|
||||
|
||||
streamer_mode = self.options.streamer_mode_enabled
|
||||
(lower_diff_threshold, higher_diff_threshold) = self.get_difficulty_range()
|
||||
@@ -88,7 +87,7 @@ class MuseDashWorld(World):
|
||||
available_song_keys = self.md_collection.get_songs_with_settings(
|
||||
dlc_songs, bool(streamer_mode.value), lower_diff_threshold, higher_diff_threshold)
|
||||
|
||||
available_song_keys = self.handle_plando(available_song_keys)
|
||||
available_song_keys = self.handle_plando(available_song_keys, dlc_songs)
|
||||
|
||||
count_needed_for_start = max(0, starter_song_count - len(self.starting_songs))
|
||||
if len(available_song_keys) + len(self.included_songs) >= count_needed_for_start + 11:
|
||||
@@ -109,7 +108,7 @@ class MuseDashWorld(World):
|
||||
for song in self.starting_songs:
|
||||
self.multiworld.push_precollected(self.create_item(song))
|
||||
|
||||
def handle_plando(self, available_song_keys: List[str]) -> List[str]:
|
||||
def handle_plando(self, available_song_keys: List[str], dlc_songs: Set[str]) -> List[str]:
|
||||
song_items = self.md_collection.song_items
|
||||
|
||||
start_items = self.options.start_inventory.value.keys()
|
||||
@@ -117,7 +116,9 @@ class MuseDashWorld(World):
|
||||
exclude_songs = self.options.exclude_songs.value
|
||||
|
||||
self.starting_songs = [s for s in start_items if s in song_items]
|
||||
self.starting_songs = self.md_collection.filter_songs_to_dlc(self.starting_songs, dlc_songs)
|
||||
self.included_songs = [s for s in include_songs if s in song_items and s not in self.starting_songs]
|
||||
self.included_songs = self.md_collection.filter_songs_to_dlc(self.included_songs, dlc_songs)
|
||||
|
||||
return [s for s in available_song_keys if s not in start_items
|
||||
and s not in include_songs and s not in exclude_songs]
|
||||
@@ -148,7 +149,7 @@ class MuseDashWorld(World):
|
||||
self.victory_song_name = available_song_keys[chosen_song - included_song_count]
|
||||
del available_song_keys[chosen_song - included_song_count]
|
||||
|
||||
# Next, make sure the starting songs are fufilled
|
||||
# Next, make sure the starting songs are fulfilled
|
||||
if len(self.starting_songs) < starting_song_count:
|
||||
for _ in range(len(self.starting_songs), starting_song_count):
|
||||
if len(available_song_keys) > 0:
|
||||
@@ -156,7 +157,7 @@ class MuseDashWorld(World):
|
||||
else:
|
||||
self.starting_songs.append(self.included_songs.pop())
|
||||
|
||||
# Then attempt to fufill any remaining songs for interim songs
|
||||
# Then attempt to fulfill any remaining songs for interim songs
|
||||
if len(self.included_songs) < additional_song_count:
|
||||
for _ in range(len(self.included_songs), self.options.additional_song_count):
|
||||
if len(available_song_keys) <= 0:
|
||||
@@ -174,11 +175,7 @@ class MuseDashWorld(World):
|
||||
if filler:
|
||||
return MuseDashFixedItem(name, ItemClassification.filler, filler, self.player)
|
||||
|
||||
trap = self.md_collection.vfx_trap_items.get(name)
|
||||
if trap:
|
||||
return MuseDashFixedItem(name, ItemClassification.trap, trap, self.player)
|
||||
|
||||
trap = self.md_collection.sfx_trap_items.get(name)
|
||||
trap = self.md_collection.trap_items.get(name)
|
||||
if trap:
|
||||
return MuseDashFixedItem(name, ItemClassification.trap, trap, self.player)
|
||||
|
||||
@@ -252,9 +249,7 @@ class MuseDashWorld(World):
|
||||
|
||||
def create_regions(self) -> None:
|
||||
menu_region = Region("Menu", self.player, self.multiworld)
|
||||
song_select_region = Region("Song Select", self.player, self.multiworld)
|
||||
self.multiworld.regions += [menu_region, song_select_region]
|
||||
menu_region.connect(song_select_region)
|
||||
self.multiworld.regions += [menu_region]
|
||||
|
||||
# Make a collection of all songs available for this rando.
|
||||
# 1. All starting songs
|
||||
@@ -268,35 +263,27 @@ class MuseDashWorld(World):
|
||||
self.random.shuffle(included_song_copy)
|
||||
all_selected_locations.extend(included_song_copy)
|
||||
|
||||
# Make a region per song/album, then adds 1-2 item locations to them
|
||||
# Adds 2 item locations per song/album to the menu region.
|
||||
for i in range(0, len(all_selected_locations)):
|
||||
name = all_selected_locations[i]
|
||||
region = Region(name, self.player, self.multiworld)
|
||||
self.multiworld.regions.append(region)
|
||||
song_select_region.connect(region, name, lambda state, place=name: state.has(place, self.player))
|
||||
loc1 = MuseDashLocation(self.player, name + "-0", self.md_collection.song_locations[name + "-0"], menu_region)
|
||||
loc1.access_rule = lambda state, place=name: state.has(place, self.player)
|
||||
menu_region.locations.append(loc1)
|
||||
|
||||
# Muse Dash requires 2 locations per song to be *interesting*. Balanced out by filler.
|
||||
region.add_locations({
|
||||
name + "-0": self.md_collection.song_locations[name + "-0"],
|
||||
name + "-1": self.md_collection.song_locations[name + "-1"]
|
||||
}, MuseDashLocation)
|
||||
loc2 = MuseDashLocation(self.player, name + "-1", self.md_collection.song_locations[name + "-1"], menu_region)
|
||||
loc2.access_rule = lambda state, place=name: state.has(place, self.player)
|
||||
menu_region.locations.append(loc2)
|
||||
|
||||
def set_rules(self) -> None:
|
||||
self.multiworld.completion_condition[self.player] = lambda state: \
|
||||
state.has(self.md_collection.MUSIC_SHEET_NAME, self.player, self.get_music_sheet_win_count())
|
||||
|
||||
def get_available_traps(self) -> List[str]:
|
||||
sfx_traps_available = self.options.allow_just_as_planned_dlc_songs.value
|
||||
full_trap_list = self.md_collection.trap_items.keys()
|
||||
if self.md_collection.MUSE_PLUS_DLC not in self.options.dlc_packs.value:
|
||||
full_trap_list = [trap for trap in full_trap_list if trap not in self.md_collection.sfx_trap_items]
|
||||
|
||||
trap_list = []
|
||||
if self.options.available_trap_types.value & 1 != 0:
|
||||
trap_list += self.md_collection.vfx_trap_items.keys()
|
||||
|
||||
# SFX options are only available under Just as Planned DLC.
|
||||
if sfx_traps_available and self.options.available_trap_types.value & 2 != 0:
|
||||
trap_list += self.md_collection.sfx_trap_items.keys()
|
||||
|
||||
return trap_list
|
||||
return [trap for trap in full_trap_list if trap in self.options.chosen_traps.value]
|
||||
|
||||
def get_trap_count(self) -> int:
|
||||
multiplier = self.options.trap_count_percentage.value / 100.0
|
||||
|
||||
@@ -9,25 +9,26 @@ class CollectionsTest(unittest.TestCase):
|
||||
for name in collection.song_items.keys():
|
||||
for c in name:
|
||||
# This is taken directly from OoT. Represents the generally excepted characters.
|
||||
if (0x20 <= ord(c) < 0x7e):
|
||||
if 0x20 <= ord(c) < 0x7e:
|
||||
continue
|
||||
|
||||
bad_names.append(name)
|
||||
break
|
||||
|
||||
self.assertEqual(len(bad_names), 0, f"Muse Dash has {len(bad_names)} songs with non-ASCII characters.\n{bad_names}")
|
||||
self.assertEqual(len(bad_names), 0,
|
||||
f"Muse Dash has {len(bad_names)} songs with non-ASCII characters.\n{bad_names}")
|
||||
|
||||
def test_ids_dont_change(self) -> None:
|
||||
collection = MuseDashCollections()
|
||||
itemsBefore = {name: code for name, code in collection.item_names_to_id.items()}
|
||||
locationsBefore = {name: code for name, code in collection.location_names_to_id.items()}
|
||||
items_before = {name: code for name, code in collection.item_names_to_id.items()}
|
||||
locations_before = {name: code for name, code in collection.location_names_to_id.items()}
|
||||
|
||||
collection.__init__()
|
||||
itemsAfter = {name: code for name, code in collection.item_names_to_id.items()}
|
||||
locationsAfter = {name: code for name, code in collection.location_names_to_id.items()}
|
||||
items_after = {name: code for name, code in collection.item_names_to_id.items()}
|
||||
locations_after = {name: code for name, code in collection.location_names_to_id.items()}
|
||||
|
||||
self.assertDictEqual(itemsBefore, itemsAfter, "Item ID changed after secondary init.")
|
||||
self.assertDictEqual(locationsBefore, locationsAfter, "Location ID changed after secondary init.")
|
||||
self.assertDictEqual(items_before, items_after, "Item ID changed after secondary init.")
|
||||
self.assertDictEqual(locations_before, locations_after, "Location ID changed after secondary init.")
|
||||
|
||||
def test_free_dlc_included_in_base_songs(self) -> None:
|
||||
collection = MuseDashCollections()
|
||||
|
||||
@@ -3,31 +3,31 @@ from . import MuseDashTestBase
|
||||
|
||||
class DifficultyRanges(MuseDashTestBase):
|
||||
def test_all_difficulty_ranges(self) -> None:
|
||||
muse_dash_world = self.multiworld.worlds[1]
|
||||
muse_dash_world = self.get_world()
|
||||
dlc_set = {x for x in muse_dash_world.md_collection.DLC}
|
||||
difficulty_choice = muse_dash_world.options.song_difficulty_mode
|
||||
difficulty_min = muse_dash_world.options.song_difficulty_min
|
||||
difficulty_max = muse_dash_world.options.song_difficulty_max
|
||||
|
||||
def test_range(inputRange, lower, upper):
|
||||
self.assertEqual(inputRange[0], lower)
|
||||
self.assertEqual(inputRange[1], upper)
|
||||
def test_range(input_range, lower, upper):
|
||||
self.assertEqual(input_range[0], lower)
|
||||
self.assertEqual(input_range[1], upper)
|
||||
|
||||
songs = muse_dash_world.md_collection.get_songs_with_settings(dlc_set, False, inputRange[0], inputRange[1])
|
||||
songs = muse_dash_world.md_collection.get_songs_with_settings(dlc_set, False, input_range[0], input_range[1])
|
||||
for songKey in songs:
|
||||
song = muse_dash_world.md_collection.song_items[songKey]
|
||||
if (song.easy is not None and inputRange[0] <= song.easy <= inputRange[1]):
|
||||
if song.easy is not None and input_range[0] <= song.easy <= input_range[1]:
|
||||
continue
|
||||
|
||||
if (song.hard is not None and inputRange[0] <= song.hard <= inputRange[1]):
|
||||
if song.hard is not None and input_range[0] <= song.hard <= input_range[1]:
|
||||
continue
|
||||
|
||||
if (song.master is not None and inputRange[0] <= song.master <= inputRange[1]):
|
||||
if song.master is not None and input_range[0] <= song.master <= input_range[1]:
|
||||
continue
|
||||
|
||||
self.fail(f"Invalid song '{songKey}' was given for range '{inputRange[0]} to {inputRange[1]}'")
|
||||
self.fail(f"Invalid song '{songKey}' was given for range '{input_range[0]} to {input_range[1]}'")
|
||||
|
||||
#auto ranges
|
||||
# auto ranges
|
||||
difficulty_choice.value = 0
|
||||
test_range(muse_dash_world.get_difficulty_range(), 0, 12)
|
||||
difficulty_choice.value = 1
|
||||
@@ -61,7 +61,7 @@ class DifficultyRanges(MuseDashTestBase):
|
||||
test_range(muse_dash_world.get_difficulty_range(), 4, 6)
|
||||
|
||||
def test_songs_have_difficulty(self) -> None:
|
||||
muse_dash_world = self.multiworld.worlds[1]
|
||||
muse_dash_world = self.get_world()
|
||||
|
||||
for song_name in muse_dash_world.md_collection.DIFF_OVERRIDES:
|
||||
song = muse_dash_world.md_collection.song_items[song_name]
|
||||
@@ -73,4 +73,4 @@ class DifficultyRanges(MuseDashTestBase):
|
||||
f"Song '{song_name}' difficulty not set when it should be.")
|
||||
else:
|
||||
self.assertTrue(song.easy is not None and song.hard is not None and song.master is not None,
|
||||
f"Song '{song_name}' difficulty not set when it should be.")
|
||||
f"Song '{song_name}' difficulty not set when it should be.")
|
||||
|
||||
@@ -4,7 +4,32 @@ from . import MuseDashTestBase
|
||||
class TestPlandoSettings(MuseDashTestBase):
|
||||
options = {
|
||||
"additional_song_count": 15,
|
||||
"allow_just_as_planned_dlc_songs": True,
|
||||
"dlc_packs": {"Muse Plus"},
|
||||
"include_songs": [
|
||||
"Lunatic",
|
||||
"Out of Sense",
|
||||
"Magic Knight Girl",
|
||||
]
|
||||
}
|
||||
|
||||
def test_included_songs_didnt_grow_item_count(self) -> None:
|
||||
muse_dash_world = self.get_world()
|
||||
self.assertEqual(len(muse_dash_world.included_songs), 15, "Logical songs size grew when it shouldn't.")
|
||||
|
||||
def test_included_songs_plando(self) -> None:
|
||||
muse_dash_world = self.get_world()
|
||||
songs = muse_dash_world.included_songs.copy()
|
||||
songs.append(muse_dash_world.victory_song_name)
|
||||
|
||||
self.assertIn("Lunatic", songs, "Logical songs is missing a plando song: Lunatic")
|
||||
self.assertIn("Out of Sense", songs, "Logical songs is missing a plando song: Out of Sense")
|
||||
self.assertIn("Magic Knight Girl", songs, "Logical songs is missing a plando song: Magic Knight Girl")
|
||||
|
||||
|
||||
class TestFilteredPlandoSettings(MuseDashTestBase):
|
||||
options = {
|
||||
"additional_song_count": 15,
|
||||
"dlc_packs": {"MSR Anthology"},
|
||||
"include_songs": [
|
||||
"Operation Blade",
|
||||
"Autumn Moods",
|
||||
@@ -13,15 +38,15 @@ class TestPlandoSettings(MuseDashTestBase):
|
||||
}
|
||||
|
||||
def test_included_songs_didnt_grow_item_count(self) -> None:
|
||||
muse_dash_world = self.multiworld.worlds[1]
|
||||
self.assertEqual(len(muse_dash_world.included_songs), 15,
|
||||
f"Logical songs size grew when it shouldn't. Expected 15. Got {len(muse_dash_world.included_songs)}")
|
||||
muse_dash_world = self.get_world()
|
||||
self.assertEqual(len(muse_dash_world.included_songs), 15, "Logical songs size grew when it shouldn't.")
|
||||
|
||||
def test_included_songs_plando(self) -> None:
|
||||
muse_dash_world = self.multiworld.worlds[1]
|
||||
# Tests for excluding included songs when the right dlc isn't enabled
|
||||
def test_filtered_included_songs_plando(self) -> None:
|
||||
muse_dash_world = self.get_world()
|
||||
songs = muse_dash_world.included_songs.copy()
|
||||
songs.append(muse_dash_world.victory_song_name)
|
||||
|
||||
self.assertIn("Operation Blade", songs, "Logical songs is missing a plando song: Operation Blade")
|
||||
self.assertIn("Autumn Moods", songs, "Logical songs is missing a plando song: Autumn Moods")
|
||||
self.assertIn("Fireflies", songs, "Logical songs is missing a plando song: Fireflies")
|
||||
self.assertNotIn("Fireflies", songs, "Logical songs has added a filtered a plando song: Fireflies")
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
from . import MuseDashTestBase
|
||||
|
||||
|
||||
class TestNoTraps(MuseDashTestBase):
|
||||
def test_no_traps(self) -> None:
|
||||
md_world = self.get_world()
|
||||
md_world.options.chosen_traps.value.clear()
|
||||
self.assertEqual(len(md_world.get_available_traps()), 0, "Got an available trap when we expected none.")
|
||||
|
||||
def test_all_traps(self) -> None:
|
||||
md_world = self.get_world()
|
||||
md_world.options.dlc_packs.value.add(md_world.md_collection.MUSE_PLUS_DLC)
|
||||
|
||||
for trap in md_world.md_collection.trap_items.keys():
|
||||
md_world.options.chosen_traps.value.add(trap)
|
||||
|
||||
trap_count = len(md_world.get_available_traps())
|
||||
true_count = len(md_world.md_collection.trap_items.keys())
|
||||
|
||||
self.assertEqual(trap_count, true_count, "Got a different amount of traps than what was expected.")
|
||||
|
||||
def test_exclude_sfx_traps(self) -> None:
|
||||
md_world = self.get_world()
|
||||
if "Muse Plus" in md_world.options.dlc_packs.value:
|
||||
md_world.options.dlc_packs.value.remove("Muse Plus")
|
||||
|
||||
for trap in md_world.md_collection.trap_items.keys():
|
||||
md_world.options.chosen_traps.value.add(trap)
|
||||
|
||||
trap_count = len(md_world.get_available_traps())
|
||||
true_count = len(md_world.md_collection.trap_items.keys()) - len(md_world.md_collection.sfx_trap_items)
|
||||
|
||||
self.assertEqual(trap_count, true_count, "Got a different amount of traps than what was expected.")
|
||||
@@ -4,30 +4,33 @@ from . import MuseDashTestBase
|
||||
# This ends up with only 25 valid songs that can be chosen.
|
||||
# These tests ensure that this won't fail generation
|
||||
|
||||
|
||||
class TestWorstCaseHighDifficulty(MuseDashTestBase):
|
||||
options = {
|
||||
"starting_song_count": 10,
|
||||
"allow_just_as_planned_dlc_songs": False,
|
||||
"dlc_packs": [],
|
||||
"streamer_mode_enabled": True,
|
||||
"song_difficulty_mode": 6,
|
||||
"song_difficulty_min": 11,
|
||||
"song_difficulty_max": 11,
|
||||
}
|
||||
|
||||
|
||||
class TestWorstCaseMidDifficulty(MuseDashTestBase):
|
||||
options = {
|
||||
"starting_song_count": 10,
|
||||
"allow_just_as_planned_dlc_songs": False,
|
||||
"dlc_packs": [],
|
||||
"streamer_mode_enabled": True,
|
||||
"song_difficulty_mode": 6,
|
||||
"song_difficulty_min": 6,
|
||||
"song_difficulty_max": 6,
|
||||
}
|
||||
|
||||
|
||||
class TestWorstCaseLowDifficulty(MuseDashTestBase):
|
||||
options = {
|
||||
"starting_song_count": 10,
|
||||
"allow_just_as_planned_dlc_songs": False,
|
||||
"dlc_packs": [],
|
||||
"streamer_mode_enabled": True,
|
||||
"song_difficulty_mode": 6,
|
||||
"song_difficulty_min": 1,
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
from test.bases import WorldTestBase
|
||||
|
||||
from .. import MuseDashWorld
|
||||
from typing import cast
|
||||
|
||||
class MuseDashTestBase(WorldTestBase):
|
||||
game = "Muse Dash"
|
||||
|
||||
def get_world(self) -> MuseDashWorld:
|
||||
return cast(MuseDashWorld, self.multiworld.worlds[1])
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ and won't show up in the wild. Previously they would be forced to show up exactl
|
||||
|
||||
- The Lilycove Wailmer now logically block you from the east. Actual game behavior is still unchanged for now.
|
||||
- Water encounters in Slateport now correctly require Surf.
|
||||
- Mirage Tower can no longer be your only logical access to a species in the wild, since it can permanently disappear.
|
||||
- Updated the tracker link in the setup guide.
|
||||
|
||||
# 2.1.1
|
||||
|
||||
@@ -25,13 +25,20 @@ IGNORABLE_MAPS = {
|
||||
}
|
||||
"""These maps exist but don't show up in the rando or are unused, and so should be discarded"""
|
||||
|
||||
POSTGAME_MAPS = {
|
||||
OUT_OF_LOGIC_MAPS = {
|
||||
"MAP_DESERT_UNDERPASS",
|
||||
"MAP_SAFARI_ZONE_NORTHEAST",
|
||||
"MAP_SAFARI_ZONE_SOUTHEAST",
|
||||
"MAP_METEOR_FALLS_STEVENS_CAVE",
|
||||
"MAP_MIRAGE_TOWER_1F",
|
||||
"MAP_MIRAGE_TOWER_2F",
|
||||
"MAP_MIRAGE_TOWER_3F",
|
||||
"MAP_MIRAGE_TOWER_4F",
|
||||
}
|
||||
"""These maps have encounters and are locked behind beating the champion. Those encounter slots should be ignored for logical access to a species."""
|
||||
"""
|
||||
These maps have encounters and are locked behind beating the champion or are missable.
|
||||
Those encounter slots should be ignored for logical access to a species.
|
||||
"""
|
||||
|
||||
NUM_REAL_SPECIES = 386
|
||||
|
||||
|
||||
@@ -4,9 +4,8 @@ Functions related to pokemon species and moves
|
||||
import functools
|
||||
from typing import TYPE_CHECKING, Dict, List, Set, Optional, Tuple
|
||||
|
||||
from Options import Toggle
|
||||
|
||||
from .data import NUM_REAL_SPECIES, POSTGAME_MAPS, EncounterTableData, LearnsetMove, MiscPokemonData, SpeciesData, data
|
||||
from .data import (NUM_REAL_SPECIES, OUT_OF_LOGIC_MAPS, EncounterTableData, LearnsetMove, MiscPokemonData,
|
||||
SpeciesData, data)
|
||||
from .options import (Goal, HmCompatibility, LevelUpMoves, RandomizeAbilities, RandomizeLegendaryEncounters,
|
||||
RandomizeMiscPokemon, RandomizeStarters, RandomizeTypes, RandomizeWildPokemon,
|
||||
TmTutorCompatibility)
|
||||
@@ -266,7 +265,8 @@ def randomize_wild_encounters(world: "PokemonEmeraldWorld") -> None:
|
||||
species_old_to_new_map: Dict[int, int] = {}
|
||||
for species_id in table.slots:
|
||||
if species_id not in species_old_to_new_map:
|
||||
if not placed_priority_species and len(priority_species) > 0:
|
||||
if not placed_priority_species and len(priority_species) > 0 \
|
||||
and map_name not in OUT_OF_LOGIC_MAPS:
|
||||
new_species_id = priority_species.pop()
|
||||
placed_priority_species = True
|
||||
else:
|
||||
@@ -329,7 +329,7 @@ def randomize_wild_encounters(world: "PokemonEmeraldWorld") -> None:
|
||||
new_species_id = world.random.choice(candidates).species_id
|
||||
species_old_to_new_map[species_id] = new_species_id
|
||||
|
||||
if world.options.dexsanity and map_data.name not in POSTGAME_MAPS:
|
||||
if world.options.dexsanity and map_name not in OUT_OF_LOGIC_MAPS:
|
||||
already_placed.add(new_species_id)
|
||||
|
||||
# Actually create the new list of slots and encounter table
|
||||
|
||||
@@ -7,7 +7,7 @@ from .ror2environments import environment_vanilla_table, environment_vanilla_ord
|
||||
environment_sotv_orderedstages_table, environment_sotv_table, collapse_dict_list_vertical, shift_by_offset
|
||||
|
||||
from BaseClasses import Item, ItemClassification, Tutorial
|
||||
from .options import ItemWeights, ROR2Options
|
||||
from .options import ItemWeights, ROR2Options, ror2_option_groups
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from .regions import create_explore_regions, create_classic_regions
|
||||
from typing import List, Dict, Any
|
||||
@@ -23,6 +23,8 @@ class RiskOfWeb(WebWorld):
|
||||
["Ijwu", "Kindasneaki"]
|
||||
)]
|
||||
|
||||
option_groups = ror2_option_groups
|
||||
|
||||
|
||||
class RiskOfRainWorld(World):
|
||||
"""
|
||||
@@ -44,7 +46,7 @@ class RiskOfRainWorld(World):
|
||||
}
|
||||
location_name_to_id = item_pickups
|
||||
|
||||
required_client_version = (0, 4, 5)
|
||||
required_client_version = (0, 5, 0)
|
||||
web = RiskOfWeb()
|
||||
total_revivals: int
|
||||
|
||||
|
||||
+43
-5
@@ -1,5 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from Options import Toggle, DefaultOnToggle, DeathLink, Range, Choice, PerGameCommonOptions
|
||||
from Options import Toggle, DefaultOnToggle, DeathLink, Range, Choice, PerGameCommonOptions, OptionGroup
|
||||
|
||||
|
||||
# NOTE be aware that since the range of item ids that RoR2 uses is based off of the maximums of checks
|
||||
@@ -350,7 +350,7 @@ class ItemPoolPresetToggle(Toggle):
|
||||
|
||||
|
||||
class ItemWeights(Choice):
|
||||
"""Set item_pool_presets to true if you want to use one of these presets.
|
||||
"""Set Use Item Weight Presets to yes if you want to use one of these presets.
|
||||
Preset choices for determining the weights of the item pool.
|
||||
- New is a test for a potential adjustment to the default weights.
|
||||
- Uncommon puts a large number of uncommon items in the pool.
|
||||
@@ -375,6 +375,44 @@ class ItemWeights(Choice):
|
||||
option_void = 9
|
||||
|
||||
|
||||
ror2_option_groups = [
|
||||
OptionGroup("Explore Mode Options", [
|
||||
ChestsPerEnvironment,
|
||||
ShrinesPerEnvironment,
|
||||
ScavengersPerEnvironment,
|
||||
ScannersPerEnvironment,
|
||||
AltarsPerEnvironment,
|
||||
RequireStages,
|
||||
ProgressiveStages,
|
||||
]),
|
||||
OptionGroup("Classic Mode Options", [
|
||||
TotalLocations,
|
||||
], start_collapsed=True),
|
||||
OptionGroup("Weighted Choices", [
|
||||
ItemWeights,
|
||||
ItemPoolPresetToggle,
|
||||
WhiteScrap,
|
||||
GreenScrap,
|
||||
YellowScrap,
|
||||
RedScrap,
|
||||
CommonItem,
|
||||
UncommonItem,
|
||||
LegendaryItem,
|
||||
BossItem,
|
||||
LunarItem,
|
||||
VoidItem,
|
||||
Equipment,
|
||||
Money,
|
||||
LunarCoin,
|
||||
Experience,
|
||||
MountainTrap,
|
||||
TimeWarpTrap,
|
||||
CombatTrap,
|
||||
TeleportTrap,
|
||||
]),
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class ROR2Options(PerGameCommonOptions):
|
||||
goal: Goal
|
||||
@@ -399,10 +437,10 @@ class ROR2Options(PerGameCommonOptions):
|
||||
item_weights: ItemWeights
|
||||
item_pool_presets: ItemPoolPresetToggle
|
||||
# define the weights of the generated item pool.
|
||||
green_scrap: GreenScrap
|
||||
red_scrap: RedScrap
|
||||
yellow_scrap: YellowScrap
|
||||
white_scrap: WhiteScrap
|
||||
green_scrap: GreenScrap
|
||||
yellow_scrap: YellowScrap
|
||||
red_scrap: RedScrap
|
||||
common_item: CommonItem
|
||||
uncommon_item: UncommonItem
|
||||
legendary_item: LegendaryItem
|
||||
|
||||
@@ -19,11 +19,13 @@ def create_explore_regions(ror2_world: "RiskOfRainWorld") -> None:
|
||||
# Default Locations
|
||||
non_dlc_regions: Dict[str, RoRRegionData] = {
|
||||
"Menu": RoRRegionData(None, ["Distant Roost", "Distant Roost (2)",
|
||||
"Titanic Plains", "Titanic Plains (2)"]),
|
||||
"Titanic Plains", "Titanic Plains (2)",
|
||||
"Verdant Falls"]),
|
||||
"Distant Roost": RoRRegionData([], ["OrderedStage_1"]),
|
||||
"Distant Roost (2)": RoRRegionData([], ["OrderedStage_1"]),
|
||||
"Titanic Plains": RoRRegionData([], ["OrderedStage_1"]),
|
||||
"Titanic Plains (2)": RoRRegionData([], ["OrderedStage_1"]),
|
||||
"Verdant Falls": RoRRegionData([], ["OrderedStage_1"]),
|
||||
"Abandoned Aqueduct": RoRRegionData([], ["OrderedStage_2"]),
|
||||
"Wetland Aspect": RoRRegionData([], ["OrderedStage_2"]),
|
||||
"Rallypoint Delta": RoRRegionData([], ["OrderedStage_3"]),
|
||||
|
||||
@@ -7,6 +7,7 @@ environment_vanilla_orderedstage_1_table: Dict[str, int] = {
|
||||
"Distant Roost (2)": 8, # blackbeach2
|
||||
"Titanic Plains": 15, # golemplains
|
||||
"Titanic Plains (2)": 16, # golemplains2
|
||||
"Verdant Falls": 28, # lakes
|
||||
}
|
||||
environment_vanilla_orderedstage_2_table: Dict[str, int] = {
|
||||
"Abandoned Aqueduct": 17, # goolake
|
||||
|
||||
@@ -107,10 +107,10 @@ class ColouredMessage:
|
||||
def coloured(self, text: str, colour: str) -> 'ColouredMessage':
|
||||
add_json_text(self.parts, text, type="color", color=colour)
|
||||
return self
|
||||
def location(self, location_id: int, player_id: int = 0) -> 'ColouredMessage':
|
||||
def location(self, location_id: int, player_id: int) -> 'ColouredMessage':
|
||||
add_json_location(self.parts, location_id, player_id)
|
||||
return self
|
||||
def item(self, item_id: int, player_id: int = 0, flags: int = 0) -> 'ColouredMessage':
|
||||
def item(self, item_id: int, player_id: int, flags: int = 0) -> 'ColouredMessage':
|
||||
add_json_item(self.parts, item_id, player_id, flags)
|
||||
return self
|
||||
def player(self, player_id: int) -> 'ColouredMessage':
|
||||
@@ -122,7 +122,6 @@ class ColouredMessage:
|
||||
|
||||
class StarcraftClientProcessor(ClientCommandProcessor):
|
||||
ctx: SC2Context
|
||||
echo_commands = True
|
||||
|
||||
def formatted_print(self, text: str) -> None:
|
||||
"""Prints with kivy formatting to the GUI, and also prints to command-line and to all logs"""
|
||||
@@ -257,7 +256,7 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
||||
for item in received_items_of_this_type:
|
||||
print_faction_title()
|
||||
has_printed_faction_title = True
|
||||
(ColouredMessage('* ').item(item.item, flags=item.flags)
|
||||
(ColouredMessage('* ').item(item.item, self.ctx.slot, flags=item.flags)
|
||||
(" from ").location(item.location, self.ctx.slot)
|
||||
(" by ").player(item.player)
|
||||
).send(self.ctx)
|
||||
@@ -278,7 +277,7 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
||||
received_items_of_this_type = items_received.get(child_item, [])
|
||||
for item in received_items_of_this_type:
|
||||
filter_match_count += len(received_items_of_this_type)
|
||||
(ColouredMessage(' * ').item(item.item, flags=item.flags)
|
||||
(ColouredMessage(' * ').item(item.item, self.ctx.slot, flags=item.flags)
|
||||
(" from ").location(item.location, self.ctx.slot)
|
||||
(" by ").player(item.player)
|
||||
).send(self.ctx)
|
||||
|
||||
@@ -10,15 +10,15 @@ class ItemDict(TypedDict):
|
||||
base_id = 82000
|
||||
|
||||
item_table: List[ItemDict] = [
|
||||
{"name": "Stick", "id": base_id + 1, "count": 8, "classification": ItemClassification.progression_skip_balancing},
|
||||
{"name": "Stick", "id": base_id + 1, "count": 0, "classification": ItemClassification.progression_skip_balancing},
|
||||
{"name": "Seashell", "id": base_id + 2, "count": 23, "classification": ItemClassification.progression_skip_balancing},
|
||||
{"name": "Golden Feather", "id": base_id + 3, "count": 0, "classification": ItemClassification.progression},
|
||||
{"name": "Silver Feather", "id": base_id + 4, "count": 0, "classification": ItemClassification.useful},
|
||||
{"name": "Bucket", "id": base_id + 5, "count": 0, "classification": ItemClassification.progression},
|
||||
{"name": "Bait", "id": base_id + 6, "count": 2, "classification": ItemClassification.filler},
|
||||
{"name": "Fishing Rod", "id": base_id + 7, "count": 2, "classification": ItemClassification.progression},
|
||||
{"name": "Progressive Fishing Rod", "id": base_id + 7, "count": 2, "classification": ItemClassification.progression},
|
||||
{"name": "Shovel", "id": base_id + 8, "count": 1, "classification": ItemClassification.progression},
|
||||
{"name": "Toy Shovel", "id": base_id + 9, "count": 5, "classification": ItemClassification.progression_skip_balancing},
|
||||
{"name": "Toy Shovel", "id": base_id + 9, "count": 0, "classification": ItemClassification.progression_skip_balancing},
|
||||
{"name": "Compass", "id": base_id + 10, "count": 1, "classification": ItemClassification.useful},
|
||||
{"name": "Medal", "id": base_id + 11, "count": 3, "classification": ItemClassification.filler},
|
||||
{"name": "Shell Necklace", "id": base_id + 12, "count": 1, "classification": ItemClassification.progression},
|
||||
@@ -36,7 +36,7 @@ item_table: List[ItemDict] = [
|
||||
{"name": "Headband", "id": base_id + 24, "count": 1, "classification": ItemClassification.progression},
|
||||
{"name": "Running Shoes", "id": base_id + 25, "count": 1, "classification": ItemClassification.useful},
|
||||
{"name": "Camping Permit", "id": base_id + 26, "count": 1, "classification": ItemClassification.progression},
|
||||
{"name": "Walkie Talkie", "id": base_id + 27, "count": 1, "classification": ItemClassification.useful},
|
||||
{"name": "Walkie Talkie", "id": base_id + 27, "count": 0, "classification": ItemClassification.useful},
|
||||
|
||||
# Not in the item pool for now
|
||||
#{"name": "Boating Manual", "id": base_id + ~, "count": 1, "classification": ItemClassification.filler},
|
||||
@@ -48,9 +48,9 @@ item_table: List[ItemDict] = [
|
||||
{"name": "21 Coins", "id": base_id + 31, "count": 2, "classification": ItemClassification.filler},
|
||||
{"name": "25 Coins", "id": base_id + 32, "count": 7, "classification": ItemClassification.filler},
|
||||
{"name": "27 Coins", "id": base_id + 33, "count": 1, "classification": ItemClassification.filler},
|
||||
{"name": "32 Coins", "id": base_id + 34, "count": 1, "classification": ItemClassification.filler},
|
||||
{"name": "33 Coins", "id": base_id + 35, "count": 6, "classification": ItemClassification.filler},
|
||||
{"name": "50 Coins", "id": base_id + 36, "count": 1, "classification": ItemClassification.filler},
|
||||
{"name": "32 Coins", "id": base_id + 34, "count": 1, "classification": ItemClassification.useful},
|
||||
{"name": "33 Coins", "id": base_id + 35, "count": 6, "classification": ItemClassification.useful},
|
||||
{"name": "50 Coins", "id": base_id + 36, "count": 1, "classification": ItemClassification.useful},
|
||||
|
||||
# Filler item determined by settings
|
||||
{"name": "13 Coins", "id": base_id + 37, "count": 0, "classification": ItemClassification.filler},
|
||||
|
||||
+132
-132
@@ -5,7 +5,7 @@ class LocationInfo(TypedDict):
|
||||
id: int
|
||||
inGameId: str
|
||||
needsShovel: bool
|
||||
purchase: bool
|
||||
purchase: int
|
||||
minGoldenFeathers: int
|
||||
minGoldenFeathersEasy: int
|
||||
minGoldenFeathersBucket: int
|
||||
@@ -17,311 +17,311 @@ location_table: List[LocationInfo] = [
|
||||
{"name": "Start Beach Seashell",
|
||||
"id": base_id + 1,
|
||||
"inGameId": "PickUps.3",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Beach Hut Seashell",
|
||||
"id": base_id + 2,
|
||||
"inGameId": "PickUps.2",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Beach Umbrella Seashell",
|
||||
"id": base_id + 3,
|
||||
"inGameId": "PickUps.8",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Sid Beach Mound Seashell",
|
||||
"id": base_id + 4,
|
||||
"inGameId": "PickUps.12",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Sid Beach Seashell",
|
||||
"id": base_id + 5,
|
||||
"inGameId": "PickUps.11",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Shirley's Point Beach Seashell",
|
||||
"id": base_id + 6,
|
||||
"inGameId": "PickUps.18",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Shirley's Point Rock Seashell",
|
||||
"id": base_id + 7,
|
||||
"inGameId": "PickUps.17",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Visitor's Center Beach Seashell",
|
||||
"id": base_id + 8,
|
||||
"inGameId": "PickUps.19",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "West River Seashell",
|
||||
"id": base_id + 9,
|
||||
"inGameId": "PickUps.10",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "West Riverbank Seashell",
|
||||
"id": base_id + 10,
|
||||
"inGameId": "PickUps.4",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Stone Tower Riverbank Seashell",
|
||||
"id": base_id + 11,
|
||||
"inGameId": "PickUps.23",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "North Beach Seashell",
|
||||
"id": base_id + 12,
|
||||
"inGameId": "PickUps.6",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "North Coast Seashell",
|
||||
"id": base_id + 13,
|
||||
"inGameId": "PickUps.7",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Boat Cliff Seashell",
|
||||
"id": base_id + 14,
|
||||
"inGameId": "PickUps.14",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Boat Isle Mound Seashell",
|
||||
"id": base_id + 15,
|
||||
"inGameId": "PickUps.22",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "East Coast Seashell",
|
||||
"id": base_id + 16,
|
||||
"inGameId": "PickUps.21",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "House North Beach Seashell",
|
||||
"id": base_id + 17,
|
||||
"inGameId": "PickUps.16",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Airstream Island North Seashell",
|
||||
"id": base_id + 18,
|
||||
"inGameId": "PickUps.13",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Airstream Island South Seashell",
|
||||
"id": base_id + 19,
|
||||
"inGameId": "PickUps.15",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Secret Island Beach Seashell",
|
||||
"id": base_id + 20,
|
||||
"inGameId": "PickUps.1",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Meteor Lake Seashell",
|
||||
"id": base_id + 126,
|
||||
"inGameId": "PickUps.20",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Good Creek Path Seashell",
|
||||
"id": base_id + 127,
|
||||
"inGameId": "PickUps.9",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
|
||||
# Visitor's Center Shop
|
||||
{"name": "Visitor's Center Shop Golden Feather 1",
|
||||
"id": base_id + 21,
|
||||
"inGameId": "CampRangerNPC[0]",
|
||||
"needsShovel": False, "purchase": True,
|
||||
"needsShovel": False, "purchase": 40,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Visitor's Center Shop Golden Feather 2",
|
||||
"id": base_id + 22,
|
||||
"inGameId": "CampRangerNPC[1]",
|
||||
"needsShovel": False, "purchase": True,
|
||||
"needsShovel": False, "purchase": 40,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Visitor's Center Shop Hat",
|
||||
"id": base_id + 23,
|
||||
"inGameId": "CampRangerNPC[9]",
|
||||
"needsShovel": False, "purchase": True,
|
||||
"needsShovel": False, "purchase": 100,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
|
||||
# Tough Bird Salesman
|
||||
{"name": "Tough Bird Salesman Golden Feather 1",
|
||||
"id": base_id + 24,
|
||||
"inGameId": "ToughBirdNPC (1)[0]",
|
||||
"needsShovel": False, "purchase": True,
|
||||
"needsShovel": False, "purchase": 100,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Tough Bird Salesman Golden Feather 2",
|
||||
"id": base_id + 25,
|
||||
"inGameId": "ToughBirdNPC (1)[1]",
|
||||
"needsShovel": False, "purchase": True,
|
||||
"needsShovel": False, "purchase": 100,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Tough Bird Salesman Golden Feather 3",
|
||||
"id": base_id + 26,
|
||||
"inGameId": "ToughBirdNPC (1)[2]",
|
||||
"needsShovel": False, "purchase": True,
|
||||
"needsShovel": False, "purchase": 100,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Tough Bird Salesman Golden Feather 4",
|
||||
"id": base_id + 27,
|
||||
"inGameId": "ToughBirdNPC (1)[3]",
|
||||
"needsShovel": False, "purchase": True,
|
||||
"needsShovel": False, "purchase": 100,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Tough Bird Salesman (400 Coins)",
|
||||
"id": base_id + 28,
|
||||
"inGameId": "ToughBirdNPC (1)[9]",
|
||||
"needsShovel": False, "purchase": True,
|
||||
"needsShovel": False, "purchase": 400,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
|
||||
# Beachstickball
|
||||
{"name": "Beachstickball (10 Hits)",
|
||||
"id": base_id + 29,
|
||||
"inGameId": "VolleyballOpponent[0]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Beachstickball (20 Hits)",
|
||||
"id": base_id + 30,
|
||||
"inGameId": "VolleyballOpponent[1]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Beachstickball (30 Hits)",
|
||||
"id": base_id + 31,
|
||||
"inGameId": "VolleyballOpponent[2]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
|
||||
# Misc Item Locations
|
||||
{"name": "Shovel Kid Trade",
|
||||
"id": base_id + 32,
|
||||
"inGameId": "Frog_StandingNPC[0]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Compass Guy",
|
||||
"id": base_id + 33,
|
||||
"inGameId": "Fox_WalkingNPC[0]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Hawk Peak Bucket Rock",
|
||||
"id": base_id + 34,
|
||||
"inGameId": "Tools.23",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Orange Islands Bucket Rock",
|
||||
"id": base_id + 35,
|
||||
"inGameId": "Tools.42",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Bill the Walrus Fisherman",
|
||||
"id": base_id + 36,
|
||||
"inGameId": "SittingNPC (1)[0]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Catch 3 Fish Reward",
|
||||
"id": base_id + 37,
|
||||
"inGameId": "FishBuyer[0]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Catch All Fish Reward",
|
||||
"id": base_id + 38,
|
||||
"inGameId": "FishBuyer[1]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 7, "minGoldenFeathersEasy": 9, "minGoldenFeathersBucket": 7},
|
||||
{"name": "Permit Guy Bribe",
|
||||
"id": base_id + 39,
|
||||
"inGameId": "CamperNPC[0]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Catch Fish with Permit",
|
||||
"id": base_id + 129,
|
||||
"inGameId": "Player[0]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Return Camping Permit",
|
||||
"id": base_id + 130,
|
||||
"inGameId": "CamperNPC[1]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
|
||||
# Original Pickaxe Locations
|
||||
{"name": "Blocked Mine Pickaxe 1",
|
||||
"id": base_id + 40,
|
||||
"inGameId": "Tools.31",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Blocked Mine Pickaxe 2",
|
||||
"id": base_id + 41,
|
||||
"inGameId": "Tools.32",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Blocked Mine Pickaxe 3",
|
||||
"id": base_id + 42,
|
||||
"inGameId": "Tools.33",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
|
||||
# Original Toy Shovel Locations
|
||||
{"name": "Blackwood Trail Lookout Toy Shovel",
|
||||
"id": base_id + 43,
|
||||
"inGameId": "PickUps.27",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Shirley's Point Beach Toy Shovel",
|
||||
"id": base_id + 44,
|
||||
"inGameId": "PickUps.30",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Visitor's Center Beach Toy Shovel",
|
||||
"id": base_id + 45,
|
||||
"inGameId": "PickUps.29",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Blackwood Trail Rock Toy Shovel",
|
||||
"id": base_id + 46,
|
||||
"inGameId": "PickUps.26",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Beach Hut Cliff Toy Shovel",
|
||||
"id": base_id + 128,
|
||||
"inGameId": "PickUps.28",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
|
||||
# Original Stick Locations
|
||||
{"name": "Secret Island Beach Trail Stick",
|
||||
"id": base_id + 47,
|
||||
"inGameId": "PickUps.25",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Below Lighthouse Walkway Stick",
|
||||
"id": base_id + 48,
|
||||
"inGameId": "Tools.3",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Beach Hut Rocky Pool Sand Stick",
|
||||
"id": base_id + 49,
|
||||
"inGameId": "Tools.0",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Cliff Overlooking West River Waterfall Stick",
|
||||
"id": base_id + 50,
|
||||
"inGameId": "Tools.2",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 2, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Trail to Tough Bird Salesman Stick",
|
||||
"id": base_id + 51,
|
||||
"inGameId": "Tools.8",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "North Beach Stick",
|
||||
"id": base_id + 52,
|
||||
"inGameId": "Tools.4",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Beachstickball Court Stick",
|
||||
"id": base_id + 53,
|
||||
"inGameId": "VolleyballMinigame.4",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Stick Under Sid Beach Umbrella",
|
||||
"id": base_id + 54,
|
||||
"inGameId": "Tools.1",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
|
||||
# Boating
|
||||
@@ -333,377 +333,377 @@ location_table: List[LocationInfo] = [
|
||||
{"name": "Boat Challenge Reward",
|
||||
"id": base_id + 56,
|
||||
"inGameId": "DeerKidBoat[0]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
|
||||
# Not a location for now, corresponding with the Boating Manual
|
||||
# {"name": "Receive Boating Manual",
|
||||
# "id": base_id + 133,
|
||||
# "inGameId": "DadDeer[1]",
|
||||
# "needsShovel": False, "purchase": False,
|
||||
# "needsShovel": False, "purchase": 0,
|
||||
# "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
|
||||
# Original Map Locations
|
||||
{"name": "Outlook Point Dog Gift",
|
||||
"id": base_id + 57,
|
||||
"inGameId": "Dog_WalkingNPC_BlueEyed[0]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
|
||||
# Original Clothes Locations
|
||||
{"name": "Collect 15 Seashells",
|
||||
"id": base_id + 58,
|
||||
"inGameId": "LittleKidNPCVariant (1)[0]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Return to Shell Kid",
|
||||
"id": base_id + 132,
|
||||
"inGameId": "LittleKidNPCVariant (1)[1]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Taylor the Turtle Headband Gift",
|
||||
"id": base_id + 59,
|
||||
"inGameId": "Turtle_WalkingNPC[0]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Sue the Rabbit Shoes Reward",
|
||||
"id": base_id + 60,
|
||||
"inGameId": "Bunny_WalkingNPC (1)[0]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Purchase Sunhat",
|
||||
"id": base_id + 61,
|
||||
"inGameId": "SittingNPC[0]",
|
||||
"needsShovel": False, "purchase": True,
|
||||
"needsShovel": False, "purchase": 100,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
|
||||
# Original Golden Feather Locations
|
||||
{"name": "Blackwood Forest Golden Feather",
|
||||
"id": base_id + 62,
|
||||
"inGameId": "Feathers.3",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Ranger May Shell Necklace Golden Feather",
|
||||
"id": base_id + 63,
|
||||
"inGameId": "AuntMayNPC[0]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Sand Castle Golden Feather",
|
||||
"id": base_id + 64,
|
||||
"inGameId": "SandProvince.3",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Artist Golden Feather",
|
||||
"id": base_id + 65,
|
||||
"inGameId": "StandingNPC[0]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Visitor Camp Rock Golden Feather",
|
||||
"id": base_id + 66,
|
||||
"inGameId": "Feathers.8",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Outlook Cliff Golden Feather",
|
||||
"id": base_id + 67,
|
||||
"inGameId": "Feathers.2",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Meteor Lake Cliff Golden Feather",
|
||||
"id": base_id + 68,
|
||||
"inGameId": "Feathers.7",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 0},
|
||||
|
||||
# Original Silver Feather Locations
|
||||
{"name": "Secret Island Peak",
|
||||
"id": base_id + 69,
|
||||
"inGameId": "PickUps.24",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 5, "minGoldenFeathersEasy": 7, "minGoldenFeathersBucket": 7},
|
||||
{"name": "Wristwatch Trade",
|
||||
"id": base_id + 70,
|
||||
"inGameId": "Goat_StandingNPC[0]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
|
||||
# Golden Chests
|
||||
{"name": "Lighthouse Golden Chest",
|
||||
"id": base_id + 71,
|
||||
"inGameId": "Feathers.0",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 2, "minGoldenFeathersEasy": 3, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Outlook Golden Chest",
|
||||
"id": base_id + 72,
|
||||
"inGameId": "Feathers.6",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Stone Tower Golden Chest",
|
||||
"id": base_id + 73,
|
||||
"inGameId": "Feathers.5",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "North Cliff Golden Chest",
|
||||
"id": base_id + 74,
|
||||
"inGameId": "Feathers.4",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 3, "minGoldenFeathersEasy": 10, "minGoldenFeathersBucket": 10},
|
||||
|
||||
# Chests
|
||||
{"name": "Blackwood Cliff Chest",
|
||||
"id": base_id + 75,
|
||||
"inGameId": "Coins.22",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "White Coast Trail Chest",
|
||||
"id": base_id + 76,
|
||||
"inGameId": "Coins.6",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Sid Beach Chest",
|
||||
"id": base_id + 77,
|
||||
"inGameId": "Coins.7",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Sid Beach Buried Treasure Chest",
|
||||
"id": base_id + 78,
|
||||
"inGameId": "Coins.46",
|
||||
"needsShovel": True, "purchase": False,
|
||||
"needsShovel": True, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Sid Beach Cliff Chest",
|
||||
"id": base_id + 79,
|
||||
"inGameId": "Coins.9",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Visitor's Center Buried Chest",
|
||||
"id": base_id + 80,
|
||||
"inGameId": "Coins.94",
|
||||
"needsShovel": True, "purchase": False,
|
||||
"needsShovel": True, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Visitor's Center Hidden Chest",
|
||||
"id": base_id + 81,
|
||||
"inGameId": "Coins.42",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Shirley's Point Chest",
|
||||
"id": base_id + 82,
|
||||
"inGameId": "Coins.10",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 1, "minGoldenFeathersEasy": 2, "minGoldenFeathersBucket": 2},
|
||||
{"name": "Caravan Cliff Chest",
|
||||
"id": base_id + 83,
|
||||
"inGameId": "Coins.12",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Caravan Arch Chest",
|
||||
"id": base_id + 84,
|
||||
"inGameId": "Coins.11",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "King Buried Treasure Chest",
|
||||
"id": base_id + 85,
|
||||
"inGameId": "Coins.41",
|
||||
"needsShovel": True, "purchase": False,
|
||||
"needsShovel": True, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Good Creek Path Buried Chest",
|
||||
"id": base_id + 86,
|
||||
"inGameId": "Coins.48",
|
||||
"needsShovel": True, "purchase": False,
|
||||
"needsShovel": True, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Good Creek Path West Chest",
|
||||
"id": base_id + 87,
|
||||
"inGameId": "Coins.33",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Good Creek Path East Chest",
|
||||
"id": base_id + 88,
|
||||
"inGameId": "Coins.62",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "West Waterfall Chest",
|
||||
"id": base_id + 89,
|
||||
"inGameId": "Coins.20",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Stone Tower West Cliff Chest",
|
||||
"id": base_id + 90,
|
||||
"inGameId": "PickUps.0",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Bucket Path Chest",
|
||||
"id": base_id + 91,
|
||||
"inGameId": "Coins.50",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Bucket Cliff Chest",
|
||||
"id": base_id + 92,
|
||||
"inGameId": "Coins.49",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 3, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 5},
|
||||
{"name": "In Her Shadow Buried Treasure Chest",
|
||||
"id": base_id + 93,
|
||||
"inGameId": "Feathers.9",
|
||||
"needsShovel": True, "purchase": False,
|
||||
"needsShovel": True, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Meteor Lake Buried Chest",
|
||||
"id": base_id + 94,
|
||||
"inGameId": "Coins.86",
|
||||
"needsShovel": True, "purchase": False,
|
||||
"needsShovel": True, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Meteor Lake Chest",
|
||||
"id": base_id + 95,
|
||||
"inGameId": "Coins.64",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "House North Beach Chest",
|
||||
"id": base_id + 96,
|
||||
"inGameId": "Coins.65",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "East Coast Chest",
|
||||
"id": base_id + 97,
|
||||
"inGameId": "Coins.98",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Fisherman's Boat Chest 1",
|
||||
"id": base_id + 99,
|
||||
"inGameId": "Boat.0",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Fisherman's Boat Chest 2",
|
||||
"id": base_id + 100,
|
||||
"inGameId": "Boat.7",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Airstream Island Chest",
|
||||
"id": base_id + 101,
|
||||
"inGameId": "Coins.31",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "West River Waterfall Head Chest",
|
||||
"id": base_id + 102,
|
||||
"inGameId": "Coins.34",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Old Building Chest",
|
||||
"id": base_id + 103,
|
||||
"inGameId": "Coins.104",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Old Building West Chest",
|
||||
"id": base_id + 104,
|
||||
"inGameId": "Coins.109",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Old Building East Chest",
|
||||
"id": base_id + 105,
|
||||
"inGameId": "Coins.8",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Hawk Peak West Chest",
|
||||
"id": base_id + 106,
|
||||
"inGameId": "Coins.21",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 3, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 5},
|
||||
{"name": "Hawk Peak East Buried Chest",
|
||||
"id": base_id + 107,
|
||||
"inGameId": "Coins.76",
|
||||
"needsShovel": True, "purchase": False,
|
||||
"needsShovel": True, "purchase": 0,
|
||||
"minGoldenFeathers": 3, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 5},
|
||||
{"name": "Hawk Peak Northeast Chest",
|
||||
"id": base_id + 108,
|
||||
"inGameId": "Coins.79",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 3, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 5},
|
||||
{"name": "Northern East Coast Chest",
|
||||
"id": base_id + 109,
|
||||
"inGameId": "Coins.45",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 2, "minGoldenFeathersBucket": 0},
|
||||
{"name": "North Coast Chest",
|
||||
"id": base_id + 110,
|
||||
"inGameId": "Coins.28",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "North Coast Buried Chest",
|
||||
"id": base_id + 111,
|
||||
"inGameId": "Coins.47",
|
||||
"needsShovel": True, "purchase": False,
|
||||
"needsShovel": True, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Small South Island Buried Chest",
|
||||
"id": base_id + 112,
|
||||
"inGameId": "Coins.87",
|
||||
"needsShovel": True, "purchase": False,
|
||||
"needsShovel": True, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Secret Island Bottom Chest",
|
||||
"id": base_id + 113,
|
||||
"inGameId": "Coins.88",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Secret Island Treehouse Chest",
|
||||
"id": base_id + 114,
|
||||
"inGameId": "Coins.89",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 1, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 1},
|
||||
{"name": "Sunhat Island Buried Chest",
|
||||
"id": base_id + 115,
|
||||
"inGameId": "Coins.112",
|
||||
"needsShovel": True, "purchase": False,
|
||||
"needsShovel": True, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Orange Islands South Buried Chest",
|
||||
"id": base_id + 116,
|
||||
"inGameId": "Coins.119",
|
||||
"needsShovel": True, "purchase": False,
|
||||
"needsShovel": True, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Orange Islands West Chest",
|
||||
"id": base_id + 117,
|
||||
"inGameId": "Coins.121",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Orange Islands North Buried Chest",
|
||||
"id": base_id + 118,
|
||||
"inGameId": "Coins.117",
|
||||
"needsShovel": True, "purchase": False,
|
||||
"needsShovel": True, "purchase": 0,
|
||||
"minGoldenFeathers": 1, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Orange Islands East Chest",
|
||||
"id": base_id + 119,
|
||||
"inGameId": "Coins.120",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Orange Islands South Hidden Chest",
|
||||
"id": base_id + 120,
|
||||
"inGameId": "Coins.124",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "A Stormy View Buried Treasure Chest",
|
||||
"id": base_id + 121,
|
||||
"inGameId": "Coins.113",
|
||||
"needsShovel": True, "purchase": False,
|
||||
"needsShovel": True, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Orange Islands Ruins Buried Chest",
|
||||
"id": base_id + 122,
|
||||
"inGameId": "Coins.118",
|
||||
"needsShovel": True, "purchase": False,
|
||||
"needsShovel": True, "purchase": 0,
|
||||
"minGoldenFeathers": 2, "minGoldenFeathersEasy": 4, "minGoldenFeathersBucket": 0},
|
||||
|
||||
# Race Rewards
|
||||
{"name": "Lighthouse Race Reward",
|
||||
"id": base_id + 123,
|
||||
"inGameId": "RaceOpponent[0]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 2, "minGoldenFeathersEasy": 3, "minGoldenFeathersBucket": 1},
|
||||
{"name": "Old Building Race Reward",
|
||||
"id": base_id + 124,
|
||||
"inGameId": "RaceOpponent[1]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 1, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 0},
|
||||
{"name": "Hawk Peak Race Reward",
|
||||
"id": base_id + 125,
|
||||
"inGameId": "RaceOpponent[2]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 7, "minGoldenFeathersEasy": 9, "minGoldenFeathersBucket": 7},
|
||||
{"name": "Lose Race Gift",
|
||||
"id": base_id + 131,
|
||||
"inGameId": "RaceOpponent[9]",
|
||||
"needsShovel": False, "purchase": False,
|
||||
"needsShovel": False, "purchase": 0,
|
||||
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from Options import Choice, PerGameCommonOptions, Range, StartInventoryPool, Toggle
|
||||
from Options import Choice, OptionGroup, PerGameCommonOptions, Range, StartInventoryPool, Toggle, DefaultOnToggle
|
||||
|
||||
class Goal(Choice):
|
||||
"""Choose the end goal.
|
||||
@@ -22,8 +22,10 @@ class CoinsInShops(Toggle):
|
||||
default = False
|
||||
|
||||
class GoldenFeathers(Range):
|
||||
"""Number of Golden Feathers in the item pool.
|
||||
(Note that for the Photo and Help Everyone goals, a minimum of 12 Golden Feathers is enforced)"""
|
||||
"""
|
||||
Number of Golden Feathers in the item pool.
|
||||
(Note that for the Photo and Help Everyone goals, a minimum of 12 Golden Feathers is enforced)
|
||||
"""
|
||||
display_name = "Golden Feathers"
|
||||
range_start = 0
|
||||
range_end = 20
|
||||
@@ -43,6 +45,20 @@ class Buckets(Range):
|
||||
range_end = 2
|
||||
default = 2
|
||||
|
||||
class Sticks(Range):
|
||||
"""Number of Sticks in the item pool."""
|
||||
display_name = "Sticks"
|
||||
range_start = 1
|
||||
range_end = 8
|
||||
default = 8
|
||||
|
||||
class ToyShovels(Range):
|
||||
"""Number of Toy Shovels in the item pool."""
|
||||
display_name = "Toy Shovels"
|
||||
range_start = 1
|
||||
range_end = 5
|
||||
default = 5
|
||||
|
||||
class GoldenFeatherProgression(Choice):
|
||||
"""Determines which locations are considered in logic based on the required amount of golden feathers to reach them.
|
||||
Easy: Locations will be considered inaccessible until the player has enough golden feathers to easily reach them. A minimum of 10 golden feathers is recommended for this setting.
|
||||
@@ -76,6 +92,40 @@ class FillerCoinAmount(Choice):
|
||||
option_50_coins = 9
|
||||
default = 1
|
||||
|
||||
class RandomWalkieTalkie(DefaultOnToggle):
|
||||
"""
|
||||
When enabled, the Walkie Talkie item will be placed into the item pool. Otherwise, it will be placed in its vanilla location.
|
||||
This item usually allows the player to locate Avery around the map or restart a race.
|
||||
"""
|
||||
display_name = "Randomize Walkie Talkie"
|
||||
|
||||
class EasierRaces(Toggle):
|
||||
"""When enabled, the Running Shoes will be added as a logical requirement for beating any of the races."""
|
||||
display_name = "Easier Races"
|
||||
|
||||
class ShopCheckLogic(Choice):
|
||||
"""Determines which items will be added as logical requirements to making certain purchases in shops."""
|
||||
display_name = "Shop Check Logic"
|
||||
option_nothing = 0
|
||||
option_fishing_rod = 1
|
||||
option_shovel = 2
|
||||
option_fishing_rod_and_shovel = 3
|
||||
option_golden_fishing_rod = 4
|
||||
option_golden_fishing_rod_and_shovel = 5
|
||||
default = 1
|
||||
|
||||
class MinShopCheckLogic(Choice):
|
||||
"""
|
||||
Determines the minimum cost of a shop item that will have the shop check logic applied to it.
|
||||
If the cost of a shop item is less than this value, no items will be required to access it.
|
||||
This is based on the vanilla prices of the shop item. The set cost multiplier will not affect this value.
|
||||
"""
|
||||
display_name = "Minimum Shop Check Logic Application"
|
||||
option_40_coins = 0
|
||||
option_100_coins = 1
|
||||
option_400_coins = 2
|
||||
default = 1
|
||||
|
||||
@dataclass
|
||||
class ShortHikeOptions(PerGameCommonOptions):
|
||||
start_inventory_from_pool: StartInventoryPool
|
||||
@@ -84,6 +134,37 @@ class ShortHikeOptions(PerGameCommonOptions):
|
||||
golden_feathers: GoldenFeathers
|
||||
silver_feathers: SilverFeathers
|
||||
buckets: Buckets
|
||||
sticks: Sticks
|
||||
toy_shovels: ToyShovels
|
||||
golden_feather_progression: GoldenFeatherProgression
|
||||
cost_multiplier: CostMultiplier
|
||||
filler_coin_amount: FillerCoinAmount
|
||||
random_walkie_talkie: RandomWalkieTalkie
|
||||
easier_races: EasierRaces
|
||||
shop_check_logic: ShopCheckLogic
|
||||
min_shop_check_logic: MinShopCheckLogic
|
||||
|
||||
shorthike_option_groups = [
|
||||
OptionGroup("General Options", [
|
||||
Goal,
|
||||
FillerCoinAmount,
|
||||
RandomWalkieTalkie
|
||||
]),
|
||||
OptionGroup("Logic Options", [
|
||||
GoldenFeatherProgression,
|
||||
EasierRaces
|
||||
]),
|
||||
OptionGroup("Item Pool Options", [
|
||||
GoldenFeathers,
|
||||
SilverFeathers,
|
||||
Buckets,
|
||||
Sticks,
|
||||
ToyShovels
|
||||
]),
|
||||
OptionGroup("Shop Options", [
|
||||
CoinsInShops,
|
||||
CostMultiplier,
|
||||
ShopCheckLogic,
|
||||
MinShopCheckLogic
|
||||
])
|
||||
]
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from worlds.generic.Rules import forbid_items_for_player, add_rule
|
||||
from .Options import Goal, GoldenFeatherProgression, MinShopCheckLogic, ShopCheckLogic
|
||||
|
||||
|
||||
def create_rules(self, location_table):
|
||||
multiworld = self.multiworld
|
||||
@@ -11,11 +13,23 @@ def create_rules(self, location_table):
|
||||
forbid_items_for_player(multiworld.get_location(loc["name"], player), self.item_name_groups['Maps'], player)
|
||||
add_rule(multiworld.get_location(loc["name"], player),
|
||||
lambda state: state.has("Shovel", player))
|
||||
|
||||
# Shop Rules
|
||||
if loc["purchase"] and not options.coins_in_shops:
|
||||
forbid_items_for_player(multiworld.get_location(loc["name"], player), self.item_name_groups['Coins'], player)
|
||||
if loc["purchase"] >= get_min_shop_logic_cost(self) and options.shop_check_logic != ShopCheckLogic.option_nothing:
|
||||
if options.shop_check_logic in {ShopCheckLogic.option_fishing_rod, ShopCheckLogic.option_fishing_rod_and_shovel}:
|
||||
add_rule(multiworld.get_location(loc["name"], player),
|
||||
lambda state: state.has("Progressive Fishing Rod", player))
|
||||
if options.shop_check_logic in {ShopCheckLogic.option_golden_fishing_rod, ShopCheckLogic.option_golden_fishing_rod_and_shovel}:
|
||||
add_rule(multiworld.get_location(loc["name"], player),
|
||||
lambda state: state.has("Progressive Fishing Rod", player, 2))
|
||||
if options.shop_check_logic in {ShopCheckLogic.option_shovel, ShopCheckLogic.option_fishing_rod_and_shovel, ShopCheckLogic.option_golden_fishing_rod_and_shovel}:
|
||||
add_rule(multiworld.get_location(loc["name"], player),
|
||||
lambda state: state.has("Shovel", player))
|
||||
|
||||
# Minimum Feather Rules
|
||||
if options.golden_feather_progression != 2:
|
||||
if options.golden_feather_progression != GoldenFeatherProgression.option_hard:
|
||||
min_feathers = get_min_feathers(self, loc["minGoldenFeathers"], loc["minGoldenFeathersEasy"])
|
||||
|
||||
if options.buckets > 0 and loc["minGoldenFeathersBucket"] < min_feathers:
|
||||
@@ -32,11 +46,11 @@ def create_rules(self, location_table):
|
||||
|
||||
# Fishing Rules
|
||||
add_rule(multiworld.get_location("Catch 3 Fish Reward", player),
|
||||
lambda state: state.has("Fishing Rod", player))
|
||||
lambda state: state.has("Progressive Fishing Rod", player))
|
||||
add_rule(multiworld.get_location("Catch Fish with Permit", player),
|
||||
lambda state: state.has("Fishing Rod", player))
|
||||
lambda state: state.has("Progressive Fishing Rod", player))
|
||||
add_rule(multiworld.get_location("Catch All Fish Reward", player),
|
||||
lambda state: state.has("Fishing Rod", player))
|
||||
lambda state: state.has("Progressive Fishing Rod", player, 2))
|
||||
|
||||
# Misc Rules
|
||||
add_rule(multiworld.get_location("Return Camping Permit", player),
|
||||
@@ -59,15 +73,34 @@ def create_rules(self, location_table):
|
||||
lambda state: state.has("Stick", player))
|
||||
add_rule(multiworld.get_location("Beachstickball (30 Hits)", player),
|
||||
lambda state: state.has("Stick", player))
|
||||
|
||||
# Race Rules
|
||||
if options.easier_races:
|
||||
add_rule(multiworld.get_location("Lighthouse Race Reward", player),
|
||||
lambda state: state.has("Running Shoes", player))
|
||||
add_rule(multiworld.get_location("Old Building Race Reward", player),
|
||||
lambda state: state.has("Running Shoes", player))
|
||||
add_rule(multiworld.get_location("Hawk Peak Race Reward", player),
|
||||
lambda state: state.has("Running Shoes", player))
|
||||
|
||||
def get_min_feathers(self, min_golden_feathers, min_golden_feathers_easy):
|
||||
options = self.options
|
||||
|
||||
min_feathers = min_golden_feathers
|
||||
if options.golden_feather_progression == 0:
|
||||
if options.golden_feather_progression == GoldenFeatherProgression.option_easy:
|
||||
min_feathers = min_golden_feathers_easy
|
||||
if min_feathers > options.golden_feathers:
|
||||
if options.goal != 1 and options.goal != 3:
|
||||
if options.goal not in {Goal.option_help_everyone, Goal.option_photo}:
|
||||
min_feathers = options.golden_feathers
|
||||
|
||||
return min_feathers
|
||||
|
||||
def get_min_shop_logic_cost(self):
|
||||
options = self.options
|
||||
|
||||
if options.min_shop_check_logic == MinShopCheckLogic.option_40_coins:
|
||||
return 40
|
||||
elif options.min_shop_check_logic == MinShopCheckLogic.option_100_coins:
|
||||
return 100
|
||||
elif options.min_shop_check_logic == MinShopCheckLogic.option_400_coins:
|
||||
return 400
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
from collections import Counter
|
||||
from typing import ClassVar, Dict, Any, Type
|
||||
from BaseClasses import Region, Location, Item, Tutorial
|
||||
from BaseClasses import ItemClassification, Region, Location, Item, Tutorial
|
||||
from Options import PerGameCommonOptions
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from .Items import item_table, group_table, base_id
|
||||
from .Locations import location_table
|
||||
from .Rules import create_rules, get_min_feathers
|
||||
from .Options import ShortHikeOptions
|
||||
from .Options import ShortHikeOptions, shorthike_option_groups
|
||||
|
||||
class ShortHikeWeb(WebWorld):
|
||||
theme = "ocean"
|
||||
@@ -18,6 +17,7 @@ class ShortHikeWeb(WebWorld):
|
||||
"setup/en",
|
||||
["Chandler"]
|
||||
)]
|
||||
option_groups = shorthike_option_groups
|
||||
|
||||
class ShortHikeWorld(World):
|
||||
"""
|
||||
@@ -47,9 +47,14 @@ class ShortHikeWorld(World):
|
||||
item_id: int = self.item_name_to_id[name]
|
||||
id = item_id - base_id - 1
|
||||
|
||||
return ShortHikeItem(name, item_table[id]["classification"], item_id, player=self.player)
|
||||
classification = item_table[id]["classification"]
|
||||
if self.options.easier_races and name == "Running Shoes":
|
||||
classification = ItemClassification.progression
|
||||
|
||||
return ShortHikeItem(name, classification, item_id, player=self.player)
|
||||
|
||||
def create_items(self) -> None:
|
||||
itempool = []
|
||||
for item in item_table:
|
||||
count = item["count"]
|
||||
|
||||
@@ -57,18 +62,28 @@ class ShortHikeWorld(World):
|
||||
continue
|
||||
else:
|
||||
for i in range(count):
|
||||
self.multiworld.itempool.append(self.create_item(item["name"]))
|
||||
itempool.append(self.create_item(item["name"]))
|
||||
|
||||
feather_count = self.options.golden_feathers
|
||||
if self.options.goal == 1 or self.options.goal == 3:
|
||||
if feather_count < 12:
|
||||
feather_count = 12
|
||||
|
||||
junk = 45 - self.options.silver_feathers - feather_count - self.options.buckets
|
||||
self.multiworld.itempool += [self.create_item(self.get_filler_item_name()) for _ in range(junk)]
|
||||
self.multiworld.itempool += [self.create_item("Golden Feather") for _ in range(feather_count)]
|
||||
self.multiworld.itempool += [self.create_item("Silver Feather") for _ in range(self.options.silver_feathers)]
|
||||
self.multiworld.itempool += [self.create_item("Bucket") for _ in range(self.options.buckets)]
|
||||
itempool += [self.create_item("Golden Feather") for _ in range(feather_count)]
|
||||
itempool += [self.create_item("Silver Feather") for _ in range(self.options.silver_feathers)]
|
||||
itempool += [self.create_item("Bucket") for _ in range(self.options.buckets)]
|
||||
itempool += [self.create_item("Stick") for _ in range(self.options.sticks)]
|
||||
itempool += [self.create_item("Toy Shovel") for _ in range(self.options.toy_shovels)]
|
||||
|
||||
if self.options.random_walkie_talkie:
|
||||
itempool.append(self.create_item("Walkie Talkie"))
|
||||
else:
|
||||
self.multiworld.get_location("Lose Race Gift", self.player).place_locked_item(self.create_item("Walkie Talkie"))
|
||||
|
||||
junk = len(self.multiworld.get_unfilled_locations(self.player)) - len(itempool)
|
||||
itempool += [self.create_item(self.get_filler_item_name()) for _ in range(junk)]
|
||||
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
def create_regions(self) -> None:
|
||||
menu_region = Region("Menu", self.player, self.multiworld)
|
||||
@@ -92,20 +107,23 @@ class ShortHikeWorld(World):
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has("Golden Feather", self.player, 12)
|
||||
elif self.options.goal == "races":
|
||||
# Races
|
||||
self.multiworld.completion_condition[self.player] = lambda state: (state.has("Golden Feather", self.player, get_min_feathers(self, 7, 9))
|
||||
or (state.has("Bucket", self.player) and state.has("Golden Feather", self.player, 7)))
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.can_reach_location("Hawk Peak Race Reward", self.player)
|
||||
elif self.options.goal == "help_everyone":
|
||||
# Help Everyone
|
||||
self.multiworld.completion_condition[self.player] = lambda state: (state.has("Golden Feather", self.player, 12)
|
||||
and state.has("Toy Shovel", self.player) and state.has("Camping Permit", self.player)
|
||||
and state.has("Motorboat Key", self.player) and state.has("Headband", self.player)
|
||||
and state.has("Wristwatch", self.player) and state.has("Seashell", self.player, 15)
|
||||
and state.has("Shell Necklace", self.player))
|
||||
self.multiworld.completion_condition[self.player] = lambda state: (state.can_reach_location("Collect 15 Seashells", self.player)
|
||||
and state.has("Golden Feather", self.player, 12)
|
||||
and state.can_reach_location("Tough Bird Salesman (400 Coins)", self.player)
|
||||
and state.can_reach_location("Ranger May Shell Necklace Golden Feather", self.player)
|
||||
and state.can_reach_location("Sue the Rabbit Shoes Reward", self.player)
|
||||
and state.can_reach_location("Wristwatch Trade", self.player)
|
||||
and state.can_reach_location("Return Camping Permit", self.player)
|
||||
and state.can_reach_location("Boat Challenge Reward", self.player)
|
||||
and state.can_reach_location("Shovel Kid Trade", self.player)
|
||||
and state.can_reach_location("Purchase Sunhat", self.player)
|
||||
and state.can_reach_location("Artist Golden Feather", self.player))
|
||||
elif self.options.goal == "fishmonger":
|
||||
# Fishmonger
|
||||
self.multiworld.completion_condition[self.player] = lambda state: (state.has("Golden Feather", self.player, get_min_feathers(self, 7, 9))
|
||||
or (state.has("Bucket", self.player) and state.has("Golden Feather", self.player, 7))
|
||||
and state.has("Fishing Rod", self.player))
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.can_reach_location("Catch All Fish Reward", self.player)
|
||||
|
||||
def set_rules(self):
|
||||
create_rules(self, location_table)
|
||||
@@ -117,6 +135,9 @@ class ShortHikeWorld(World):
|
||||
"goal": int(options.goal),
|
||||
"logicLevel": int(options.golden_feather_progression),
|
||||
"costMultiplier": int(options.cost_multiplier),
|
||||
"shopCheckLogic": int(options.shop_check_logic),
|
||||
"minShopCheckLogic": int(options.min_shop_check_logic),
|
||||
"easierRaces": bool(options.easier_races),
|
||||
}
|
||||
|
||||
slot_data = {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
- A Short Hike: [Steam](https://store.steampowered.com/app/1055540/A_Short_Hike/)
|
||||
- The Epic Games Store or itch.io version of A Short Hike will also work.
|
||||
- A Short Hike Modding Tools: [GitHub](https://github.com/BrandenEK/AShortHike.ModdingTools)
|
||||
- A Short Hike Randomizer: [GitHub](https://github.com/BrandenEK/AShortHike.Randomizer)
|
||||
|
||||
## Optional Software
|
||||
@@ -14,18 +13,13 @@
|
||||
|
||||
## Installation
|
||||
|
||||
1. Open the [Modding Tools GitHub page](https://github.com/BrandenEK/AShortHike.ModdingTools/), and follow
|
||||
the installation instructions. After this step, your `A Short Hike/` folder should have an empty `Modding/` subfolder.
|
||||
|
||||
2. After the Modding Tools have been installed, download the
|
||||
[Randomizer](https://github.com/BrandenEK/AShortHike.Randomizer/releases) zip, extract it, and move the contents
|
||||
of the `Randomizer/` folder into your `Modding/` folder. After this step, your `Modding/` folder should have
|
||||
`data/` and `plugins/` subfolders.
|
||||
Open the [Randomizer Repository](https://github.com/BrandenEK/AShortHike.Randomizer) and follow
|
||||
the installation instructions listed there.
|
||||
|
||||
## Connecting
|
||||
|
||||
A Short Hike will prompt you with the server details when a new game is started or a previous one is continued.
|
||||
Enter in the Server Port, Name, and Password (optional) in the popup menu that appears and hit connect.
|
||||
Enter in the Server Address and Port, Name, and Password (optional) in the popup menu that appears and hit connect.
|
||||
|
||||
## Tracking
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w
|
||||
logic = TimespinnerLogic(world, player, precalculated_weights)
|
||||
|
||||
connect(world, player, 'Lake desolation', 'Lower lake desolation', lambda state: flooded.flood_lake_desolation or logic.has_timestop(state) or state.has('Talaria Attachment', player))
|
||||
connect(world, player, 'Lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player))
|
||||
connect(world, player, 'Lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player), "Upper Lake Serene")
|
||||
connect(world, player, 'Lake desolation', 'Skeleton Shaft', lambda state: flooded.flood_lake_desolation or logic.has_doublejump(state))
|
||||
connect(world, player, 'Lake desolation', 'Space time continuum', logic.has_teleport)
|
||||
connect(world, player, 'Upper lake desolation', 'Lake desolation')
|
||||
@@ -80,7 +80,7 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w
|
||||
connect(world, player, 'Eastern lake desolation', 'Space time continuum', logic.has_teleport)
|
||||
connect(world, player, 'Eastern lake desolation', 'Library')
|
||||
connect(world, player, 'Eastern lake desolation', 'Lower lake desolation')
|
||||
connect(world, player, 'Eastern lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player))
|
||||
connect(world, player, 'Eastern lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player), "Upper Lake Serene")
|
||||
connect(world, player, 'Library', 'Eastern lake desolation')
|
||||
connect(world, player, 'Library', 'Library top', lambda state: logic.has_doublejump(state) or state.has('Talaria Attachment', player))
|
||||
connect(world, player, 'Library', 'Varndagroth tower left', logic.has_keycard_D)
|
||||
@@ -185,7 +185,7 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w
|
||||
if is_option_enabled(world, player, "GyreArchives"):
|
||||
connect(world, player, 'The lab (upper)', 'Ravenlord\'s Lair', lambda state: state.has('Merchant Crow', player))
|
||||
connect(world, player, 'Ravenlord\'s Lair', 'The lab (upper)')
|
||||
connect(world, player, 'Library top', 'Ifrit\'s Lair', lambda state: state.has('Kobo', player) and state.can_reach('Refugee Camp', 'Region', player))
|
||||
connect(world, player, 'Library top', 'Ifrit\'s Lair', lambda state: state.has('Kobo', player) and state.can_reach('Refugee Camp', 'Region', player), "Refugee Camp")
|
||||
connect(world, player, 'Ifrit\'s Lair', 'Library top')
|
||||
|
||||
|
||||
@@ -242,11 +242,19 @@ def connectStartingRegion(world: MultiWorld, player: int):
|
||||
|
||||
|
||||
def connect(world: MultiWorld, player: int, source: str, target: str,
|
||||
rule: Optional[Callable[[CollectionState], bool]] = None):
|
||||
rule: Optional[Callable[[CollectionState], bool]] = None,
|
||||
indirect: str = ""):
|
||||
|
||||
sourceRegion = world.get_region(source, player)
|
||||
targetRegion = world.get_region(target, player)
|
||||
sourceRegion.connect(targetRegion, rule=rule)
|
||||
entrance = sourceRegion.connect(targetRegion, rule=rule)
|
||||
|
||||
if indirect:
|
||||
indirectRegion = world.get_region(indirect, player)
|
||||
if indirectRegion in world.indirect_connections:
|
||||
world.indirect_connections[indirectRegion].add(entrance)
|
||||
else:
|
||||
world.indirect_connections[indirectRegion] = {entrance}
|
||||
|
||||
|
||||
def split_location_datas_per_region(locations: List[LocationData]) -> Dict[str, List[LocationData]]:
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
## Required Software
|
||||
|
||||
- [TUNIC](https://tunicgame.com/) for PC (Steam Deck also supported)
|
||||
- [BepInEx (Unity IL2CPP)](https://github.com/BepInEx/BepInEx/releases/tag/v6.0.0-pre.1)
|
||||
- [TUNIC Randomizer Mod](https://github.com/silent-destroyer/tunic-randomizer/releases/latest)
|
||||
- [BepInEx 6.0.0-pre.1 (Unity IL2CPP x64)](https://github.com/BepInEx/BepInEx/releases/tag/v6.0.0-pre.1)
|
||||
|
||||
## Optional Software
|
||||
- [TUNIC Randomizer Map Tracker](https://github.com/SapphireSapphic/TunicTracker/releases/latest) (For use with EmoTracker/PopTracker)
|
||||
- [TUNIC Randomizer Map Tracker](https://github.com/SapphireSapphic/TunicTracker/releases/latest)
|
||||
- Requires [PopTracker](https://github.com/black-sliver/PopTracker/releases)
|
||||
- [TUNIC Randomizer Item Auto-tracker](https://github.com/radicoon/tunic-rando-tracker/releases/latest)
|
||||
- [Archipelago Text Client](https://github.com/ArchipelagoMW/Archipelago/releases/latest)
|
||||
|
||||
@@ -27,7 +28,7 @@ Find your TUNIC game installation directory:
|
||||
|
||||
BepInEx is a general purpose framework for modding Unity games, and is used to run the TUNIC Randomizer.
|
||||
|
||||
Download [BepInEx](https://github.com/BepInEx/BepInEx/releases/download/v6.0.0-pre.1/BepInEx_UnityIL2CPP_x64_6.0.0-pre.1.zip).
|
||||
Download [BepInEx 6.0.0-pre.1 (Unity IL2CPP x64)](https://github.com/BepInEx/BepInEx/releases/tag/v6.0.0-pre.1).
|
||||
|
||||
If playing on Steam Deck, follow this [guide to set up BepInEx via Proton](https://docs.bepinex.dev/articles/advanced/proton_wine.html).
|
||||
|
||||
|
||||
@@ -1462,8 +1462,6 @@ def set_er_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int])
|
||||
# Quarry
|
||||
set_rule(multiworld.get_location("Quarry - [Central] Above Ladder Dash Chest", player),
|
||||
lambda state: state.has(laurels, player))
|
||||
set_rule(multiworld.get_location("Quarry - [West] Upper Area Bombable Wall", player),
|
||||
lambda state: has_mask(state, player, options))
|
||||
|
||||
# Ziggurat
|
||||
set_rule(multiworld.get_location("Rooted Ziggurat Upper - Near Bridge Switch", player),
|
||||
|
||||
@@ -304,15 +304,15 @@ def set_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) ->
|
||||
# Quarry
|
||||
set_rule(multiworld.get_location("Quarry - [Central] Above Ladder Dash Chest", player),
|
||||
lambda state: state.has(laurels, player))
|
||||
set_rule(multiworld.get_location("Quarry - [West] Upper Area Bombable Wall", player),
|
||||
lambda state: has_mask(state, player, options))
|
||||
# nmg - kill boss scav with orb + firecracker, or similar
|
||||
set_rule(multiworld.get_location("Rooted Ziggurat Lower - Hexagon Blue", player),
|
||||
lambda state: has_sword(state, player) or (state.has(grapple, player) and options.logic_rules))
|
||||
|
||||
# Swamp
|
||||
set_rule(multiworld.get_location("Cathedral Gauntlet - Gauntlet Reward", player),
|
||||
lambda state: state.has(laurels, player) and state.has(fire_wand, player) and has_sword(state, player))
|
||||
lambda state: (state.has(fire_wand, player) and has_sword(state, player))
|
||||
and (state.has(laurels, player)
|
||||
or has_ice_grapple_logic(False, state, player, options, ability_unlocks)))
|
||||
set_rule(multiworld.get_location("Swamp - [Entrance] Above Entryway", player),
|
||||
lambda state: state.has(laurels, player))
|
||||
set_rule(multiworld.get_location("Swamp - [South Graveyard] Upper Walkway Dash Chest", player),
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from collections import defaultdict
|
||||
from functools import lru_cache
|
||||
from typing import Dict, List, Set, Tuple
|
||||
|
||||
from Utils import cache_argsless
|
||||
|
||||
from .item_definition_classes import (
|
||||
CATEGORY_NAME_MAPPINGS,
|
||||
DoorItemDefinition,
|
||||
@@ -260,17 +261,17 @@ def get_parent_progressive_item(item_name: str) -> str:
|
||||
return _progressive_lookup.get(item_name, item_name)
|
||||
|
||||
|
||||
@lru_cache
|
||||
@cache_argsless
|
||||
def get_vanilla() -> StaticWitnessLogicObj:
|
||||
return StaticWitnessLogicObj(get_vanilla_logic())
|
||||
|
||||
|
||||
@lru_cache
|
||||
@cache_argsless
|
||||
def get_sigma_normal() -> StaticWitnessLogicObj:
|
||||
return StaticWitnessLogicObj(get_sigma_normal_logic())
|
||||
|
||||
|
||||
@lru_cache
|
||||
@cache_argsless
|
||||
def get_sigma_expert() -> StaticWitnessLogicObj:
|
||||
return StaticWitnessLogicObj(get_sigma_expert_logic())
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from functools import lru_cache
|
||||
from math import floor
|
||||
from pkgutil import get_data
|
||||
from random import random
|
||||
@@ -103,10 +102,15 @@ def parse_lambda(lambda_string) -> WitnessRule:
|
||||
return lambda_set
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
_adjustment_file_cache = dict()
|
||||
|
||||
|
||||
def get_adjustment_file(adjustment_file: str) -> List[str]:
|
||||
data = get_data(__name__, adjustment_file).decode("utf-8")
|
||||
return [line.strip() for line in data.split("\n")]
|
||||
if adjustment_file not in _adjustment_file_cache:
|
||||
data = get_data(__name__, adjustment_file).decode("utf-8")
|
||||
_adjustment_file_cache[adjustment_file] = [line.strip() for line in data.split("\n")]
|
||||
|
||||
return _adjustment_file_cache[adjustment_file]
|
||||
|
||||
|
||||
def get_disable_unrandomized_list() -> List[str]:
|
||||
|
||||
@@ -5,7 +5,7 @@ from NetUtils import ClientStatus, NetworkItem
|
||||
|
||||
import worlds._bizhawk as bizhawk
|
||||
from worlds._bizhawk.client import BizHawkClient
|
||||
from worlds.yugioh06 import item_to_index
|
||||
from . import item_to_index
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from worlds._bizhawk.context import BizHawkClientContext
|
||||
|
||||
@@ -3,8 +3,8 @@ from typing import Dict, List, NamedTuple, Optional, Union
|
||||
from BaseClasses import MultiWorld
|
||||
from worlds.generic.Rules import CollectionRule
|
||||
|
||||
from worlds.yugioh06 import item_to_index, tier_1_opponents, yugioh06_difficulty
|
||||
from worlds.yugioh06.locations import special
|
||||
from . import item_to_index, tier_1_opponents, yugioh06_difficulty
|
||||
from .locations import special
|
||||
|
||||
|
||||
class OpponentData(NamedTuple):
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
zilliandomizer @ git+https://github.com/beauxq/zilliandomizer@b36a23b5a138c78732ac8efb5b5ca8b0be07dcff#0.7.0
|
||||
zilliandomizer @ git+https://github.com/beauxq/zilliandomizer@1dd2ce01c9d818caba5844529699b3ad026d6a07#0.7.1
|
||||
typing-extensions>=4.7, <5
|
||||
|
||||
Reference in New Issue
Block a user