Compare commits

..

1 Commits

Author SHA1 Message Date
NewSoupVi
8cfdf4b340 Core: Remove broken unused code from Options.py
"Unused" is a baseless assertion, but this code path has been crashing on the first statement for 6 months and noone's complained
2024-08-12 16:55:03 +02:00
211 changed files with 14585 additions and 69783 deletions

1
.gitattributes vendored
View File

@@ -1 +0,0 @@
worlds/blasphemous/region_data.py linguist-generated=true

View File

@@ -37,13 +37,12 @@ jobs:
- {version: '3.9'}
- {version: '3.10'}
- {version: '3.11'}
- {version: '3.12'}
include:
- python: {version: '3.8'} # win7 compat
os: windows-latest
- python: {version: '3.12'} # current
- python: {version: '3.11'} # current
os: windows-latest
- python: {version: '3.12'} # current
- python: {version: '3.11'} # current
os: macos-latest
steps:
@@ -71,7 +70,7 @@ jobs:
os:
- ubuntu-latest
python:
- {version: '3.12'} # current
- {version: '3.11'} # current
steps:
- uses: actions/checkout@v4

View File

@@ -11,10 +11,8 @@ from argparse import Namespace
from collections import Counter, deque
from collections.abc import Collection, MutableSequence
from enum import IntEnum, IntFlag
from typing import (AbstractSet, Any, Callable, ClassVar, Dict, Iterable, Iterator, List, Mapping, NamedTuple,
Optional, Protocol, Set, Tuple, Union, Type)
from typing_extensions import NotRequired, TypedDict
from typing import Any, Callable, Dict, Iterable, Iterator, List, Mapping, NamedTuple, Optional, Set, Tuple, \
TypedDict, Union, Type, ClassVar
import NetUtils
import Options
@@ -24,16 +22,16 @@ if typing.TYPE_CHECKING:
from worlds import AutoWorld
class Group(TypedDict):
class Group(TypedDict, total=False):
name: str
game: str
world: "AutoWorld.World"
players: AbstractSet[int]
item_pool: NotRequired[Set[str]]
replacement_items: NotRequired[Dict[int, Optional[str]]]
local_items: NotRequired[Set[str]]
non_local_items: NotRequired[Set[str]]
link_replacement: NotRequired[bool]
players: Set[int]
item_pool: Set[str]
replacement_items: Dict[int, Optional[str]]
local_items: Set[str]
non_local_items: Set[str]
link_replacement: bool
class ThreadBarrierProxy:
@@ -50,11 +48,6 @@ class ThreadBarrierProxy:
"Please use multiworld.per_slot_randoms[player] or randomize ahead of output.")
class HasNameAndPlayer(Protocol):
name: str
player: int
class MultiWorld():
debug_types = False
player_name: Dict[int, str]
@@ -163,7 +156,7 @@ class MultiWorld():
self.start_inventory_from_pool: Dict[int, Options.StartInventoryPool] = {}
for player in range(1, players + 1):
def set_player_attr(attr: str, val) -> None:
def set_player_attr(attr, val):
self.__dict__.setdefault(attr, {})[player] = val
set_player_attr('plando_items', [])
set_player_attr('plando_texts', {})
@@ -172,13 +165,13 @@ class MultiWorld():
set_player_attr('completion_condition', lambda state: True)
self.worlds = {}
self.per_slot_randoms = Utils.DeprecateDict("Using per_slot_randoms is now deprecated. Please use the "
"world's random object instead (usually self.random)")
"world's random object instead (usually self.random)")
self.plando_options = PlandoOptions.none
def get_all_ids(self) -> Tuple[int, ...]:
return self.player_ids + tuple(self.groups)
def add_group(self, name: str, game: str, players: AbstractSet[int] = frozenset()) -> Tuple[int, Group]:
def add_group(self, name: str, game: str, players: Set[int] = frozenset()) -> Tuple[int, Group]:
"""Create a group with name and return the assigned player ID and group.
If a group of this name already exists, the set of players is extended instead of creating a new one."""
from worlds import AutoWorld
@@ -202,7 +195,7 @@ class MultiWorld():
return new_id, new_group
def get_player_groups(self, player: int) -> Set[int]:
def get_player_groups(self, player) -> Set[int]:
return {group_id for group_id, group in self.groups.items() if player in group["players"]}
def set_seed(self, seed: Optional[int] = None, secure: bool = False, name: Optional[str] = None):
@@ -265,7 +258,7 @@ class MultiWorld():
"link_replacement": replacement_prio.index(item_link["link_replacement"]),
}
for _name, item_link in item_links.items():
for name, item_link in item_links.items():
current_item_name_groups = AutoWorld.AutoWorldRegister.world_types[item_link["game"]].item_name_groups
pool = set()
local_items = set()
@@ -395,7 +388,7 @@ class MultiWorld():
return tuple(world for player, world in self.worlds.items() if
player not in self.groups and self.game[player] == game_name)
def get_name_string_for_object(self, obj: HasNameAndPlayer) -> str:
def get_name_string_for_object(self, obj) -> str:
return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_name(obj.player)})'
def get_player_name(self, player: int) -> str:
@@ -437,7 +430,7 @@ class MultiWorld():
subworld = self.worlds[player]
for item in subworld.get_pre_fill_items():
subworld.collect(ret, item)
ret.sweep_for_advancements()
ret.sweep_for_events()
if use_cache:
self._all_state = ret
@@ -446,7 +439,7 @@ class MultiWorld():
def get_items(self) -> List[Item]:
return [loc.item for loc in self.get_filled_locations()] + self.itempool
def find_item_locations(self, item: str, player: int, resolve_group_locations: bool = False) -> List[Location]:
def find_item_locations(self, item, player: int, resolve_group_locations: bool = False) -> List[Location]:
if resolve_group_locations:
player_groups = self.get_player_groups(player)
return [location for location in self.get_locations() if
@@ -455,7 +448,7 @@ class MultiWorld():
return [location for location in self.get_locations() if
location.item and location.item.name == item and location.item.player == player]
def find_item(self, item: str, player: int) -> Location:
def find_item(self, item, player: int) -> Location:
return next(location for location in self.get_locations() if
location.item and location.item.name == item and location.item.player == player)
@@ -548,9 +541,9 @@ class MultiWorld():
return True
state = starting_state.copy()
else:
state = CollectionState(self)
if self.has_beaten_game(state):
if self.has_beaten_game(self.state):
return True
state = CollectionState(self)
prog_locations = {location for location in self.get_locations() if location.item
and location.item.advancement and location not in state.locations_checked}
@@ -623,7 +616,8 @@ class MultiWorld():
def location_relevant(location: Location) -> bool:
"""Determine if this location is relevant to sweep."""
return location.player in players["full"] or location.advancement
return location.progress_type != LocationProgressType.EXCLUDED \
and (location.player in players["full"] or location.advancement)
def all_done() -> bool:
"""Check if all access rules are fulfilled"""
@@ -668,7 +662,7 @@ class CollectionState():
multiworld: MultiWorld
reachable_regions: Dict[int, Set[Region]]
blocked_connections: Dict[int, Set[Entrance]]
advancements: Set[Location]
events: Set[Location]
path: Dict[Union[Region, Entrance], PathValue]
locations_checked: Set[Location]
stale: Dict[int, bool]
@@ -680,7 +674,7 @@ class CollectionState():
self.multiworld = parent
self.reachable_regions = {player: set() for player in parent.get_all_ids()}
self.blocked_connections = {player: set() for player in parent.get_all_ids()}
self.advancements = set()
self.events = set()
self.path = {}
self.locations_checked = set()
self.stale = {player: True for player in parent.get_all_ids()}
@@ -729,7 +723,7 @@ class CollectionState():
self.reachable_regions.items()}
ret.blocked_connections = {player: entrance_set.copy() for player, entrance_set in
self.blocked_connections.items()}
ret.advancements = self.advancements.copy()
ret.events = self.events.copy()
ret.path = self.path.copy()
ret.locations_checked = self.locations_checked.copy()
for function in self.additional_copy_functions:
@@ -762,24 +756,19 @@ class CollectionState():
return self.multiworld.get_region(spot, player).can_reach(self)
def sweep_for_events(self, locations: Optional[Iterable[Location]] = None) -> None:
Utils.deprecate("sweep_for_events has been renamed to sweep_for_advancements. The functionality is the same. "
"Please switch over to sweep_for_advancements.")
return self.sweep_for_advancements(locations)
def sweep_for_advancements(self, locations: Optional[Iterable[Location]] = None) -> None:
if locations is None:
locations = self.multiworld.get_filled_locations()
reachable_advancements = True
# since the loop has a good chance to run more than once, only filter the advancements once
locations = {location for location in locations if location.advancement and location not in self.advancements}
reachable_events = True
# since the loop has a good chance to run more than once, only filter the events once
locations = {location for location in locations if location.advancement and location not in self.events}
while reachable_advancements:
reachable_advancements = {location for location in locations if location.can_reach(self)}
locations -= reachable_advancements
for advancement in reachable_advancements:
self.advancements.add(advancement)
assert isinstance(advancement.item, Item), "tried to collect Event with no Item"
self.collect(advancement.item, True, advancement)
while reachable_events:
reachable_events = {location for location in locations if location.can_reach(self)}
locations -= reachable_events
for event in reachable_events:
self.events.add(event)
assert isinstance(event.item, Item), "tried to collect Event with no Item"
self.collect(event.item, True, event)
# item name related
def has(self, item: str, player: int, count: int = 1) -> bool:
@@ -813,7 +802,7 @@ class CollectionState():
if found >= count:
return True
return False
def has_from_list_unique(self, items: Iterable[str], player: int, count: int) -> bool:
"""Returns True if the state contains at least `count` items matching any of the item names from a list.
Ignores duplicates of the same item."""
@@ -828,7 +817,7 @@ class CollectionState():
def count_from_list(self, items: Iterable[str], player: int) -> int:
"""Returns the cumulative count of items from a list present in state."""
return sum(self.prog_items[player][item_name] for item_name in items)
def count_from_list_unique(self, items: Iterable[str], player: int) -> int:
"""Returns the cumulative count of items from a list present in state. Ignores duplicates of the same item."""
return sum(self.prog_items[player][item_name] > 0 for item_name in items)
@@ -874,16 +863,20 @@ class CollectionState():
)
# Item related
def collect(self, item: Item, prevent_sweep: bool = False, location: Optional[Location] = None) -> bool:
def collect(self, item: Item, event: bool = False, location: Optional[Location] = None) -> bool:
if location:
self.locations_checked.add(location)
changed = self.multiworld.worlds[item.player].collect(self, item)
if not changed and event:
self.prog_items[item.player][item.name] += 1
changed = True
self.stale[item.player] = True
if changed and not prevent_sweep:
self.sweep_for_advancements()
if changed and not event:
self.sweep_for_events()
return changed
@@ -907,7 +900,7 @@ class Entrance:
addresses = None
target = None
def __init__(self, player: int, name: str = "", parent: Optional[Region] = None) -> None:
def __init__(self, player: int, name: str = '', parent: Region = None):
self.name = name
self.parent_region = parent
self.player = player
@@ -927,6 +920,9 @@ class Entrance:
region.entrances.append(self)
def __repr__(self):
return self.__str__()
def __str__(self):
multiworld = self.parent_region.multiworld if self.parent_region else None
return multiworld.get_name_string_for_object(self) if multiworld else f'{self.name} (Player {self.player})'
@@ -1052,7 +1048,7 @@ class Region:
self.locations.append(location_type(self.player, location, address, self))
def connect(self, connecting_region: Region, name: Optional[str] = None,
rule: Optional[Callable[[CollectionState], bool]] = None) -> Entrance:
rule: Optional[Callable[[CollectionState], bool]] = None) -> entrance_type:
"""
Connects this Region to another Region, placing the provided rule on the connection.
@@ -1092,6 +1088,9 @@ class Region:
rules[connecting_region] if rules and connecting_region in rules else None)
def __repr__(self):
return self.__str__()
def __str__(self):
return self.multiworld.get_name_string_for_object(self) if self.multiworld else f'{self.name} (Player {self.player})'
@@ -1110,9 +1109,9 @@ class Location:
locked: bool = False
show_in_spoiler: bool = True
progress_type: LocationProgressType = LocationProgressType.DEFAULT
always_allow: Callable[[CollectionState, Item], bool] = staticmethod(lambda state, item: False)
always_allow = staticmethod(lambda state, item: False)
access_rule: Callable[[CollectionState], bool] = staticmethod(lambda state: True)
item_rule: Callable[[Item], bool] = staticmethod(lambda item: True)
item_rule = staticmethod(lambda item: True)
item: Optional[Item] = None
def __init__(self, player: int, name: str = '', address: Optional[int] = None, parent: Optional[Region] = None):
@@ -1121,15 +1120,11 @@ class Location:
self.address = address
self.parent_region = parent
def can_fill(self, state: CollectionState, item: Item, check_access: bool = True) -> bool:
return ((
self.always_allow(state, item)
and item.name not in state.multiworld.worlds[item.player].options.non_local_items
) or (
(self.progress_type != LocationProgressType.EXCLUDED or not (item.advancement or item.useful))
and self.item_rule(item)
and (not check_access or self.can_reach(state))
))
def can_fill(self, state: CollectionState, item: Item, check_access=True) -> bool:
return ((self.always_allow(state, item) and item.name not in state.multiworld.worlds[item.player].options.non_local_items)
or ((self.progress_type != LocationProgressType.EXCLUDED or not (item.advancement or item.useful))
and self.item_rule(item)
and (not check_access or self.can_reach(state))))
def can_reach(self, state: CollectionState) -> bool:
# Region.can_reach is just a cache lookup, so placing it first for faster abort on average
@@ -1144,6 +1139,9 @@ class Location:
self.locked = True
def __repr__(self):
return self.__str__()
def __str__(self):
multiworld = self.parent_region.multiworld if self.parent_region and self.parent_region.multiworld else None
return multiworld.get_name_string_for_object(self) if multiworld else f'{self.name} (Player {self.player})'
@@ -1165,7 +1163,7 @@ class Location:
@property
def native_item(self) -> bool:
"""Returns True if the item in this location matches game."""
return self.item is not None and self.item.game == self.game
return self.item and self.item.game == self.game
@property
def hint_text(self) -> str:
@@ -1248,6 +1246,9 @@ class Item:
return hash((self.name, self.player))
def __repr__(self) -> str:
return self.__str__()
def __str__(self) -> str:
if self.location and self.location.parent_region and self.location.parent_region.multiworld:
return self.location.parent_region.multiworld.get_name_string_for_object(self)
return f"{self.name} (Player {self.player})"
@@ -1325,9 +1326,9 @@ class Spoiler:
# in the second phase, we cull each sphere such that the game is still beatable,
# reducing each range of influence to the bare minimum required inside it
restore_later: Dict[Location, Item] = {}
restore_later = {}
for num, sphere in reversed(tuple(enumerate(collection_spheres))):
to_delete: Set[Location] = set()
to_delete = set()
for location in sphere:
# we remove the item at location and check if game is still beatable
logging.debug('Checking if %s (Player %d) is required to beat the game.', location.item.name,
@@ -1345,7 +1346,7 @@ class Spoiler:
sphere -= to_delete
# second phase, sphere 0
removed_precollected: List[Item] = []
removed_precollected = []
for item in (i for i in chain.from_iterable(multiworld.precollected_items.values()) if i.advancement):
logging.debug('Checking if %s (Player %d) is required to beat the game.', item.name, item.player)
multiworld.precollected_items[item.player].remove(item)
@@ -1426,7 +1427,7 @@ class Spoiler:
# Maybe move the big bomb over to the Event system instead?
if any(exit_path == 'Pyramid Fairy' for path in self.paths.values()
for (_, exit_path) in path):
if multiworld.worlds[player].options.mode != 'inverted':
if multiworld.mode[player] != 'inverted':
self.paths[str(multiworld.get_region('Big Bomb Shop', player))] = \
get_path(state, multiworld.get_region('Big Bomb Shop', player))
else:
@@ -1498,9 +1499,9 @@ class Spoiler:
if self.paths:
outfile.write('\n\nPaths:\n\n')
path_listings: List[str] = []
path_listings = []
for location, path in sorted(self.paths.items()):
path_lines: List[str] = []
path_lines = []
for region, exit in path:
if exit is not None:
path_lines.append("{} -> {}".format(region, exit))

View File

@@ -252,7 +252,7 @@ class CommonContext:
starting_reconnect_delay: int = 5
current_reconnect_delay: int = starting_reconnect_delay
command_processor: typing.Type[CommandProcessor] = ClientCommandProcessor
ui: typing.Optional["kvui.GameManager"] = None
ui = None
ui_task: typing.Optional["asyncio.Task[None]"] = None
input_task: typing.Optional["asyncio.Task[None]"] = None
keep_alive_task: typing.Optional["asyncio.Task[None]"] = None

33
Fill.py
View File

@@ -12,12 +12,7 @@ from worlds.generic.Rules import add_item_rule
class FillError(RuntimeError):
def __init__(self, *args: typing.Union[str, typing.Any], **kwargs) -> None:
if "multiworld" in kwargs and isinstance(args[0], str):
placements = (args[0] + f"\nAll Placements:\n" +
f"{[(loc, loc.item) for loc in kwargs['multiworld'].get_filled_locations()]}")
args = (placements, *args[1:])
super().__init__(*args)
pass
def _log_fill_progress(name: str, placed: int, total_items: int) -> None:
@@ -29,7 +24,7 @@ def sweep_from_pool(base_state: CollectionState, itempool: typing.Sequence[Item]
new_state = base_state.copy()
for item in itempool:
new_state.collect(item, True)
new_state.sweep_for_advancements(locations=locations)
new_state.sweep_for_events(locations=locations)
return new_state
@@ -217,7 +212,7 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati
f"Unfilled locations:\n"
f"{', '.join(str(location) for location in locations)}\n"
f"Already placed {len(placements)}:\n"
f"{', '.join(str(place) for place in placements)}", multiworld=multiworld)
f"{', '.join(str(place) for place in placements)}")
item_pool.extend(unplaced_items)
@@ -304,7 +299,7 @@ def remaining_fill(multiworld: MultiWorld,
f"Unfilled locations:\n"
f"{', '.join(str(location) for location in locations)}\n"
f"Already placed {len(placements)}:\n"
f"{', '.join(str(place) for place in placements)}", multiworld=multiworld)
f"{', '.join(str(place) for place in placements)}")
itempool.extend(unplaced_items)
@@ -329,8 +324,8 @@ def accessibility_corrections(multiworld: MultiWorld, state: CollectionState, lo
pool.append(location.item)
state.remove(location.item)
location.item = None
if location in state.advancements:
state.advancements.remove(location)
if location in state.events:
state.events.remove(location)
locations.append(location)
if pool and locations:
locations.sort(key=lambda loc: loc.progress_type != LocationProgressType.PRIORITY)
@@ -363,7 +358,7 @@ def distribute_early_items(multiworld: MultiWorld,
early_priority_locations: typing.List[Location] = []
loc_indexes_to_remove: typing.Set[int] = set()
base_state = multiworld.state.copy()
base_state.sweep_for_advancements(locations=(loc for loc in multiworld.get_filled_locations() if loc.address is None))
base_state.sweep_for_events(locations=(loc for loc in multiworld.get_filled_locations() if loc.address is None))
for i, loc in enumerate(fill_locations):
if loc.can_reach(base_state):
if loc.progress_type == LocationProgressType.PRIORITY:
@@ -511,8 +506,7 @@ def distribute_items_restrictive(multiworld: MultiWorld,
if progitempool:
raise FillError(
f"Not enough locations for progression items. "
f"There are {len(progitempool)} more progression items than there are available locations.",
multiworld=multiworld,
f"There are {len(progitempool)} more progression items than there are available locations."
)
accessibility_corrections(multiworld, multiworld.state, defaultlocations)
@@ -529,8 +523,7 @@ def distribute_items_restrictive(multiworld: MultiWorld,
if excludedlocations:
raise FillError(
f"Not enough filler items for excluded locations. "
f"There are {len(excludedlocations)} more excluded locations than filler or trap items.",
multiworld=multiworld,
f"There are {len(excludedlocations)} more excluded locations than filler or trap items."
)
restitempool = filleritempool + usefulitempool
@@ -558,7 +551,7 @@ def flood_items(multiworld: MultiWorld) -> None:
progress_done = False
# sweep once to pick up preplaced items
multiworld.state.sweep_for_advancements()
multiworld.state.sweep_for_events()
# fill multiworld from top of itempool while we can
while not progress_done:
@@ -596,7 +589,7 @@ def flood_items(multiworld: MultiWorld) -> None:
if candidate_item_to_place is not None:
item_to_place = candidate_item_to_place
else:
raise FillError('No more progress items left to place.', multiworld=multiworld)
raise FillError('No more progress items left to place.')
# find item to replace with progress item
location_list = multiworld.get_reachable_locations()
@@ -746,7 +739,7 @@ def balance_multiworld_progression(multiworld: MultiWorld) -> None:
), items_to_test):
reducing_state.collect(location.item, True, location)
reducing_state.sweep_for_advancements(locations=locations_to_test)
reducing_state.sweep_for_events(locations=locations_to_test)
if multiworld.has_beaten_game(balancing_state):
if not multiworld.has_beaten_game(reducing_state):
@@ -829,7 +822,7 @@ def distribute_planned(multiworld: MultiWorld) -> None:
warn(warning, force)
swept_state = multiworld.state.copy()
swept_state.sweep_for_advancements()
swept_state.sweep_for_events()
reachable = frozenset(multiworld.get_reachable_locations(swept_state))
early_locations: typing.Dict[int, typing.List[str]] = collections.defaultdict(list)
non_early_locations: typing.Dict[int, typing.List[str]] = collections.defaultdict(list)

View File

@@ -511,7 +511,7 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
continue
logging.warning(f"{option_key} is not a valid option name for {ret.game} and is not present in triggers.")
if PlandoOptions.items in plando_options:
ret.plando_items = copy.deepcopy(game_weights.get("plando_items", []))
ret.plando_items = game_weights.get("plando_items", [])
if ret.game == "A Link to the Past":
roll_alttp_settings(ret, game_weights)

View File

@@ -1,9 +0,0 @@
if __name__ == '__main__':
import ModuleUpdate
ModuleUpdate.update()
import Utils
Utils.init_logging("KH1Client", exception_logger="Client")
from worlds.kh1.Client import launch
launch()

View File

@@ -266,7 +266,7 @@ def run_gui():
if file and component:
run_component(component, file)
else:
logging.warning(f"unable to identify component for {file}")
logging.warning(f"unable to identify component for {filename}")
def _stop(self, *largs):
# ran into what appears to be https://groups.google.com/g/kivy-users/c/saWDLoYCSZ4 with PyCharm.

View File

@@ -14,7 +14,7 @@ import tkinter as tk
from argparse import Namespace
from concurrent.futures import as_completed, ThreadPoolExecutor
from glob import glob
from tkinter import Tk, Frame, Label, StringVar, Entry, filedialog, messagebox, Button, Radiobutton, LEFT, X, BOTH, TOP, LabelFrame, \
from tkinter import Tk, Frame, Label, StringVar, Entry, filedialog, messagebox, Button, Radiobutton, LEFT, X, TOP, LabelFrame, \
IntVar, Checkbutton, E, W, OptionMenu, Toplevel, BOTTOM, RIGHT, font as font, PhotoImage
from tkinter.constants import DISABLED, NORMAL
from urllib.parse import urlparse
@@ -29,8 +29,7 @@ from Utils import output_path, local_path, user_path, open_file, get_cert_none_s
GAME_ALTTP = "A Link to the Past"
WINDOW_MIN_HEIGHT = 525
WINDOW_MIN_WIDTH = 425
class AdjusterWorld(object):
def __init__(self, sprite_pool):
@@ -243,17 +242,16 @@ def adjustGUI():
from argparse import Namespace
from Utils import __version__ as MWVersion
adjustWindow = Tk()
adjustWindow.minsize(WINDOW_MIN_WIDTH, WINDOW_MIN_HEIGHT)
adjustWindow.wm_title("Archipelago %s LttP Adjuster" % MWVersion)
set_icon(adjustWindow)
rom_options_frame, rom_vars, set_sprite = get_rom_options_frame(adjustWindow)
bottomFrame2 = Frame(adjustWindow, padx=8, pady=2)
bottomFrame2 = Frame(adjustWindow)
romFrame, romVar = get_rom_frame(adjustWindow)
romDialogFrame = Frame(adjustWindow, padx=8, pady=2)
romDialogFrame = Frame(adjustWindow)
baseRomLabel2 = Label(romDialogFrame, text='Rom to adjust')
romVar2 = StringVar()
romEntry2 = Entry(romDialogFrame, textvariable=romVar2)
@@ -263,9 +261,9 @@ def adjustGUI():
romVar2.set(rom)
romSelectButton2 = Button(romDialogFrame, text='Select Rom', command=RomSelect2)
romDialogFrame.pack(side=TOP, expand=False, fill=X)
baseRomLabel2.pack(side=LEFT, expand=False, fill=X, padx=(0, 8))
romEntry2.pack(side=LEFT, expand=True, fill=BOTH, pady=1)
romDialogFrame.pack(side=TOP, expand=True, fill=X)
baseRomLabel2.pack(side=LEFT)
romEntry2.pack(side=LEFT, expand=True, fill=X)
romSelectButton2.pack(side=LEFT)
def adjustRom():
@@ -333,11 +331,12 @@ def adjustGUI():
messagebox.showinfo(title="Success", message="Settings saved to persistent storage")
adjustButton = Button(bottomFrame2, text='Adjust Rom', command=adjustRom)
rom_options_frame.pack(side=TOP, padx=8, pady=8, fill=BOTH, expand=True)
rom_options_frame.pack(side=TOP)
adjustButton.pack(side=LEFT, padx=(5,5))
saveButton = Button(bottomFrame2, text='Save Settings', command=saveGUISettings)
saveButton.pack(side=LEFT, padx=(5,5))
bottomFrame2.pack(side=TOP, pady=(5,5))
tkinter_center_window(adjustWindow)
@@ -577,7 +576,7 @@ class AttachTooltip(object):
def get_rom_frame(parent=None):
adjuster_settings = get_adjuster_settings(GAME_ALTTP)
romFrame = Frame(parent, padx=8, pady=8)
romFrame = Frame(parent)
baseRomLabel = Label(romFrame, text='LttP Base Rom: ')
romVar = StringVar(value=adjuster_settings.baserom)
romEntry = Entry(romFrame, textvariable=romVar)
@@ -597,19 +596,20 @@ def get_rom_frame(parent=None):
romSelectButton = Button(romFrame, text='Select Rom', command=RomSelect)
baseRomLabel.pack(side=LEFT)
romEntry.pack(side=LEFT, expand=True, fill=BOTH, pady=1)
romEntry.pack(side=LEFT, expand=True, fill=X)
romSelectButton.pack(side=LEFT)
romFrame.pack(side=TOP, fill=X)
romFrame.pack(side=TOP, expand=True, fill=X)
return romFrame, romVar
def get_rom_options_frame(parent=None):
adjuster_settings = get_adjuster_settings(GAME_ALTTP)
romOptionsFrame = LabelFrame(parent, text="Rom options", padx=8, pady=8)
romOptionsFrame = LabelFrame(parent, text="Rom options")
romOptionsFrame.columnconfigure(0, weight=1)
romOptionsFrame.columnconfigure(1, weight=1)
for i in range(5):
romOptionsFrame.rowconfigure(i, weight=0, pad=4)
romOptionsFrame.rowconfigure(i, weight=1)
vars = Namespace()
vars.MusicVar = IntVar()
@@ -660,7 +660,7 @@ def get_rom_options_frame(parent=None):
spriteSelectButton = Button(spriteDialogFrame, text='...', command=SpriteSelect)
baseSpriteLabel.pack(side=LEFT)
spriteEntry.pack(side=LEFT, expand=True, fill=X)
spriteEntry.pack(side=LEFT)
spriteSelectButton.pack(side=LEFT)
oofDialogFrame = Frame(romOptionsFrame)

View File

@@ -11,8 +11,7 @@ from typing import Dict, List, Optional, Set, Tuple, Union
import worlds
from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld, Region
from Fill import FillError, balance_multiworld_progression, distribute_items_restrictive, distribute_planned, \
flood_items
from Fill import balance_multiworld_progression, distribute_items_restrictive, distribute_planned, flood_items
from Options import StartInventoryPool
from Utils import __version__, output_path, version_tuple, get_settings
from settings import get_settings
@@ -101,7 +100,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
multiworld.early_items[player][item_name] = max(0, early-count)
remaining_count = count-early
if remaining_count > 0:
local_early = multiworld.local_early_items[player].get(item_name, 0)
local_early = multiworld.early_local_items[player].get(item_name, 0)
if local_early:
multiworld.early_items[player][item_name] = max(0, local_early - remaining_count)
del local_early
@@ -347,7 +346,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
output_file_futures.append(pool.submit(write_multidata))
if not check_accessibility_task.result():
if not multiworld.can_beat_game():
raise FillError("Game appears as unbeatable. Aborting.", multiworld=multiworld)
raise Exception("Game appears as unbeatable. Aborting.")
else:
logger.warning("Location Accessibility requirements not fulfilled.")

View File

@@ -75,13 +75,13 @@ def update(yes: bool = False, force: bool = False) -> None:
if not update_ran:
update_ran = True
install_pkg_resources(yes=yes)
import pkg_resources
if force:
update_command()
return
install_pkg_resources(yes=yes)
import pkg_resources
prev = "" # if a line ends in \ we store here and merge later
for req_file in requirements_files:
path = os.path.join(os.path.dirname(sys.argv[0]), req_file)

View File

@@ -67,21 +67,6 @@ def update_dict(dictionary, entries):
return dictionary
def queue_gc():
import gc
from threading import Thread
gc_thread: typing.Optional[Thread] = getattr(queue_gc, "_thread", None)
def async_collect():
time.sleep(2)
setattr(queue_gc, "_thread", None)
gc.collect()
if not gc_thread:
gc_thread = Thread(target=async_collect)
setattr(queue_gc, "_thread", gc_thread)
gc_thread.start()
# functions callable on storable data on the server by clients
modify_functions = {
# generic:
@@ -566,9 +551,6 @@ class Context:
self.logger.info(f"Saving failed. Retry in {self.auto_save_interval} seconds.")
else:
self.save_dirty = False
if not atexit_save: # if atexit is used, that keeps a reference anyway
queue_gc()
self.auto_saver_thread = threading.Thread(target=save_regularly, daemon=True)
self.auto_saver_thread.start()
@@ -1009,7 +991,7 @@ def collect_player(ctx: Context, team: int, slot: int, is_group: bool = False):
collect_player(ctx, team, group, True)
def get_remaining(ctx: Context, team: int, slot: int) -> typing.List[typing.Tuple[int, int]]:
def get_remaining(ctx: Context, team: int, slot: int) -> typing.List[int]:
return ctx.locations.get_remaining(ctx.location_checks, team, slot)
@@ -1221,10 +1203,6 @@ class CommonCommandProcessor(CommandProcessor):
timer = int(seconds, 10)
except ValueError:
timer = 10
else:
if timer > 60 * 60:
raise ValueError(f"{timer} is invalid. Maximum is 1 hour.")
async_start(countdown(self.ctx, timer))
return True
@@ -1372,10 +1350,10 @@ class ClientMessageProcessor(CommonCommandProcessor):
def _cmd_remaining(self) -> bool:
"""List remaining items in your game, but not their location or recipient"""
if self.ctx.remaining_mode == "enabled":
rest_locations = get_remaining(self.ctx, self.client.team, self.client.slot)
if rest_locations:
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[slot]][item_id]
for slot, item_id in rest_locations))
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
if remaining_item_ids:
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[self.client.slot]][item_id]
for item_id in remaining_item_ids))
else:
self.output("No remaining items found.")
return True
@@ -1385,10 +1363,10 @@ class ClientMessageProcessor(CommonCommandProcessor):
return False
else: # is goal
if self.ctx.client_game_state[self.client.team, self.client.slot] == ClientStatus.CLIENT_GOAL:
rest_locations = get_remaining(self.ctx, self.client.team, self.client.slot)
if rest_locations:
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[slot]][item_id]
for slot, item_id in rest_locations))
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
if remaining_item_ids:
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[self.client.slot]][item_id]
for item_id in remaining_item_ids))
else:
self.output("No remaining items found.")
return True
@@ -2061,8 +2039,6 @@ class ServerCommandProcessor(CommonCommandProcessor):
item_name, usable, response = get_intended_text(item_name, names)
if usable:
amount: int = int(amount)
if amount > 100:
raise ValueError(f"{amount} is invalid. Maximum is 100.")
new_items = [NetworkItem(names[item_name], -1, 0) for _ in range(int(amount))]
send_items_to(self.ctx, team, slot, *new_items)

View File

@@ -79,7 +79,6 @@ class NetworkItem(typing.NamedTuple):
item: int
location: int
player: int
""" Sending player, except in LocationInfo (from LocationScouts), where it is the receiving player. """
flags: int = 0
@@ -398,12 +397,12 @@ class _LocationStore(dict, typing.MutableMapping[int, typing.Dict[int, typing.Tu
location_id not in checked]
def get_remaining(self, state: typing.Dict[typing.Tuple[int, int], typing.Set[int]], team: int, slot: int
) -> typing.List[typing.Tuple[int, int]]:
) -> typing.List[int]:
checked = state[team, slot]
player_locations = self[slot]
return sorted([(player_locations[location_id][1], player_locations[location_id][0]) for
location_id in player_locations if
location_id not in checked])
return sorted([player_locations[location_id][0] for
location_id in player_locations if
location_id not in checked])
if typing.TYPE_CHECKING: # type-check with pure python implementation until we have a typing stub

View File

@@ -73,9 +73,6 @@ Currently, the following games are supported:
* Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006
* A Hat in Time
* Old School Runescape
* Kingdom Hearts 1
* Mega Man 2
* Yacht Dice
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled

View File

@@ -1,4 +1,3 @@
import argparse
import os
import multiprocessing
import logging
@@ -32,15 +31,6 @@ def get_app() -> "Flask":
import yaml
app.config.from_file(configpath, yaml.safe_load)
logging.info(f"Updated config from {configpath}")
# inside get_app() so it's usable in systems like gunicorn, which do not run WebHost.py, but import it.
parser = argparse.ArgumentParser()
parser.add_argument('--config_override', default=None,
help="Path to yaml config file that overrules config.yaml.")
args = parser.parse_known_args()[0]
if args.config_override:
import yaml
app.config.from_file(os.path.abspath(args.config_override), yaml.safe_load)
logging.info(f"Updated config from {args.config_override}")
if not app.config["HOST_ADDRESS"]:
logging.info("Getting public IP, as HOST_ADDRESS is empty.")
app.config["HOST_ADDRESS"] = Utils.get_public_ipv4()

View File

@@ -72,14 +72,6 @@ class WebHostContext(Context):
self.video = {}
self.tags = ["AP", "WebHost"]
def __del__(self):
try:
import psutil
from Utils import format_SI_prefix
self.logger.debug(f"Context destroyed, Mem: {format_SI_prefix(psutil.Process().memory_info().rss, 1024)}iB")
except ImportError:
self.logger.debug("Context destroyed")
def _load_game_data(self):
for key, value in self.static_server_data.items():
# NOTE: attributes are mutable and shared, so they will have to be copied before being modified
@@ -257,7 +249,6 @@ def run_server_process(name: str, ponyconfig: dict, static_server_data: dict,
ctx = WebHostContext(static_server_data, logger)
ctx.load(room_id)
ctx.init_save()
assert ctx.server is None
try:
ctx.server = websockets.serve(
functools.partial(server, ctx=ctx), ctx.host, ctx.port, ssl=ssl_context)
@@ -288,7 +279,6 @@ def run_server_process(name: str, ponyconfig: dict, static_server_data: dict,
ctx.auto_shutdown = Room.get(id=room_id).timeout
if ctx.saving:
setattr(asyncio.current_task(), "save", lambda: ctx._save(True))
assert ctx.shutdown_task is None
ctx.shutdown_task = asyncio.create_task(auto_shutdown(ctx, []))
await ctx.shutdown_task
@@ -335,7 +325,7 @@ def run_server_process(name: str, ponyconfig: dict, static_server_data: dict,
def run(self):
while 1:
next_room = rooms_to_run.get(block=True, timeout=None)
gc.collect()
gc.collect(0)
task = asyncio.run_coroutine_threadsafe(start_room(next_room), loop)
self._tasks.append(task)
task.add_done_callback(self._done)

View File

@@ -1,11 +1,10 @@
flask>=3.0.3
werkzeug>=3.0.4
pony>=0.7.19
werkzeug>=3.0.3
pony>=0.7.17
waitress>=3.0.0
Flask-Caching>=2.3.0
Flask-Compress>=1.15
Flask-Limiter>=3.8.0
Flask-Limiter>=3.7.0
bokeh>=3.1.1; python_version <= '3.8'
bokeh>=3.4.3; python_version == '3.9'
bokeh>=3.5.2; python_version >= '3.10'
bokeh>=3.4.1; python_version >= '3.9'
markupsafe>=2.1.5

View File

@@ -138,7 +138,7 @@
id="{{ option_name }}-{{ key }}"
name="{{ option_name }}||{{ key }}"
value="1"
{{ "checked" if key in option.default }}
checked="{{ "checked" if key in option.default else "" }}"
/>
<label for="{{ option_name }}-{{ key }}">
{{ key }}

View File

@@ -287,15 +287,15 @@ cdef class LocationStore:
entry in self.entries[start:start + count] if
entry.location not in checked]
def get_remaining(self, state: State, team: int, slot: int) -> List[Tuple[int, int]]:
def get_remaining(self, state: State, team: int, slot: int) -> List[int]:
cdef LocationEntry* entry
cdef ap_player_t sender = slot
cdef size_t start = self.sender_index[sender].start
cdef size_t count = self.sender_index[sender].count
cdef set checked = state[team, slot]
return sorted([(entry.receiver, entry.item) for
entry in self.entries[start:start+count] if
entry.location not in checked])
return sorted([entry.item for
entry in self.entries[start:start+count] if
entry.location not in checked])
@cython.auto_pickle(False)

View File

@@ -78,9 +78,6 @@
# Kirby's Dream Land 3
/worlds/kdl3/ @Silvris
# Kingdom Hearts
/worlds/kh1/ @gaithern
# Kingdom Hearts 2
/worlds/kh2/ @JaredWeakStrike
@@ -106,9 +103,6 @@
# Minecraft
/worlds/minecraft/ @KonoTyran @espeon65536
# Mega Man 2
/worlds/mm2/ @Silvris
# MegaMan Battle Network 3
/worlds/mmbn3/ @digiholic
@@ -202,9 +196,6 @@
# The Witness
/worlds/witness/ @NewSoupVi @blastron
# Yacht Dice
/worlds/yachtdice/ @spinerak
# Yoshi's Island
/worlds/yoshisisland/ @PinkSwitch

View File

@@ -702,18 +702,14 @@ GameData is a **dict** but contains these keys and values. It's broken out into
| checksum | str | A checksum hash of this game's data. |
### Tags
Tags are represented as a list of strings, the common client tags follow:
Tags are represented as a list of strings, the common Client tags follow:
| Name | Notes |
|-----------|--------------------------------------------------------------------------------------------------------------------------------------|
| AP | Signifies that this client is a reference client, its usefulness is mostly in debugging to compare client behaviours more easily. |
| DeathLink | Client participates in the DeathLink mechanic, therefore will send and receive DeathLink bounce packets. |
| HintGame | Indicates the client is a hint game, made to send hints instead of locations. Special join/leave message,¹ `game` is optional.² |
| Tracker | Indicates the client is a tracker, made to track instead of sending locations. Special join/leave message,¹ `game` is optional.² |
| TextOnly | Indicates the client is a basic client, made to chat instead of sending locations. Special join/leave message,¹ `game` is optional.² |
¹: When connecting or disconnecting, the chat message shows e.g. "tracking".\
²: Allows `game` to be empty or null in [Connect](#connect). Game and version validation will then be skipped.
| Name | Notes |
|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| AP | Signifies that this client is a reference client, its usefulness is mostly in debugging to compare client behaviours more easily. |
| DeathLink | Client participates in the DeathLink mechanic, therefore will send and receive DeathLink bounce packets |
| Tracker | Tells the server that this client will not send locations and is actually a Tracker. When specified and used with empty or null `game` in [Connect](#connect), game and game's version validation will be skipped. |
| TextOnly | Tells the server that this client will not send locations and is intended for chat. When specified and used with empty or null `game` in [Connect](#connect), game and game's version validation will be skipped. |
### DeathLink
A special kind of Bounce packet that can be supported by any AP game. It targets the tag "DeathLink" and carries the following data:

View File

@@ -8,7 +8,7 @@ use that version. These steps are for developers or platforms without compiled r
What you'll need:
* [Python 3.8.7 or newer](https://www.python.org/downloads/), not the Windows Store version
* Python 3.12.x is currently the newest supported version
* **Python 3.12 is currently unsupported**
* pip: included in downloads from python.org, separate in many Linux distributions
* Matching C compiler
* possibly optional, read operating system specific sections
@@ -31,7 +31,7 @@ After this, you should be able to run the programs.
Recommended steps
* Download and install a "Windows installer (64-bit)" from the [Python download page](https://www.python.org/downloads)
* [read above](#General) which versions are supported
* **Python 3.12 is currently unsupported**
* **Optional**: Download and install Visual Studio Build Tools from
[Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/).

View File

@@ -26,17 +26,8 @@ Unless these are shared between multiple people, we expect the following from ea
### Adding a World
When we merge your world into the core Archipelago repository, you automatically become world maintainer unless you
nominate someone else (i.e. there are multiple devs).
### Being added as a maintainer to an existing implementation
At any point, a world maintainer can approve the addition of another maintainer to their world.
In order to do this, either an existing maintainer or the new maintainer must open a PR updating the
[CODEOWNERS](/docs/CODEOWNERS) file.
This change must be approved by all existing maintainers of the affected world, the new maintainer candidate, and
one core maintainer.
To help the core team review the change, information about the new maintainer and their contributions should be
included in the PR description.
nominate someone else (i.e. there are multiple devs). You can define who is allowed to approve changes to your world
in the [CODEOWNERS](/docs/CODEOWNERS) document.
### Getting Voted

View File

@@ -186,11 +186,6 @@ Root: HKCR; Subkey: "{#MyAppName}cv64patch"; ValueData: "Arc
Root: HKCR; Subkey: "{#MyAppName}cv64patch\DefaultIcon"; ValueData: "{app}\ArchipelagoBizHawkClient.exe,0"; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}cv64patch\shell\open\command"; ValueData: """{app}\ArchipelagoBizHawkClient.exe"" ""%1"""; ValueType: string; ValueName: "";
Root: HKCR; Subkey: ".apmm2"; ValueData: "{#MyAppName}mm2patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}mm2patch"; ValueData: "Archipelago Mega Man 2 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}mm2patch\DefaultIcon"; ValueData: "{app}\ArchipelagoBizHawkClient.exe,0"; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}mm2patch\shell\open\command"; ValueData: """{app}\ArchipelagoBizHawkClient.exe"" ""%1"""; ValueType: string; ValueName: "";
Root: HKCR; Subkey: ".apladx"; ValueData: "{#MyAppName}ladxpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}ladxpatch"; ValueData: "Archipelago Links Awakening DX Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}ladxpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoLinksAwakeningClient.exe,0"; ValueType: string; ValueName: "";

View File

@@ -5,8 +5,6 @@ import typing
import re
from collections import deque
assert "kivy" not in sys.modules, "kvui should be imported before kivy for frozen compatibility"
if sys.platform == "win32":
import ctypes

View File

@@ -1,14 +1,14 @@
colorama>=0.4.6
websockets>=13.0.1
PyYAML>=6.0.2
jellyfish>=1.1.0
websockets>=12.0
PyYAML>=6.0.1
jellyfish>=1.0.3
jinja2>=3.1.4
schema>=0.7.7
kivy>=2.3.0
bsdiff4>=1.2.4
platformdirs>=4.2.2
certifi>=2024.8.30
cython>=3.0.11
certifi>=2024.6.2
cython>=3.0.10
cymem>=2.0.8
orjson>=3.10.7
typing_extensions>=4.12.2
orjson>=3.10.3
typing_extensions>=4.12.1

View File

@@ -23,8 +23,8 @@ class TestBase(unittest.TestCase):
state = CollectionState(self.multiworld)
for item in items:
item.classification = ItemClassification.progression
state.collect(item, prevent_sweep=True)
state.sweep_for_advancements()
state.collect(item, event=True)
state.sweep_for_events()
state.update_reachable_regions(1)
self._state_cache[self.multiworld, tuple(items)] = state
return state
@@ -221,8 +221,8 @@ class WorldTestBase(unittest.TestCase):
if isinstance(items, Item):
items = (items,)
for item in items:
if item.location and item.advancement and item.location in self.multiworld.state.advancements:
self.multiworld.state.advancements.remove(item.location)
if item.location and item.advancement and item.location in self.multiworld.state.events:
self.multiworld.state.events.remove(item.location)
self.multiworld.state.remove(item)
def can_reach_location(self, location: str) -> bool:
@@ -293,11 +293,13 @@ class WorldTestBase(unittest.TestCase):
if not (self.run_default_tests and self.constructed):
return
with self.subTest("Game", game=self.game, seed=self.multiworld.seed):
excluded = self.multiworld.worlds[self.player].options.exclude_locations.value
state = self.multiworld.get_all_state(False)
for location in self.multiworld.get_locations():
with self.subTest("Location should be reached", location=location.name):
reachable = location.can_reach(state)
self.assertTrue(reachable, f"{location.name} unreachable")
if location.name not in excluded:
with self.subTest("Location should be reached", location=location.name):
reachable = location.can_reach(state)
self.assertTrue(reachable, f"{location.name} unreachable")
with self.subTest("Beatable"):
self.multiworld.state = state
self.assertBeatable(True)

View File

@@ -192,7 +192,7 @@ class TestFillRestrictive(unittest.TestCase):
location_pool = player1.locations[1:] + player2.locations
item_pool = player1.prog_items[:-1] + player2.prog_items
fill_restrictive(multiworld, multiworld.state, location_pool, item_pool)
multiworld.state.sweep_for_advancements() # collect everything
multiworld.state.sweep_for_events() # collect everything
# all of player2's locations and items should be accessible (not all of player1's)
for item in player2.prog_items:
@@ -443,8 +443,8 @@ class TestFillRestrictive(unittest.TestCase):
item = player1.prog_items[0]
item.code = None
location.place_locked_item(item)
multiworld.state.sweep_for_advancements()
multiworld.state.sweep_for_advancements()
multiworld.state.sweep_for_events()
multiworld.state.sweep_for_events()
self.assertTrue(multiworld.state.prog_items[item.player][item.name], "Sweep did not collect - Test flawed")
self.assertEqual(multiworld.state.prog_items[item.player][item.name], 1, "Sweep collected multiple times")

View File

@@ -14,18 +14,6 @@ class TestBase(unittest.TestCase):
"Desert Northern Cliffs", # on top of mountain, only reachable via OWG
"Dark Death Mountain Bunny Descent Area" # OWG Mountain descent
},
# These Blasphemous regions are not reachable with default options
"Blasphemous": {
"D01Z04S13[SE]", # difficulty must be hard
"D01Z05S25[E]", # difficulty must be hard
"D02Z02S05[W]", # difficulty must be hard and purified_hand must be true
"D04Z01S06[E]", # purified_hand must be true
"D04Z02S02[NE]", # difficulty must be hard and purified_hand must be true
"D05Z01S11[SW]", # difficulty must be hard
"D06Z01S08[N]", # difficulty must be hard and purified_hand must be true
"D20Z02S11[NW]", # difficulty must be hard
"D20Z02S11[E]", # difficulty must be hard
},
"Ocarina of Time": {
"Prelude of Light Warp", # Prelude is not progression by default
"Serenade of Water Warp", # Serenade is not progression by default
@@ -49,10 +37,12 @@ class TestBase(unittest.TestCase):
unreachable_regions = self.default_settings_unreachable_regions.get(game_name, set())
with self.subTest("Game", game=game_name):
multiworld = setup_solo_multiworld(world_type)
excluded = multiworld.worlds[1].options.exclude_locations.value
state = multiworld.get_all_state(False)
for location in multiworld.get_locations():
with self.subTest("Location should be reached", location=location.name):
self.assertTrue(location.can_reach(state), f"{location.name} unreachable")
if location.name not in excluded:
with self.subTest("Location should be reached", location=location.name):
self.assertTrue(location.can_reach(state), f"{location.name} unreachable")
for region in multiworld.get_regions():
if region.name in unreachable_regions:

View File

@@ -55,7 +55,7 @@ class TestAllGamesMultiworld(MultiworldTestBase):
all_worlds = list(AutoWorldRegister.world_types.values())
self.multiworld = setup_multiworld(all_worlds, ())
for world in self.multiworld.worlds.values():
world.options.accessibility.value = Accessibility.option_full
world.options.accessibility.value = Accessibility.option_locations
self.assertSteps(gen_steps)
with self.subTest("filling multiworld", seed=self.multiworld.seed):
distribute_items_restrictive(self.multiworld)
@@ -66,8 +66,8 @@ class TestAllGamesMultiworld(MultiworldTestBase):
class TestTwoPlayerMulti(MultiworldTestBase):
def test_two_player_single_game_fills(self) -> None:
"""Tests that a multiworld of two players for each registered game world can generate."""
for world_type in AutoWorldRegister.world_types.values():
self.multiworld = setup_multiworld([world_type, world_type], ())
for world in AutoWorldRegister.world_types.values():
self.multiworld = setup_multiworld([world, world], ())
for world in self.multiworld.worlds.values():
world.options.accessibility.value = Accessibility.option_full
self.assertSteps(gen_steps)

View File

@@ -130,9 +130,9 @@ class Base:
def test_get_remaining(self) -> None:
self.assertEqual(self.store.get_remaining(full_state, 0, 1), [])
self.assertEqual(self.store.get_remaining(one_state, 0, 1), [(1, 13), (2, 21)])
self.assertEqual(self.store.get_remaining(empty_state, 0, 1), [(1, 13), (2, 21), (2, 22)])
self.assertEqual(self.store.get_remaining(empty_state, 0, 3), [(4, 99)])
self.assertEqual(self.store.get_remaining(one_state, 0, 1), [13, 21])
self.assertEqual(self.store.get_remaining(empty_state, 0, 1), [13, 21, 22])
self.assertEqual(self.store.get_remaining(empty_state, 0, 3), [99])
def test_location_set_intersection(self) -> None:
locations = {10, 11, 12}

View File

@@ -132,8 +132,7 @@ def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, path
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.\n"
"If the Launcher is not open, no action needs to be taken.")
"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()

View File

@@ -968,35 +968,40 @@ def get_act_by_number(world: "HatInTimeWorld", chapter_name: str, num: int) -> R
def create_thug_shops(world: "HatInTimeWorld"):
min_items: int = world.options.NyakuzaThugMinShopItems.value
max_items: int = world.options.NyakuzaThugMaxShopItems.value
thug_location_counts: Dict[str, int] = {}
count = -1
step = 0
old_name = ""
for key, data in shop_locations.items():
thug_name = data.nyakuza_thug
if thug_name == "":
# Different shop type.
if data.nyakuza_thug == "":
continue
if thug_name not in world.nyakuza_thug_items:
shop_item_count = world.random.randint(min_items, max_items)
world.nyakuza_thug_items[thug_name] = shop_item_count
else:
shop_item_count = world.nyakuza_thug_items[thug_name]
if shop_item_count <= 0:
if old_name != "" and old_name == data.nyakuza_thug:
continue
location_count = thug_location_counts.setdefault(thug_name, 0)
if location_count >= shop_item_count:
# Already created all the locations for this thug.
continue
try:
if world.nyakuza_thug_items[data.nyakuza_thug] <= 0:
continue
except KeyError:
pass
# Create the shop location.
region = world.multiworld.get_region(data.region, world.player)
loc = HatInTimeLocation(world.player, key, data.id, region)
region.locations.append(loc)
world.shop_locs.append(loc.name)
thug_location_counts[thug_name] = location_count + 1
if count == -1:
count = world.random.randint(min_items, max_items)
world.nyakuza_thug_items.setdefault(data.nyakuza_thug, count)
if count <= 0:
continue
if count >= 1:
region = world.multiworld.get_region(data.region, world.player)
loc = HatInTimeLocation(world.player, key, data.id, region)
region.locations.append(loc)
world.shop_locs.append(loc.name)
step += 1
if step >= count:
old_name = data.nyakuza_thug
step = 0
count = -1
def create_events(world: "HatInTimeWorld") -> int:

View File

@@ -381,8 +381,8 @@ def set_moderate_rules(world: "HatInTimeWorld"):
lambda state: can_use_hat(state, world, HatType.ICE), "or")
# Moderate: Clock Tower Chest + Ruined Tower with nothing
set_rule(world.multiworld.get_location("Mafia Town - Clock Tower Chest", world.player), lambda state: True)
set_rule(world.multiworld.get_location("Mafia Town - Top of Ruined Tower", world.player), lambda state: True)
add_rule(world.multiworld.get_location("Mafia Town - Clock Tower Chest", world.player), lambda state: True)
add_rule(world.multiworld.get_location("Mafia Town - Top of Ruined Tower", world.player), lambda state: True)
# Moderate: enter and clear The Subcon Well without Hookshot and without hitting the bell
for loc in world.multiworld.get_region("The Subcon Well", world.player).locations:
@@ -432,8 +432,8 @@ def set_moderate_rules(world: "HatInTimeWorld"):
if world.is_dlc1():
# Moderate: clear Rock the Boat without Ice Hat
set_rule(world.multiworld.get_location("Rock the Boat - Post Captain Rescue", world.player), lambda state: True)
set_rule(world.multiworld.get_location("Act Completion (Rock the Boat)", world.player), lambda state: True)
add_rule(world.multiworld.get_location("Rock the Boat - Post Captain Rescue", world.player), lambda state: True)
add_rule(world.multiworld.get_location("Act Completion (Rock the Boat)", world.player), lambda state: True)
# Moderate: clear Deep Sea without Ice Hat
set_rule(world.multiworld.get_location("Act Completion (Time Rift - Deep Sea)", world.player),
@@ -855,9 +855,6 @@ def set_rift_rules(world: "HatInTimeWorld", regions: Dict[str, Region]):
for entrance in regions["Time Rift - Alpine Skyline"].entrances:
add_rule(entrance, lambda state: has_relic_combo(state, world, "Crayon"))
if entrance.parent_region.name == "Alpine Free Roam":
add_rule(entrance,
lambda state: can_use_hookshot(state, world) and can_hit(state, world, umbrella_only=True))
if world.is_dlc1():
for entrance in regions["Time Rift - Balcony"].entrances:
@@ -936,9 +933,6 @@ def set_default_rift_rules(world: "HatInTimeWorld"):
for entrance in world.multiworld.get_region("Time Rift - Alpine Skyline", world.player).entrances:
add_rule(entrance, lambda state: has_relic_combo(state, world, "Crayon"))
if entrance.parent_region.name == "Alpine Free Roam":
add_rule(entrance,
lambda state: can_use_hookshot(state, world) and can_hit(state, world, umbrella_only=True))
if world.is_dlc1():
for entrance in world.multiworld.get_region("Time Rift - Balcony", world.player).entrances:

View File

@@ -248,7 +248,7 @@ def fill_dungeons_restrictive(multiworld: MultiWorld):
pass
for item in pre_fill_items:
multiworld.worlds[item.player].collect(all_state_base, item)
all_state_base.sweep_for_advancements()
all_state_base.sweep_for_events()
# Remove completion condition so that minimal-accessibility worlds place keys properly
for player in {item.player for item in in_dungeon_items}:
@@ -262,8 +262,8 @@ def fill_dungeons_restrictive(multiworld: MultiWorld):
all_state_base.remove(item_factory(key_data[3], multiworld.worlds[player]))
loc = multiworld.get_location(key_loc, player)
if loc in all_state_base.advancements:
all_state_base.advancements.remove(loc)
if loc in all_state_base.events:
all_state_base.events.remove(loc)
fill_restrictive(multiworld, all_state_base, locations, in_dungeon_items, lock=True, allow_excluded=True,
name="LttP Dungeon Items")

View File

@@ -682,7 +682,7 @@ def get_pool_core(world, player: int):
if 'triforce_hunt' in goal:
if world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_extra:
treasure_hunt_total = (world.triforce_pieces_required[player].value
treasure_hunt_total = (world.triforce_pieces_available[player].value
+ world.triforce_pieces_extra[player].value)
elif world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_percentage:
percentage = float(world.triforce_pieces_percentage[player].value) / 100

View File

@@ -412,7 +412,7 @@ def global_rules(multiworld: MultiWorld, player: int):
lambda state: ((state._lttp_has_key('Small Key (Thieves Town)', player, 3)) or (location_item_name(state, 'Thieves\' Town - Big Chest', player) == ("Small Key (Thieves Town)", player)) and state._lttp_has_key('Small Key (Thieves Town)', player, 2)) and state.has('Hammer', player))
set_rule(multiworld.get_location('Thieves\' Town - Blind\'s Cell', player),
lambda state: state._lttp_has_key('Small Key (Thieves Town)', player))
if multiworld.accessibility[player] != 'full' and not multiworld.key_drop_shuffle[player]:
if multiworld.accessibility[player] != 'locations' and not multiworld.key_drop_shuffle[player]:
set_always_allow(multiworld.get_location('Thieves\' Town - Big Chest', player), lambda state, item: item.name == 'Small Key (Thieves Town)' and item.player == player)
set_rule(multiworld.get_location('Thieves\' Town - Attic', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3))
set_rule(multiworld.get_location('Thieves\' Town - Spike Switch Pot Key', player),
@@ -547,7 +547,7 @@ def global_rules(multiworld: MultiWorld, player: int):
location_item_name(state, 'Ganons Tower - Map Chest', player) in [('Big Key (Ganons Tower)', player)] and state._lttp_has_key('Small Key (Ganons Tower)', player, 6)))
# this seemed to be causing generation failure, disable for now
# if world.accessibility[player] != 'full':
# if world.accessibility[player] != 'locations':
# set_always_allow(world.get_location('Ganons Tower - Map Chest', player), lambda state, item: item.name == 'Small Key (Ganons Tower)' and item.player == player and state._lttp_has_key('Small Key (Ganons Tower)', player, 7) and state.can_reach('Ganons Tower (Hookshot Room)', 'region', player))
# It is possible to need more than 6 keys to get through this entrance if you spend keys elsewhere. We reflect this in the chest requirements.

View File

@@ -356,8 +356,6 @@ class ALTTPWorld(World):
self.dungeon_local_item_names |= self.item_name_groups[option.item_name_group]
if option == "original_dungeon":
self.dungeon_specific_item_names |= self.item_name_groups[option.item_name_group]
else:
self.options.local_items.value |= self.dungeon_local_item_names
self.difficulty_requirements = difficulties[multiworld.item_pool[player].current_key]

View File

@@ -2,8 +2,8 @@
## Configuration
1. All plando options are enabled by default, except for "items plando" which has to be enabled before it can be used (opt-in).
2. To enable it, go to your installation directory (Windows default: `C:\ProgramData\Archipelago`), then open the host.yaml
1. Plando features have to be enabled first, before they can be used (opt-in).
2. To do so, go to your installation directory (Windows default: `C:\ProgramData\Archipelago`), then open the host.yaml
file with a text editor.
3. In it, you're looking for the option key `plando_options`. To enable all plando modules you can set the value
to `bosses, items, texts, connections`
@@ -66,7 +66,6 @@ boss_shuffle:
- ignored if only one world is generated
- can be a number, to target that slot in the multiworld
- can be a name, to target that player's world
- can be a list of names, to target those players' worlds
- can be true, to target any other player's world
- can be false, to target own world and is the default
- can be null, to target a random world
@@ -133,15 +132,17 @@ plando_items:
### Texts
- This module is disabled by default.
- Has the options `text`, `at`, and `percentage`
- All of these options support subweights
- percentage is the percentage chance for this text to be placed, can be omitted entirely for 100%
- text is the text to be placed.
- can be weighted.
- `\n` is a newline.
- `@` is the entered player's name.
- Warning: Text Mapper does not support full unicode.
- [Alphabet](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/Text.py#L758)
- at is the location within the game to attach the text to.
- can be weighted.
- [List of targets](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/Text.py#L1499)
#### Example
@@ -161,6 +162,7 @@ and `uncle_dying_sewer`, then places the text "This is a plando. You've been war
### Connections
- This module is disabled by default.
- Has the options `percentage`, `entrance`, `exit` and `direction`.
- All options support subweights
- percentage is the percentage chance for this to be connected, can be omitted entirely for 100%

View File

@@ -54,7 +54,7 @@ class TestDungeon(LTTPTestBase):
for item in items:
item.classification = ItemClassification.progression
state.collect(item, prevent_sweep=True) # prevent_sweep=True prevents running sweep_for_advancements() and picking up
state.sweep_for_advancements() # key drop keys repeatedly
state.collect(item, event=True) # event=True prevents running sweep_for_events() and picking up
state.sweep_for_events() # key drop keys repeatedly
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access, f"failed {self.multiworld.get_location(location, 1)} with: {item_pool}")
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access, f"failed {self.multiworld.get_location(location, 1)} with: {item_pool}")

View File

@@ -1,60 +0,0 @@
from unittest import TestCase
from BaseClasses import MultiWorld
from test.general import gen_steps, setup_multiworld
from worlds.AutoWorld import call_all
from worlds.generic.Rules import locality_rules
from ... import ALTTPWorld
from ...Options import DungeonItem
class DungeonFillTestBase(TestCase):
multiworld: MultiWorld
world_1: ALTTPWorld
world_2: ALTTPWorld
options = (
"big_key_shuffle",
"small_key_shuffle",
"key_drop_shuffle",
"compass_shuffle",
"map_shuffle",
)
def setUp(self):
self.multiworld = setup_multiworld([ALTTPWorld, ALTTPWorld], ())
self.world_1 = self.multiworld.worlds[1]
self.world_2 = self.multiworld.worlds[2]
def generate_with_options(self, option_value: int):
for option in self.options:
getattr(self.world_1.options, option).value = getattr(self.world_2.options, option).value = option_value
for step in gen_steps:
call_all(self.multiworld, step)
# this is where locality rules are set in normal generation which we need to verify this test
if step == "set_rules":
locality_rules(self.multiworld)
def test_original_dungeons(self):
self.generate_with_options(DungeonItem.option_original_dungeon)
for location in self.multiworld.get_filled_locations():
with (self.subTest(location=location)):
if location.parent_region.dungeon is None:
self.assertIs(location.item.dungeon, None)
else:
self.assertEqual(location.player, location.item.player,
f"{location.item} does not belong to {location}'s player")
if location.item.dungeon is None:
continue
self.assertIs(location.item.dungeon, location.parent_region.dungeon,
f"{location.item} was not placed in its original dungeon.")
def test_own_dungeons(self):
self.generate_with_options(DungeonItem.option_own_dungeons)
for location in self.multiworld.get_filled_locations():
with self.subTest(location=location):
if location.parent_region.dungeon is None:
self.assertIs(location.item.dungeon, None)
else:
self.assertEqual(location.player, location.item.player,
f"{location.item} does not belong to {location}'s player")

View File

@@ -4,7 +4,7 @@ from BaseClasses import Tutorial
from ..AutoWorld import WebWorld, World
class AP_SudokuWebWorld(WebWorld):
options_page = False
options_page = "games/Sudoku/info/en"
theme = 'partyTime'
setup_en = Tutorial(

View File

@@ -1,7 +1,9 @@
# APSudoku Setup Guide
## Required Software
- [APSudoku](https://github.com/APSudoku/APSudoku)
- [APSudoku](https://github.com/EmilyV99/APSudoku)
- Windows (most tested on Win10)
- Other platforms might be able to build from source themselves; and may be included in the future.
## General Concept
@@ -11,33 +13,25 @@ Does not need to be added at the start of a seed, as it does not create any slot
## Installation Procedures
Go to the latest release from the [APSudoku Releases page](https://github.com/APSudoku/APSudoku/releases/latest). Download and extract the appropriate file for your platform.
Go to the latest release from the [APSudoku Releases page](https://github.com/EmilyV99/APSudoku/releases). Download and extract the `APSudoku.zip` file.
## Joining a MultiWorld Game
1. Run the APSudoku executable.
2. Under `Settings` &rarr; `Connection` at the top-right:
- Enter the server address and port number
1. Run APSudoku.exe
2. Under the 'Archipelago' tab at the top-right:
- Enter the server url & port number
- Enter the name of the slot you wish to connect to
- Enter the room password (optional)
- Select DeathLink related settings (optional)
- Press `Connect`
4. Under the `Sudoku` tab
- Choose puzzle difficulty
- Click `Start` to generate a puzzle
5. Try to solve the Sudoku. Click `Check` when done
- A correct solution rewards you with 1 hint for a location in the world you are connected to
- An incorrect solution has no penalty, unless DeathLink is enabled (see below)
- Press connect
3. Go back to the 'Sudoku' tab
- Click the various '?' buttons for information on how to play / control
4. Choose puzzle difficulty
5. Try to solve the Sudoku. Click 'Check' when done.
Info:
- You can set various settings under `Settings` &rarr; `Sudoku`, and can change the colors used under `Settings` &rarr; `Theme`.
- While connected, you can view the `Console` and `Hints` tabs for standard TextClient-like features
- You can also use the `Tracking` tab to view either a basic tracker or a valid [GodotAP tracker pack](https://github.com/EmilyV99/GodotAP/blob/main/tracker_packs/GET_PACKS.md)
- While connected, the number of "unhinted" locations for your slot is shown in the upper-left of the the `Sudoku` tab. (If this reads 0, no further hints can be earned for this slot, as every locations is already hinted)
- Click the various `?` buttons for information on controls/how to play
## DeathLink Support
If `DeathLink` is enabled when you click `Connect`:
- Lose a life if you check an incorrect puzzle (not an _incomplete_ puzzle- if any cells are empty, you get off with a warning), or if you quit a puzzle without solving it (including disconnecting).
- Your life count is customizable (default 0). Dying with 0 lives left kills linked players AND resets your puzzle.
If 'DeathLink' is enabled when you click 'Connect':
- Lose a life if you check an incorrect puzzle (not an _incomplete_ puzzle- if any cells are empty, you get off with a warning), or quit a puzzle without solving it (including disconnecting).
- Life count customizable (default 0). Dying with 0 lives left kills linked players AND resets your puzzle.
- On receiving a DeathLink from another player, your puzzle resets.

View File

@@ -1,19 +0,0 @@
{
"type": "WorldDefinition",
"configuration": "./output/StringWorldDefinition.json",
"emptyRegionsToKeep": [
"D17Z01S01",
"D01Z02S01",
"D02Z03S09",
"D03Z03S11",
"D04Z03S01",
"D06Z01S09",
"D20Z02S09",
"D09Z01S09[Cell24]",
"D09Z01S08[Cell7]",
"D09Z01S08[Cell18]",
"D09BZ01S01[Cell24]",
"D09BZ01S01[Cell17]",
"D09BZ01S01[Cell19]"
]
}

View File

@@ -637,35 +637,52 @@ item_table: List[ItemDict] = [
'classification': ItemClassification.filler}
]
event_table: Dict[str, str] = {
"OpenedDCGateW": "D01Z05S24",
"OpenedDCGateE": "D01Z05S12",
"OpenedDCLadder": "D01Z05S20",
"OpenedWOTWCave": "D02Z01S06",
"RodeGOTPElevator": "D02Z02S11",
"OpenedConventLadder": "D02Z03S11",
"BrokeJondoBellW": "D03Z02S09",
"BrokeJondoBellE": "D03Z02S05",
"OpenedMOMLadder": "D04Z02S06",
"OpenedTSCGate": "D05Z02S11",
"OpenedARLadder": "D06Z01S23",
"BrokeBOTTCStatue": "D08Z01S02",
"OpenedWOTHPGate": "D09Z01S05",
"OpenedBOTSSLadder": "D17Z01S04"
}
group_table: Dict[str, Set[str]] = {
"wounds" : {"Holy Wound of Attrition",
"wounds" : ["Holy Wound of Attrition",
"Holy Wound of Contrition",
"Holy Wound of Compunction"},
"Holy Wound of Compunction"],
"masks" : {"Deformed Mask of Orestes",
"masks" : ["Deformed Mask of Orestes",
"Mirrored Mask of Dolphos",
"Embossed Mask of Crescente"},
"Embossed Mask of Crescente"],
"marks" : {"Mark of the First Refuge",
"marks" : ["Mark of the First Refuge",
"Mark of the Second Refuge",
"Mark of the Third Refuge"},
"Mark of the Third Refuge"],
"tirso" : {"Bouquet of Rosemary",
"tirso" : ["Bouquet of Rosemary",
"Incense Garlic",
"Olive Seeds",
"Dried Clove",
"Sooty Garlic",
"Bouquet of Thyme"},
"Bouquet of Thyme"],
"tentudia": {"Tentudia's Carnal Remains",
"tentudia": ["Tentudia's Carnal Remains",
"Remains of Tentudia's Hair",
"Tentudia's Skeletal Remains"},
"Tentudia's Skeletal Remains"],
"egg" : {"Melted Golden Coins",
"egg" : ["Melted Golden Coins",
"Torn Bridal Ribbon",
"Black Grieving Veil"},
"Black Grieving Veil"],
"bones" : {"Parietal bone of Lasser, the Inquisitor",
"bones" : ["Parietal bone of Lasser, the Inquisitor",
"Jaw of Ashgan, the Inquisitor",
"Cervical vertebra of Zicher, the Brewmaster",
"Clavicle of Dalhuisen, the Schoolchild",
@@ -708,14 +725,14 @@ group_table: Dict[str, Set[str]] = {
"Scaphoid of Fierce, the Leper",
"Anklebone of Weston, the Pilgrim",
"Calcaneum of Persian, the Bandit",
"Navicular of Kahnnyhoo, the Murderer"},
"Navicular of Kahnnyhoo, the Murderer"],
"power" : {"Life Upgrade",
"power" : ["Life Upgrade",
"Fervour Upgrade",
"Empty Bile Vessel",
"Quicksilver"},
"Quicksilver"],
"prayer" : {"Seguiriya to your Eyes like Stars",
"prayer" : ["Seguiriya to your Eyes like Stars",
"Debla of the Lights",
"Saeta Dolorosa",
"Campanillero to the Sons of the Aurora",
@@ -729,17 +746,10 @@ group_table: Dict[str, Set[str]] = {
"Romance to the Crimson Mist",
"Zambra to the Resplendent Crown",
"Cantina of the Blue Rose",
"Mirabras of the Return to Port"},
"toe" : {"Little Toe made of Limestone",
"Big Toe made of Limestone",
"Fourth Toe made of Limestone"},
"eye" : {"Severed Right Eye of the Traitor",
"Broken Left Eye of the Traitor"}
"Mirabras of the Return to Port"]
}
tears_list: List[str] = [
tears_set: Set[str] = [
"Tears of Atonement (500)",
"Tears of Atonement (625)",
"Tears of Atonement (750)",
@@ -762,16 +772,16 @@ tears_list: List[str] = [
"Tears of Atonement (30000)"
]
reliquary_set: Set[str] = {
reliquary_set: Set[str] = [
"Reliquary of the Fervent Heart",
"Reliquary of the Suffering Heart",
"Reliquary of the Sorrowful Heart"
}
]
skill_set: Set[str] = {
skill_set: Set[str] = [
"Combo Skill",
"Charged Skill",
"Ranged Skill",
"Dive Skill",
"Lunge Skill"
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
from dataclasses import dataclass
from Options import Choice, Toggle, DefaultOnToggle, DeathLink, PerGameCommonOptions, OptionGroup
from Options import Choice, Toggle, DefaultOnToggle, DeathLink, StartInventoryPool
import random
@@ -21,30 +20,23 @@ class ChoiceIsRandom(Choice):
class PrieDieuWarp(DefaultOnToggle):
"""
Automatically unlocks the ability to warp between Prie Dieu shrines.
"""
"""Automatically unlocks the ability to warp between Prie Dieu shrines."""
display_name = "Unlock Fast Travel"
class SkipCutscenes(DefaultOnToggle):
"""
Automatically skips most cutscenes.
"""
"""Automatically skips most cutscenes."""
display_name = "Auto Skip Cutscenes"
class CorpseHints(DefaultOnToggle):
"""
Changes the 34 corpses in game to give various hints about item locations.
"""
"""Changes the 34 corpses in game to give various hints about item locations."""
display_name = "Corpse Hints"
class Difficulty(Choice):
"""
Adjusts the overall difficulty of the randomizer, including upgrades required to defeat bosses and advanced movement tricks or glitches.
"""
"""Adjusts the overall difficulty of the randomizer, including upgrades required to defeat bosses
and advanced movement tricks or glitches."""
display_name = "Difficulty"
option_easy = 0
option_normal = 1
@@ -53,18 +45,15 @@ class Difficulty(Choice):
class Penitence(Toggle):
"""
Allows one of the three Penitences to be chosen at the beginning of the game.
"""
"""Allows one of the three Penitences to be chosen at the beginning of the game."""
display_name = "Penitence"
class StartingLocation(ChoiceIsRandom):
"""
Choose where to start the randomizer. Note that some starting locations cannot be chosen with certain other options.
Specifically, Brotherhood and Mourning And Havoc cannot be chosen if Shuffle Dash is enabled, and Grievance Ascends cannot be chosen if Shuffle Wall Climb is enabled.
"""
"""Choose where to start the randomizer. Note that some starting locations cannot be chosen with certain
other options.
Specifically, Brotherhood and Mourning And Havoc cannot be chosen if Shuffle Dash is enabled, and Grievance Ascends
cannot be chosen if Shuffle Wall Climb is enabled."""
display_name = "Starting Location"
option_brotherhood = 0
option_albero = 1
@@ -77,15 +66,10 @@ class StartingLocation(ChoiceIsRandom):
class Ending(Choice):
"""
Choose which ending is required to complete the game.
"""Choose which ending is required to complete the game.
Talking to Tirso in Albero will tell you the selected ending for the current game.
Ending A: Collect all thorn upgrades.
Ending C: Collect all thorn upgrades and the Holy Wound of Abnegation.
"""
Ending C: Collect all thorn upgrades and the Holy Wound of Abnegation."""
display_name = "Ending"
option_any_ending = 0
option_ending_a = 1
@@ -94,18 +78,14 @@ class Ending(Choice):
class SkipLongQuests(Toggle):
"""
Ensures that the rewards for long quests will be filler items.
Affected locations: "Albero: Donate 50000 Tears", "Ossuary: 11th reward", "AtTotS: Miriam's gift", "TSC: Jocinero's final reward"
"""
"""Ensures that the rewards for long quests will be filler items.
Affected locations: \"Albero: Donate 50000 Tears\", \"Ossuary: 11th reward\", \"AtTotS: Miriam's gift\",
\"TSC: Jocinero's final reward\""""
display_name = "Skip Long Quests"
class ThornShuffle(Choice):
"""
Shuffles the Thorn given by Deogracias and all Thorn upgrades into the item pool.
"""
"""Shuffles the Thorn given by Deogracias and all Thorn upgrades into the item pool."""
display_name = "Shuffle Thorn"
option_anywhere = 0
option_local_only = 1
@@ -114,68 +94,50 @@ class ThornShuffle(Choice):
class DashShuffle(Toggle):
"""
Turns the ability to dash into an item that must be found in the multiworld.
"""
"""Turns the ability to dash into an item that must be found in the multiworld."""
display_name = "Shuffle Dash"
class WallClimbShuffle(Toggle):
"""
Turns the ability to climb walls with your sword into an item that must be found in the multiworld.
"""
"""Turns the ability to climb walls with your sword into an item that must be found in the multiworld."""
display_name = "Shuffle Wall Climb"
class ReliquaryShuffle(DefaultOnToggle):
"""
Adds the True Torment exclusive Reliquary rosary beads into the item pool.
"""
"""Adds the True Torment exclusive Reliquary rosary beads into the item pool."""
display_name = "Shuffle Penitence Rewards"
class CustomItem1(Toggle):
"""
Adds the custom relic Boots of Pleading into the item pool, which grants the ability to fall onto spikes and survive.
Must have the "Boots of Pleading" mod installed to connect to a multiworld.
"""
"""Adds the custom relic Boots of Pleading into the item pool, which grants the ability to fall onto spikes
and survive.
Must have the \"Blasphemous-Boots-of-Pleading\" mod installed to connect to a multiworld."""
display_name = "Boots of Pleading"
class CustomItem2(Toggle):
"""
Adds the custom relic Purified Hand of the Nun into the item pool, which grants the ability to jump a second time in mid-air.
Must have the "Double Jump" mod installed to connect to a multiworld.
"""
"""Adds the custom relic Purified Hand of the Nun into the item pool, which grants the ability to jump
a second time in mid-air.
Must have the \"Blasphemous-Double-Jump\" mod installed to connect to a multiworld."""
display_name = "Purified Hand of the Nun"
class StartWheel(Toggle):
"""
Changes the beginning gift to The Young Mason's Wheel.
"""
"""Changes the beginning gift to The Young Mason's Wheel."""
display_name = "Start with Wheel"
class SkillRando(Toggle):
"""
Randomizes the abilities from the skill tree into the item pool.
"""
"""Randomizes the abilities from the skill tree into the item pool."""
display_name = "Skill Randomizer"
class EnemyRando(Choice):
"""
Randomizes the enemies that appear in each room.
Shuffled: Enemies will be shuffled amongst each other, but can only appear as many times as they do in a standard game.
"""Randomizes the enemies that appear in each room.
Shuffled: Enemies will be shuffled amongst each other, but can only appear as many times as they do in
a standard game.
Randomized: Every enemy is completely random, and can appear any number of times.
Some enemies will never be randomized.
"""
Some enemies will never be randomized."""
display_name = "Enemy Randomizer"
option_disabled = 0
option_shuffled = 1
@@ -184,75 +146,43 @@ class EnemyRando(Choice):
class EnemyGroups(DefaultOnToggle):
"""
Randomized enemies will be chosen from sets of specific groups.
"""Randomized enemies will chosen from sets of specific groups.
(Weak, normal, large, flying)
Has no effect if Enemy Randomizer is disabled.
"""
Has no effect if Enemy Randomizer is disabled."""
display_name = "Enemy Groups"
class EnemyScaling(DefaultOnToggle):
"""
Randomized enemies will have their stats increased or decreased depending on the area they appear in.
Has no effect if Enemy Randomizer is disabled.
"""
"""Randomized enemies will have their stats increased or decreased depending on the area they appear in.
Has no effect if Enemy Randomizer is disabled."""
display_name = "Enemy Scaling"
class BlasphemousDeathLink(DeathLink):
"""
When you die, everyone dies. The reverse is also true.
Note that Guilt Fragments will not appear when killed by Death Link.
"""
"""When you die, everyone dies. The reverse is also true.
Note that Guilt Fragments will not appear when killed by Death Link."""
@dataclass
class BlasphemousOptions(PerGameCommonOptions):
prie_dieu_warp: PrieDieuWarp
skip_cutscenes: SkipCutscenes
corpse_hints: CorpseHints
difficulty: Difficulty
penitence: Penitence
starting_location: StartingLocation
ending: Ending
skip_long_quests: SkipLongQuests
thorn_shuffle: ThornShuffle
dash_shuffle: DashShuffle
wall_climb_shuffle: WallClimbShuffle
reliquary_shuffle: ReliquaryShuffle
boots_of_pleading: CustomItem1
purified_hand: CustomItem2
start_wheel: StartWheel
skill_randomizer: SkillRando
enemy_randomizer: EnemyRando
enemy_groups: EnemyGroups
enemy_scaling: EnemyScaling
death_link: BlasphemousDeathLink
blas_option_groups = [
OptionGroup("Quality of Life", [
PrieDieuWarp,
SkipCutscenes,
CorpseHints,
SkipLongQuests,
StartWheel
]),
OptionGroup("Moveset", [
DashShuffle,
WallClimbShuffle,
SkillRando,
CustomItem1,
CustomItem2
]),
OptionGroup("Enemy Randomizer", [
EnemyRando,
EnemyGroups,
EnemyScaling
])
]
blasphemous_options = {
"prie_dieu_warp": PrieDieuWarp,
"skip_cutscenes": SkipCutscenes,
"corpse_hints": CorpseHints,
"difficulty": Difficulty,
"penitence": Penitence,
"starting_location": StartingLocation,
"ending": Ending,
"skip_long_quests": SkipLongQuests,
"thorn_shuffle" : ThornShuffle,
"dash_shuffle": DashShuffle,
"wall_climb_shuffle": WallClimbShuffle,
"reliquary_shuffle": ReliquaryShuffle,
"boots_of_pleading": CustomItem1,
"purified_hand": CustomItem2,
"start_wheel": StartWheel,
"skill_randomizer": SkillRando,
"enemy_randomizer": EnemyRando,
"enemy_groups": EnemyGroups,
"enemy_scaling": EnemyScaling,
"death_link": BlasphemousDeathLink,
"start_inventory": StartInventoryPool
}

View File

@@ -1,582 +0,0 @@
# Preprocessor to convert Blasphemous Randomizer logic into a StringWorldDefinition for use with APHKLogicExtractor
# https://github.com/BrandenEK/Blasphemous.Randomizer
# https://github.com/ArchipelagoMW-HollowKnight/APHKLogicExtractor
import json, requests, argparse
from typing import List, Dict, Any
def load_resource_local(file: str) -> List[Dict[str, Any]]:
print(f"Reading from {file}")
loaded = []
with open(file, encoding="utf-8") as f:
loaded = read_json(f.readlines())
f.close()
return loaded
def load_resource_from_web(url: str) -> List[Dict[str, Any]]:
req = requests.get(url, timeout=1)
print(f"Reading from {url}")
req.encoding = "utf-8"
lines: List[str] = []
for line in req.text.splitlines():
while "\t" in line:
line = line[1::]
if line != "":
lines.append(line)
return read_json(lines)
def read_json(lines: List[str]) -> List[Dict[str, Any]]:
loaded = []
creating_object: bool = False
obj: str = ""
for line in lines:
stripped = line.strip()
if "{" in stripped:
creating_object = True
obj += stripped
continue
elif "}," in stripped or "}" in stripped and "]" in lines[lines.index(line)+1]:
creating_object = False
obj += "}"
#print(f"obj = {obj}")
loaded.append(json.loads(obj))
obj = ""
continue
if not creating_object:
continue
else:
try:
if "}," in lines[lines.index(line)+1] and stripped[-1] == ",":
obj += stripped[:-1]
else:
obj += stripped
except IndexError:
obj += stripped
return loaded
def get_room_from_door(door: str) -> str:
return door[:door.find("[")]
def preprocess_logic(is_door: bool, id: str, logic: str) -> str:
if id in logic and not is_door:
index: int = logic.find(id)
logic = logic[:index] + logic[index+len(id)+4:]
while ">=" in logic:
index: int = logic.find(">=")
logic = logic[:index-1] + logic[index+3:]
while ">" in logic:
index: int = logic.find(">")
count = int(logic[index+2])
count += 1
logic = logic[:index-1] + str(count) + logic[index+3:]
while "<=" in logic:
index: int = logic.find("<=")
logic = logic[:index-1] + logic[index+3:]
while "<" in logic:
index: int = logic.find("<")
count = int(logic[index+2])
count += 1
logic = logic[:index-1] + str(count) + logic[index+3:]
#print(logic)
return logic
def build_logic_conditions(logic: str) -> List[List[str]]:
all_conditions: List[List[str]] = []
parts = logic.split()
sub_part: str = ""
current_index: int = 0
parens: int = -1
current_condition: List[str] = []
parens_conditions: List[List[List[str]]] = []
for index, part in enumerate(parts):
#print(current_index, index, parens, part)
# skip parts that have already been handled
if index < current_index:
continue
# break loop if reached final part
try:
parts[index+1]
except IndexError:
#print("INDEXERROR", part)
if parens < 0:
current_condition.append(part)
if len(parens_conditions) > 0:
for i in parens_conditions:
for j in i:
all_conditions.append(j + current_condition)
else:
all_conditions.append(current_condition)
break
#print(current_condition, parens, sub_part)
# prepare for subcondition
if "(" in part:
# keep track of nested parentheses
if parens == -1:
parens = 0
for char in part:
if char == "(":
parens += 1
# add to sub part
if sub_part == "":
sub_part = part
else:
sub_part += f" {part}"
#if not ")" in part:
continue
# end of subcondition
if ")" in part:
# read every character in case of multiple closing parentheses
for char in part:
if char == ")":
parens -= 1
sub_part += f" {part}"
# if reached end of parentheses, handle subcondition
if parens == 0:
#print(current_condition, sub_part)
parens = -1
try:
parts[index+1]
except IndexError:
#print("END OF LOGIC")
if len(parens_conditions) > 0:
parens_conditions.append(build_logic_subconditions(current_condition, sub_part))
#print("PARENS:", parens_conditions)
temp_conditions: List[List[str]] = []
for i in parens_conditions[0]:
for j in parens_conditions[1]:
temp_conditions.append(i + j)
parens_conditions.pop(0)
parens_conditions.pop(0)
while len(parens_conditions) > 0:
temp_conditions2 = temp_conditions
temp_conditions = []
for k in temp_conditions2:
for l in parens_conditions[0]:
temp_conditions.append(k + l)
parens_conditions.pop(0)
#print("TEMP:", remove_duplicates(temp_conditions))
all_conditions += temp_conditions
else:
all_conditions += build_logic_subconditions(current_condition, sub_part)
else:
#print("NEXT PARTS:", parts[index+1], parts[index+2])
if parts[index+1] == "&&":
parens_conditions.append(build_logic_subconditions(current_condition, sub_part))
#print("PARENS:", parens_conditions)
else:
if len(parens_conditions) > 0:
parens_conditions.append(build_logic_subconditions(current_condition, sub_part))
#print("PARENS:", parens_conditions)
temp_conditions: List[List[str]] = []
for i in parens_conditions[0]:
for j in parens_conditions[1]:
temp_conditions.append(i + j)
parens_conditions.pop(0)
parens_conditions.pop(0)
while len(parens_conditions) > 0:
temp_conditions2 = temp_conditions
temp_conditions = []
for k in temp_conditions2:
for l in parens_conditions[0]:
temp_conditions.append(k + l)
parens_conditions.pop(0)
#print("TEMP:", remove_duplicates(temp_conditions))
all_conditions += temp_conditions
else:
all_conditions += build_logic_subconditions(current_condition, sub_part)
current_index = index+2
current_condition = []
sub_part = ""
continue
# collect all parts until reaching end of parentheses
if parens > 0:
sub_part += f" {part}"
continue
current_condition.append(part)
# continue with current condition
if parts[index+1] == "&&":
current_index = index+2
continue
# add condition to list and start new one
elif parts[index+1] == "||":
if len(parens_conditions) > 0:
for i in parens_conditions:
for j in i:
all_conditions.append(j + current_condition)
parens_conditions = []
else:
all_conditions.append(current_condition)
current_condition = []
current_index = index+2
continue
return remove_duplicates(all_conditions)
def build_logic_subconditions(current_condition: List[str], subcondition: str) -> List[List[str]]:
#print("STARTED SUBCONDITION", current_condition, subcondition)
subconditions = build_logic_conditions(subcondition[1:-1])
final_conditions = []
for condition in subconditions:
final_condition = current_condition + condition
final_conditions.append(final_condition)
#print("ENDED SUBCONDITION")
#print(final_conditions)
return final_conditions
def remove_duplicates(conditions: List[List[str]]) -> List[List[str]]:
final_conditions: List[List[str]] = []
for condition in conditions:
final_conditions.append(list(dict.fromkeys(condition)))
return final_conditions
def handle_door_visibility(door: Dict[str, Any]) -> Dict[str, Any]:
if door.get("visibilityFlags") == None:
return door
else:
flags: List[str] = str(door.get("visibilityFlags")).split(", ")
#print(flags)
temp_flags: List[str] = []
this_door: bool = False
#required_doors: str = ""
if "ThisDoor" in flags:
this_door = True
#if "requiredDoors" in flags:
# required_doors: str = " || ".join(door.get("requiredDoors"))
if "DoubleJump" in flags:
temp_flags.append("DoubleJump")
if "NormalLogic" in flags:
temp_flags.append("NormalLogic")
if "NormalLogicAndDoubleJump" in flags:
temp_flags.append("NormalLogicAndDoubleJump")
if "HardLogic" in flags:
temp_flags.append("HardLogic")
if "HardLogicAndDoubleJump" in flags:
temp_flags.append("HardLogicAndDoubleJump")
if "EnemySkips" in flags:
temp_flags.append("EnemySkips")
if "EnemySkipsAndDoubleJump" in flags:
temp_flags.append("EnemySkipsAndDoubleJump")
# remove duplicates
temp_flags = list(dict.fromkeys(temp_flags))
original_logic: str = door.get("logic")
temp_logic: str = ""
if this_door:
temp_logic = door.get("id")
if temp_flags != []:
if temp_logic != "":
temp_logic += " || "
temp_logic += ' && '.join(temp_flags)
if temp_logic != "" and original_logic != None:
if len(original_logic.split()) == 1:
if len(temp_logic.split()) == 1:
door["logic"] = f"{temp_logic} && {original_logic}"
else:
door["logic"] = f"({temp_logic}) && {original_logic}"
else:
if len(temp_logic.split()) == 1:
door["logic"] = f"{temp_logic} && ({original_logic})"
else:
door["logic"] = f"({temp_logic}) && ({original_logic})"
elif temp_logic != "" and original_logic == None:
door["logic"] = temp_logic
return door
def get_state_provider_for_condition(condition: List[str]) -> str:
for item in condition:
if (item[0] == "D" and item[3] == "Z" and item[6] == "S")\
or (item[0] == "D" and item[3] == "B" and item[4] == "Z" and item[7] == "S"):
return item
return None
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument('-l', '--local', action="store_true", help="Use local files in the same directory instead of reading resource files from the BrandenEK/Blasphemous-Randomizer repository.")
args = parser.parse_args()
return args
def main(args: argparse.Namespace):
doors = []
locations = []
if (args.local):
doors = load_resource_local("doors.json")
locations = load_resource_local("locations_items.json")
else:
doors = load_resource_from_web("https://raw.githubusercontent.com/BrandenEK/Blasphemous-Randomizer/main/resources/data/Randomizer/doors.json")
locations = load_resource_from_web("https://raw.githubusercontent.com/BrandenEK/Blasphemous-Randomizer/main/resources/data/Randomizer/locations_items.json")
original_connections: Dict[str, str] = {}
rooms: Dict[str, List[str]] = {}
output: Dict[str, Any] = {}
logic_objects: List[Dict[str, Any]] = []
for door in doors:
if door.get("originalDoor") != None:
if not door.get("id") in original_connections:
original_connections[door.get("id")] = door.get("originalDoor")
original_connections[door.get("originalDoor")] = door.get("id")
room: str = get_room_from_door(door.get("originalDoor"))
if not room in rooms.keys():
rooms[room] = [door.get("id")]
else:
rooms[room].append(door.get("id"))
def flip_doors_in_condition(condition: List[str]) -> List[str]:
new_condition = []
for item in condition:
if item in original_connections:
new_condition.append(original_connections[item])
else:
new_condition.append(item)
return new_condition
for room in rooms.keys():
obj = {
"Name": room,
"Logic": [],
"Handling": "Default"
}
for door in rooms[room]:
logic = {
"StateProvider": door,
"Conditions": [],
"StateModifiers": []
}
obj["Logic"].append(logic)
logic_objects.append(obj)
for door in doors:
if door.get("direction") == 5:
continue
handling: str = "Transition"
if "Cell" in door.get("id"):
handling = "Default"
obj = {
"Name": door.get("id"),
"Logic": [],
"Handling": handling
}
visibility_flags: List[str] = []
if door.get("visibilityFlags") != None:
visibility_flags = str(door.get("visibilityFlags")).split(", ")
if "1" in visibility_flags:
visibility_flags.remove("1")
visibility_flags.append("ThisDoor")
required_doors: List[str] = []
if door.get("requiredDoors"):
required_doors = door.get("requiredDoors")
if len(visibility_flags) > 0:
for flag in visibility_flags:
if flag == "RequiredDoors":
continue
if flag == "ThisDoor":
flag = original_connections[door.get("id")]
if door.get("logic") != None:
logic: str = door.get("logic")
logic = f"{flag} && ({logic})"
logic = preprocess_logic(True, door.get("id"), logic)
conditions = build_logic_conditions(logic)
for condition in conditions:
condition = flip_doors_in_condition(condition)
state_provider: str = get_room_from_door(door.get("id"))
if get_state_provider_for_condition(condition) != None:
state_provider = get_state_provider_for_condition(condition)
condition.remove(state_provider)
logic = {
"StateProvider": state_provider,
"Conditions": condition,
"StateModifiers": []
}
obj["Logic"].append(logic)
else:
logic = {
"StateProvider": get_room_from_door(door.get("id")),
"Conditions": [flag],
"StateModifiers": []
}
obj["Logic"].append(logic)
if "RequiredDoors" in visibility_flags:
for d in required_doors:
flipped = original_connections[d]
if door.get("logic") != None:
logic: str = preprocess_logic(True, door.get("id"), door.get("logic"))
conditions = build_logic_conditions(logic)
for condition in conditions:
condition = flip_doors_in_condition(condition)
state_provider: str = flipped
if flipped in condition:
condition.remove(flipped)
logic = {
"StateProvider": state_provider,
"Conditions": condition,
"StateModifiers": []
}
obj["Logic"].append(logic)
else:
logic = {
"StateProvider": flipped,
"Conditions": [],
"StateModifiers": []
}
obj["Logic"].append(logic)
else:
if door.get("logic") != None:
logic: str = preprocess_logic(True, door.get("id"), door.get("logic"))
conditions = build_logic_conditions(logic)
for condition in conditions:
condition = flip_doors_in_condition(condition)
stateProvider: str = get_room_from_door(door.get("id"))
if get_state_provider_for_condition(condition) != None:
stateProvider = get_state_provider_for_condition(condition)
condition.remove(stateProvider)
logic = {
"StateProvider": stateProvider,
"Conditions": condition,
"StateModifiers": []
}
obj["Logic"].append(logic)
else:
logic = {
"StateProvider": get_room_from_door(door.get("id")),
"Conditions": [],
"StateModifiers": []
}
obj["Logic"].append(logic)
logic_objects.append(obj)
for location in locations:
obj = {
"Name": location.get("id"),
"Logic": [],
"Handling": "Location"
}
if location.get("logic") != None:
for condition in build_logic_conditions(preprocess_logic(False, location.get("id"), location.get("logic"))):
condition = flip_doors_in_condition(condition)
stateProvider: str = location.get("room")
if get_state_provider_for_condition(condition) != None:
stateProvider = get_state_provider_for_condition(condition)
condition.remove(stateProvider)
if stateProvider == "Initial":
stateProvider = None
logic = {
"StateProvider": stateProvider,
"Conditions": condition,
"StateModifiers": []
}
obj["Logic"].append(logic)
else:
stateProvider: str = location.get("room")
if stateProvider == "Initial":
stateProvider = None
logic = {
"StateProvider": stateProvider,
"Conditions": [],
"StateModifiers": []
}
obj["Logic"].append(logic)
logic_objects.append(obj)
output["LogicObjects"] = logic_objects
with open("StringWorldDefinition.json", "w") as file:
print("Writing to StringWorldDefinition.json")
file.write(json.dumps(output, indent=4))
if __name__ == "__main__":
main(parse_args())

5405
worlds/blasphemous/Rooms.py Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,12 +8,12 @@ unrandomized_dict: Dict[str, str] = {
}
junk_locations: Set[str] = {
junk_locations: Set[str] = [
"Albero: Donate 50000 Tears",
"Ossuary: 11th reward",
"AtTotS: Miriam's gift",
"TSC: Jocinero's final reward"
}
]
thorn_set: Set[str] = {
@@ -44,4 +44,4 @@ skill_dict: Dict[str, str] = {
"Skill 5, Tier 1": "Lunge Skill",
"Skill 5, Tier 2": "Lunge Skill",
"Skill 5, Tier 3": "Lunge Skill",
}
}

View File

@@ -1,15 +1,15 @@
from typing import Dict, List, Set, Any
from collections import Counter
from BaseClasses import Region, Location, Item, Tutorial, ItemClassification
from Options import OptionError
from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification
from worlds.AutoWorld import World, WebWorld
from .Items import base_id, item_table, group_table, tears_list, reliquary_set
from .Locations import location_names
from .Rules import BlasRules
from worlds.generic.Rules import set_rule
from .Options import BlasphemousOptions, blas_option_groups
from .Items import base_id, item_table, group_table, tears_set, reliquary_set, event_table
from .Locations import location_table
from .Rooms import room_table, door_table
from .Rules import rules
from worlds.generic.Rules import set_rule, add_rule
from .Options import blasphemous_options
from .Vanilla import unrandomized_dict, junk_locations, thorn_set, skill_dict
from .region_data import regions, locations
class BlasphemousWeb(WebWorld):
theme = "stone"
@@ -21,33 +21,39 @@ class BlasphemousWeb(WebWorld):
"setup/en",
["TRPG"]
)]
option_groups = blas_option_groups
class BlasphemousWorld(World):
"""
Blasphemous is a challenging Metroidvania set in the cursed land of Cvstodia. Play as the Penitent One, trapped
in an endless cycle of death and rebirth, and free the world from its terrible fate in your quest to break
in an endless cycle of death and rebirth, and free the world from it's terrible fate in your quest to break
your eternal damnation!
"""
game = "Blasphemous"
game: str = "Blasphemous"
web = BlasphemousWeb()
item_name_to_id = {item["name"]: (base_id + index) for index, item in enumerate(item_table)}
location_name_to_id = {loc: (base_id + index) for index, loc in enumerate(location_names.values())}
location_name_to_id = {loc["name"]: (base_id + index) for index, loc in enumerate(location_table)}
location_name_to_game_id = {loc["name"]: loc["game_id"] for loc in location_table}
item_name_groups = group_table
options_dataclass = BlasphemousOptions
options: BlasphemousOptions
option_definitions = blasphemous_options
required_client_version = (0, 4, 7)
required_client_version = (0, 4, 2)
def __init__(self, multiworld, player):
super(BlasphemousWorld, self).__init__(multiworld, player)
self.start_room: str = "D17Z01S01"
self.disabled_locations: List[str] = []
self.door_connections: Dict[str, str] = {}
def set_rules(self):
rules(self)
for door in door_table:
add_rule(self.multiworld.get_location(door["Id"], self.player),
lambda state: state.can_reach(self.get_connected_door(door["Id"])), self.player)
def create_item(self, name: str) -> "BlasphemousItem":
@@ -62,56 +68,64 @@ class BlasphemousWorld(World):
def get_filler_item_name(self) -> str:
return self.random.choice(tears_list)
return self.multiworld.random.choice(tears_set)
def generate_early(self):
if not self.options.starting_location.randomized:
if self.options.starting_location == "mourning_havoc" and self.options.difficulty < 2:
raise OptionError(f"[Blasphemous - '{self.player_name}'] "
f"{self.options.starting_location} cannot be chosen if Difficulty is lower than Hard.")
world = self.multiworld
player = self.player
if (self.options.starting_location == "brotherhood" or self.options.starting_location == "mourning_havoc") \
and self.options.dash_shuffle:
raise OptionError(f"[Blasphemous - '{self.player_name}'] "
f"{self.options.starting_location} cannot be chosen if Shuffle Dash is enabled.")
if not world.starting_location[player].randomized:
if world.starting_location[player].value == 6 and world.difficulty[player].value < 2:
raise Exception(f"[Blasphemous - '{world.get_player_name(player)}'] {world.starting_location[player]}"
" cannot be chosen if Difficulty is lower than Hard.")
if (world.starting_location[player].value == 0 or world.starting_location[player].value == 6) \
and world.dash_shuffle[player]:
raise Exception(f"[Blasphemous - '{world.get_player_name(player)}'] {world.starting_location[player]}"
" cannot be chosen if Shuffle Dash is enabled.")
if self.options.starting_location == "grievance" and self.options.wall_climb_shuffle:
raise OptionError(f"[Blasphemous - '{self.player_name}'] "
f"{self.options.starting_location} cannot be chosen if Shuffle Wall Climb is enabled.")
if world.starting_location[player].value == 3 and world.wall_climb_shuffle[player]:
raise Exception(f"[Blasphemous - '{world.get_player_name(player)}'] {world.starting_location[player]}"
" cannot be chosen if Shuffle Wall Climb is enabled.")
else:
locations: List[int] = [ 0, 1, 2, 3, 4, 5, 6 ]
invalid: bool = False
if self.options.difficulty < 2:
if world.difficulty[player].value < 2:
locations.remove(6)
if self.options.dash_shuffle:
if world.dash_shuffle[player]:
locations.remove(0)
if 6 in locations:
locations.remove(6)
if self.options.wall_climb_shuffle:
if world.wall_climb_shuffle[player]:
locations.remove(3)
if self.options.starting_location.value not in locations:
self.options.starting_location.value = self.random.choice(locations)
if world.starting_location[player].value == 6 and world.difficulty[player].value < 2:
invalid = True
if (world.starting_location[player].value == 0 or world.starting_location[player].value == 6) \
and world.dash_shuffle[player]:
invalid = True
if world.starting_location[player].value == 3 and world.wall_climb_shuffle[player]:
invalid = True
if invalid:
world.starting_location[player].value = world.random.choice(locations)
if not self.options.dash_shuffle:
self.multiworld.push_precollected(self.create_item("Dash Ability"))
if not world.dash_shuffle[player]:
world.push_precollected(self.create_item("Dash Ability"))
if not self.options.wall_climb_shuffle:
self.multiworld.push_precollected(self.create_item("Wall Climb Ability"))
if not world.wall_climb_shuffle[player]:
world.push_precollected(self.create_item("Wall Climb Ability"))
if not self.options.boots_of_pleading:
self.disabled_locations.append("RE401")
if not self.options.purified_hand:
self.disabled_locations.append("RE402")
if self.options.skip_long_quests:
if world.skip_long_quests[player]:
for loc in junk_locations:
self.options.exclude_locations.value.add(loc)
world.exclude_locations[player].value.add(loc)
start_rooms: Dict[int, str] = {
0: "D17Z01S01",
@@ -123,10 +137,13 @@ class BlasphemousWorld(World):
6: "D20Z02S09"
}
self.start_room = start_rooms[self.options.starting_location.value]
self.start_room = start_rooms[world.starting_location[player].value]
def create_items(self):
world = self.multiworld
player = self.player
removed: int = 0
to_remove: List[str] = [
"Tears of Atonement (250)",
@@ -139,46 +156,46 @@ class BlasphemousWorld(World):
skipped_items = []
junk: int = 0
for item, count in self.options.start_inventory.value.items():
for item, count in world.start_inventory[player].value.items():
for _ in range(count):
skipped_items.append(item)
junk += 1
skipped_items.extend(unrandomized_dict.values())
if self.options.thorn_shuffle == "vanilla":
for _ in range(8):
if world.thorn_shuffle[player] == 2:
for i in range(8):
skipped_items.append("Thorn Upgrade")
if self.options.dash_shuffle:
if world.dash_shuffle[player]:
skipped_items.append(to_remove[removed])
removed += 1
elif not self.options.dash_shuffle:
elif not world.dash_shuffle[player]:
skipped_items.append("Dash Ability")
if self.options.wall_climb_shuffle:
if world.wall_climb_shuffle[player]:
skipped_items.append(to_remove[removed])
removed += 1
elif not self.options.wall_climb_shuffle:
elif not world.wall_climb_shuffle[player]:
skipped_items.append("Wall Climb Ability")
if not self.options.reliquary_shuffle:
if not world.reliquary_shuffle[player]:
skipped_items.extend(reliquary_set)
elif self.options.reliquary_shuffle:
for _ in range(3):
elif world.reliquary_shuffle[player]:
for i in range(3):
skipped_items.append(to_remove[removed])
removed += 1
if not self.options.boots_of_pleading:
if not world.boots_of_pleading[player]:
skipped_items.append("Boots of Pleading")
if not self.options.purified_hand:
if not world.purified_hand[player]:
skipped_items.append("Purified Hand of the Nun")
if self.options.start_wheel:
if world.start_wheel[player]:
skipped_items.append("The Young Mason's Wheel")
if not self.options.skill_randomizer:
if not world.skill_randomizer[player]:
skipped_items.extend(skill_dict.values())
counter = Counter(skipped_items)
@@ -191,140 +208,184 @@ class BlasphemousWorld(World):
if count <= 0:
continue
else:
for _ in range(count):
for i in range(count):
pool.append(self.create_item(item["name"]))
for _ in range(junk):
pool.append(self.create_item(self.get_filler_item_name()))
self.multiworld.itempool += pool
world.itempool += pool
def pre_fill(self):
world = self.multiworld
player = self.player
self.place_items_from_dict(unrandomized_dict)
if self.options.thorn_shuffle == "vanilla":
if world.thorn_shuffle[player] == 2:
self.place_items_from_set(thorn_set, "Thorn Upgrade")
if self.options.start_wheel:
self.get_location("Beginning gift").place_locked_item(self.create_item("The Young Mason's Wheel"))
if world.start_wheel[player]:
world.get_location("Beginning gift", player)\
.place_locked_item(self.create_item("The Young Mason's Wheel"))
if not self.options.skill_randomizer:
if not world.skill_randomizer[player]:
self.place_items_from_dict(skill_dict)
if self.options.thorn_shuffle == "local_only":
self.options.local_items.value.add("Thorn Upgrade")
if world.thorn_shuffle[player] == 1:
world.local_items[player].value.add("Thorn Upgrade")
def place_items_from_set(self, location_set: Set[str], name: str):
for loc in location_set:
self.get_location(loc).place_locked_item(self.create_item(name))
self.multiworld.get_location(loc, self.player)\
.place_locked_item(self.create_item(name))
def place_items_from_dict(self, option_dict: Dict[str, str]):
for loc, item in option_dict.items():
self.get_location(loc).place_locked_item(self.create_item(item))
self.multiworld.get_location(loc, self.player)\
.place_locked_item(self.create_item(item))
def create_regions(self) -> None:
multiworld = self.multiworld
player = self.player
created_regions: List[str] = []
for r in regions:
multiworld.regions.append(Region(r["name"], player, multiworld))
created_regions.append(r["name"])
self.get_region("Menu").add_exits({self.start_room: "New Game"})
blas_logic = BlasRules(self)
for r in regions:
region = self.get_region(r["name"])
for e in r["exits"]:
region.add_exits({e["target"]}, {e["target"]: blas_logic.load_rule(True, r["name"], e)})
for l in [l for l in r["locations"] if l not in self.disabled_locations]:
region.add_locations({location_names[l]: self.location_name_to_id[location_names[l]]}, BlasphemousLocation)
for t in r["transitions"]:
if t == r["name"]:
continue
if t in created_regions:
region.add_exits({t})
else:
multiworld.regions.append(Region(t, player, multiworld))
created_regions.append(t)
region.add_exits({t})
for l in [l for l in locations if l["name"] not in self.disabled_locations]:
location = self.get_location(location_names[l["name"]])
set_rule(location, blas_logic.load_rule(False, l["name"], l))
for rname, ename in blas_logic.indirect_conditions:
self.multiworld.register_indirect_condition(self.get_region(rname), self.get_entrance(ename))
#from Utils import visualize_regions
#visualize_regions(self.get_region("Menu"), "blasphemous_regions.puml")
world = self.multiworld
victory = Location(player, "His Holiness Escribar", None, self.get_region("D07Z01S03[W]"))
victory.place_locked_item(self.create_event("Victory"))
self.get_region("D07Z01S03[W]").locations.append(victory)
menu_region = Region("Menu", player, world)
misc_region = Region("Misc", player, world)
world.regions += [menu_region, misc_region]
if self.options.ending == "ending_a":
for room in room_table:
region = Region(room, player, world)
world.regions.append(region)
menu_region.add_exits({self.start_room: "New Game"})
world.get_region(self.start_room, player).add_exits({"Misc": "Misc"})
for door in door_table:
if door.get("OriginalDoor") is None:
continue
else:
if not door["Id"] in self.door_connections.keys():
self.door_connections[door["Id"]] = door["OriginalDoor"]
self.door_connections[door["OriginalDoor"]] = door["Id"]
parent_region: Region = self.get_room_from_door(door["Id"])
target_region: Region = self.get_room_from_door(door["OriginalDoor"])
parent_region.add_exits({
target_region.name: door["Id"]
}, {
target_region.name: lambda x: door.get("VisibilityFlags") != 1
})
for index, loc in enumerate(location_table):
if not world.boots_of_pleading[player] and loc["name"] == "BotSS: 2nd meeting with Redento":
continue
if not world.purified_hand[player] and loc["name"] == "MoM: Western room ledge":
continue
region: Region = world.get_region(loc["room"], player)
region.add_locations({loc["name"]: base_id + index})
#id = base_id + location_table.index(loc)
#reg.locations.append(BlasphemousLocation(player, loc["name"], id, reg))
for e, r in event_table.items():
region: Region = world.get_region(r, player)
event = BlasphemousLocation(player, e, None, region)
event.show_in_spoiler = False
event.place_locked_item(self.create_event(e))
region.locations.append(event)
for door in door_table:
region: Region = self.get_room_from_door(self.door_connections[door["Id"]])
event = BlasphemousLocation(player, door["Id"], None, region)
event.show_in_spoiler = False
event.place_locked_item(self.create_event(door["Id"]))
region.locations.append(event)
victory = Location(player, "His Holiness Escribar", None, world.get_region("D07Z01S03", player))
victory.place_locked_item(self.create_event("Victory"))
world.get_region("D07Z01S03", player).locations.append(victory)
if world.ending[self.player].value == 1:
set_rule(victory, lambda state: state.has("Thorn Upgrade", player, 8))
elif self.options.ending == "ending_c":
elif world.ending[self.player].value == 2:
set_rule(victory, lambda state: state.has("Thorn Upgrade", player, 8) and
state.has("Holy Wound of Abnegation", player))
multiworld.completion_condition[self.player] = lambda state: state.has("Victory", player)
world.completion_condition[self.player] = lambda state: state.has("Victory", player)
def get_room_from_door(self, door: str) -> Region:
return self.multiworld.get_region(door.split("[")[0], self.player)
def get_connected_door(self, door: str) -> Entrance:
return self.multiworld.get_entrance(self.door_connections[door], self.player)
def fill_slot_data(self) -> Dict[str, Any]:
slot_data: Dict[str, Any] = {}
locations = []
doors: Dict[str, str] = {}
world = self.multiworld
player = self.player
thorns: bool = True
if self.options.thorn_shuffle == "vanilla":
if world.thorn_shuffle[player].value == 2:
thorns = False
for loc in world.get_filled_locations(player):
if loc.item.code == None:
continue
else:
data = {
"id": self.location_name_to_game_id[loc.name],
"ap_id": loc.address,
"name": loc.item.name,
"player_name": world.player_name[loc.item.player],
"type": int(loc.item.classification)
}
locations.append(data)
config = {
"LogicDifficulty": self.options.difficulty.value,
"StartingLocation": self.options.starting_location.value,
"LogicDifficulty": world.difficulty[player].value,
"StartingLocation": world.starting_location[player].value,
"VersionCreated": "AP",
"UnlockTeleportation": bool(self.options.prie_dieu_warp.value),
"AllowHints": bool(self.options.corpse_hints.value),
"AllowPenitence": bool(self.options.penitence.value),
"UnlockTeleportation": bool(world.prie_dieu_warp[player].value),
"AllowHints": bool(world.corpse_hints[player].value),
"AllowPenitence": bool(world.penitence[player].value),
"ShuffleReliquaries": bool(self.options.reliquary_shuffle.value),
"ShuffleBootsOfPleading": bool(self.options.boots_of_pleading.value),
"ShufflePurifiedHand": bool(self.options.purified_hand.value),
"ShuffleDash": bool(self.options.dash_shuffle.value),
"ShuffleWallClimb": bool(self.options.wall_climb_shuffle.value),
"ShuffleReliquaries": bool(world.reliquary_shuffle[player].value),
"ShuffleBootsOfPleading": bool(world.boots_of_pleading[player].value),
"ShufflePurifiedHand": bool(world.purified_hand[player].value),
"ShuffleDash": bool(world.dash_shuffle[player].value),
"ShuffleWallClimb": bool(world.wall_climb_shuffle[player].value),
"ShuffleSwordSkills": bool(self.options.wall_climb_shuffle.value),
"ShuffleSwordSkills": bool(world.skill_randomizer[player].value),
"ShuffleThorns": thorns,
"JunkLongQuests": bool(self.options.skip_long_quests.value),
"StartWithWheel": bool(self.options.start_wheel.value),
"JunkLongQuests": bool(world.skip_long_quests[player].value),
"StartWithWheel": bool(world.start_wheel[player].value),
"EnemyShuffleType": self.options.enemy_randomizer.value,
"MaintainClass": bool(self.options.enemy_groups.value),
"AreaScaling": bool(self.options.enemy_scaling.value),
"EnemyShuffleType": world.enemy_randomizer[player].value,
"MaintainClass": bool(world.enemy_groups[player].value),
"AreaScaling": bool(world.enemy_scaling[player].value),
"BossShuffleType": 0,
"DoorShuffleType": 0
}
slot_data = {
"locationinfo": [{"gameId": loc, "apId": (base_id + index)} for index, loc in enumerate(location_names)],
"locations": locations,
"doors": doors,
"cfg": config,
"ending": self.options.ending.value,
"death_link": bool(self.options.death_link.value)
"ending": world.ending[self.player].value,
"death_link": bool(world.death_link[self.player].value)
}
return slot_data

View File

@@ -1,17 +1,48 @@
# Blasphemous Multiworld Setup Guide
It is recommended to use the [Mod Installer](https://github.com/BrandenEK/Blasphemous.Modding.Installer) to handle installing and updating mods. If you would prefer to install mods manually, instructions can also be found at the Mod Installer repository.
## Useful Links
You will need the [Multiworld](https://github.com/BrandenEK/Blasphemous.Randomizer.Multiworld) mod to play an Archipelago randomizer.
Required:
- Blasphemous: [Steam](https://store.steampowered.com/app/774361/Blasphemous/)
- The GOG version of Blasphemous will also work.
- Blasphemous Mod Installer: [GitHub](https://github.com/BrandenEK/Blasphemous-Mod-Installer)
- Blasphemous Modding API: [GitHub](https://github.com/BrandenEK/Blasphemous-Modding-API)
- Blasphemous Randomizer: [GitHub](https://github.com/BrandenEK/Blasphemous-Randomizer)
- Blasphemous Multiworld: [GitHub](https://github.com/BrandenEK/Blasphemous-Multiworld)
Some optional mods are also recommended:
- [Rando Map](https://github.com/BrandenEK/Blasphemous.Randomizer.MapTracker)
- [Boots of Pleading](https://github.com/BrandenEK/Blasphemous.BootsOfPleading) (Required if the "Boots of Pleading" option is enabled)
- [Double Jump](https://github.com/BrandenEK/Blasphemous.DoubleJump) (Required if the "Purified Hand of the Nun" option is enabled)
Optional:
- In-game map tracker: [GitHub](https://github.com/BrandenEK/Blasphemous-Rando-Map)
- Quick Prie Dieu warp mod: [GitHub](https://github.com/BadMagic100/Blasphemous-PrieWarp)
- Boots of Pleading mod: [GitHub](https://github.com/BrandenEK/Blasphemous-Boots-of-Pleading)
- Double Jump mod: [GitHub](https://github.com/BrandenEK/Blasphemous-Double-Jump)
To connect to a multiworld: Choose a save file and enter the address, your name, and the password (if the server has one) into the menu.
## Mod Installer (Recommended)
After connecting, there are some commands you can use in the console, which can be opened by pressing backslash `\`:
- `ap status` - Display connection status.
- `ap say [message]` - Send a message to the server.
- `ap hint [item]` - Request a hint for an item from the server.
1. Download the [Mod Installer](https://github.com/BrandenEK/Blasphemous-Mod-Installer),
and point it to your install directory for Blasphemous.
2. Install the `Modding API`, `Randomizer`, and `Multiworld` mods. Optionally, you can also install the
`Rando Map`, `PrieWarp`, `Boots of Pleading`, and `Double Jump` mods, and set up the PopTracker pack if desired.
3. Start Blasphemous. To verfy that the mods are working, look for a version number for both
the Randomizer and Multiworld on the title screen.
## Manual Installation
1. Download the [Modding API](https://github.com/BrandenEK/Blasphemous-Modding-API/releases), and follow
the [installation instructions](https://github.com/BrandenEK/Blasphemous-Modding-API#installation) on the GitHub page.
2. After the Modding API has been installed, download the
[Randomizer](https://github.com/BrandenEK/Blasphemous-Randomizer/releases) and
[Multiworld](https://github.com/BrandenEK/Blasphemous-Multiworld/releases) archives, and extract the contents of both
into the `Modding` folder. Then, add any desired additional mods.
3. Start Blasphemous. To verfy that the mods are working, look for a version number for both
the Randomizer and Multiworld on the title screen.
## Connecting
To connect to an Archipelago server, open the in-game console by pressing backslash `\` and use
the command `multiworld connect [address:port] [name] [password]`.
The port and password are both optional - if no port is provided then the default port of 38281 is used.
**Make sure to connect to the server before attempting to start a new save file.**

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
from test.bases import WorldTestBase
from .. import BlasphemousWorld
class BlasphemousTestBase(WorldTestBase):
game = "Blasphemous"
world: BlasphemousWorld

View File

@@ -1,56 +0,0 @@
from . import BlasphemousTestBase
from ..Locations import location_names
class BotSSGauntletTest(BlasphemousTestBase):
options = {
"starting_location": "albero",
"wall_climb_shuffle": True,
"dash_shuffle": True
}
@property
def run_default_tests(self) -> bool:
return False
def test_botss_gauntlet(self) -> None:
self.assertAccessDependency([location_names["CO25"]], [["Dash Ability", "Wall Climb Ability"]], True)
class BackgroundZonesTest(BlasphemousTestBase):
@property
def run_default_tests(self) -> bool:
return False
def test_dc_shroud(self) -> None:
self.assertAccessDependency([location_names["RB03"]], [["Shroud of Dreamt Sins"]], True)
def test_wothp_bronze_cells(self) -> None:
bronze_locations = [
location_names["QI70"],
location_names["RESCUED_CHERUB_03"]
]
self.assertAccessDependency(bronze_locations, [["Key of the Secular"]], True)
def test_wothp_silver_cells(self) -> None:
silver_locations = [
location_names["CO24"],
location_names["RESCUED_CHERUB_34"],
location_names["CO37"],
location_names["RESCUED_CHERUB_04"]
]
self.assertAccessDependency(silver_locations, [["Key of the Scribe"]], True)
def test_wothp_gold_cells(self) -> None:
gold_locations = [
location_names["QI51"],
location_names["CO26"],
location_names["CO02"]
]
self.assertAccessDependency(gold_locations, [["Key of the Inquisitor"]], True)
def test_wothp_quirce(self) -> None:
self.assertAccessDependency([location_names["BS14"]], [["Key of the Secular", "Key of the Scribe", "Key of the Inquisitor"]], True)

View File

@@ -1,135 +0,0 @@
from . import BlasphemousTestBase
class TestBrotherhoodEasy(BlasphemousTestBase):
options = {
"starting_location": "brotherhood",
"difficulty": "easy"
}
class TestBrotherhoodNormal(BlasphemousTestBase):
options = {
"starting_location": "brotherhood",
"difficulty": "normal"
}
class TestBrotherhoodHard(BlasphemousTestBase):
options = {
"starting_location": "brotherhood",
"difficulty": "hard"
}
class TestAlberoEasy(BlasphemousTestBase):
options = {
"starting_location": "albero",
"difficulty": "easy"
}
class TestAlberoNormal(BlasphemousTestBase):
options = {
"starting_location": "albero",
"difficulty": "normal"
}
class TestAlberoHard(BlasphemousTestBase):
options = {
"starting_location": "albero",
"difficulty": "hard"
}
class TestConventEasy(BlasphemousTestBase):
options = {
"starting_location": "convent",
"difficulty": "easy"
}
class TestConventNormal(BlasphemousTestBase):
options = {
"starting_location": "convent",
"difficulty": "normal"
}
class TestConventHard(BlasphemousTestBase):
options = {
"starting_location": "convent",
"difficulty": "hard"
}
class TestGrievanceEasy(BlasphemousTestBase):
options = {
"starting_location": "grievance",
"difficulty": "easy"
}
class TestGrievanceNormal(BlasphemousTestBase):
options = {
"starting_location": "grievance",
"difficulty": "normal"
}
class TestGrievanceHard(BlasphemousTestBase):
options = {
"starting_location": "grievance",
"difficulty": "hard"
}
class TestKnotOfWordsEasy(BlasphemousTestBase):
options = {
"starting_location": "knot_of_words",
"difficulty": "easy"
}
class TestKnotOfWordsNormal(BlasphemousTestBase):
options = {
"starting_location": "knot_of_words",
"difficulty": "normal"
}
class TestKnotOfWordsHard(BlasphemousTestBase):
options = {
"starting_location": "knot_of_words",
"difficulty": "hard"
}
class TestRooftopsEasy(BlasphemousTestBase):
options = {
"starting_location": "rooftops",
"difficulty": "easy"
}
class TestRooftopsNormal(BlasphemousTestBase):
options = {
"starting_location": "rooftops",
"difficulty": "normal"
}
class TestRooftopsHard(BlasphemousTestBase):
options = {
"starting_location": "rooftops",
"difficulty": "hard"
}
# mourning and havoc can't be selected on easy or normal. hard only
class TestMourningHavocHard(BlasphemousTestBase):
options = {
"starting_location": "mourning_havoc",
"difficulty": "hard"
}

View File

@@ -28,7 +28,7 @@ An Example `AP.json` file:
```
{
"Url": "archipelago.gg:12345",
"Url": "archipelago:12345",
"SlotName": "Maddy",
"Password": ""
}

View File

@@ -44,15 +44,15 @@ class ChecksFinderWorld(World):
self.multiworld.regions += [menu, board]
def create_items(self):
# Generate list of items
items_to_create = []
# Generate item pool
itempool = []
# Add the map width and height stuff
items_to_create += ["Map Width"] * 5 # 10 - 5
items_to_create += ["Map Height"] * 5 # 10 - 5
itempool += ["Map Width"] * 5 # 10 - 5
itempool += ["Map Height"] * 5 # 10 - 5
# Add the map bombs
items_to_create += ["Map Bombs"] * 15 # 20 - 5
# Convert list into real items
itempool = [self.create_item(item) for item in items_to_create]
itempool += ["Map Bombs"] * 15 # 20 - 5
# Convert itempool into real items
itempool = [self.create_item(item) for item in itempool]
self.multiworld.itempool += itempool

View File

@@ -238,6 +238,15 @@ class DS3ItemData:
ds3_code = cast(int, self.ds3_code) + level,
filler = False,
)
def __hash__(self) -> int:
return (self.name, self.ds3_code).__hash__()
def __eq__(self, other: Any) -> bool:
if isinstance(other, self.__class__):
return self.name == other.name and self.ds3_code == other.ds3_code
else:
return False
class DarkSouls3Item(Item):

View File

@@ -1252,9 +1252,6 @@ class DarkSouls3World(World):
lambda item: not item.advancement
)
# Prevent the player from prioritizing and "excluding" the same location
self.options.priority_locations.value -= allow_useful_locations
if self.options.excluded_location_behavior == "allow_useful":
self.options.exclude_locations.value.clear()
@@ -1295,10 +1292,10 @@ class DarkSouls3World(World):
locations = location if isinstance(location, list) else [location]
for location in locations:
data = location_dictionary[location]
if data.dlc and not self.options.enable_dlc: continue
if data.ngp and not self.options.enable_ngp: continue
if data.dlc and not self.options.enable_dlc: return
if data.ngp and not self.options.enable_ngp: return
if not self._is_location_available(location): continue
if not self._is_location_available(location): return
if isinstance(rule, str):
assert item_dictionary[rule].classification == ItemClassification.progression
rule = lambda state, item=rule: state.has(item, self.player)
@@ -1507,19 +1504,16 @@ class DarkSouls3World(World):
# We include all the items the game knows about so that users can manually request items
# that aren't randomized, and then we _also_ include all the items that are placed in
# practice `item_dictionary.values()` doesn't include upgraded or infused weapons.
items_by_name = {
location.item.name: cast(DarkSouls3Item, location.item).data
all_items = {
cast(DarkSouls3Item, location.item).data
for location in self.multiworld.get_filled_locations()
# item.code None is used for events, which we want to skip
if location.item.code is not None and location.item.player == self.player
}
for item in item_dictionary.values():
if item.name not in items_by_name:
items_by_name[item.name] = item
}.union(item_dictionary.values())
ap_ids_to_ds3_ids: Dict[str, int] = {}
item_counts: Dict[str, int] = {}
for item in items_by_name.values():
for item in all_items:
if item.ap_code is None: continue
if item.ds3_code: ap_ids_to_ds3_ids[str(item.ap_code)] = item.ds3_code
if item.count != 1: item_counts[str(item.ap_code)] = item.count

View File

@@ -1,9 +1,5 @@
import struct
from .options import KirbyFlavorPreset, GooeyFlavorPreset
from typing import TYPE_CHECKING, Optional, Dict, List, Tuple
if TYPE_CHECKING:
from . import KDL3World
from .Options import KirbyFlavorPreset, GooeyFlavorPreset
kirby_flavor_presets = {
1: {
@@ -227,23 +223,6 @@ kirby_flavor_presets = {
"14": "E6E6FA",
"15": "976FBD",
},
14: {
"1": "373B3E",
"2": "98d5d3",
"3": "1aa5ab",
"4": "168f95",
"5": "4f5559",
"6": "1dbac2",
"7": "137a7f",
"8": "093a3c",
"9": "86cecb",
"10": "a0afbc",
"11": "62bfbb",
"12": "50b8b4",
"13": "bec8d1",
"14": "bce4e2",
"15": "91a2b1",
}
}
gooey_flavor_presets = {
@@ -419,21 +398,21 @@ gooey_target_palettes = {
}
def get_kirby_palette(world: "KDL3World") -> Optional[Dict[str, str]]:
def get_kirby_palette(world):
palette = world.options.kirby_flavor_preset.value
if palette == KirbyFlavorPreset.option_custom:
return world.options.kirby_flavor.value
return kirby_flavor_presets.get(palette, None)
def get_gooey_palette(world: "KDL3World") -> Optional[Dict[str, str]]:
def get_gooey_palette(world):
palette = world.options.gooey_flavor_preset.value
if palette == GooeyFlavorPreset.option_custom:
return world.options.gooey_flavor.value
return gooey_flavor_presets.get(palette, None)
def rgb888_to_bgr555(red: int, green: int, blue: int) -> bytes:
def rgb888_to_bgr555(red, green, blue) -> bytes:
red = red >> 3
green = green >> 3
blue = blue >> 3
@@ -441,15 +420,15 @@ def rgb888_to_bgr555(red: int, green: int, blue: int) -> bytes:
return struct.pack("H", outcol)
def get_palette_bytes(palette: Dict[str, str], target: List[str], offset: int, factor: float) -> bytes:
def get_palette_bytes(palette, target, offset, factor):
output_data = bytearray()
for color in target:
hexcol = palette[color]
if hexcol.startswith("#"):
hexcol = hexcol.replace("#", "")
colint = int(hexcol, 16)
col: Tuple[int, ...] = ((colint & 0xFF0000) >> 16, (colint & 0xFF00) >> 8, colint & 0xFF)
col = ((colint & 0xFF0000) >> 16, (colint & 0xFF00) >> 8, colint & 0xFF)
col = tuple(int(int(factor*x) + offset) for x in col)
byte_data = rgb888_to_bgr555(col[0], col[1], col[2])
output_data.extend(bytearray(byte_data))
return bytes(output_data)
return output_data

View File

@@ -11,13 +11,13 @@ from MultiServer import mark_raw
from NetUtils import ClientStatus, color
from Utils import async_start
from worlds.AutoSNIClient import SNIClient
from .locations import boss_locations
from .gifting import kdl3_gifting_options, kdl3_trap_gifts, kdl3_gifts, update_object, pop_object, initialize_giftboxes
from .client_addrs import consumable_addrs, star_addrs
from .Locations import boss_locations
from .Gifting import kdl3_gifting_options, kdl3_trap_gifts, kdl3_gifts, update_object, pop_object, initialize_giftboxes
from .ClientAddrs import consumable_addrs, star_addrs
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from SNIClient import SNIClientCommandProcessor, SNIContext
from SNIClient import SNIClientCommandProcessor
snes_logger = logging.getLogger("SNES")
@@ -81,16 +81,17 @@ deathlink_messages = defaultdict(lambda: " was defeated.", {
@mark_raw
def cmd_gift(self: "SNIClientCommandProcessor") -> None:
def cmd_gift(self: "SNIClientCommandProcessor"):
"""Toggles gifting for the current game."""
handler = self.ctx.client_handler
assert isinstance(handler, KDL3SNIClient)
handler.gifting = not handler.gifting
self.output(f"Gifting set to {handler.gifting}")
if not getattr(self.ctx, "gifting", None):
self.ctx.gifting = True
else:
self.ctx.gifting = not self.ctx.gifting
self.output(f"Gifting set to {self.ctx.gifting}")
async_start(update_object(self.ctx, f"Giftboxes;{self.ctx.team}", {
f"{self.ctx.slot}":
{
"IsOpen": handler.gifting,
"IsOpen": self.ctx.gifting,
**kdl3_gifting_options
}
}))
@@ -99,17 +100,16 @@ def cmd_gift(self: "SNIClientCommandProcessor") -> None:
class KDL3SNIClient(SNIClient):
game = "Kirby's Dream Land 3"
patch_suffix = ".apkdl3"
levels: typing.Dict[int, typing.List[int]] = {}
consumables: typing.Optional[bool] = None
stars: typing.Optional[bool] = None
item_queue: typing.List[int] = []
initialize_gifting: bool = False
gifting: bool = False
levels = None
consumables = None
stars = None
item_queue: typing.List = []
initialize_gifting = False
giftbox_key: str = ""
motherbox_key: str = ""
client_random: random.Random = random.Random()
async def deathlink_kill_player(self, ctx: "SNIContext") -> None:
async def deathlink_kill_player(self, ctx) -> None:
from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read
game_state = await snes_read(ctx, KDL3_GAME_STATE, 1)
if game_state[0] == 0xFF:
@@ -131,7 +131,7 @@ class KDL3SNIClient(SNIClient):
ctx.death_state = DeathState.dead
ctx.last_death_link = time.time()
async def validate_rom(self, ctx: "SNIContext") -> bool:
async def validate_rom(self, ctx) -> bool:
from SNIClient import snes_read
rom_name = await snes_read(ctx, KDL3_ROMNAME, 0x15)
if rom_name is None or rom_name == bytes([0] * 0x15) or rom_name[:4] != b"KDL3":
@@ -141,7 +141,7 @@ class KDL3SNIClient(SNIClient):
ctx.game = self.game
ctx.rom = rom_name
ctx.items_handling = 0b101 # default local items with remote start inventory
ctx.items_handling = 0b111 # always remote items
ctx.allow_collect = True
if "gift" not in ctx.command_processor.commands:
ctx.command_processor.commands["gift"] = cmd_gift
@@ -149,10 +149,9 @@ class KDL3SNIClient(SNIClient):
death_link = await snes_read(ctx, KDL3_DEATH_LINK_ADDR, 1)
if death_link:
await ctx.update_death_link(bool(death_link[0] & 0b1))
ctx.items_handling |= (death_link[0] & 0b10) # set local items if enabled
return True
async def pop_item(self, ctx: "SNIContext", in_stage: bool) -> None:
async def pop_item(self, ctx, in_stage):
from SNIClient import snes_buffered_write, snes_read
if len(self.item_queue) > 0:
item = self.item_queue.pop()
@@ -169,8 +168,8 @@ class KDL3SNIClient(SNIClient):
else:
self.item_queue.append(item) # no more slots, get it next go around
async def pop_gift(self, ctx: "SNIContext") -> None:
if self.giftbox_key in ctx.stored_data and ctx.stored_data[self.giftbox_key]:
async def pop_gift(self, ctx):
if ctx.stored_data[self.giftbox_key]:
from SNIClient import snes_read, snes_buffered_write
key, gift = ctx.stored_data[self.giftbox_key].popitem()
await pop_object(ctx, self.giftbox_key, key)
@@ -215,7 +214,7 @@ class KDL3SNIClient(SNIClient):
quality = min(10, quality * 2)
else:
# it's not really edible, but he'll eat it anyway
quality = self.client_random.choices(range(0, 2), [75, 25])[0]
quality = self.client_random.choices(range(0, 2), {0: 75, 1: 25})[0]
kirby_hp = await snes_read(ctx, KDL3_KIRBY_HP, 1)
gooey_hp = await snes_read(ctx, KDL3_KIRBY_HP + 2, 1)
snes_buffered_write(ctx, KDL3_SOUND_FX, bytes([0x26]))
@@ -225,8 +224,7 @@ class KDL3SNIClient(SNIClient):
else:
snes_buffered_write(ctx, KDL3_KIRBY_HP, struct.pack("H", min(kirby_hp[0] + quality, 10)))
async def pick_gift_recipient(self, ctx: "SNIContext", gift: int) -> None:
assert ctx.slot
async def pick_gift_recipient(self, ctx, gift):
if gift != 4:
gift_base = kdl3_gifts[gift]
else:
@@ -240,7 +238,7 @@ class KDL3SNIClient(SNIClient):
if desire > most_applicable:
most_applicable = desire
most_applicable_slot = int(slot)
elif most_applicable_slot != ctx.slot and most_applicable == -1 and info["AcceptsAnyGift"]:
elif most_applicable_slot == ctx.slot and info["AcceptsAnyGift"]:
# only send to ourselves if no one else will take it
most_applicable_slot = int(slot)
# print(most_applicable, most_applicable_slot)
@@ -259,7 +257,7 @@ class KDL3SNIClient(SNIClient):
item_uuid: item,
})
async def game_watcher(self, ctx: "SNIContext") -> None:
async def game_watcher(self, ctx) -> None:
try:
from SNIClient import snes_buffered_write, snes_flush_writes, snes_read
rom = await snes_read(ctx, KDL3_ROMNAME, 0x15)
@@ -280,12 +278,11 @@ class KDL3SNIClient(SNIClient):
await initialize_giftboxes(ctx, self.giftbox_key, self.motherbox_key, bool(enable_gifting[0]))
self.initialize_gifting = True
# can't check debug anymore, without going and copying the value. might be important later.
if not self.levels:
if self.levels is None:
self.levels = dict()
for i in range(5):
level_data = await snes_read(ctx, KDL3_LEVEL_ADDR + (14 * i), 14)
self.levels[i] = [int.from_bytes(level_data[idx:idx+1], "little")
for idx in range(0, len(level_data), 2)]
self.levels[i] = unpack("HHHHHHH", level_data)
self.levels[5] = [0x0205, # Hyper Zone
0, # MG-5, can't send from here
0x0300, # Boss Butch
@@ -374,7 +371,7 @@ class KDL3SNIClient(SNIClient):
stages_raw = await snes_read(ctx, KDL3_COMPLETED_STAGES, 60)
stages = struct.unpack("HHHHHHHHHHHHHHHHHHHHHHHHHHHHHH", stages_raw)
for i in range(30):
loc_id = 0x770000 + i
loc_id = 0x770000 + i + 1
if stages[i] == 1 and loc_id not in ctx.checked_locations:
new_checks.append(loc_id)
elif loc_id in ctx.checked_locations:
@@ -384,8 +381,8 @@ class KDL3SNIClient(SNIClient):
heart_stars = await snes_read(ctx, KDL3_HEART_STARS, 35)
for i in range(5):
start_ind = i * 7
for j in range(6):
level_ind = start_ind + j
for j in range(1, 7):
level_ind = start_ind + j - 1
loc_id = 0x770100 + (6 * i) + j
if heart_stars[level_ind] and loc_id not in ctx.checked_locations:
new_checks.append(loc_id)
@@ -404,9 +401,6 @@ class KDL3SNIClient(SNIClient):
if star not in ctx.checked_locations and stars[star_addrs[star]] == 0x01:
new_checks.append(star)
if not game_state:
return
if game_state[0] != 0xFF:
await self.pop_gift(ctx)
await self.pop_item(ctx, game_state[0] != 0xFF)
@@ -414,7 +408,7 @@ class KDL3SNIClient(SNIClient):
# boss status
boss_flag_bytes = await snes_read(ctx, KDL3_BOSS_STATUS, 2)
boss_flag = int.from_bytes(boss_flag_bytes, "little")
boss_flag = unpack("H", boss_flag_bytes)[0]
for bitmask, boss in zip(range(1, 11, 2), boss_locations.keys()):
if boss_flag & (1 << bitmask) > 0 and boss not in ctx.checked_locations:
new_checks.append(boss)

View File

@@ -1,11 +1,8 @@
# Small subfile to handle gifting info such as desired traits and giftbox management
import typing
if typing.TYPE_CHECKING:
from SNIClient import SNIContext
async def update_object(ctx: "SNIContext", key: str, value: typing.Dict[str, typing.Any]) -> None:
async def update_object(ctx, key: str, value: typing.Dict):
await ctx.send_msgs([
{
"cmd": "Set",
@@ -19,7 +16,7 @@ async def update_object(ctx: "SNIContext", key: str, value: typing.Dict[str, typ
])
async def pop_object(ctx: "SNIContext", key: str, value: str) -> None:
async def pop_object(ctx, key: str, value: str):
await ctx.send_msgs([
{
"cmd": "Set",
@@ -33,14 +30,14 @@ async def pop_object(ctx: "SNIContext", key: str, value: str) -> None:
])
async def initialize_giftboxes(ctx: "SNIContext", giftbox_key: str, motherbox_key: str, is_open: bool) -> None:
async def initialize_giftboxes(ctx, giftbox_key: str, motherbox_key: str, is_open: bool):
ctx.set_notify(motherbox_key, giftbox_key)
await update_object(ctx, f"Giftboxes;{ctx.team}", {f"{ctx.slot}":
{
"IsOpen": is_open,
**kdl3_gifting_options
}})
ctx.client_handler.gifting = is_open
{
"IsOpen": is_open,
**kdl3_gifting_options
}})
ctx.gifting = is_open
kdl3_gifting_options = {

View File

@@ -77,9 +77,9 @@ filler_item_weights = {
}
star_item_weights = {
"Little Star": 16,
"Medium Star": 8,
"Big Star": 4
"Little Star": 4,
"Medium Star": 2,
"Big Star": 1
}
total_filler_weights = {
@@ -102,4 +102,4 @@ item_names = {
"Animal Friend": set(animal_friend_table),
}
lookup_item_to_id: typing.Dict[str, int] = {item_name: data.code for item_name, data in item_table.items() if data.code}
lookup_name_to_id: typing.Dict[str, int] = {item_name: data.code for item_name, data in item_table.items() if data.code}

940
worlds/kdl3/Locations.py Normal file
View File

@@ -0,0 +1,940 @@
import typing
from BaseClasses import Location, Region
from .Names import LocationName
if typing.TYPE_CHECKING:
from .Room import KDL3Room
class KDL3Location(Location):
game: str = "Kirby's Dream Land 3"
room: typing.Optional["KDL3Room"] = None
def __init__(self, player: int, name: str, address: typing.Optional[int], parent: typing.Union[Region, None]):
super().__init__(player, name, address, parent)
if not address:
self.show_in_spoiler = False
stage_locations = {
0x770001: LocationName.grass_land_1,
0x770002: LocationName.grass_land_2,
0x770003: LocationName.grass_land_3,
0x770004: LocationName.grass_land_4,
0x770005: LocationName.grass_land_5,
0x770006: LocationName.grass_land_6,
0x770007: LocationName.ripple_field_1,
0x770008: LocationName.ripple_field_2,
0x770009: LocationName.ripple_field_3,
0x77000A: LocationName.ripple_field_4,
0x77000B: LocationName.ripple_field_5,
0x77000C: LocationName.ripple_field_6,
0x77000D: LocationName.sand_canyon_1,
0x77000E: LocationName.sand_canyon_2,
0x77000F: LocationName.sand_canyon_3,
0x770010: LocationName.sand_canyon_4,
0x770011: LocationName.sand_canyon_5,
0x770012: LocationName.sand_canyon_6,
0x770013: LocationName.cloudy_park_1,
0x770014: LocationName.cloudy_park_2,
0x770015: LocationName.cloudy_park_3,
0x770016: LocationName.cloudy_park_4,
0x770017: LocationName.cloudy_park_5,
0x770018: LocationName.cloudy_park_6,
0x770019: LocationName.iceberg_1,
0x77001A: LocationName.iceberg_2,
0x77001B: LocationName.iceberg_3,
0x77001C: LocationName.iceberg_4,
0x77001D: LocationName.iceberg_5,
0x77001E: LocationName.iceberg_6,
}
heart_star_locations = {
0x770101: LocationName.grass_land_tulip,
0x770102: LocationName.grass_land_muchi,
0x770103: LocationName.grass_land_pitcherman,
0x770104: LocationName.grass_land_chao,
0x770105: LocationName.grass_land_mine,
0x770106: LocationName.grass_land_pierre,
0x770107: LocationName.ripple_field_kamuribana,
0x770108: LocationName.ripple_field_bakasa,
0x770109: LocationName.ripple_field_elieel,
0x77010A: LocationName.ripple_field_toad,
0x77010B: LocationName.ripple_field_mama_pitch,
0x77010C: LocationName.ripple_field_hb002,
0x77010D: LocationName.sand_canyon_mushrooms,
0x77010E: LocationName.sand_canyon_auntie,
0x77010F: LocationName.sand_canyon_caramello,
0x770110: LocationName.sand_canyon_hikari,
0x770111: LocationName.sand_canyon_nyupun,
0x770112: LocationName.sand_canyon_rob,
0x770113: LocationName.cloudy_park_hibanamodoki,
0x770114: LocationName.cloudy_park_piyokeko,
0x770115: LocationName.cloudy_park_mrball,
0x770116: LocationName.cloudy_park_mikarin,
0x770117: LocationName.cloudy_park_pick,
0x770118: LocationName.cloudy_park_hb007,
0x770119: LocationName.iceberg_kogoesou,
0x77011A: LocationName.iceberg_samus,
0x77011B: LocationName.iceberg_kawasaki,
0x77011C: LocationName.iceberg_name,
0x77011D: LocationName.iceberg_shiro,
0x77011E: LocationName.iceberg_angel,
}
boss_locations = {
0x770200: LocationName.grass_land_whispy,
0x770201: LocationName.ripple_field_acro,
0x770202: LocationName.sand_canyon_poncon,
0x770203: LocationName.cloudy_park_ado,
0x770204: LocationName.iceberg_dedede,
}
consumable_locations = {
0x770300: LocationName.grass_land_1_u1,
0x770301: LocationName.grass_land_1_m1,
0x770302: LocationName.grass_land_2_u1,
0x770303: LocationName.grass_land_3_u1,
0x770304: LocationName.grass_land_3_m1,
0x770305: LocationName.grass_land_4_m1,
0x770306: LocationName.grass_land_4_u1,
0x770307: LocationName.grass_land_4_m2,
0x770308: LocationName.grass_land_4_m3,
0x770309: LocationName.grass_land_6_u1,
0x77030A: LocationName.grass_land_6_u2,
0x77030B: LocationName.ripple_field_2_u1,
0x77030C: LocationName.ripple_field_2_m1,
0x77030D: LocationName.ripple_field_3_m1,
0x77030E: LocationName.ripple_field_3_u1,
0x77030F: LocationName.ripple_field_4_m2,
0x770310: LocationName.ripple_field_4_u1,
0x770311: LocationName.ripple_field_4_m1,
0x770312: LocationName.ripple_field_5_u1,
0x770313: LocationName.ripple_field_5_m2,
0x770314: LocationName.ripple_field_5_m1,
0x770315: LocationName.sand_canyon_1_u1,
0x770316: LocationName.sand_canyon_2_u1,
0x770317: LocationName.sand_canyon_2_m1,
0x770318: LocationName.sand_canyon_4_m1,
0x770319: LocationName.sand_canyon_4_u1,
0x77031A: LocationName.sand_canyon_4_m2,
0x77031B: LocationName.sand_canyon_5_u1,
0x77031C: LocationName.sand_canyon_5_u3,
0x77031D: LocationName.sand_canyon_5_m1,
0x77031E: LocationName.sand_canyon_5_u4,
0x77031F: LocationName.sand_canyon_5_u2,
0x770320: LocationName.cloudy_park_1_m1,
0x770321: LocationName.cloudy_park_1_u1,
0x770322: LocationName.cloudy_park_4_u1,
0x770323: LocationName.cloudy_park_4_m1,
0x770324: LocationName.cloudy_park_5_m1,
0x770325: LocationName.cloudy_park_6_u1,
0x770326: LocationName.iceberg_3_m1,
0x770327: LocationName.iceberg_5_u1,
0x770328: LocationName.iceberg_5_u2,
0x770329: LocationName.iceberg_5_u3,
0x77032A: LocationName.iceberg_6_m1,
0x77032B: LocationName.iceberg_6_u1,
}
level_consumables = {
1: [0, 1],
2: [2],
3: [3, 4],
4: [5, 6, 7, 8],
6: [9, 10],
8: [11, 12],
9: [13, 14],
10: [15, 16, 17],
11: [18, 19, 20],
13: [21],
14: [22, 23],
16: [24, 25, 26],
17: [27, 28, 29, 30, 31],
19: [32, 33],
22: [34, 35],
23: [36],
24: [37],
27: [38],
29: [39, 40, 41],
30: [42, 43],
}
star_locations = {
0x770401: LocationName.grass_land_1_s1,
0x770402: LocationName.grass_land_1_s2,
0x770403: LocationName.grass_land_1_s3,
0x770404: LocationName.grass_land_1_s4,
0x770405: LocationName.grass_land_1_s5,
0x770406: LocationName.grass_land_1_s6,
0x770407: LocationName.grass_land_1_s7,
0x770408: LocationName.grass_land_1_s8,
0x770409: LocationName.grass_land_1_s9,
0x77040a: LocationName.grass_land_1_s10,
0x77040b: LocationName.grass_land_1_s11,
0x77040c: LocationName.grass_land_1_s12,
0x77040d: LocationName.grass_land_1_s13,
0x77040e: LocationName.grass_land_1_s14,
0x77040f: LocationName.grass_land_1_s15,
0x770410: LocationName.grass_land_1_s16,
0x770411: LocationName.grass_land_1_s17,
0x770412: LocationName.grass_land_1_s18,
0x770413: LocationName.grass_land_1_s19,
0x770414: LocationName.grass_land_1_s20,
0x770415: LocationName.grass_land_1_s21,
0x770416: LocationName.grass_land_1_s22,
0x770417: LocationName.grass_land_1_s23,
0x770418: LocationName.grass_land_2_s1,
0x770419: LocationName.grass_land_2_s2,
0x77041a: LocationName.grass_land_2_s3,
0x77041b: LocationName.grass_land_2_s4,
0x77041c: LocationName.grass_land_2_s5,
0x77041d: LocationName.grass_land_2_s6,
0x77041e: LocationName.grass_land_2_s7,
0x77041f: LocationName.grass_land_2_s8,
0x770420: LocationName.grass_land_2_s9,
0x770421: LocationName.grass_land_2_s10,
0x770422: LocationName.grass_land_2_s11,
0x770423: LocationName.grass_land_2_s12,
0x770424: LocationName.grass_land_2_s13,
0x770425: LocationName.grass_land_2_s14,
0x770426: LocationName.grass_land_2_s15,
0x770427: LocationName.grass_land_2_s16,
0x770428: LocationName.grass_land_2_s17,
0x770429: LocationName.grass_land_2_s18,
0x77042a: LocationName.grass_land_2_s19,
0x77042b: LocationName.grass_land_2_s20,
0x77042c: LocationName.grass_land_2_s21,
0x77042d: LocationName.grass_land_3_s1,
0x77042e: LocationName.grass_land_3_s2,
0x77042f: LocationName.grass_land_3_s3,
0x770430: LocationName.grass_land_3_s4,
0x770431: LocationName.grass_land_3_s5,
0x770432: LocationName.grass_land_3_s6,
0x770433: LocationName.grass_land_3_s7,
0x770434: LocationName.grass_land_3_s8,
0x770435: LocationName.grass_land_3_s9,
0x770436: LocationName.grass_land_3_s10,
0x770437: LocationName.grass_land_3_s11,
0x770438: LocationName.grass_land_3_s12,
0x770439: LocationName.grass_land_3_s13,
0x77043a: LocationName.grass_land_3_s14,
0x77043b: LocationName.grass_land_3_s15,
0x77043c: LocationName.grass_land_3_s16,
0x77043d: LocationName.grass_land_3_s17,
0x77043e: LocationName.grass_land_3_s18,
0x77043f: LocationName.grass_land_3_s19,
0x770440: LocationName.grass_land_3_s20,
0x770441: LocationName.grass_land_3_s21,
0x770442: LocationName.grass_land_3_s22,
0x770443: LocationName.grass_land_3_s23,
0x770444: LocationName.grass_land_3_s24,
0x770445: LocationName.grass_land_3_s25,
0x770446: LocationName.grass_land_3_s26,
0x770447: LocationName.grass_land_3_s27,
0x770448: LocationName.grass_land_3_s28,
0x770449: LocationName.grass_land_3_s29,
0x77044a: LocationName.grass_land_3_s30,
0x77044b: LocationName.grass_land_3_s31,
0x77044c: LocationName.grass_land_4_s1,
0x77044d: LocationName.grass_land_4_s2,
0x77044e: LocationName.grass_land_4_s3,
0x77044f: LocationName.grass_land_4_s4,
0x770450: LocationName.grass_land_4_s5,
0x770451: LocationName.grass_land_4_s6,
0x770452: LocationName.grass_land_4_s7,
0x770453: LocationName.grass_land_4_s8,
0x770454: LocationName.grass_land_4_s9,
0x770455: LocationName.grass_land_4_s10,
0x770456: LocationName.grass_land_4_s11,
0x770457: LocationName.grass_land_4_s12,
0x770458: LocationName.grass_land_4_s13,
0x770459: LocationName.grass_land_4_s14,
0x77045a: LocationName.grass_land_4_s15,
0x77045b: LocationName.grass_land_4_s16,
0x77045c: LocationName.grass_land_4_s17,
0x77045d: LocationName.grass_land_4_s18,
0x77045e: LocationName.grass_land_4_s19,
0x77045f: LocationName.grass_land_4_s20,
0x770460: LocationName.grass_land_4_s21,
0x770461: LocationName.grass_land_4_s22,
0x770462: LocationName.grass_land_4_s23,
0x770463: LocationName.grass_land_4_s24,
0x770464: LocationName.grass_land_4_s25,
0x770465: LocationName.grass_land_4_s26,
0x770466: LocationName.grass_land_4_s27,
0x770467: LocationName.grass_land_4_s28,
0x770468: LocationName.grass_land_4_s29,
0x770469: LocationName.grass_land_4_s30,
0x77046a: LocationName.grass_land_4_s31,
0x77046b: LocationName.grass_land_4_s32,
0x77046c: LocationName.grass_land_4_s33,
0x77046d: LocationName.grass_land_4_s34,
0x77046e: LocationName.grass_land_4_s35,
0x77046f: LocationName.grass_land_4_s36,
0x770470: LocationName.grass_land_4_s37,
0x770471: LocationName.grass_land_5_s1,
0x770472: LocationName.grass_land_5_s2,
0x770473: LocationName.grass_land_5_s3,
0x770474: LocationName.grass_land_5_s4,
0x770475: LocationName.grass_land_5_s5,
0x770476: LocationName.grass_land_5_s6,
0x770477: LocationName.grass_land_5_s7,
0x770478: LocationName.grass_land_5_s8,
0x770479: LocationName.grass_land_5_s9,
0x77047a: LocationName.grass_land_5_s10,
0x77047b: LocationName.grass_land_5_s11,
0x77047c: LocationName.grass_land_5_s12,
0x77047d: LocationName.grass_land_5_s13,
0x77047e: LocationName.grass_land_5_s14,
0x77047f: LocationName.grass_land_5_s15,
0x770480: LocationName.grass_land_5_s16,
0x770481: LocationName.grass_land_5_s17,
0x770482: LocationName.grass_land_5_s18,
0x770483: LocationName.grass_land_5_s19,
0x770484: LocationName.grass_land_5_s20,
0x770485: LocationName.grass_land_5_s21,
0x770486: LocationName.grass_land_5_s22,
0x770487: LocationName.grass_land_5_s23,
0x770488: LocationName.grass_land_5_s24,
0x770489: LocationName.grass_land_5_s25,
0x77048a: LocationName.grass_land_5_s26,
0x77048b: LocationName.grass_land_5_s27,
0x77048c: LocationName.grass_land_5_s28,
0x77048d: LocationName.grass_land_5_s29,
0x77048e: LocationName.grass_land_6_s1,
0x77048f: LocationName.grass_land_6_s2,
0x770490: LocationName.grass_land_6_s3,
0x770491: LocationName.grass_land_6_s4,
0x770492: LocationName.grass_land_6_s5,
0x770493: LocationName.grass_land_6_s6,
0x770494: LocationName.grass_land_6_s7,
0x770495: LocationName.grass_land_6_s8,
0x770496: LocationName.grass_land_6_s9,
0x770497: LocationName.grass_land_6_s10,
0x770498: LocationName.grass_land_6_s11,
0x770499: LocationName.grass_land_6_s12,
0x77049a: LocationName.grass_land_6_s13,
0x77049b: LocationName.grass_land_6_s14,
0x77049c: LocationName.grass_land_6_s15,
0x77049d: LocationName.grass_land_6_s16,
0x77049e: LocationName.grass_land_6_s17,
0x77049f: LocationName.grass_land_6_s18,
0x7704a0: LocationName.grass_land_6_s19,
0x7704a1: LocationName.grass_land_6_s20,
0x7704a2: LocationName.grass_land_6_s21,
0x7704a3: LocationName.grass_land_6_s22,
0x7704a4: LocationName.grass_land_6_s23,
0x7704a5: LocationName.grass_land_6_s24,
0x7704a6: LocationName.grass_land_6_s25,
0x7704a7: LocationName.grass_land_6_s26,
0x7704a8: LocationName.grass_land_6_s27,
0x7704a9: LocationName.grass_land_6_s28,
0x7704aa: LocationName.grass_land_6_s29,
0x7704ab: LocationName.ripple_field_1_s1,
0x7704ac: LocationName.ripple_field_1_s2,
0x7704ad: LocationName.ripple_field_1_s3,
0x7704ae: LocationName.ripple_field_1_s4,
0x7704af: LocationName.ripple_field_1_s5,
0x7704b0: LocationName.ripple_field_1_s6,
0x7704b1: LocationName.ripple_field_1_s7,
0x7704b2: LocationName.ripple_field_1_s8,
0x7704b3: LocationName.ripple_field_1_s9,
0x7704b4: LocationName.ripple_field_1_s10,
0x7704b5: LocationName.ripple_field_1_s11,
0x7704b6: LocationName.ripple_field_1_s12,
0x7704b7: LocationName.ripple_field_1_s13,
0x7704b8: LocationName.ripple_field_1_s14,
0x7704b9: LocationName.ripple_field_1_s15,
0x7704ba: LocationName.ripple_field_1_s16,
0x7704bb: LocationName.ripple_field_1_s17,
0x7704bc: LocationName.ripple_field_1_s18,
0x7704bd: LocationName.ripple_field_1_s19,
0x7704be: LocationName.ripple_field_2_s1,
0x7704bf: LocationName.ripple_field_2_s2,
0x7704c0: LocationName.ripple_field_2_s3,
0x7704c1: LocationName.ripple_field_2_s4,
0x7704c2: LocationName.ripple_field_2_s5,
0x7704c3: LocationName.ripple_field_2_s6,
0x7704c4: LocationName.ripple_field_2_s7,
0x7704c5: LocationName.ripple_field_2_s8,
0x7704c6: LocationName.ripple_field_2_s9,
0x7704c7: LocationName.ripple_field_2_s10,
0x7704c8: LocationName.ripple_field_2_s11,
0x7704c9: LocationName.ripple_field_2_s12,
0x7704ca: LocationName.ripple_field_2_s13,
0x7704cb: LocationName.ripple_field_2_s14,
0x7704cc: LocationName.ripple_field_2_s15,
0x7704cd: LocationName.ripple_field_2_s16,
0x7704ce: LocationName.ripple_field_2_s17,
0x7704cf: LocationName.ripple_field_3_s1,
0x7704d0: LocationName.ripple_field_3_s2,
0x7704d1: LocationName.ripple_field_3_s3,
0x7704d2: LocationName.ripple_field_3_s4,
0x7704d3: LocationName.ripple_field_3_s5,
0x7704d4: LocationName.ripple_field_3_s6,
0x7704d5: LocationName.ripple_field_3_s7,
0x7704d6: LocationName.ripple_field_3_s8,
0x7704d7: LocationName.ripple_field_3_s9,
0x7704d8: LocationName.ripple_field_3_s10,
0x7704d9: LocationName.ripple_field_3_s11,
0x7704da: LocationName.ripple_field_3_s12,
0x7704db: LocationName.ripple_field_3_s13,
0x7704dc: LocationName.ripple_field_3_s14,
0x7704dd: LocationName.ripple_field_3_s15,
0x7704de: LocationName.ripple_field_3_s16,
0x7704df: LocationName.ripple_field_3_s17,
0x7704e0: LocationName.ripple_field_3_s18,
0x7704e1: LocationName.ripple_field_3_s19,
0x7704e2: LocationName.ripple_field_3_s20,
0x7704e3: LocationName.ripple_field_3_s21,
0x7704e4: LocationName.ripple_field_4_s1,
0x7704e5: LocationName.ripple_field_4_s2,
0x7704e6: LocationName.ripple_field_4_s3,
0x7704e7: LocationName.ripple_field_4_s4,
0x7704e8: LocationName.ripple_field_4_s5,
0x7704e9: LocationName.ripple_field_4_s6,
0x7704ea: LocationName.ripple_field_4_s7,
0x7704eb: LocationName.ripple_field_4_s8,
0x7704ec: LocationName.ripple_field_4_s9,
0x7704ed: LocationName.ripple_field_4_s10,
0x7704ee: LocationName.ripple_field_4_s11,
0x7704ef: LocationName.ripple_field_4_s12,
0x7704f0: LocationName.ripple_field_4_s13,
0x7704f1: LocationName.ripple_field_4_s14,
0x7704f2: LocationName.ripple_field_4_s15,
0x7704f3: LocationName.ripple_field_4_s16,
0x7704f4: LocationName.ripple_field_4_s17,
0x7704f5: LocationName.ripple_field_4_s18,
0x7704f6: LocationName.ripple_field_4_s19,
0x7704f7: LocationName.ripple_field_4_s20,
0x7704f8: LocationName.ripple_field_4_s21,
0x7704f9: LocationName.ripple_field_4_s22,
0x7704fa: LocationName.ripple_field_4_s23,
0x7704fb: LocationName.ripple_field_4_s24,
0x7704fc: LocationName.ripple_field_4_s25,
0x7704fd: LocationName.ripple_field_4_s26,
0x7704fe: LocationName.ripple_field_4_s27,
0x7704ff: LocationName.ripple_field_4_s28,
0x770500: LocationName.ripple_field_4_s29,
0x770501: LocationName.ripple_field_4_s30,
0x770502: LocationName.ripple_field_4_s31,
0x770503: LocationName.ripple_field_4_s32,
0x770504: LocationName.ripple_field_4_s33,
0x770505: LocationName.ripple_field_4_s34,
0x770506: LocationName.ripple_field_4_s35,
0x770507: LocationName.ripple_field_4_s36,
0x770508: LocationName.ripple_field_4_s37,
0x770509: LocationName.ripple_field_4_s38,
0x77050a: LocationName.ripple_field_4_s39,
0x77050b: LocationName.ripple_field_4_s40,
0x77050c: LocationName.ripple_field_4_s41,
0x77050d: LocationName.ripple_field_4_s42,
0x77050e: LocationName.ripple_field_4_s43,
0x77050f: LocationName.ripple_field_4_s44,
0x770510: LocationName.ripple_field_4_s45,
0x770511: LocationName.ripple_field_4_s46,
0x770512: LocationName.ripple_field_4_s47,
0x770513: LocationName.ripple_field_4_s48,
0x770514: LocationName.ripple_field_4_s49,
0x770515: LocationName.ripple_field_4_s50,
0x770516: LocationName.ripple_field_4_s51,
0x770517: LocationName.ripple_field_5_s1,
0x770518: LocationName.ripple_field_5_s2,
0x770519: LocationName.ripple_field_5_s3,
0x77051a: LocationName.ripple_field_5_s4,
0x77051b: LocationName.ripple_field_5_s5,
0x77051c: LocationName.ripple_field_5_s6,
0x77051d: LocationName.ripple_field_5_s7,
0x77051e: LocationName.ripple_field_5_s8,
0x77051f: LocationName.ripple_field_5_s9,
0x770520: LocationName.ripple_field_5_s10,
0x770521: LocationName.ripple_field_5_s11,
0x770522: LocationName.ripple_field_5_s12,
0x770523: LocationName.ripple_field_5_s13,
0x770524: LocationName.ripple_field_5_s14,
0x770525: LocationName.ripple_field_5_s15,
0x770526: LocationName.ripple_field_5_s16,
0x770527: LocationName.ripple_field_5_s17,
0x770528: LocationName.ripple_field_5_s18,
0x770529: LocationName.ripple_field_5_s19,
0x77052a: LocationName.ripple_field_5_s20,
0x77052b: LocationName.ripple_field_5_s21,
0x77052c: LocationName.ripple_field_5_s22,
0x77052d: LocationName.ripple_field_5_s23,
0x77052e: LocationName.ripple_field_5_s24,
0x77052f: LocationName.ripple_field_5_s25,
0x770530: LocationName.ripple_field_5_s26,
0x770531: LocationName.ripple_field_5_s27,
0x770532: LocationName.ripple_field_5_s28,
0x770533: LocationName.ripple_field_5_s29,
0x770534: LocationName.ripple_field_5_s30,
0x770535: LocationName.ripple_field_5_s31,
0x770536: LocationName.ripple_field_5_s32,
0x770537: LocationName.ripple_field_5_s33,
0x770538: LocationName.ripple_field_5_s34,
0x770539: LocationName.ripple_field_5_s35,
0x77053a: LocationName.ripple_field_5_s36,
0x77053b: LocationName.ripple_field_5_s37,
0x77053c: LocationName.ripple_field_5_s38,
0x77053d: LocationName.ripple_field_5_s39,
0x77053e: LocationName.ripple_field_5_s40,
0x77053f: LocationName.ripple_field_5_s41,
0x770540: LocationName.ripple_field_5_s42,
0x770541: LocationName.ripple_field_5_s43,
0x770542: LocationName.ripple_field_5_s44,
0x770543: LocationName.ripple_field_5_s45,
0x770544: LocationName.ripple_field_5_s46,
0x770545: LocationName.ripple_field_5_s47,
0x770546: LocationName.ripple_field_5_s48,
0x770547: LocationName.ripple_field_5_s49,
0x770548: LocationName.ripple_field_5_s50,
0x770549: LocationName.ripple_field_5_s51,
0x77054a: LocationName.ripple_field_6_s1,
0x77054b: LocationName.ripple_field_6_s2,
0x77054c: LocationName.ripple_field_6_s3,
0x77054d: LocationName.ripple_field_6_s4,
0x77054e: LocationName.ripple_field_6_s5,
0x77054f: LocationName.ripple_field_6_s6,
0x770550: LocationName.ripple_field_6_s7,
0x770551: LocationName.ripple_field_6_s8,
0x770552: LocationName.ripple_field_6_s9,
0x770553: LocationName.ripple_field_6_s10,
0x770554: LocationName.ripple_field_6_s11,
0x770555: LocationName.ripple_field_6_s12,
0x770556: LocationName.ripple_field_6_s13,
0x770557: LocationName.ripple_field_6_s14,
0x770558: LocationName.ripple_field_6_s15,
0x770559: LocationName.ripple_field_6_s16,
0x77055a: LocationName.ripple_field_6_s17,
0x77055b: LocationName.ripple_field_6_s18,
0x77055c: LocationName.ripple_field_6_s19,
0x77055d: LocationName.ripple_field_6_s20,
0x77055e: LocationName.ripple_field_6_s21,
0x77055f: LocationName.ripple_field_6_s22,
0x770560: LocationName.ripple_field_6_s23,
0x770561: LocationName.sand_canyon_1_s1,
0x770562: LocationName.sand_canyon_1_s2,
0x770563: LocationName.sand_canyon_1_s3,
0x770564: LocationName.sand_canyon_1_s4,
0x770565: LocationName.sand_canyon_1_s5,
0x770566: LocationName.sand_canyon_1_s6,
0x770567: LocationName.sand_canyon_1_s7,
0x770568: LocationName.sand_canyon_1_s8,
0x770569: LocationName.sand_canyon_1_s9,
0x77056a: LocationName.sand_canyon_1_s10,
0x77056b: LocationName.sand_canyon_1_s11,
0x77056c: LocationName.sand_canyon_1_s12,
0x77056d: LocationName.sand_canyon_1_s13,
0x77056e: LocationName.sand_canyon_1_s14,
0x77056f: LocationName.sand_canyon_1_s15,
0x770570: LocationName.sand_canyon_1_s16,
0x770571: LocationName.sand_canyon_1_s17,
0x770572: LocationName.sand_canyon_1_s18,
0x770573: LocationName.sand_canyon_1_s19,
0x770574: LocationName.sand_canyon_1_s20,
0x770575: LocationName.sand_canyon_1_s21,
0x770576: LocationName.sand_canyon_1_s22,
0x770577: LocationName.sand_canyon_2_s1,
0x770578: LocationName.sand_canyon_2_s2,
0x770579: LocationName.sand_canyon_2_s3,
0x77057a: LocationName.sand_canyon_2_s4,
0x77057b: LocationName.sand_canyon_2_s5,
0x77057c: LocationName.sand_canyon_2_s6,
0x77057d: LocationName.sand_canyon_2_s7,
0x77057e: LocationName.sand_canyon_2_s8,
0x77057f: LocationName.sand_canyon_2_s9,
0x770580: LocationName.sand_canyon_2_s10,
0x770581: LocationName.sand_canyon_2_s11,
0x770582: LocationName.sand_canyon_2_s12,
0x770583: LocationName.sand_canyon_2_s13,
0x770584: LocationName.sand_canyon_2_s14,
0x770585: LocationName.sand_canyon_2_s15,
0x770586: LocationName.sand_canyon_2_s16,
0x770587: LocationName.sand_canyon_2_s17,
0x770588: LocationName.sand_canyon_2_s18,
0x770589: LocationName.sand_canyon_2_s19,
0x77058a: LocationName.sand_canyon_2_s20,
0x77058b: LocationName.sand_canyon_2_s21,
0x77058c: LocationName.sand_canyon_2_s22,
0x77058d: LocationName.sand_canyon_2_s23,
0x77058e: LocationName.sand_canyon_2_s24,
0x77058f: LocationName.sand_canyon_2_s25,
0x770590: LocationName.sand_canyon_2_s26,
0x770591: LocationName.sand_canyon_2_s27,
0x770592: LocationName.sand_canyon_2_s28,
0x770593: LocationName.sand_canyon_2_s29,
0x770594: LocationName.sand_canyon_2_s30,
0x770595: LocationName.sand_canyon_2_s31,
0x770596: LocationName.sand_canyon_2_s32,
0x770597: LocationName.sand_canyon_2_s33,
0x770598: LocationName.sand_canyon_2_s34,
0x770599: LocationName.sand_canyon_2_s35,
0x77059a: LocationName.sand_canyon_2_s36,
0x77059b: LocationName.sand_canyon_2_s37,
0x77059c: LocationName.sand_canyon_2_s38,
0x77059d: LocationName.sand_canyon_2_s39,
0x77059e: LocationName.sand_canyon_2_s40,
0x77059f: LocationName.sand_canyon_2_s41,
0x7705a0: LocationName.sand_canyon_2_s42,
0x7705a1: LocationName.sand_canyon_2_s43,
0x7705a2: LocationName.sand_canyon_2_s44,
0x7705a3: LocationName.sand_canyon_2_s45,
0x7705a4: LocationName.sand_canyon_2_s46,
0x7705a5: LocationName.sand_canyon_2_s47,
0x7705a6: LocationName.sand_canyon_2_s48,
0x7705a7: LocationName.sand_canyon_3_s1,
0x7705a8: LocationName.sand_canyon_3_s2,
0x7705a9: LocationName.sand_canyon_3_s3,
0x7705aa: LocationName.sand_canyon_3_s4,
0x7705ab: LocationName.sand_canyon_3_s5,
0x7705ac: LocationName.sand_canyon_3_s6,
0x7705ad: LocationName.sand_canyon_3_s7,
0x7705ae: LocationName.sand_canyon_3_s8,
0x7705af: LocationName.sand_canyon_3_s9,
0x7705b0: LocationName.sand_canyon_3_s10,
0x7705b1: LocationName.sand_canyon_4_s1,
0x7705b2: LocationName.sand_canyon_4_s2,
0x7705b3: LocationName.sand_canyon_4_s3,
0x7705b4: LocationName.sand_canyon_4_s4,
0x7705b5: LocationName.sand_canyon_4_s5,
0x7705b6: LocationName.sand_canyon_4_s6,
0x7705b7: LocationName.sand_canyon_4_s7,
0x7705b8: LocationName.sand_canyon_4_s8,
0x7705b9: LocationName.sand_canyon_4_s9,
0x7705ba: LocationName.sand_canyon_4_s10,
0x7705bb: LocationName.sand_canyon_4_s11,
0x7705bc: LocationName.sand_canyon_4_s12,
0x7705bd: LocationName.sand_canyon_4_s13,
0x7705be: LocationName.sand_canyon_4_s14,
0x7705bf: LocationName.sand_canyon_4_s15,
0x7705c0: LocationName.sand_canyon_4_s16,
0x7705c1: LocationName.sand_canyon_4_s17,
0x7705c2: LocationName.sand_canyon_4_s18,
0x7705c3: LocationName.sand_canyon_4_s19,
0x7705c4: LocationName.sand_canyon_4_s20,
0x7705c5: LocationName.sand_canyon_4_s21,
0x7705c6: LocationName.sand_canyon_4_s22,
0x7705c7: LocationName.sand_canyon_4_s23,
0x7705c8: LocationName.sand_canyon_5_s1,
0x7705c9: LocationName.sand_canyon_5_s2,
0x7705ca: LocationName.sand_canyon_5_s3,
0x7705cb: LocationName.sand_canyon_5_s4,
0x7705cc: LocationName.sand_canyon_5_s5,
0x7705cd: LocationName.sand_canyon_5_s6,
0x7705ce: LocationName.sand_canyon_5_s7,
0x7705cf: LocationName.sand_canyon_5_s8,
0x7705d0: LocationName.sand_canyon_5_s9,
0x7705d1: LocationName.sand_canyon_5_s10,
0x7705d2: LocationName.sand_canyon_5_s11,
0x7705d3: LocationName.sand_canyon_5_s12,
0x7705d4: LocationName.sand_canyon_5_s13,
0x7705d5: LocationName.sand_canyon_5_s14,
0x7705d6: LocationName.sand_canyon_5_s15,
0x7705d7: LocationName.sand_canyon_5_s16,
0x7705d8: LocationName.sand_canyon_5_s17,
0x7705d9: LocationName.sand_canyon_5_s18,
0x7705da: LocationName.sand_canyon_5_s19,
0x7705db: LocationName.sand_canyon_5_s20,
0x7705dc: LocationName.sand_canyon_5_s21,
0x7705dd: LocationName.sand_canyon_5_s22,
0x7705de: LocationName.sand_canyon_5_s23,
0x7705df: LocationName.sand_canyon_5_s24,
0x7705e0: LocationName.sand_canyon_5_s25,
0x7705e1: LocationName.sand_canyon_5_s26,
0x7705e2: LocationName.sand_canyon_5_s27,
0x7705e3: LocationName.sand_canyon_5_s28,
0x7705e4: LocationName.sand_canyon_5_s29,
0x7705e5: LocationName.sand_canyon_5_s30,
0x7705e6: LocationName.sand_canyon_5_s31,
0x7705e7: LocationName.sand_canyon_5_s32,
0x7705e8: LocationName.sand_canyon_5_s33,
0x7705e9: LocationName.sand_canyon_5_s34,
0x7705ea: LocationName.sand_canyon_5_s35,
0x7705eb: LocationName.sand_canyon_5_s36,
0x7705ec: LocationName.sand_canyon_5_s37,
0x7705ed: LocationName.sand_canyon_5_s38,
0x7705ee: LocationName.sand_canyon_5_s39,
0x7705ef: LocationName.sand_canyon_5_s40,
0x7705f0: LocationName.cloudy_park_1_s1,
0x7705f1: LocationName.cloudy_park_1_s2,
0x7705f2: LocationName.cloudy_park_1_s3,
0x7705f3: LocationName.cloudy_park_1_s4,
0x7705f4: LocationName.cloudy_park_1_s5,
0x7705f5: LocationName.cloudy_park_1_s6,
0x7705f6: LocationName.cloudy_park_1_s7,
0x7705f7: LocationName.cloudy_park_1_s8,
0x7705f8: LocationName.cloudy_park_1_s9,
0x7705f9: LocationName.cloudy_park_1_s10,
0x7705fa: LocationName.cloudy_park_1_s11,
0x7705fb: LocationName.cloudy_park_1_s12,
0x7705fc: LocationName.cloudy_park_1_s13,
0x7705fd: LocationName.cloudy_park_1_s14,
0x7705fe: LocationName.cloudy_park_1_s15,
0x7705ff: LocationName.cloudy_park_1_s16,
0x770600: LocationName.cloudy_park_1_s17,
0x770601: LocationName.cloudy_park_1_s18,
0x770602: LocationName.cloudy_park_1_s19,
0x770603: LocationName.cloudy_park_1_s20,
0x770604: LocationName.cloudy_park_1_s21,
0x770605: LocationName.cloudy_park_1_s22,
0x770606: LocationName.cloudy_park_1_s23,
0x770607: LocationName.cloudy_park_2_s1,
0x770608: LocationName.cloudy_park_2_s2,
0x770609: LocationName.cloudy_park_2_s3,
0x77060a: LocationName.cloudy_park_2_s4,
0x77060b: LocationName.cloudy_park_2_s5,
0x77060c: LocationName.cloudy_park_2_s6,
0x77060d: LocationName.cloudy_park_2_s7,
0x77060e: LocationName.cloudy_park_2_s8,
0x77060f: LocationName.cloudy_park_2_s9,
0x770610: LocationName.cloudy_park_2_s10,
0x770611: LocationName.cloudy_park_2_s11,
0x770612: LocationName.cloudy_park_2_s12,
0x770613: LocationName.cloudy_park_2_s13,
0x770614: LocationName.cloudy_park_2_s14,
0x770615: LocationName.cloudy_park_2_s15,
0x770616: LocationName.cloudy_park_2_s16,
0x770617: LocationName.cloudy_park_2_s17,
0x770618: LocationName.cloudy_park_2_s18,
0x770619: LocationName.cloudy_park_2_s19,
0x77061a: LocationName.cloudy_park_2_s20,
0x77061b: LocationName.cloudy_park_2_s21,
0x77061c: LocationName.cloudy_park_2_s22,
0x77061d: LocationName.cloudy_park_2_s23,
0x77061e: LocationName.cloudy_park_2_s24,
0x77061f: LocationName.cloudy_park_2_s25,
0x770620: LocationName.cloudy_park_2_s26,
0x770621: LocationName.cloudy_park_2_s27,
0x770622: LocationName.cloudy_park_2_s28,
0x770623: LocationName.cloudy_park_2_s29,
0x770624: LocationName.cloudy_park_2_s30,
0x770625: LocationName.cloudy_park_2_s31,
0x770626: LocationName.cloudy_park_2_s32,
0x770627: LocationName.cloudy_park_2_s33,
0x770628: LocationName.cloudy_park_2_s34,
0x770629: LocationName.cloudy_park_2_s35,
0x77062a: LocationName.cloudy_park_2_s36,
0x77062b: LocationName.cloudy_park_2_s37,
0x77062c: LocationName.cloudy_park_2_s38,
0x77062d: LocationName.cloudy_park_2_s39,
0x77062e: LocationName.cloudy_park_2_s40,
0x77062f: LocationName.cloudy_park_2_s41,
0x770630: LocationName.cloudy_park_2_s42,
0x770631: LocationName.cloudy_park_2_s43,
0x770632: LocationName.cloudy_park_2_s44,
0x770633: LocationName.cloudy_park_2_s45,
0x770634: LocationName.cloudy_park_2_s46,
0x770635: LocationName.cloudy_park_2_s47,
0x770636: LocationName.cloudy_park_2_s48,
0x770637: LocationName.cloudy_park_2_s49,
0x770638: LocationName.cloudy_park_2_s50,
0x770639: LocationName.cloudy_park_2_s51,
0x77063a: LocationName.cloudy_park_2_s52,
0x77063b: LocationName.cloudy_park_2_s53,
0x77063c: LocationName.cloudy_park_2_s54,
0x77063d: LocationName.cloudy_park_3_s1,
0x77063e: LocationName.cloudy_park_3_s2,
0x77063f: LocationName.cloudy_park_3_s3,
0x770640: LocationName.cloudy_park_3_s4,
0x770641: LocationName.cloudy_park_3_s5,
0x770642: LocationName.cloudy_park_3_s6,
0x770643: LocationName.cloudy_park_3_s7,
0x770644: LocationName.cloudy_park_3_s8,
0x770645: LocationName.cloudy_park_3_s9,
0x770646: LocationName.cloudy_park_3_s10,
0x770647: LocationName.cloudy_park_3_s11,
0x770648: LocationName.cloudy_park_3_s12,
0x770649: LocationName.cloudy_park_3_s13,
0x77064a: LocationName.cloudy_park_3_s14,
0x77064b: LocationName.cloudy_park_3_s15,
0x77064c: LocationName.cloudy_park_3_s16,
0x77064d: LocationName.cloudy_park_3_s17,
0x77064e: LocationName.cloudy_park_3_s18,
0x77064f: LocationName.cloudy_park_3_s19,
0x770650: LocationName.cloudy_park_3_s20,
0x770651: LocationName.cloudy_park_3_s21,
0x770652: LocationName.cloudy_park_3_s22,
0x770653: LocationName.cloudy_park_4_s1,
0x770654: LocationName.cloudy_park_4_s2,
0x770655: LocationName.cloudy_park_4_s3,
0x770656: LocationName.cloudy_park_4_s4,
0x770657: LocationName.cloudy_park_4_s5,
0x770658: LocationName.cloudy_park_4_s6,
0x770659: LocationName.cloudy_park_4_s7,
0x77065a: LocationName.cloudy_park_4_s8,
0x77065b: LocationName.cloudy_park_4_s9,
0x77065c: LocationName.cloudy_park_4_s10,
0x77065d: LocationName.cloudy_park_4_s11,
0x77065e: LocationName.cloudy_park_4_s12,
0x77065f: LocationName.cloudy_park_4_s13,
0x770660: LocationName.cloudy_park_4_s14,
0x770661: LocationName.cloudy_park_4_s15,
0x770662: LocationName.cloudy_park_4_s16,
0x770663: LocationName.cloudy_park_4_s17,
0x770664: LocationName.cloudy_park_4_s18,
0x770665: LocationName.cloudy_park_4_s19,
0x770666: LocationName.cloudy_park_4_s20,
0x770667: LocationName.cloudy_park_4_s21,
0x770668: LocationName.cloudy_park_4_s22,
0x770669: LocationName.cloudy_park_4_s23,
0x77066a: LocationName.cloudy_park_4_s24,
0x77066b: LocationName.cloudy_park_4_s25,
0x77066c: LocationName.cloudy_park_4_s26,
0x77066d: LocationName.cloudy_park_4_s27,
0x77066e: LocationName.cloudy_park_4_s28,
0x77066f: LocationName.cloudy_park_4_s29,
0x770670: LocationName.cloudy_park_4_s30,
0x770671: LocationName.cloudy_park_4_s31,
0x770672: LocationName.cloudy_park_4_s32,
0x770673: LocationName.cloudy_park_4_s33,
0x770674: LocationName.cloudy_park_4_s34,
0x770675: LocationName.cloudy_park_4_s35,
0x770676: LocationName.cloudy_park_4_s36,
0x770677: LocationName.cloudy_park_4_s37,
0x770678: LocationName.cloudy_park_4_s38,
0x770679: LocationName.cloudy_park_4_s39,
0x77067a: LocationName.cloudy_park_4_s40,
0x77067b: LocationName.cloudy_park_4_s41,
0x77067c: LocationName.cloudy_park_4_s42,
0x77067d: LocationName.cloudy_park_4_s43,
0x77067e: LocationName.cloudy_park_4_s44,
0x77067f: LocationName.cloudy_park_4_s45,
0x770680: LocationName.cloudy_park_4_s46,
0x770681: LocationName.cloudy_park_4_s47,
0x770682: LocationName.cloudy_park_4_s48,
0x770683: LocationName.cloudy_park_4_s49,
0x770684: LocationName.cloudy_park_4_s50,
0x770685: LocationName.cloudy_park_5_s1,
0x770686: LocationName.cloudy_park_5_s2,
0x770687: LocationName.cloudy_park_5_s3,
0x770688: LocationName.cloudy_park_5_s4,
0x770689: LocationName.cloudy_park_5_s5,
0x77068a: LocationName.cloudy_park_5_s6,
0x77068b: LocationName.cloudy_park_6_s1,
0x77068c: LocationName.cloudy_park_6_s2,
0x77068d: LocationName.cloudy_park_6_s3,
0x77068e: LocationName.cloudy_park_6_s4,
0x77068f: LocationName.cloudy_park_6_s5,
0x770690: LocationName.cloudy_park_6_s6,
0x770691: LocationName.cloudy_park_6_s7,
0x770692: LocationName.cloudy_park_6_s8,
0x770693: LocationName.cloudy_park_6_s9,
0x770694: LocationName.cloudy_park_6_s10,
0x770695: LocationName.cloudy_park_6_s11,
0x770696: LocationName.cloudy_park_6_s12,
0x770697: LocationName.cloudy_park_6_s13,
0x770698: LocationName.cloudy_park_6_s14,
0x770699: LocationName.cloudy_park_6_s15,
0x77069a: LocationName.cloudy_park_6_s16,
0x77069b: LocationName.cloudy_park_6_s17,
0x77069c: LocationName.cloudy_park_6_s18,
0x77069d: LocationName.cloudy_park_6_s19,
0x77069e: LocationName.cloudy_park_6_s20,
0x77069f: LocationName.cloudy_park_6_s21,
0x7706a0: LocationName.cloudy_park_6_s22,
0x7706a1: LocationName.cloudy_park_6_s23,
0x7706a2: LocationName.cloudy_park_6_s24,
0x7706a3: LocationName.cloudy_park_6_s25,
0x7706a4: LocationName.cloudy_park_6_s26,
0x7706a5: LocationName.cloudy_park_6_s27,
0x7706a6: LocationName.cloudy_park_6_s28,
0x7706a7: LocationName.cloudy_park_6_s29,
0x7706a8: LocationName.cloudy_park_6_s30,
0x7706a9: LocationName.cloudy_park_6_s31,
0x7706aa: LocationName.cloudy_park_6_s32,
0x7706ab: LocationName.cloudy_park_6_s33,
0x7706ac: LocationName.iceberg_1_s1,
0x7706ad: LocationName.iceberg_1_s2,
0x7706ae: LocationName.iceberg_1_s3,
0x7706af: LocationName.iceberg_1_s4,
0x7706b0: LocationName.iceberg_1_s5,
0x7706b1: LocationName.iceberg_1_s6,
0x7706b2: LocationName.iceberg_2_s1,
0x7706b3: LocationName.iceberg_2_s2,
0x7706b4: LocationName.iceberg_2_s3,
0x7706b5: LocationName.iceberg_2_s4,
0x7706b6: LocationName.iceberg_2_s5,
0x7706b7: LocationName.iceberg_2_s6,
0x7706b8: LocationName.iceberg_2_s7,
0x7706b9: LocationName.iceberg_2_s8,
0x7706ba: LocationName.iceberg_2_s9,
0x7706bb: LocationName.iceberg_2_s10,
0x7706bc: LocationName.iceberg_2_s11,
0x7706bd: LocationName.iceberg_2_s12,
0x7706be: LocationName.iceberg_2_s13,
0x7706bf: LocationName.iceberg_2_s14,
0x7706c0: LocationName.iceberg_2_s15,
0x7706c1: LocationName.iceberg_2_s16,
0x7706c2: LocationName.iceberg_2_s17,
0x7706c3: LocationName.iceberg_2_s18,
0x7706c4: LocationName.iceberg_2_s19,
0x7706c5: LocationName.iceberg_3_s1,
0x7706c6: LocationName.iceberg_3_s2,
0x7706c7: LocationName.iceberg_3_s3,
0x7706c8: LocationName.iceberg_3_s4,
0x7706c9: LocationName.iceberg_3_s5,
0x7706ca: LocationName.iceberg_3_s6,
0x7706cb: LocationName.iceberg_3_s7,
0x7706cc: LocationName.iceberg_3_s8,
0x7706cd: LocationName.iceberg_3_s9,
0x7706ce: LocationName.iceberg_3_s10,
0x7706cf: LocationName.iceberg_3_s11,
0x7706d0: LocationName.iceberg_3_s12,
0x7706d1: LocationName.iceberg_3_s13,
0x7706d2: LocationName.iceberg_3_s14,
0x7706d3: LocationName.iceberg_3_s15,
0x7706d4: LocationName.iceberg_3_s16,
0x7706d5: LocationName.iceberg_3_s17,
0x7706d6: LocationName.iceberg_3_s18,
0x7706d7: LocationName.iceberg_3_s19,
0x7706d8: LocationName.iceberg_3_s20,
0x7706d9: LocationName.iceberg_3_s21,
0x7706da: LocationName.iceberg_4_s1,
0x7706db: LocationName.iceberg_4_s2,
0x7706dc: LocationName.iceberg_4_s3,
0x7706dd: LocationName.iceberg_5_s1,
0x7706de: LocationName.iceberg_5_s2,
0x7706df: LocationName.iceberg_5_s3,
0x7706e0: LocationName.iceberg_5_s4,
0x7706e1: LocationName.iceberg_5_s5,
0x7706e2: LocationName.iceberg_5_s6,
0x7706e3: LocationName.iceberg_5_s7,
0x7706e4: LocationName.iceberg_5_s8,
0x7706e5: LocationName.iceberg_5_s9,
0x7706e6: LocationName.iceberg_5_s10,
0x7706e7: LocationName.iceberg_5_s11,
0x7706e8: LocationName.iceberg_5_s12,
0x7706e9: LocationName.iceberg_5_s13,
0x7706ea: LocationName.iceberg_5_s14,
0x7706eb: LocationName.iceberg_5_s15,
0x7706ec: LocationName.iceberg_5_s16,
0x7706ed: LocationName.iceberg_5_s17,
0x7706ee: LocationName.iceberg_5_s18,
0x7706ef: LocationName.iceberg_5_s19,
0x7706f0: LocationName.iceberg_5_s20,
0x7706f1: LocationName.iceberg_5_s21,
0x7706f2: LocationName.iceberg_5_s22,
0x7706f3: LocationName.iceberg_5_s23,
0x7706f4: LocationName.iceberg_5_s24,
0x7706f5: LocationName.iceberg_5_s25,
0x7706f6: LocationName.iceberg_5_s26,
0x7706f7: LocationName.iceberg_5_s27,
0x7706f8: LocationName.iceberg_5_s28,
0x7706f9: LocationName.iceberg_5_s29,
0x7706fa: LocationName.iceberg_5_s30,
0x7706fb: LocationName.iceberg_5_s31,
0x7706fc: LocationName.iceberg_5_s32,
0x7706fd: LocationName.iceberg_5_s33,
0x7706fe: LocationName.iceberg_5_s34,
0x7706ff: LocationName.iceberg_6_s1,
}
location_table = {
**stage_locations,
**heart_star_locations,
**boss_locations,
**consumable_locations,
**star_locations
}

View File

@@ -1,5 +1,3 @@
from typing import List
grass_land_1_a1 = "Grass Land 1 - Animal 1" # Nago
grass_land_1_a2 = "Grass Land 1 - Animal 2" # Rick
grass_land_2_a1 = "Grass Land 2 - Animal 1" # ChuChu
@@ -199,12 +197,3 @@ animal_friend_spawns = {
iceberg_6_a5: "ChuChu Spawn",
iceberg_6_a6: "Nago Spawn",
}
problematic_sets: List[List[str]] = [
# Animal groups that must be guaranteed unique. Potential for softlocks on future-ER if not.
[ripple_field_4_a1, ripple_field_4_a2, ripple_field_4_a3],
[sand_canyon_3_a1, sand_canyon_3_a2, sand_canyon_3_a3],
[cloudy_park_6_a1, cloudy_park_6_a2, cloudy_park_6_a3],
[iceberg_6_a1, iceberg_6_a2, iceberg_6_a3],
[iceberg_6_a4, iceberg_6_a5, iceberg_6_a6]
]

View File

@@ -809,7 +809,7 @@ vanilla_enemies = {'Waddle Dee': 'No Ability',
enemy_restrictive: List[Tuple[List[str], List[str]]] = [
# abilities, enemies, set_all (False to set any)
(["Stone Ability"], ["Rocky", "Sparky", "Babut", "Squishy", ]), # Ribbon Field 5 - 7
(["Burning Ability", "Stone Ability"], ["Rocky", "Sparky", "Babut", "Squishy", ]), # Ribbon Field 5 - 7
# Sand Canyon 6
(["Parasol Ability", "Cutter Ability"], ['Bukiset (Parasol)', 'Bukiset (Cutter)']),
(["Spark Ability", "Clean Ability"], ['Bukiset (Spark)', 'Bukiset (Clean)']),

View File

@@ -1,21 +1,13 @@
import random
from dataclasses import dataclass
from typing import List
from Options import DeathLinkMixin, Choice, Toggle, OptionDict, Range, PlandoBosses, DefaultOnToggle, \
PerGameCommonOptions, Visibility, NamedRange, OptionGroup, PlandoConnections
from .names import location_name
class RemoteItems(DefaultOnToggle):
"""
Enables receiving items from your own world, primarily for co-op play.
"""
display_name = "Remote Items"
from Options import DeathLink, Choice, Toggle, OptionDict, Range, PlandoBosses, DefaultOnToggle, \
PerGameCommonOptions, PlandoConnections
from .Names import LocationName
class KDL3PlandoConnections(PlandoConnections):
entrances = exits = {f"{i} {j}" for i in location_name.level_names for j in range(1, 7)}
entrances = exits = {f"{i} {j}" for i in LocationName.level_names for j in range(1, 7)}
class Goal(Choice):
@@ -38,7 +30,6 @@ class Goal(Choice):
return cls.name_lookup[value].upper()
return super().get_option_name(value)
class GoalSpeed(Choice):
"""
Normal: the goal is unlocked after purifying the five bosses
@@ -49,14 +40,13 @@ class GoalSpeed(Choice):
option_fast = 1
class MaxHeartStars(Range):
class TotalHeartStars(Range):
"""
Maximum number of heart stars to include in the pool of items.
If fewer available locations exist in the pool than this number, the number of available locations will be used instead.
"""
display_name = "Max Heart Stars"
range_start = 5 # set to 5 so strict bosses does not degrade
range_end = 99 # previously set to 50, set to highest it can be should there be less locations than heart stars
range_end = 50 # 30 default locations + 30 stage clears + 5 bosses - 14 progression items = 51, so round down
default = 30
@@ -94,9 +84,9 @@ class BossShuffle(PlandoBosses):
Singularity: All (non-Zero) bosses will be replaced with a single boss
Supports plando placement.
"""
bosses = frozenset(location_name.boss_names.keys())
bosses = frozenset(LocationName.boss_names.keys())
locations = frozenset(location_name.level_names.keys())
locations = frozenset(LocationName.level_names.keys())
duplicate_bosses = True
@@ -288,8 +278,7 @@ class KirbyFlavorPreset(Choice):
option_orange = 11
option_lime = 12
option_lavender = 13
option_miku = 14
option_custom = 15
option_custom = 14
default = 0
@classmethod
@@ -307,7 +296,6 @@ class KirbyFlavor(OptionDict):
A custom color for Kirby. To use a custom color, set the preset to Custom and then define a dict of keys from "1" to
"15", with their values being an HTML hex color.
"""
display_name = "Custom Kirby Flavor"
default = {
"1": "B01810",
"2": "F0E0E8",
@@ -325,7 +313,6 @@ class KirbyFlavor(OptionDict):
"14": "F8F8F8",
"15": "B03830",
}
visibility = Visibility.template | Visibility.spoiler # likely never supported on guis
class GooeyFlavorPreset(Choice):
@@ -365,7 +352,6 @@ class GooeyFlavor(OptionDict):
A custom color for Gooey. To use a custom color, set the preset to Custom and then define a dict of keys from "1" to
"15", with their values being an HTML hex color.
"""
display_name = "Custom Gooey Flavor"
default = {
"1": "000808",
"2": "102838",
@@ -377,7 +363,6 @@ class GooeyFlavor(OptionDict):
"8": "D0C0C0",
"9": "F8F8F8",
}
visibility = Visibility.template | Visibility.spoiler # likely never supported on guis
class MusicShuffle(Choice):
@@ -417,27 +402,14 @@ class Gifting(Toggle):
display_name = "Gifting"
class TotalHeartStars(NamedRange):
"""
Deprecated. Use max_heart_stars instead. Supported for only one version.
"""
default = -1
range_start = 5
range_end = 99
special_range_names = {
"default": -1
}
visibility = Visibility.none
@dataclass
class KDL3Options(PerGameCommonOptions, DeathLinkMixin):
remote_items: RemoteItems
class KDL3Options(PerGameCommonOptions):
plando_connections: KDL3PlandoConnections
death_link: DeathLink
game_language: GameLanguage
goal: Goal
goal_speed: GoalSpeed
max_heart_stars: MaxHeartStars
total_heart_stars: TotalHeartStars
heart_stars_required: HeartStarsRequired
filler_percentage: FillerPercentage
trap_percentage: TrapPercentage
@@ -463,17 +435,3 @@ class KDL3Options(PerGameCommonOptions, DeathLinkMixin):
gooey_flavor: GooeyFlavor
music_shuffle: MusicShuffle
virtual_console: VirtualConsoleChanges
total_heart_stars: TotalHeartStars # remove in 2 versions
kdl3_option_groups: List[OptionGroup] = [
OptionGroup("Goal Options", [Goal, GoalSpeed, MaxHeartStars, HeartStarsRequired, JumpingTarget, ]),
OptionGroup("World Options", [RemoteItems, StrictBosses, OpenWorld, OpenWorldBossRequirement, ConsumableChecks,
StarChecks, FillerPercentage, TrapPercentage, GooeyTrapPercentage,
SlowTrapPercentage, AbilityTrapPercentage, LevelShuffle, BossShuffle,
AnimalRandomization, CopyAbilityRandomization, BossRequirementRandom,
Gifting, ]),
OptionGroup("Cosmetic Options", [GameLanguage, BossShuffleAllowBB, KirbyFlavorPreset, KirbyFlavor,
GooeyFlavorPreset, GooeyFlavor, MusicShuffle, VirtualConsoleChanges, ]),
]

View File

@@ -25,7 +25,6 @@ all_random = {
"ow_boss_requirement": "random",
"boss_requirement_random": "random",
"consumables": "random",
"starsanity": "random",
"kirby_flavor_preset": "random",
"gooey_flavor_preset": "random",
"music_shuffle": "random",

View File

@@ -1,62 +1,60 @@
import orjson
import os
from pkgutil import get_data
from copy import deepcopy
from typing import TYPE_CHECKING, List, Dict, Optional, Union, Callable
from BaseClasses import Region, CollectionState
from typing import TYPE_CHECKING, List, Dict, Optional, Union
from BaseClasses import Region
from worlds.generic.Rules import add_item_rule
from .locations import KDL3Location
from .names import location_name
from .options import BossShuffle
from .room import KDL3Room
from .Locations import KDL3Location
from .Names import LocationName
from .Options import BossShuffle
from .Room import KDL3Room
if TYPE_CHECKING:
from . import KDL3World
default_levels = {
1: [0x770000, 0x770001, 0x770002, 0x770003, 0x770004, 0x770005, 0x770200],
2: [0x770006, 0x770007, 0x770008, 0x770009, 0x77000A, 0x77000B, 0x770201],
3: [0x77000C, 0x77000D, 0x77000E, 0x77000F, 0x770010, 0x770011, 0x770202],
4: [0x770012, 0x770013, 0x770014, 0x770015, 0x770016, 0x770017, 0x770203],
5: [0x770018, 0x770019, 0x77001A, 0x77001B, 0x77001C, 0x77001D, 0x770204],
1: [0x770001, 0x770002, 0x770003, 0x770004, 0x770005, 0x770006, 0x770200],
2: [0x770007, 0x770008, 0x770009, 0x77000A, 0x77000B, 0x77000C, 0x770201],
3: [0x77000D, 0x77000E, 0x77000F, 0x770010, 0x770011, 0x770012, 0x770202],
4: [0x770013, 0x770014, 0x770015, 0x770016, 0x770017, 0x770018, 0x770203],
5: [0x770019, 0x77001A, 0x77001B, 0x77001C, 0x77001D, 0x77001E, 0x770204],
}
first_stage_blacklist = {
# We want to confirm that the first stage can be completed without any items
0x77000A, # 2-5 needs Kine
0x770010, # 3-5 needs Cutter
0x77001B, # 5-4 needs Burning
0x77000B, # 2-5 needs Kine
0x770011, # 3-5 needs Cutter
0x77001C, # 5-4 needs Burning
}
first_world_limit = {
# We need to limit the number of very restrictive stages in level 1 on solo gens
*first_stage_blacklist, # all three of the blacklist stages need 2+ items for both checks
0x770006,
0x770007,
0x770012,
0x77001D,
0x770008,
0x770013,
0x77001E,
}
def generate_valid_level(world: "KDL3World", level: int, stage: int,
possible_stages: List[int], placed_stages: List[Optional[int]]) -> int:
possible_stages: List[int], placed_stages: List[int]):
new_stage = world.random.choice(possible_stages)
if level == 1:
if stage == 0 and new_stage in first_stage_blacklist:
possible_stages.remove(new_stage)
return generate_valid_level(world, level, stage, possible_stages, placed_stages)
elif (not (world.multiworld.players > 1 or world.options.consumables or world.options.starsanity) and
new_stage in first_world_limit and
sum(p_stage in first_world_limit for p_stage in placed_stages)
new_stage in first_world_limit and
sum(p_stage in first_world_limit for p_stage in placed_stages)
>= (2 if world.options.open_world else 1)):
return generate_valid_level(world, level, stage, possible_stages, placed_stages)
return new_stage
def generate_rooms(world: "KDL3World", level_regions: Dict[int, Region]) -> None:
level_names = {location_name.level_names[level]: level for level in location_name.level_names}
def generate_rooms(world: "KDL3World", level_regions: Dict[int, Region]):
level_names = {LocationName.level_names[level]: level for level in LocationName.level_names}
room_data = orjson.loads(get_data(__name__, os.path.join("data", "Rooms.json")))
rooms: Dict[str, KDL3Room] = dict()
for room_entry in room_data:
@@ -65,7 +63,7 @@ def generate_rooms(world: "KDL3World", level_regions: Dict[int, Region]) -> None
room_entry["default_exits"], room_entry["animal_pointers"], room_entry["enemies"],
room_entry["entity_load"], room_entry["consumables"], room_entry["consumables_pointer"])
room.add_locations({location: world.location_name_to_id[location] if location in world.location_name_to_id else
None for location in room_entry["locations"]
None for location in room_entry["locations"]
if (not any(x in location for x in ["1-Up", "Maxim"]) or
world.options.consumables.value) and ("Star" not in location
or world.options.starsanity.value)},
@@ -85,8 +83,8 @@ def generate_rooms(world: "KDL3World", level_regions: Dict[int, Region]) -> None
if room.stage == 7:
first_rooms[0x770200 + room.level - 1] = room
else:
first_rooms[0x770000 + ((room.level - 1) * 6) + room.stage - 1] = room
exits: Dict[str, Callable[[CollectionState], bool]] = dict()
first_rooms[0x770000 + ((room.level - 1) * 6) + room.stage] = room
exits = dict()
for def_exit in room.default_exits:
target = f"{level_names[room.level]} {room.stage} - {def_exit['room']}"
access_rule = tuple(def_exit["access_rule"])
@@ -117,54 +115,50 @@ def generate_rooms(world: "KDL3World", level_regions: Dict[int, Region]) -> None
if world.options.open_world:
level_regions[level].add_exits([first_rooms[0x770200 + level - 1].name])
else:
world.multiworld.get_location(world.location_id_to_name[world.player_levels[level][5]], world.player) \
world.multiworld.get_location(world.location_id_to_name[world.player_levels[level][5]], world.player)\
.parent_region.add_exits([first_rooms[0x770200 + level - 1].name])
def generate_valid_levels(world: "KDL3World", shuffle_mode: int) -> Dict[int, List[int]]:
if shuffle_mode:
levels: Dict[int, List[Optional[int]]] = {
1: [None] * 7,
2: [None] * 7,
3: [None] * 7,
4: [None] * 7,
5: [None] * 7,
}
def generate_valid_levels(world: "KDL3World", enforce_world: bool, enforce_pattern: bool) -> dict:
levels: Dict[int, List[Optional[int]]] = {
1: [None] * 7,
2: [None] * 7,
3: [None] * 7,
4: [None] * 7,
5: [None] * 7,
}
possible_stages = [default_levels[level][stage] for level in default_levels for stage in range(6)]
if world.options.plando_connections:
for connection in world.options.plando_connections:
try:
entrance_world, entrance_stage = connection.entrance.rsplit(" ", 1)
stage_world, stage_stage = connection.exit.rsplit(" ", 1)
new_stage = default_levels[location_name.level_names[stage_world.strip()]][int(stage_stage) - 1]
levels[location_name.level_names[entrance_world.strip()]][int(entrance_stage) - 1] = new_stage
possible_stages.remove(new_stage)
possible_stages = [default_levels[level][stage] for level in default_levels for stage in range(6)]
if world.options.plando_connections:
for connection in world.options.plando_connections:
try:
entrance_world, entrance_stage = connection.entrance.rsplit(" ", 1)
stage_world, stage_stage = connection.exit.rsplit(" ", 1)
new_stage = default_levels[LocationName.level_names[stage_world.strip()]][int(stage_stage) - 1]
levels[LocationName.level_names[entrance_world.strip()]][int(entrance_stage) - 1] = new_stage
possible_stages.remove(new_stage)
except Exception:
raise Exception(
f"Invalid connection: {connection.entrance} =>"
f" {connection.exit} for player {world.player} ({world.player_name})")
except Exception:
raise Exception(
f"Invalid connection: {connection.entrance} =>"
f" {connection.exit} for player {world.player} ({world.player_name})")
for level in range(1, 6):
for stage in range(6):
# Randomize bosses separately
for level in range(1, 6):
for stage in range(6):
# Randomize bosses separately
try:
if levels[level][stage] is None:
stage_candidates = [candidate for candidate in possible_stages
if (shuffle_mode == 1 and candidate in default_levels[level])
or (shuffle_mode == 2 and (candidate & 0x00FFFF) % 6 == stage)
or (shuffle_mode == 3)
if (enforce_world and candidate in default_levels[level])
or (enforce_pattern and ((candidate - 1) & 0x00FFFF) % 6 == stage)
or (enforce_pattern == enforce_world)
]
if not stage_candidates:
raise Exception(
f"Failed to find valid stage for {level}-{stage}. Remaining Stages:{possible_stages}")
new_stage = generate_valid_level(world, level, stage, stage_candidates, levels[level])
possible_stages.remove(new_stage)
levels[level][stage] = new_stage
else:
levels = deepcopy(default_levels)
for level in levels:
levels[level][6] = None
except Exception:
raise Exception(f"Failed to find valid stage for {level}-{stage}. Remaining Stages:{possible_stages}")
# now handle bosses
boss_shuffle: Union[int, str] = world.options.boss_shuffle.value
plando_bosses = []
@@ -174,17 +168,17 @@ def generate_valid_levels(world: "KDL3World", shuffle_mode: int) -> Dict[int, Li
boss_shuffle = BossShuffle.options[options.pop()]
for option in options:
if "-" in option:
loc, plando_boss = option.split("-")
loc, boss = option.split("-")
loc = loc.title()
plando_boss = plando_boss.title()
levels[location_name.level_names[loc]][6] = location_name.boss_names[plando_boss]
plando_bosses.append(location_name.boss_names[plando_boss])
boss = boss.title()
levels[LocationName.level_names[loc]][6] = LocationName.boss_names[boss]
plando_bosses.append(LocationName.boss_names[boss])
else:
option = option.title()
for level in levels:
if levels[level][6] is None:
levels[level][6] = location_name.boss_names[option]
plando_bosses.append(location_name.boss_names[option])
levels[level][6] = LocationName.boss_names[option]
plando_bosses.append(LocationName.boss_names[option])
if boss_shuffle > 0:
if boss_shuffle == BossShuffle.option_full:
@@ -229,14 +223,15 @@ def create_levels(world: "KDL3World") -> None:
5: level5,
}
level_shuffle = world.options.stage_shuffle.value
if hasattr(world.multiworld, "re_gen_passthrough"):
world.player_levels = getattr(world.multiworld, "re_gen_passthrough")["Kirby's Dream Land 3"]["player_levels"]
else:
world.player_levels = generate_valid_levels(world, level_shuffle)
if level_shuffle != 0:
world.player_levels = generate_valid_levels(
world,
level_shuffle == 1,
level_shuffle == 2)
generate_rooms(world, levels)
level6.add_locations({location_name.goals[world.options.goal.value]: None}, KDL3Location)
level6.add_locations({LocationName.goals[world.options.goal]: None}, KDL3Location)
menu.connect(level1, "Start Game")
level1.connect(level2, "To Level 2")

577
worlds/kdl3/Rom.py Normal file
View File

@@ -0,0 +1,577 @@
import typing
from pkgutil import get_data
import Utils
from typing import Optional, TYPE_CHECKING
import hashlib
import os
import struct
import settings
from worlds.Files import APDeltaPatch
from .Aesthetics import get_palette_bytes, kirby_target_palettes, get_kirby_palette, gooey_target_palettes, \
get_gooey_palette
from .Compression import hal_decompress
import bsdiff4
if TYPE_CHECKING:
from . import KDL3World
KDL3UHASH = "201e7658f6194458a3869dde36bf8ec2"
KDL3JHASH = "b2f2d004ea640c3db66df958fce122b2"
level_pointers = {
0x770001: 0x0084,
0x770002: 0x009C,
0x770003: 0x00B8,
0x770004: 0x00D8,
0x770005: 0x0104,
0x770006: 0x0124,
0x770007: 0x014C,
0x770008: 0x0170,
0x770009: 0x0190,
0x77000A: 0x01B0,
0x77000B: 0x01E8,
0x77000C: 0x0218,
0x77000D: 0x024C,
0x77000E: 0x0270,
0x77000F: 0x02A0,
0x770010: 0x02C4,
0x770011: 0x02EC,
0x770012: 0x0314,
0x770013: 0x03CC,
0x770014: 0x0404,
0x770015: 0x042C,
0x770016: 0x044C,
0x770017: 0x0478,
0x770018: 0x049C,
0x770019: 0x04E4,
0x77001A: 0x0504,
0x77001B: 0x0530,
0x77001C: 0x0554,
0x77001D: 0x05A8,
0x77001E: 0x0640,
0x770200: 0x0148,
0x770201: 0x0248,
0x770202: 0x03C8,
0x770203: 0x04E0,
0x770204: 0x06A4,
0x770205: 0x06A8,
}
bb_bosses = {
0x770200: 0xED85F1,
0x770201: 0xF01360,
0x770202: 0xEDA3DF,
0x770203: 0xEDC2B9,
0x770204: 0xED7C3F,
0x770205: 0xEC29D2,
}
level_sprites = {
0x19B2C6: 1827,
0x1A195C: 1584,
0x19F6F3: 1679,
0x19DC8B: 1717,
0x197900: 1872
}
stage_tiles = {
0: [
0, 1, 2,
16, 17, 18,
32, 33, 34,
48, 49, 50
],
1: [
3, 4, 5,
19, 20, 21,
35, 36, 37,
51, 52, 53
],
2: [
6, 7, 8,
22, 23, 24,
38, 39, 40,
54, 55, 56
],
3: [
9, 10, 11,
25, 26, 27,
41, 42, 43,
57, 58, 59,
],
4: [
12, 13, 64,
28, 29, 65,
44, 45, 66,
60, 61, 67
],
5: [
14, 15, 68,
30, 31, 69,
46, 47, 70,
62, 63, 71
]
}
heart_star_address = 0x2D0000
heart_star_size = 456
consumable_address = 0x2F91DD
consumable_size = 698
stage_palettes = [0x60964, 0x60B64, 0x60D64, 0x60F64, 0x61164]
music_choices = [
2, # Boss 1
3, # Boss 2 (Unused)
4, # Boss 3 (Miniboss)
7, # Dedede
9, # Event 2 (used once)
10, # Field 1
11, # Field 2
12, # Field 3
13, # Field 4
14, # Field 5
15, # Field 6
16, # Field 7
17, # Field 8
18, # Field 9
19, # Field 10
20, # Field 11
21, # Field 12 (Gourmet Race)
23, # Dark Matter in the Hyper Zone
24, # Zero
25, # Level 1
26, # Level 2
27, # Level 4
28, # Level 3
29, # Heart Star Failed
30, # Level 5
31, # Minigame
38, # Animal Friend 1
39, # Animal Friend 2
40, # Animal Friend 3
]
# extra room pointers we don't want to track other than for music
room_pointers = [
3079990, # Zero
2983409, # BB Whispy
3150688, # BB Acro
2991071, # BB PonCon
2998969, # BB Ado
2980927, # BB Dedede
2894290 # BB Zero
]
enemy_remap = {
"Waddle Dee": 0,
"Bronto Burt": 2,
"Rocky": 3,
"Bobo": 5,
"Chilly": 6,
"Poppy Bros Jr.": 7,
"Sparky": 8,
"Polof": 9,
"Broom Hatter": 11,
"Cappy": 12,
"Bouncy": 13,
"Nruff": 15,
"Glunk": 16,
"Togezo": 18,
"Kabu": 19,
"Mony": 20,
"Blipper": 21,
"Squishy": 22,
"Gabon": 24,
"Oro": 25,
"Galbo": 26,
"Sir Kibble": 27,
"Nidoo": 28,
"Kany": 29,
"Sasuke": 30,
"Yaban": 32,
"Boten": 33,
"Coconut": 34,
"Doka": 35,
"Icicle": 36,
"Pteran": 39,
"Loud": 40,
"Como": 41,
"Klinko": 42,
"Babut": 43,
"Wappa": 44,
"Mariel": 45,
"Tick": 48,
"Apolo": 49,
"Popon Ball": 50,
"KeKe": 51,
"Magoo": 53,
"Raft Waddle Dee": 57,
"Madoo": 58,
"Corori": 60,
"Kapar": 67,
"Batamon": 68,
"Peran": 72,
"Bobin": 73,
"Mopoo": 74,
"Gansan": 75,
"Bukiset (Burning)": 76,
"Bukiset (Stone)": 77,
"Bukiset (Ice)": 78,
"Bukiset (Needle)": 79,
"Bukiset (Clean)": 80,
"Bukiset (Parasol)": 81,
"Bukiset (Spark)": 82,
"Bukiset (Cutter)": 83,
"Waddle Dee Drawing": 84,
"Bronto Burt Drawing": 85,
"Bouncy Drawing": 86,
"Kabu (Dekabu)": 87,
"Wapod": 88,
"Propeller": 89,
"Dogon": 90,
"Joe": 91
}
miniboss_remap = {
"Captain Stitch": 0,
"Yuki": 1,
"Blocky": 2,
"Jumper Shoot": 3,
"Boboo": 4,
"Haboki": 5
}
ability_remap = {
"No Ability": 0,
"Burning Ability": 1,
"Stone Ability": 2,
"Ice Ability": 3,
"Needle Ability": 4,
"Clean Ability": 5,
"Parasol Ability": 6,
"Spark Ability": 7,
"Cutter Ability": 8,
}
class RomData:
def __init__(self, file: str, name: typing.Optional[str] = None):
self.file = bytearray()
self.read_from_file(file)
self.name = name
def read_byte(self, offset: int):
return self.file[offset]
def read_bytes(self, offset: int, length: int):
return self.file[offset:offset + length]
def write_byte(self, offset: int, value: int):
self.file[offset] = value
def write_bytes(self, offset: int, values: typing.Sequence) -> None:
self.file[offset:offset + len(values)] = values
def write_to_file(self, file: str):
with open(file, 'wb') as outfile:
outfile.write(self.file)
def read_from_file(self, file: str):
with open(file, 'rb') as stream:
self.file = bytearray(stream.read())
def apply_patch(self, patch: bytes):
self.file = bytearray(bsdiff4.patch(bytes(self.file), patch))
def write_crc(self):
crc = (sum(self.file[:0x7FDC] + self.file[0x7FE0:]) + 0x01FE) & 0xFFFF
inv = crc ^ 0xFFFF
self.write_bytes(0x7FDC, [inv & 0xFF, (inv >> 8) & 0xFF, crc & 0xFF, (crc >> 8) & 0xFF])
def handle_level_sprites(stages, sprites, palettes):
palette_by_level = list()
for palette in palettes:
palette_by_level.extend(palette[10:16])
for i in range(5):
for j in range(6):
palettes[i][10 + j] = palette_by_level[stages[i][j] - 1]
palettes[i] = [x for palette in palettes[i] for x in palette]
tiles_by_level = list()
for spritesheet in sprites:
decompressed = hal_decompress(spritesheet)
tiles = [decompressed[i:i + 32] for i in range(0, 2304, 32)]
tiles_by_level.extend([[tiles[x] for x in stage_tiles[stage]] for stage in stage_tiles])
for world in range(5):
levels = [stages[world][x] - 1 for x in range(6)]
world_tiles: typing.List[typing.Optional[bytes]] = [None for _ in range(72)]
for i in range(6):
for x in range(12):
world_tiles[stage_tiles[i][x]] = tiles_by_level[levels[i]][x]
sprites[world] = list()
for tile in world_tiles:
sprites[world].extend(tile)
# insert our fake compression
sprites[world][0:0] = [0xe3, 0xff]
sprites[world][1026:1026] = [0xe3, 0xff]
sprites[world][2052:2052] = [0xe0, 0xff]
sprites[world].append(0xff)
return sprites, palettes
def write_heart_star_sprites(rom: RomData):
compressed = rom.read_bytes(heart_star_address, heart_star_size)
decompressed = hal_decompress(compressed)
patch = get_data(__name__, os.path.join("data", "APHeartStar.bsdiff4"))
patched = bytearray(bsdiff4.patch(decompressed, patch))
rom.write_bytes(0x1AF7DF, patched)
patched[0:0] = [0xE3, 0xFF]
patched.append(0xFF)
rom.write_bytes(0x1CD000, patched)
rom.write_bytes(0x3F0EBF, [0x00, 0xD0, 0x39])
def write_consumable_sprites(rom: RomData, consumables: bool, stars: bool):
compressed = rom.read_bytes(consumable_address, consumable_size)
decompressed = hal_decompress(compressed)
patched = bytearray(decompressed)
if consumables:
patch = get_data(__name__, os.path.join("data", "APConsumable.bsdiff4"))
patched = bytearray(bsdiff4.patch(bytes(patched), patch))
if stars:
patch = get_data(__name__, os.path.join("data", "APStars.bsdiff4"))
patched = bytearray(bsdiff4.patch(bytes(patched), patch))
patched[0:0] = [0xE3, 0xFF]
patched.append(0xFF)
rom.write_bytes(0x1CD500, patched)
rom.write_bytes(0x3F0DAE, [0x00, 0xD5, 0x39])
class KDL3DeltaPatch(APDeltaPatch):
hash = [KDL3UHASH, KDL3JHASH]
game = "Kirby's Dream Land 3"
patch_file_ending = ".apkdl3"
@classmethod
def get_source_data(cls) -> bytes:
return get_base_rom_bytes()
def patch(self, target: str):
super().patch(target)
rom = RomData(target)
target_language = rom.read_byte(0x3C020)
rom.write_byte(0x7FD9, target_language)
write_heart_star_sprites(rom)
if rom.read_bytes(0x3D014, 1)[0] > 0:
stages = [struct.unpack("HHHHHHH", rom.read_bytes(0x3D020 + x * 14, 14)) for x in range(5)]
palettes = [rom.read_bytes(full_pal, 512) for full_pal in stage_palettes]
palettes = [[palette[i:i + 32] for i in range(0, 512, 32)] for palette in palettes]
sprites = [rom.read_bytes(offset, level_sprites[offset]) for offset in level_sprites]
sprites, palettes = handle_level_sprites(stages, sprites, palettes)
for addr, palette in zip(stage_palettes, palettes):
rom.write_bytes(addr, palette)
for addr, level_sprite in zip([0x1CA000, 0x1CA920, 0x1CB230, 0x1CBB40, 0x1CC450], sprites):
rom.write_bytes(addr, level_sprite)
rom.write_bytes(0x460A, [0x00, 0xA0, 0x39, 0x20, 0xA9, 0x39, 0x30, 0xB2, 0x39, 0x40, 0xBB, 0x39,
0x50, 0xC4, 0x39])
write_consumable_sprites(rom, rom.read_byte(0x3D018) > 0, rom.read_byte(0x3D01A) > 0)
rom_name = rom.read_bytes(0x3C000, 21)
rom.write_bytes(0x7FC0, rom_name)
rom.write_crc()
rom.write_to_file(target)
def patch_rom(world: "KDL3World", rom: RomData):
rom.apply_patch(get_data(__name__, os.path.join("data", "kdl3_basepatch.bsdiff4")))
tiles = get_data(__name__, os.path.join("data", "APPauseIcons.dat"))
rom.write_bytes(0x3F000, tiles)
# Write open world patch
if world.options.open_world:
rom.write_bytes(0x143C7, [0xAD, 0xC1, 0x5A, 0xCD, 0xC1, 0x5A, ])
# changes the stage flag function to compare $5AC1 to $5AC1,
# always running the "new stage" function
# This has further checks present for bosses already, so we just
# need to handle regular stages
# write check for boss to be unlocked
if world.options.consumables:
# reroute maxim tomatoes to use the 1-UP function, then null out the function
rom.write_bytes(0x3002F, [0x37, 0x00])
rom.write_bytes(0x30037, [0xA9, 0x26, 0x00, # LDA #$0026
0x22, 0x27, 0xD9, 0x00, # JSL $00D927
0xA4, 0xD2, # LDY $D2
0x6B, # RTL
0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, # NOP #10
])
# stars handling is built into the rom, so no changes there
rooms = world.rooms
if world.options.music_shuffle > 0:
if world.options.music_shuffle == 1:
shuffled_music = music_choices.copy()
world.random.shuffle(shuffled_music)
music_map = dict(zip(music_choices, shuffled_music))
# Avoid putting star twinkle in the pool
music_map[5] = world.random.choice(music_choices)
# Heart Star music doesn't work on regular stages
music_map[8] = world.random.choice(music_choices)
for room in rooms:
room.music = music_map[room.music]
for room in room_pointers:
old_music = rom.read_byte(room + 2)
rom.write_byte(room + 2, music_map[old_music])
for i in range(5):
# level themes
old_music = rom.read_byte(0x133F2 + i)
rom.write_byte(0x133F2 + i, music_map[old_music])
# Zero
rom.write_byte(0x9AE79, music_map[0x18])
# Heart Star success and fail
rom.write_byte(0x4A388, music_map[0x08])
rom.write_byte(0x4A38D, music_map[0x1D])
elif world.options.music_shuffle == 2:
for room in rooms:
room.music = world.random.choice(music_choices)
for room in room_pointers:
rom.write_byte(room + 2, world.random.choice(music_choices))
for i in range(5):
# level themes
rom.write_byte(0x133F2 + i, world.random.choice(music_choices))
# Zero
rom.write_byte(0x9AE79, world.random.choice(music_choices))
# Heart Star success and fail
rom.write_byte(0x4A388, world.random.choice(music_choices))
rom.write_byte(0x4A38D, world.random.choice(music_choices))
for room in rooms:
room.patch(rom)
if world.options.virtual_console in [1, 3]:
# Flash Reduction
rom.write_byte(0x9AE68, 0x10)
rom.write_bytes(0x9AE8E, [0x08, 0x00, 0x22, 0x5D, 0xF7, 0x00, 0xA2, 0x08, ])
rom.write_byte(0x9AEA1, 0x08)
rom.write_byte(0x9AEC9, 0x01)
rom.write_bytes(0x9AED2, [0xA9, 0x1F])
rom.write_byte(0x9AEE1, 0x08)
if world.options.virtual_console in [2, 3]:
# Hyper Zone BB colors
rom.write_bytes(0x2C5E16, [0xEE, 0x1B, 0x18, 0x5B, 0xD3, 0x4A, 0xF4, 0x3B, ])
rom.write_bytes(0x2C8217, [0xFF, 0x1E, ])
# boss requirements
rom.write_bytes(0x3D000, struct.pack("HHHHH", world.boss_requirements[0], world.boss_requirements[1],
world.boss_requirements[2], world.boss_requirements[3],
world.boss_requirements[4]))
rom.write_bytes(0x3D00A, struct.pack("H", world.required_heart_stars if world.options.goal_speed == 1 else 0xFFFF))
rom.write_byte(0x3D00C, world.options.goal_speed.value)
rom.write_byte(0x3D00E, world.options.open_world.value)
rom.write_byte(0x3D010, world.options.death_link.value)
rom.write_byte(0x3D012, world.options.goal.value)
rom.write_byte(0x3D014, world.options.stage_shuffle.value)
rom.write_byte(0x3D016, world.options.ow_boss_requirement.value)
rom.write_byte(0x3D018, world.options.consumables.value)
rom.write_byte(0x3D01A, world.options.starsanity.value)
rom.write_byte(0x3D01C, world.options.gifting.value if world.multiworld.players > 1 else 0)
rom.write_byte(0x3D01E, world.options.strict_bosses.value)
# don't write gifting for solo game, since there's no one to send anything to
for level in world.player_levels:
for i in range(len(world.player_levels[level])):
rom.write_bytes(0x3F002E + ((level - 1) * 14) + (i * 2),
struct.pack("H", level_pointers[world.player_levels[level][i]]))
rom.write_bytes(0x3D020 + (level - 1) * 14 + (i * 2),
struct.pack("H", world.player_levels[level][i] & 0x00FFFF))
if (i == 0) or (i > 0 and i % 6 != 0):
rom.write_bytes(0x3D080 + (level - 1) * 12 + (i * 2),
struct.pack("H", (world.player_levels[level][i] & 0x00FFFF) % 6))
for i in range(6):
if world.boss_butch_bosses[i]:
rom.write_bytes(0x3F0000 + (level_pointers[0x770200 + i]), struct.pack("I", bb_bosses[0x770200 + i]))
# copy ability shuffle
if world.options.copy_ability_randomization.value > 0:
for enemy in world.copy_abilities:
if enemy in miniboss_remap:
rom.write_bytes(0xB417E + (miniboss_remap[enemy] << 1),
struct.pack("H", ability_remap[world.copy_abilities[enemy]]))
else:
rom.write_bytes(0xB3CAC + (enemy_remap[enemy] << 1),
struct.pack("H", ability_remap[world.copy_abilities[enemy]]))
# following only needs done on non-door rando
# incredibly lucky this follows the same order (including 5E == star block)
rom.write_byte(0x2F77EA, 0x5E + (ability_remap[world.copy_abilities["Sparky"]] << 1))
rom.write_byte(0x2F7811, 0x5E + (ability_remap[world.copy_abilities["Sparky"]] << 1))
rom.write_byte(0x2F9BC4, 0x5E + (ability_remap[world.copy_abilities["Blocky"]] << 1))
rom.write_byte(0x2F9BEB, 0x5E + (ability_remap[world.copy_abilities["Blocky"]] << 1))
rom.write_byte(0x2FAC06, 0x5E + (ability_remap[world.copy_abilities["Jumper Shoot"]] << 1))
rom.write_byte(0x2FAC2D, 0x5E + (ability_remap[world.copy_abilities["Jumper Shoot"]] << 1))
rom.write_byte(0x2F9E7B, 0x5E + (ability_remap[world.copy_abilities["Yuki"]] << 1))
rom.write_byte(0x2F9EA2, 0x5E + (ability_remap[world.copy_abilities["Yuki"]] << 1))
rom.write_byte(0x2FA951, 0x5E + (ability_remap[world.copy_abilities["Sir Kibble"]] << 1))
rom.write_byte(0x2FA978, 0x5E + (ability_remap[world.copy_abilities["Sir Kibble"]] << 1))
rom.write_byte(0x2FA132, 0x5E + (ability_remap[world.copy_abilities["Haboki"]] << 1))
rom.write_byte(0x2FA159, 0x5E + (ability_remap[world.copy_abilities["Haboki"]] << 1))
rom.write_byte(0x2FA3E8, 0x5E + (ability_remap[world.copy_abilities["Boboo"]] << 1))
rom.write_byte(0x2FA40F, 0x5E + (ability_remap[world.copy_abilities["Boboo"]] << 1))
rom.write_byte(0x2F90E2, 0x5E + (ability_remap[world.copy_abilities["Captain Stitch"]] << 1))
rom.write_byte(0x2F9109, 0x5E + (ability_remap[world.copy_abilities["Captain Stitch"]] << 1))
if world.options.copy_ability_randomization == 2:
for enemy in enemy_remap:
# we just won't include it for minibosses
rom.write_bytes(0xB3E40 + (enemy_remap[enemy] << 1), struct.pack("h", world.random.randint(-1, 2)))
# write jumping goal
rom.write_bytes(0x94F8, struct.pack("H", world.options.jumping_target))
rom.write_bytes(0x944E, struct.pack("H", world.options.jumping_target))
from Utils import __version__
rom.name = bytearray(
f'KDL3{__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0', 'utf8')[:21]
rom.name.extend([0] * (21 - len(rom.name)))
rom.write_bytes(0x3C000, rom.name)
rom.write_byte(0x3C020, world.options.game_language.value)
# handle palette
if world.options.kirby_flavor_preset.value != 0:
for addr in kirby_target_palettes:
target = kirby_target_palettes[addr]
palette = get_kirby_palette(world)
rom.write_bytes(addr, get_palette_bytes(palette, target[0], target[1], target[2]))
if world.options.gooey_flavor_preset.value != 0:
for addr in gooey_target_palettes:
target = gooey_target_palettes[addr]
palette = get_gooey_palette(world)
rom.write_bytes(addr, get_palette_bytes(palette, target[0], target[1], target[2]))
def get_base_rom_bytes() -> bytes:
rom_file: str = get_base_rom_path()
base_rom_bytes: Optional[bytes] = getattr(get_base_rom_bytes, "base_rom_bytes", None)
if not base_rom_bytes:
base_rom_bytes = bytes(Utils.read_snes_rom(open(rom_file, "rb")))
basemd5 = hashlib.md5()
basemd5.update(base_rom_bytes)
if basemd5.hexdigest() not in {KDL3UHASH, KDL3JHASH}:
raise Exception("Supplied Base Rom does not match known MD5 for US or JP release. "
"Get the correct game and version, then dump it")
get_base_rom_bytes.base_rom_bytes = base_rom_bytes
return base_rom_bytes
def get_base_rom_path(file_name: str = "") -> str:
options: settings.Settings = settings.get_settings()
if not file_name:
file_name = options["kdl3_options"]["rom_file"]
if not os.path.exists(file_name):
file_name = Utils.user_path(file_name)
return file_name

95
worlds/kdl3/Room.py Normal file
View File

@@ -0,0 +1,95 @@
import struct
import typing
from BaseClasses import Region, ItemClassification
if typing.TYPE_CHECKING:
from .Rom import RomData
animal_map = {
"Rick Spawn": 0,
"Kine Spawn": 1,
"Coo Spawn": 2,
"Nago Spawn": 3,
"ChuChu Spawn": 4,
"Pitch Spawn": 5
}
class KDL3Room(Region):
pointer: int = 0
level: int = 0
stage: int = 0
room: int = 0
music: int = 0
default_exits: typing.List[typing.Dict[str, typing.Union[int, typing.List[str]]]]
animal_pointers: typing.List[int]
enemies: typing.List[str]
entity_load: typing.List[typing.List[int]]
consumables: typing.List[typing.Dict[str, typing.Union[int, str]]]
def __init__(self, name, player, multiworld, hint, level, stage, room, pointer, music, default_exits,
animal_pointers, enemies, entity_load, consumables, consumable_pointer):
super().__init__(name, player, multiworld, hint)
self.level = level
self.stage = stage
self.room = room
self.pointer = pointer
self.music = music
self.default_exits = default_exits
self.animal_pointers = animal_pointers
self.enemies = enemies
self.entity_load = entity_load
self.consumables = consumables
self.consumable_pointer = consumable_pointer
def patch(self, rom: "RomData"):
rom.write_byte(self.pointer + 2, self.music)
animals = [x.item.name for x in self.locations if "Animal" in x.name]
if len(animals) > 0:
for current_animal, address in zip(animals, self.animal_pointers):
rom.write_byte(self.pointer + address + 7, animal_map[current_animal])
if self.multiworld.worlds[self.player].options.consumables:
load_len = len(self.entity_load)
for consumable in self.consumables:
location = next(x for x in self.locations if x.name == consumable["name"])
assert location.item
is_progression = location.item.classification & ItemClassification.progression
if load_len == 8:
# edge case, there is exactly 1 room with 8 entities and only 1 consumable among them
if not (any(x in self.entity_load for x in [[0, 22], [1, 22]])
and any(x in self.entity_load for x in [[2, 22], [3, 22]])):
replacement_target = self.entity_load.index(
next(x for x in self.entity_load if x in [[0, 22], [1, 22], [2, 22], [3, 22]]))
if is_progression:
vtype = 0
else:
vtype = 2
rom.write_byte(self.pointer + 88 + (replacement_target * 2), vtype)
self.entity_load[replacement_target] = [vtype, 22]
else:
if is_progression:
# we need to see if 1-ups are in our load list
if any(x not in self.entity_load for x in [[0, 22], [1, 22]]):
self.entity_load.append([0, 22])
else:
if any(x not in self.entity_load for x in [[2, 22], [3, 22]]):
# edge case: if (1, 22) is in, we need to load (3, 22) instead
if [1, 22] in self.entity_load:
self.entity_load.append([3, 22])
else:
self.entity_load.append([2, 22])
if load_len < len(self.entity_load):
rom.write_bytes(self.pointer + 88 + (load_len * 2), bytes(self.entity_load[load_len]))
rom.write_bytes(self.pointer + 104 + (load_len * 2),
bytes(struct.pack("H", self.consumable_pointer)))
if is_progression:
if [1, 22] in self.entity_load:
vtype = 1
else:
vtype = 0
else:
if [3, 22] in self.entity_load:
vtype = 3
else:
vtype = 2
rom.write_byte(self.pointer + consumable["pointer"] + 7, vtype)

View File

@@ -1,7 +1,7 @@
from worlds.generic.Rules import set_rule, add_rule
from .names import location_name, enemy_abilities, animal_friend_spawns
from .locations import location_table
from .options import GoalSpeed
from .Names import LocationName, EnemyAbilities
from .Locations import location_table
from .Options import GoalSpeed
import typing
if typing.TYPE_CHECKING:
@@ -10,9 +10,9 @@ if typing.TYPE_CHECKING:
def can_reach_boss(state: "CollectionState", player: int, level: int, open_world: int,
ow_boss_req: int, player_levels: typing.Dict[int, typing.List[int]]) -> bool:
ow_boss_req: int, player_levels: typing.Dict[int, typing.Dict[int, int]]):
if open_world:
return state.has(f"{location_name.level_names_inverse[level]} - Stage Completion", player, ow_boss_req)
return state.has(f"{LocationName.level_names_inverse[level]} - Stage Completion", player, ow_boss_req)
else:
return state.can_reach(location_table[player_levels[level][5]], "Location", player)
@@ -86,11 +86,11 @@ ability_map: typing.Dict[str, typing.Callable[["CollectionState", int], bool]] =
}
def can_assemble_rob(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]) -> bool:
def can_assemble_rob(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]):
# check animal requirements
if not (can_reach_coo(state, player) and can_reach_kine(state, player)):
return False
for abilities, bukisets in enemy_abilities.enemy_restrictive[1:5]:
for abilities, bukisets in EnemyAbilities.enemy_restrictive[1:5]:
iterator = iter(x for x in bukisets if copy_abilities[x] in abilities)
target_bukiset = next(iterator, None)
can_reach = False
@@ -103,7 +103,7 @@ def can_assemble_rob(state: "CollectionState", player: int, copy_abilities: typi
return can_reach_parasol(state, player) and can_reach_stone(state, player)
def can_fix_angel_wings(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]) -> bool:
def can_fix_angel_wings(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]):
can_reach = True
for enemy in {"Sparky", "Blocky", "Jumper Shoot", "Yuki", "Sir Kibble", "Haboki", "Boboo", "Captain Stitch"}:
can_reach = can_reach & ability_map[copy_abilities[enemy]](state, player)
@@ -112,114 +112,114 @@ def can_fix_angel_wings(state: "CollectionState", player: int, copy_abilities: t
def set_rules(world: "KDL3World") -> None:
# Level 1
set_rule(world.multiworld.get_location(location_name.grass_land_muchi, world.player),
set_rule(world.multiworld.get_location(LocationName.grass_land_muchi, world.player),
lambda state: can_reach_chuchu(state, world.player))
set_rule(world.multiworld.get_location(location_name.grass_land_chao, world.player),
set_rule(world.multiworld.get_location(LocationName.grass_land_chao, world.player),
lambda state: can_reach_stone(state, world.player))
set_rule(world.multiworld.get_location(location_name.grass_land_mine, world.player),
set_rule(world.multiworld.get_location(LocationName.grass_land_mine, world.player),
lambda state: can_reach_kine(state, world.player))
# Level 2
set_rule(world.multiworld.get_location(location_name.ripple_field_5, world.player),
set_rule(world.multiworld.get_location(LocationName.ripple_field_5, world.player),
lambda state: can_reach_kine(state, world.player))
set_rule(world.multiworld.get_location(location_name.ripple_field_kamuribana, world.player),
set_rule(world.multiworld.get_location(LocationName.ripple_field_kamuribana, world.player),
lambda state: can_reach_pitch(state, world.player) and can_reach_clean(state, world.player))
set_rule(world.multiworld.get_location(location_name.ripple_field_bakasa, world.player),
set_rule(world.multiworld.get_location(LocationName.ripple_field_bakasa, world.player),
lambda state: can_reach_kine(state, world.player) and can_reach_parasol(state, world.player))
set_rule(world.multiworld.get_location(location_name.ripple_field_toad, world.player),
set_rule(world.multiworld.get_location(LocationName.ripple_field_toad, world.player),
lambda state: can_reach_needle(state, world.player))
set_rule(world.multiworld.get_location(location_name.ripple_field_mama_pitch, world.player),
set_rule(world.multiworld.get_location(LocationName.ripple_field_mama_pitch, world.player),
lambda state: (can_reach_pitch(state, world.player) and
can_reach_kine(state, world.player) and
can_reach_burning(state, world.player) and
can_reach_stone(state, world.player)))
# Level 3
set_rule(world.multiworld.get_location(location_name.sand_canyon_5, world.player),
set_rule(world.multiworld.get_location(LocationName.sand_canyon_5, world.player),
lambda state: can_reach_cutter(state, world.player))
set_rule(world.multiworld.get_location(location_name.sand_canyon_auntie, world.player),
set_rule(world.multiworld.get_location(LocationName.sand_canyon_auntie, world.player),
lambda state: can_reach_clean(state, world.player))
set_rule(world.multiworld.get_location(location_name.sand_canyon_nyupun, world.player),
set_rule(world.multiworld.get_location(LocationName.sand_canyon_nyupun, world.player),
lambda state: can_reach_chuchu(state, world.player) and can_reach_cutter(state, world.player))
set_rule(world.multiworld.get_location(location_name.sand_canyon_rob, world.player),
set_rule(world.multiworld.get_location(LocationName.sand_canyon_rob, world.player),
lambda state: can_assemble_rob(state, world.player, world.copy_abilities)
)
# Level 4
set_rule(world.multiworld.get_location(location_name.cloudy_park_hibanamodoki, world.player),
set_rule(world.multiworld.get_location(LocationName.cloudy_park_hibanamodoki, world.player),
lambda state: can_reach_coo(state, world.player) and can_reach_clean(state, world.player))
set_rule(world.multiworld.get_location(location_name.cloudy_park_piyokeko, world.player),
set_rule(world.multiworld.get_location(LocationName.cloudy_park_piyokeko, world.player),
lambda state: can_reach_needle(state, world.player))
set_rule(world.multiworld.get_location(location_name.cloudy_park_mikarin, world.player),
set_rule(world.multiworld.get_location(LocationName.cloudy_park_mikarin, world.player),
lambda state: can_reach_coo(state, world.player))
set_rule(world.multiworld.get_location(location_name.cloudy_park_pick, world.player),
set_rule(world.multiworld.get_location(LocationName.cloudy_park_pick, world.player),
lambda state: can_reach_rick(state, world.player))
# Level 5
set_rule(world.multiworld.get_location(location_name.iceberg_4, world.player),
set_rule(world.multiworld.get_location(LocationName.iceberg_4, world.player),
lambda state: can_reach_burning(state, world.player))
set_rule(world.multiworld.get_location(location_name.iceberg_kogoesou, world.player),
set_rule(world.multiworld.get_location(LocationName.iceberg_kogoesou, world.player),
lambda state: can_reach_burning(state, world.player))
set_rule(world.multiworld.get_location(location_name.iceberg_samus, world.player),
set_rule(world.multiworld.get_location(LocationName.iceberg_samus, world.player),
lambda state: can_reach_ice(state, world.player))
set_rule(world.multiworld.get_location(location_name.iceberg_name, world.player),
set_rule(world.multiworld.get_location(LocationName.iceberg_name, world.player),
lambda state: (can_reach_coo(state, world.player) and
can_reach_burning(state, world.player) and
can_reach_chuchu(state, world.player)))
# ChuChu is guaranteed here, but we use this for consistency
set_rule(world.multiworld.get_location(location_name.iceberg_shiro, world.player),
set_rule(world.multiworld.get_location(LocationName.iceberg_shiro, world.player),
lambda state: can_reach_nago(state, world.player))
set_rule(world.multiworld.get_location(location_name.iceberg_angel, world.player),
set_rule(world.multiworld.get_location(LocationName.iceberg_angel, world.player),
lambda state: can_fix_angel_wings(state, world.player, world.copy_abilities))
# Consumables
if world.options.consumables:
set_rule(world.multiworld.get_location(location_name.grass_land_1_u1, world.player),
set_rule(world.multiworld.get_location(LocationName.grass_land_1_u1, world.player),
lambda state: can_reach_parasol(state, world.player))
set_rule(world.multiworld.get_location(location_name.grass_land_1_m1, world.player),
set_rule(world.multiworld.get_location(LocationName.grass_land_1_m1, world.player),
lambda state: can_reach_spark(state, world.player))
set_rule(world.multiworld.get_location(location_name.grass_land_2_u1, world.player),
set_rule(world.multiworld.get_location(LocationName.grass_land_2_u1, world.player),
lambda state: can_reach_needle(state, world.player))
set_rule(world.multiworld.get_location(location_name.ripple_field_2_u1, world.player),
set_rule(world.multiworld.get_location(LocationName.ripple_field_2_u1, world.player),
lambda state: can_reach_kine(state, world.player))
set_rule(world.multiworld.get_location(location_name.ripple_field_2_m1, world.player),
set_rule(world.multiworld.get_location(LocationName.ripple_field_2_m1, world.player),
lambda state: can_reach_kine(state, world.player))
set_rule(world.multiworld.get_location(location_name.ripple_field_3_u1, world.player),
set_rule(world.multiworld.get_location(LocationName.ripple_field_3_u1, world.player),
lambda state: can_reach_cutter(state, world.player) or can_reach_spark(state, world.player))
set_rule(world.multiworld.get_location(location_name.ripple_field_4_u1, world.player),
set_rule(world.multiworld.get_location(LocationName.ripple_field_4_u1, world.player),
lambda state: can_reach_stone(state, world.player))
set_rule(world.multiworld.get_location(location_name.ripple_field_4_m2, world.player),
set_rule(world.multiworld.get_location(LocationName.ripple_field_4_m2, world.player),
lambda state: can_reach_stone(state, world.player))
set_rule(world.multiworld.get_location(location_name.ripple_field_5_m1, world.player),
set_rule(world.multiworld.get_location(LocationName.ripple_field_5_m1, world.player),
lambda state: can_reach_kine(state, world.player))
set_rule(world.multiworld.get_location(location_name.ripple_field_5_u1, world.player),
set_rule(world.multiworld.get_location(LocationName.ripple_field_5_u1, world.player),
lambda state: (can_reach_kine(state, world.player) and
can_reach_burning(state, world.player) and
can_reach_stone(state, world.player)))
set_rule(world.multiworld.get_location(location_name.ripple_field_5_m2, world.player),
set_rule(world.multiworld.get_location(LocationName.ripple_field_5_m2, world.player),
lambda state: (can_reach_kine(state, world.player) and
can_reach_burning(state, world.player) and
can_reach_stone(state, world.player)))
set_rule(world.multiworld.get_location(location_name.sand_canyon_4_u1, world.player),
set_rule(world.multiworld.get_location(LocationName.sand_canyon_4_u1, world.player),
lambda state: can_reach_clean(state, world.player))
set_rule(world.multiworld.get_location(location_name.sand_canyon_4_m2, world.player),
set_rule(world.multiworld.get_location(LocationName.sand_canyon_4_m2, world.player),
lambda state: can_reach_needle(state, world.player))
set_rule(world.multiworld.get_location(location_name.sand_canyon_5_u2, world.player),
set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u2, world.player),
lambda state: can_reach_ice(state, world.player) and
(can_reach_rick(state, world.player) or can_reach_coo(state, world.player)
or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player)
or can_reach_nago(state, world.player)))
set_rule(world.multiworld.get_location(location_name.sand_canyon_5_u3, world.player),
set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u3, world.player),
lambda state: can_reach_ice(state, world.player) and
(can_reach_rick(state, world.player) or can_reach_coo(state, world.player)
or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player)
or can_reach_nago(state, world.player)))
set_rule(world.multiworld.get_location(location_name.sand_canyon_5_u4, world.player),
set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u4, world.player),
lambda state: can_reach_ice(state, world.player) and
(can_reach_rick(state, world.player) or can_reach_coo(state, world.player)
or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player)
or can_reach_nago(state, world.player)))
set_rule(world.multiworld.get_location(location_name.cloudy_park_6_u1, world.player),
set_rule(world.multiworld.get_location(LocationName.cloudy_park_6_u1, world.player),
lambda state: can_reach_cutter(state, world.player))
if world.options.starsanity:
@@ -274,57 +274,50 @@ def set_rules(world: "KDL3World") -> None:
# copy ability access edge cases
# Kirby cannot eat enemies fully submerged in water. Vast majority of cases, the enemy can be brought to the surface
# and eaten by inhaling while falling on top of them
set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_2_E3, world.player),
set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_2_E3, world.player),
lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player))
set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_3_E6, world.player),
set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_3_E6, world.player),
lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player))
# Ripple Field 4 E5, E7, and E8 are doable, but too strict to leave in logic
set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_4_E5, world.player),
set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E5, world.player),
lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player))
set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_4_E7, world.player),
set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E7, world.player),
lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player))
set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_4_E8, world.player),
set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E8, world.player),
lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player))
set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_5_E1, world.player),
set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E1, world.player),
lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player))
set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_5_E2, world.player),
set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E2, world.player),
lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player))
set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_5_E3, world.player),
set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E3, world.player),
lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player))
set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_5_E4, world.player),
set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E4, world.player),
lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player))
set_rule(world.multiworld.get_location(enemy_abilities.Sand_Canyon_4_E7, world.player),
set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E7, world.player),
lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player))
set_rule(world.multiworld.get_location(enemy_abilities.Sand_Canyon_4_E8, world.player),
set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E8, world.player),
lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player))
set_rule(world.multiworld.get_location(enemy_abilities.Sand_Canyon_4_E9, world.player),
set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E9, world.player),
lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player))
set_rule(world.multiworld.get_location(enemy_abilities.Sand_Canyon_4_E10, world.player),
set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E10, world.player),
lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player))
# animal friend rules
set_rule(world.multiworld.get_location(animal_friend_spawns.iceberg_4_a2, world.player),
lambda state: can_reach_coo(state, world.player) and can_reach_burning(state, world.player))
set_rule(world.multiworld.get_location(animal_friend_spawns.iceberg_4_a3, world.player),
lambda state: can_reach_chuchu(state, world.player) and can_reach_coo(state, world.player)
and can_reach_burning(state, world.player))
for boss_flag, purification, i in zip(["Level 1 Boss - Purified", "Level 2 Boss - Purified",
"Level 3 Boss - Purified", "Level 4 Boss - Purified",
"Level 5 Boss - Purified"],
[location_name.grass_land_whispy, location_name.ripple_field_acro,
location_name.sand_canyon_poncon, location_name.cloudy_park_ado,
location_name.iceberg_dedede],
[LocationName.grass_land_whispy, LocationName.ripple_field_acro,
LocationName.sand_canyon_poncon, LocationName.cloudy_park_ado,
LocationName.iceberg_dedede],
range(1, 6)):
set_rule(world.multiworld.get_location(boss_flag, world.player),
lambda state, x=i: (state.has("Heart Star", world.player, world.boss_requirements[x - 1])
and can_reach_boss(state, world.player, x,
lambda state, i=i: (state.has("Heart Star", world.player, world.boss_requirements[i - 1])
and can_reach_boss(state, world.player, i,
world.options.open_world.value,
world.options.ow_boss_requirement.value,
world.player_levels)))
set_rule(world.multiworld.get_location(purification, world.player),
lambda state, x=i: (state.has("Heart Star", world.player, world.boss_requirements[x - 1])
and can_reach_boss(state, world.player, x,
lambda state, i=i: (state.has("Heart Star", world.player, world.boss_requirements[i - 1])
and can_reach_boss(state, world.player, i,
world.options.open_world.value,
world.options.ow_boss_requirement.value,
world.player_levels)))
@@ -334,12 +327,12 @@ def set_rules(world: "KDL3World") -> None:
for level in range(2, 6):
set_rule(world.multiworld.get_entrance(f"To Level {level}", world.player),
lambda state, x=level: state.has(f"Level {x - 1} Boss Defeated", world.player))
lambda state, i=level: state.has(f"Level {i - 1} Boss Defeated", world.player))
if world.options.strict_bosses:
for level in range(2, 6):
add_rule(world.multiworld.get_entrance(f"To Level {level}", world.player),
lambda state, x=level: state.has(f"Level {x - 1} Boss Purified", world.player))
lambda state, i=level: state.has(f"Level {i - 1} Boss Purified", world.player))
if world.options.goal_speed == GoalSpeed.option_normal:
add_rule(world.multiworld.get_entrance("To Level 6", world.player),

View File

@@ -1,25 +1,25 @@
import logging
import typing
from BaseClasses import Tutorial, ItemClassification, MultiWorld, CollectionState, Item
from BaseClasses import Tutorial, ItemClassification, MultiWorld
from Fill import fill_restrictive
from Options import PerGameCommonOptions
from worlds.AutoWorld import World, WebWorld
from .items import item_table, item_names, copy_ability_table, animal_friend_table, filler_item_weights, KDL3Item, \
trap_item_table, copy_ability_access_table, star_item_weights, total_filler_weights, animal_friend_spawn_table,\
lookup_item_to_id
from .locations import location_table, KDL3Location, level_consumables, consumable_locations, star_locations
from .names.animal_friend_spawns import animal_friend_spawns, problematic_sets
from .names.enemy_abilities import vanilla_enemies, enemy_mapping, enemy_restrictive
from .regions import create_levels, default_levels
from .options import KDL3Options, kdl3_option_groups
from .presets import kdl3_options_presets
from .names import location_name
from .room import KDL3Room
from .rules import set_rules
from .rom import KDL3ProcedurePatch, get_base_rom_path, patch_rom, KDL3JHASH, KDL3UHASH
from .client import KDL3SNIClient
from .Items import item_table, item_names, copy_ability_table, animal_friend_table, filler_item_weights, KDL3Item, \
trap_item_table, copy_ability_access_table, star_item_weights, total_filler_weights
from .Locations import location_table, KDL3Location, level_consumables, consumable_locations, star_locations
from .Names.AnimalFriendSpawns import animal_friend_spawns
from .Names.EnemyAbilities import vanilla_enemies, enemy_mapping, enemy_restrictive
from .Regions import create_levels, default_levels
from .Options import KDL3Options
from .Presets import kdl3_options_presets
from .Names import LocationName
from .Room import KDL3Room
from .Rules import set_rules
from .Rom import KDL3DeltaPatch, get_base_rom_path, RomData, patch_rom, KDL3JHASH, KDL3UHASH
from .Client import KDL3SNIClient
from typing import Dict, TextIO, Optional, List, Any, Mapping, ClassVar, Type
from typing import Dict, TextIO, Optional, List
import os
import math
import threading
@@ -53,7 +53,6 @@ class KDL3WebWorld(WebWorld):
)
]
options_presets = kdl3_options_presets
option_groups = kdl3_option_groups
class KDL3World(World):
@@ -62,35 +61,35 @@ class KDL3World(World):
"""
game = "Kirby's Dream Land 3"
options_dataclass: ClassVar[Type[PerGameCommonOptions]] = KDL3Options
options_dataclass: typing.ClassVar[typing.Type[PerGameCommonOptions]] = KDL3Options
options: KDL3Options
item_name_to_id = lookup_item_to_id
item_name_to_id = {item: item_table[item].code for item in item_table}
location_name_to_id = {location_table[location]: location for location in location_table}
item_name_groups = item_names
web = KDL3WebWorld()
settings: ClassVar[KDL3Settings]
settings: typing.ClassVar[KDL3Settings]
def __init__(self, multiworld: MultiWorld, player: int):
self.rom_name: bytes = bytes()
self.rom_name = None
self.rom_name_available_event = threading.Event()
super().__init__(multiworld, player)
self.copy_abilities: Dict[str, str] = vanilla_enemies.copy()
self.required_heart_stars: int = 0 # we fill this during create_items
self.boss_requirements: List[int] = []
self.boss_requirements: Dict[int, int] = dict()
self.player_levels = default_levels.copy()
self.stage_shuffle_enabled = False
self.boss_butch_bosses: List[Optional[bool]] = []
self.rooms: List[KDL3Room] = []
self.boss_butch_bosses: List[Optional[bool]] = list()
self.rooms: Optional[List[KDL3Room]] = None
@classmethod
def stage_assert_generate(cls, multiworld: MultiWorld) -> None:
rom_file: str = get_base_rom_path()
if not os.path.exists(rom_file):
raise FileNotFoundError(f"Could not find base ROM for {cls.game}: {rom_file}")
create_regions = create_levels
def generate_early(self) -> None:
if self.options.total_heart_stars != -1:
logger.warning(f"Kirby's Dream Land 3 ({self.player_name}): Use of \"total_heart_stars\" is deprecated. "
f"Please use \"max_heart_stars\" instead.")
self.options.max_heart_stars.value = self.options.total_heart_stars.value
def create_item(self, name: str, force_non_progression: bool = False) -> KDL3Item:
def create_item(self, name: str, force_non_progression=False) -> KDL3Item:
item = item_table[name]
classification = ItemClassification.filler
if item.progression and not force_non_progression:
@@ -100,7 +99,7 @@ class KDL3World(World):
classification = ItemClassification.trap
return KDL3Item(name, classification, item.code, self.player)
def get_filler_item_name(self, include_stars: bool = True) -> str:
def get_filler_item_name(self, include_stars=True) -> str:
if include_stars:
return self.random.choices(list(total_filler_weights.keys()),
weights=list(total_filler_weights.values()))[0]
@@ -113,8 +112,8 @@ class KDL3World(World):
self.options.slow_trap_weight.value,
self.options.ability_trap_weight.value])[0]
def get_restrictive_copy_ability_placement(self, copy_ability: str, enemies_to_set: List[str],
level: int, stage: int) -> Optional[str]:
def get_restrictive_copy_ability_placement(self, copy_ability: str, enemies_to_set: typing.List[str],
level: int, stage: int):
valid_rooms = [room for room in self.rooms if (room.level < level)
or (room.level == level and room.stage < stage)] # leave out the stage in question to avoid edge
valid_enemies = set()
@@ -125,10 +124,6 @@ class KDL3World(World):
return None # a valid enemy got placed by a more restrictive placement
return self.random.choice(sorted([enemy for enemy in valid_enemies if enemy not in placed_enemies]))
def get_pre_fill_items(self) -> List[Item]:
return [self.create_item(item)
for item in [*copy_ability_access_table.keys(), *animal_friend_spawn_table.keys()]]
def pre_fill(self) -> None:
if self.options.copy_ability_randomization:
# randomize copy abilities
@@ -201,40 +196,21 @@ class KDL3World(World):
else:
animal_base = ["Rick Spawn", "Kine Spawn", "Coo Spawn", "Nago Spawn", "ChuChu Spawn", "Pitch Spawn"]
animal_pool = [self.random.choice(animal_base)
for _ in range(len(animal_friend_spawns) - 10)]
for _ in range(len(animal_friend_spawns) - 9)]
# have to guarantee one of each animal
animal_pool.extend(animal_base)
if guaranteed_animal == "Kine Spawn":
animal_pool.append("Coo Spawn")
else:
animal_pool.append("Kine Spawn")
# Weird fill hack, this forces ChuChu to be the last animal friend placed
# If Kine is ever the last animal friend placed, he will cause fill errors on closed world
animal_pool.sort()
locations = [self.multiworld.get_location(spawn, self.player) for spawn in spawns]
items: List[Item] = [self.create_item(animal) for animal in animal_pool]
allstate = CollectionState(self.multiworld)
for item in [*copy_ability_table, *animal_friend_table, *["Heart Star" for _ in range(99)]]:
self.collect(allstate, self.create_item(item))
items = [self.create_item(animal) for animal in animal_pool]
allstate = self.multiworld.get_all_state(False)
self.random.shuffle(locations)
fill_restrictive(self.multiworld, allstate, locations, items, True, True)
# Need to ensure all of these are unique items, and replace them if they aren't
for spawns in problematic_sets:
placed = [self.get_location(spawn).item for spawn in spawns]
placed_names = set([item.name for item in placed])
if len(placed_names) != len(placed):
# have a duplicate
animals = []
for spawn in spawns:
spawn_location = self.get_location(spawn)
if spawn_location.item.name not in animals:
animals.append(spawn_location.item.name)
else:
new_animal = self.random.choice([x for x in ["Rick Spawn", "Coo Spawn", "Kine Spawn",
"ChuChu Spawn", "Nago Spawn", "Pitch Spawn"]
if x not in placed_names and x not in animals])
spawn_location.item = None
spawn_location.place_locked_item(self.create_item(new_animal))
animals.append(new_animal)
# logically, this should be sound pre-ER. May need to adjust around it with ER in the future
else:
animal_friends = animal_friend_spawns.copy()
for animal in animal_friends:
@@ -249,20 +225,21 @@ class KDL3World(World):
remaining_items = len(location_table) - len(itempool)
if not self.options.consumables:
remaining_items -= len(consumable_locations)
if not self.options.starsanity:
remaining_items -= len(star_locations)
max_heart_stars = self.options.max_heart_stars.value
if max_heart_stars > remaining_items:
max_heart_stars = remaining_items
remaining_items -= len(star_locations)
if self.options.starsanity:
# star fill, keep consumable pool locked to consumable and fill 767 stars specifically
star_items = list(star_item_weights.keys())
star_weights = list(star_item_weights.values())
itempool.extend([self.create_item(item) for item in self.random.choices(star_items, weights=star_weights,
k=767)])
total_heart_stars = self.options.total_heart_stars
# ensure at least 1 heart star required per world
required_heart_stars = min(max(int(max_heart_stars * required_percentage), 5), 99)
filler_items = remaining_items - required_heart_stars
converted_heart_stars = math.floor((max_heart_stars - required_heart_stars) * (self.options.filler_percentage / 100.0))
non_required_heart_stars = max_heart_stars - converted_heart_stars - required_heart_stars
filler_items -= non_required_heart_stars
trap_amount = math.floor(filler_items * (self.options.trap_percentage / 100.0))
filler_items -= trap_amount
required_heart_stars = max(int(total_heart_stars * required_percentage), 5)
filler_items = total_heart_stars - required_heart_stars
filler_amount = math.floor(filler_items * (self.options.filler_percentage / 100.0))
trap_amount = math.floor(filler_amount * (self.options.trap_percentage / 100.0))
filler_amount -= trap_amount
non_required_heart_stars = filler_items - filler_amount - trap_amount
self.required_heart_stars = required_heart_stars
# handle boss requirements here
requirements = [required_heart_stars]
@@ -284,8 +261,8 @@ class KDL3World(World):
requirements.insert(i - 1, quotient * i)
self.boss_requirements = requirements
itempool.extend([self.create_item("Heart Star") for _ in range(required_heart_stars)])
itempool.extend([self.create_item(self.get_filler_item_name(bool(self.options.starsanity.value)))
for _ in range(filler_items)])
itempool.extend([self.create_item(self.get_filler_item_name(False))
for _ in range(filler_amount + (remaining_items - total_heart_stars))])
itempool.extend([self.create_item(self.get_trap_item_name())
for _ in range(trap_amount)])
itempool.extend([self.create_item("Heart Star", True) for _ in range(non_required_heart_stars)])
@@ -296,15 +273,15 @@ class KDL3World(World):
self.multiworld.get_location(location_table[self.player_levels[level][stage]]
.replace("Complete", "Stage Completion"), self.player) \
.place_locked_item(KDL3Item(
f"{location_name.level_names_inverse[level]} - Stage Completion",
f"{LocationName.level_names_inverse[level]} - Stage Completion",
ItemClassification.progression, None, self.player))
set_rules = set_rules
def generate_basic(self) -> None:
self.stage_shuffle_enabled = self.options.stage_shuffle > 0
goal = self.options.goal.value
goal_location = self.multiworld.get_location(location_name.goals[goal], self.player)
goal = self.options.goal
goal_location = self.multiworld.get_location(LocationName.goals[goal], self.player)
goal_location.place_locked_item(KDL3Item("Love-Love Rod", ItemClassification.progression, None, self.player))
for level in range(1, 6):
self.multiworld.get_location(f"Level {level} Boss - Defeated", self.player) \
@@ -323,65 +300,60 @@ class KDL3World(World):
else:
self.boss_butch_bosses = [False for _ in range(6)]
def generate_output(self, output_directory: str) -> None:
def generate_output(self, output_directory: str):
rom_path = ""
try:
patch = KDL3ProcedurePatch()
patch_rom(self, patch)
rom = RomData(get_base_rom_path())
patch_rom(self, rom)
self.rom_name = patch.name
rom_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.sfc")
rom.write_to_file(rom_path)
self.rom_name = rom.name
patch.write(os.path.join(output_directory,
f"{self.multiworld.get_out_file_name_base(self.player)}{patch.patch_file_ending}"))
patch = KDL3DeltaPatch(os.path.splitext(rom_path)[0] + KDL3DeltaPatch.patch_file_ending, player=self.player,
player_name=self.multiworld.player_name[self.player], patched_path=rom_path)
patch.write()
except Exception:
raise
finally:
self.rom_name_available_event.set() # make sure threading continues and errors are collected
if os.path.exists(rom_path):
os.unlink(rom_path)
def modify_multidata(self, multidata: Dict[str, Any]) -> None:
def modify_multidata(self, multidata: dict):
# wait for self.rom_name to be available.
self.rom_name_available_event.wait()
assert isinstance(self.rom_name, bytes)
rom_name = getattr(self, "rom_name", None)
# we skip in case of error, so that the original error in the output thread is the one that gets raised
if rom_name:
new_name = base64.b64encode(self.rom_name).decode()
new_name = base64.b64encode(bytes(self.rom_name)).decode()
multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]]
def fill_slot_data(self) -> Mapping[str, Any]:
# UT support
return {"player_levels": self.player_levels}
def interpret_slot_data(self, slot_data: Mapping[str, Any]):
# UT support
player_levels = {int(key): value for key, value in slot_data["player_levels"].items()}
return {"player_levels": player_levels}
def write_spoiler(self, spoiler_handle: TextIO) -> None:
if self.stage_shuffle_enabled:
spoiler_handle.write(f"\nLevel Layout ({self.multiworld.get_player_name(self.player)}):\n")
for level in location_name.level_names:
for stage, i in zip(self.player_levels[location_name.level_names[level]], range(1, 7)):
for level in LocationName.level_names:
for stage, i in zip(self.player_levels[LocationName.level_names[level]], range(1, 7)):
spoiler_handle.write(f"{level} {i}: {location_table[stage].replace(' - Complete', '')}\n")
if self.options.animal_randomization:
spoiler_handle.write(f"\nAnimal Friends ({self.multiworld.get_player_name(self.player)}):\n")
for lvl in self.player_levels:
for level in self.player_levels:
for stage in range(6):
rooms = [room for room in self.rooms if room.level == lvl and room.stage == stage]
rooms = [room for room in self.rooms if room.level == level and room.stage == stage]
animals = []
for room in rooms:
animals.extend([location.item.name.replace(" Spawn", "")
for location in room.locations if "Animal" in location.name
and location.item is not None])
spoiler_handle.write(f"{location_table[self.player_levels[lvl][stage]].replace(' - Complete','')}"
for location in room.locations if "Animal" in location.name])
spoiler_handle.write(f"{location_table[self.player_levels[level][stage]].replace(' - Complete','')}"
f": {', '.join(animals)}\n")
if self.options.copy_ability_randomization:
spoiler_handle.write(f"\nCopy Abilities ({self.multiworld.get_player_name(self.player)}):\n")
for enemy in self.copy_abilities:
spoiler_handle.write(f"{enemy}: {self.copy_abilities[enemy].replace('No Ability', 'None').replace(' Ability', '')}\n")
def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]) -> None:
def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]):
if self.stage_shuffle_enabled:
regions = {location_name.level_names[level]: level for level in location_name.level_names}
regions = {LocationName.level_names[level]: level for level in LocationName.level_names}
level_hint_data = {}
for level in regions:
for stage in range(7):
@@ -389,6 +361,6 @@ class KDL3World(World):
self.player).name.replace(" - Complete", "")
stage_regions = [room for room in self.rooms if stage_name in room.name]
for region in stage_regions:
for location in [location for location in list(region.get_locations()) if location.address]:
for location in [location for location in region.locations if location.address]:
level_hint_data[location.address] = f"{regions[level]} {stage + 1 if stage < 6 else 'Boss'}"
hint_data[self.player] = level_hint_data

View File

@@ -1,940 +0,0 @@
import typing
from BaseClasses import Location, Region
from .names import location_name
if typing.TYPE_CHECKING:
from .room import KDL3Room
class KDL3Location(Location):
game: str = "Kirby's Dream Land 3"
room: typing.Optional["KDL3Room"] = None
def __init__(self, player: int, name: str, address: typing.Optional[int], parent: typing.Union[Region, None]):
super().__init__(player, name, address, parent)
if not address:
self.show_in_spoiler = False
stage_locations = {
0x770000: location_name.grass_land_1,
0x770001: location_name.grass_land_2,
0x770002: location_name.grass_land_3,
0x770003: location_name.grass_land_4,
0x770004: location_name.grass_land_5,
0x770005: location_name.grass_land_6,
0x770006: location_name.ripple_field_1,
0x770007: location_name.ripple_field_2,
0x770008: location_name.ripple_field_3,
0x770009: location_name.ripple_field_4,
0x77000A: location_name.ripple_field_5,
0x77000B: location_name.ripple_field_6,
0x77000C: location_name.sand_canyon_1,
0x77000D: location_name.sand_canyon_2,
0x77000E: location_name.sand_canyon_3,
0x77000F: location_name.sand_canyon_4,
0x770010: location_name.sand_canyon_5,
0x770011: location_name.sand_canyon_6,
0x770012: location_name.cloudy_park_1,
0x770013: location_name.cloudy_park_2,
0x770014: location_name.cloudy_park_3,
0x770015: location_name.cloudy_park_4,
0x770016: location_name.cloudy_park_5,
0x770017: location_name.cloudy_park_6,
0x770018: location_name.iceberg_1,
0x770019: location_name.iceberg_2,
0x77001A: location_name.iceberg_3,
0x77001B: location_name.iceberg_4,
0x77001C: location_name.iceberg_5,
0x77001D: location_name.iceberg_6,
}
heart_star_locations = {
0x770100: location_name.grass_land_tulip,
0x770101: location_name.grass_land_muchi,
0x770102: location_name.grass_land_pitcherman,
0x770103: location_name.grass_land_chao,
0x770104: location_name.grass_land_mine,
0x770105: location_name.grass_land_pierre,
0x770106: location_name.ripple_field_kamuribana,
0x770107: location_name.ripple_field_bakasa,
0x770108: location_name.ripple_field_elieel,
0x770109: location_name.ripple_field_toad,
0x77010A: location_name.ripple_field_mama_pitch,
0x77010B: location_name.ripple_field_hb002,
0x77010C: location_name.sand_canyon_mushrooms,
0x77010D: location_name.sand_canyon_auntie,
0x77010E: location_name.sand_canyon_caramello,
0x77010F: location_name.sand_canyon_hikari,
0x770110: location_name.sand_canyon_nyupun,
0x770111: location_name.sand_canyon_rob,
0x770112: location_name.cloudy_park_hibanamodoki,
0x770113: location_name.cloudy_park_piyokeko,
0x770114: location_name.cloudy_park_mrball,
0x770115: location_name.cloudy_park_mikarin,
0x770116: location_name.cloudy_park_pick,
0x770117: location_name.cloudy_park_hb007,
0x770118: location_name.iceberg_kogoesou,
0x770119: location_name.iceberg_samus,
0x77011A: location_name.iceberg_kawasaki,
0x77011B: location_name.iceberg_name,
0x77011C: location_name.iceberg_shiro,
0x77011D: location_name.iceberg_angel,
}
boss_locations = {
0x770200: location_name.grass_land_whispy,
0x770201: location_name.ripple_field_acro,
0x770202: location_name.sand_canyon_poncon,
0x770203: location_name.cloudy_park_ado,
0x770204: location_name.iceberg_dedede,
}
consumable_locations = {
0x770300: location_name.grass_land_1_u1,
0x770301: location_name.grass_land_1_m1,
0x770302: location_name.grass_land_2_u1,
0x770303: location_name.grass_land_3_u1,
0x770304: location_name.grass_land_3_m1,
0x770305: location_name.grass_land_4_m1,
0x770306: location_name.grass_land_4_u1,
0x770307: location_name.grass_land_4_m2,
0x770308: location_name.grass_land_4_m3,
0x770309: location_name.grass_land_6_u1,
0x77030A: location_name.grass_land_6_u2,
0x77030B: location_name.ripple_field_2_u1,
0x77030C: location_name.ripple_field_2_m1,
0x77030D: location_name.ripple_field_3_m1,
0x77030E: location_name.ripple_field_3_u1,
0x77030F: location_name.ripple_field_4_m2,
0x770310: location_name.ripple_field_4_u1,
0x770311: location_name.ripple_field_4_m1,
0x770312: location_name.ripple_field_5_u1,
0x770313: location_name.ripple_field_5_m2,
0x770314: location_name.ripple_field_5_m1,
0x770315: location_name.sand_canyon_1_u1,
0x770316: location_name.sand_canyon_2_u1,
0x770317: location_name.sand_canyon_2_m1,
0x770318: location_name.sand_canyon_4_m1,
0x770319: location_name.sand_canyon_4_u1,
0x77031A: location_name.sand_canyon_4_m2,
0x77031B: location_name.sand_canyon_5_u1,
0x77031C: location_name.sand_canyon_5_u3,
0x77031D: location_name.sand_canyon_5_m1,
0x77031E: location_name.sand_canyon_5_u4,
0x77031F: location_name.sand_canyon_5_u2,
0x770320: location_name.cloudy_park_1_m1,
0x770321: location_name.cloudy_park_1_u1,
0x770322: location_name.cloudy_park_4_u1,
0x770323: location_name.cloudy_park_4_m1,
0x770324: location_name.cloudy_park_5_m1,
0x770325: location_name.cloudy_park_6_u1,
0x770326: location_name.iceberg_3_m1,
0x770327: location_name.iceberg_5_u1,
0x770328: location_name.iceberg_5_u2,
0x770329: location_name.iceberg_5_u3,
0x77032A: location_name.iceberg_6_m1,
0x77032B: location_name.iceberg_6_u1,
}
level_consumables = {
1: [0, 1],
2: [2],
3: [3, 4],
4: [5, 6, 7, 8],
6: [9, 10],
8: [11, 12],
9: [13, 14],
10: [15, 16, 17],
11: [18, 19, 20],
13: [21],
14: [22, 23],
16: [24, 25, 26],
17: [27, 28, 29, 30, 31],
19: [32, 33],
22: [34, 35],
23: [36],
24: [37],
27: [38],
29: [39, 40, 41],
30: [42, 43],
}
star_locations = {
0x770401: location_name.grass_land_1_s1,
0x770402: location_name.grass_land_1_s2,
0x770403: location_name.grass_land_1_s3,
0x770404: location_name.grass_land_1_s4,
0x770405: location_name.grass_land_1_s5,
0x770406: location_name.grass_land_1_s6,
0x770407: location_name.grass_land_1_s7,
0x770408: location_name.grass_land_1_s8,
0x770409: location_name.grass_land_1_s9,
0x77040a: location_name.grass_land_1_s10,
0x77040b: location_name.grass_land_1_s11,
0x77040c: location_name.grass_land_1_s12,
0x77040d: location_name.grass_land_1_s13,
0x77040e: location_name.grass_land_1_s14,
0x77040f: location_name.grass_land_1_s15,
0x770410: location_name.grass_land_1_s16,
0x770411: location_name.grass_land_1_s17,
0x770412: location_name.grass_land_1_s18,
0x770413: location_name.grass_land_1_s19,
0x770414: location_name.grass_land_1_s20,
0x770415: location_name.grass_land_1_s21,
0x770416: location_name.grass_land_1_s22,
0x770417: location_name.grass_land_1_s23,
0x770418: location_name.grass_land_2_s1,
0x770419: location_name.grass_land_2_s2,
0x77041a: location_name.grass_land_2_s3,
0x77041b: location_name.grass_land_2_s4,
0x77041c: location_name.grass_land_2_s5,
0x77041d: location_name.grass_land_2_s6,
0x77041e: location_name.grass_land_2_s7,
0x77041f: location_name.grass_land_2_s8,
0x770420: location_name.grass_land_2_s9,
0x770421: location_name.grass_land_2_s10,
0x770422: location_name.grass_land_2_s11,
0x770423: location_name.grass_land_2_s12,
0x770424: location_name.grass_land_2_s13,
0x770425: location_name.grass_land_2_s14,
0x770426: location_name.grass_land_2_s15,
0x770427: location_name.grass_land_2_s16,
0x770428: location_name.grass_land_2_s17,
0x770429: location_name.grass_land_2_s18,
0x77042a: location_name.grass_land_2_s19,
0x77042b: location_name.grass_land_2_s20,
0x77042c: location_name.grass_land_2_s21,
0x77042d: location_name.grass_land_3_s1,
0x77042e: location_name.grass_land_3_s2,
0x77042f: location_name.grass_land_3_s3,
0x770430: location_name.grass_land_3_s4,
0x770431: location_name.grass_land_3_s5,
0x770432: location_name.grass_land_3_s6,
0x770433: location_name.grass_land_3_s7,
0x770434: location_name.grass_land_3_s8,
0x770435: location_name.grass_land_3_s9,
0x770436: location_name.grass_land_3_s10,
0x770437: location_name.grass_land_3_s11,
0x770438: location_name.grass_land_3_s12,
0x770439: location_name.grass_land_3_s13,
0x77043a: location_name.grass_land_3_s14,
0x77043b: location_name.grass_land_3_s15,
0x77043c: location_name.grass_land_3_s16,
0x77043d: location_name.grass_land_3_s17,
0x77043e: location_name.grass_land_3_s18,
0x77043f: location_name.grass_land_3_s19,
0x770440: location_name.grass_land_3_s20,
0x770441: location_name.grass_land_3_s21,
0x770442: location_name.grass_land_3_s22,
0x770443: location_name.grass_land_3_s23,
0x770444: location_name.grass_land_3_s24,
0x770445: location_name.grass_land_3_s25,
0x770446: location_name.grass_land_3_s26,
0x770447: location_name.grass_land_3_s27,
0x770448: location_name.grass_land_3_s28,
0x770449: location_name.grass_land_3_s29,
0x77044a: location_name.grass_land_3_s30,
0x77044b: location_name.grass_land_3_s31,
0x77044c: location_name.grass_land_4_s1,
0x77044d: location_name.grass_land_4_s2,
0x77044e: location_name.grass_land_4_s3,
0x77044f: location_name.grass_land_4_s4,
0x770450: location_name.grass_land_4_s5,
0x770451: location_name.grass_land_4_s6,
0x770452: location_name.grass_land_4_s7,
0x770453: location_name.grass_land_4_s8,
0x770454: location_name.grass_land_4_s9,
0x770455: location_name.grass_land_4_s10,
0x770456: location_name.grass_land_4_s11,
0x770457: location_name.grass_land_4_s12,
0x770458: location_name.grass_land_4_s13,
0x770459: location_name.grass_land_4_s14,
0x77045a: location_name.grass_land_4_s15,
0x77045b: location_name.grass_land_4_s16,
0x77045c: location_name.grass_land_4_s17,
0x77045d: location_name.grass_land_4_s18,
0x77045e: location_name.grass_land_4_s19,
0x77045f: location_name.grass_land_4_s20,
0x770460: location_name.grass_land_4_s21,
0x770461: location_name.grass_land_4_s22,
0x770462: location_name.grass_land_4_s23,
0x770463: location_name.grass_land_4_s24,
0x770464: location_name.grass_land_4_s25,
0x770465: location_name.grass_land_4_s26,
0x770466: location_name.grass_land_4_s27,
0x770467: location_name.grass_land_4_s28,
0x770468: location_name.grass_land_4_s29,
0x770469: location_name.grass_land_4_s30,
0x77046a: location_name.grass_land_4_s31,
0x77046b: location_name.grass_land_4_s32,
0x77046c: location_name.grass_land_4_s33,
0x77046d: location_name.grass_land_4_s34,
0x77046e: location_name.grass_land_4_s35,
0x77046f: location_name.grass_land_4_s36,
0x770470: location_name.grass_land_4_s37,
0x770471: location_name.grass_land_5_s1,
0x770472: location_name.grass_land_5_s2,
0x770473: location_name.grass_land_5_s3,
0x770474: location_name.grass_land_5_s4,
0x770475: location_name.grass_land_5_s5,
0x770476: location_name.grass_land_5_s6,
0x770477: location_name.grass_land_5_s7,
0x770478: location_name.grass_land_5_s8,
0x770479: location_name.grass_land_5_s9,
0x77047a: location_name.grass_land_5_s10,
0x77047b: location_name.grass_land_5_s11,
0x77047c: location_name.grass_land_5_s12,
0x77047d: location_name.grass_land_5_s13,
0x77047e: location_name.grass_land_5_s14,
0x77047f: location_name.grass_land_5_s15,
0x770480: location_name.grass_land_5_s16,
0x770481: location_name.grass_land_5_s17,
0x770482: location_name.grass_land_5_s18,
0x770483: location_name.grass_land_5_s19,
0x770484: location_name.grass_land_5_s20,
0x770485: location_name.grass_land_5_s21,
0x770486: location_name.grass_land_5_s22,
0x770487: location_name.grass_land_5_s23,
0x770488: location_name.grass_land_5_s24,
0x770489: location_name.grass_land_5_s25,
0x77048a: location_name.grass_land_5_s26,
0x77048b: location_name.grass_land_5_s27,
0x77048c: location_name.grass_land_5_s28,
0x77048d: location_name.grass_land_5_s29,
0x77048e: location_name.grass_land_6_s1,
0x77048f: location_name.grass_land_6_s2,
0x770490: location_name.grass_land_6_s3,
0x770491: location_name.grass_land_6_s4,
0x770492: location_name.grass_land_6_s5,
0x770493: location_name.grass_land_6_s6,
0x770494: location_name.grass_land_6_s7,
0x770495: location_name.grass_land_6_s8,
0x770496: location_name.grass_land_6_s9,
0x770497: location_name.grass_land_6_s10,
0x770498: location_name.grass_land_6_s11,
0x770499: location_name.grass_land_6_s12,
0x77049a: location_name.grass_land_6_s13,
0x77049b: location_name.grass_land_6_s14,
0x77049c: location_name.grass_land_6_s15,
0x77049d: location_name.grass_land_6_s16,
0x77049e: location_name.grass_land_6_s17,
0x77049f: location_name.grass_land_6_s18,
0x7704a0: location_name.grass_land_6_s19,
0x7704a1: location_name.grass_land_6_s20,
0x7704a2: location_name.grass_land_6_s21,
0x7704a3: location_name.grass_land_6_s22,
0x7704a4: location_name.grass_land_6_s23,
0x7704a5: location_name.grass_land_6_s24,
0x7704a6: location_name.grass_land_6_s25,
0x7704a7: location_name.grass_land_6_s26,
0x7704a8: location_name.grass_land_6_s27,
0x7704a9: location_name.grass_land_6_s28,
0x7704aa: location_name.grass_land_6_s29,
0x7704ab: location_name.ripple_field_1_s1,
0x7704ac: location_name.ripple_field_1_s2,
0x7704ad: location_name.ripple_field_1_s3,
0x7704ae: location_name.ripple_field_1_s4,
0x7704af: location_name.ripple_field_1_s5,
0x7704b0: location_name.ripple_field_1_s6,
0x7704b1: location_name.ripple_field_1_s7,
0x7704b2: location_name.ripple_field_1_s8,
0x7704b3: location_name.ripple_field_1_s9,
0x7704b4: location_name.ripple_field_1_s10,
0x7704b5: location_name.ripple_field_1_s11,
0x7704b6: location_name.ripple_field_1_s12,
0x7704b7: location_name.ripple_field_1_s13,
0x7704b8: location_name.ripple_field_1_s14,
0x7704b9: location_name.ripple_field_1_s15,
0x7704ba: location_name.ripple_field_1_s16,
0x7704bb: location_name.ripple_field_1_s17,
0x7704bc: location_name.ripple_field_1_s18,
0x7704bd: location_name.ripple_field_1_s19,
0x7704be: location_name.ripple_field_2_s1,
0x7704bf: location_name.ripple_field_2_s2,
0x7704c0: location_name.ripple_field_2_s3,
0x7704c1: location_name.ripple_field_2_s4,
0x7704c2: location_name.ripple_field_2_s5,
0x7704c3: location_name.ripple_field_2_s6,
0x7704c4: location_name.ripple_field_2_s7,
0x7704c5: location_name.ripple_field_2_s8,
0x7704c6: location_name.ripple_field_2_s9,
0x7704c7: location_name.ripple_field_2_s10,
0x7704c8: location_name.ripple_field_2_s11,
0x7704c9: location_name.ripple_field_2_s12,
0x7704ca: location_name.ripple_field_2_s13,
0x7704cb: location_name.ripple_field_2_s14,
0x7704cc: location_name.ripple_field_2_s15,
0x7704cd: location_name.ripple_field_2_s16,
0x7704ce: location_name.ripple_field_2_s17,
0x7704cf: location_name.ripple_field_3_s1,
0x7704d0: location_name.ripple_field_3_s2,
0x7704d1: location_name.ripple_field_3_s3,
0x7704d2: location_name.ripple_field_3_s4,
0x7704d3: location_name.ripple_field_3_s5,
0x7704d4: location_name.ripple_field_3_s6,
0x7704d5: location_name.ripple_field_3_s7,
0x7704d6: location_name.ripple_field_3_s8,
0x7704d7: location_name.ripple_field_3_s9,
0x7704d8: location_name.ripple_field_3_s10,
0x7704d9: location_name.ripple_field_3_s11,
0x7704da: location_name.ripple_field_3_s12,
0x7704db: location_name.ripple_field_3_s13,
0x7704dc: location_name.ripple_field_3_s14,
0x7704dd: location_name.ripple_field_3_s15,
0x7704de: location_name.ripple_field_3_s16,
0x7704df: location_name.ripple_field_3_s17,
0x7704e0: location_name.ripple_field_3_s18,
0x7704e1: location_name.ripple_field_3_s19,
0x7704e2: location_name.ripple_field_3_s20,
0x7704e3: location_name.ripple_field_3_s21,
0x7704e4: location_name.ripple_field_4_s1,
0x7704e5: location_name.ripple_field_4_s2,
0x7704e6: location_name.ripple_field_4_s3,
0x7704e7: location_name.ripple_field_4_s4,
0x7704e8: location_name.ripple_field_4_s5,
0x7704e9: location_name.ripple_field_4_s6,
0x7704ea: location_name.ripple_field_4_s7,
0x7704eb: location_name.ripple_field_4_s8,
0x7704ec: location_name.ripple_field_4_s9,
0x7704ed: location_name.ripple_field_4_s10,
0x7704ee: location_name.ripple_field_4_s11,
0x7704ef: location_name.ripple_field_4_s12,
0x7704f0: location_name.ripple_field_4_s13,
0x7704f1: location_name.ripple_field_4_s14,
0x7704f2: location_name.ripple_field_4_s15,
0x7704f3: location_name.ripple_field_4_s16,
0x7704f4: location_name.ripple_field_4_s17,
0x7704f5: location_name.ripple_field_4_s18,
0x7704f6: location_name.ripple_field_4_s19,
0x7704f7: location_name.ripple_field_4_s20,
0x7704f8: location_name.ripple_field_4_s21,
0x7704f9: location_name.ripple_field_4_s22,
0x7704fa: location_name.ripple_field_4_s23,
0x7704fb: location_name.ripple_field_4_s24,
0x7704fc: location_name.ripple_field_4_s25,
0x7704fd: location_name.ripple_field_4_s26,
0x7704fe: location_name.ripple_field_4_s27,
0x7704ff: location_name.ripple_field_4_s28,
0x770500: location_name.ripple_field_4_s29,
0x770501: location_name.ripple_field_4_s30,
0x770502: location_name.ripple_field_4_s31,
0x770503: location_name.ripple_field_4_s32,
0x770504: location_name.ripple_field_4_s33,
0x770505: location_name.ripple_field_4_s34,
0x770506: location_name.ripple_field_4_s35,
0x770507: location_name.ripple_field_4_s36,
0x770508: location_name.ripple_field_4_s37,
0x770509: location_name.ripple_field_4_s38,
0x77050a: location_name.ripple_field_4_s39,
0x77050b: location_name.ripple_field_4_s40,
0x77050c: location_name.ripple_field_4_s41,
0x77050d: location_name.ripple_field_4_s42,
0x77050e: location_name.ripple_field_4_s43,
0x77050f: location_name.ripple_field_4_s44,
0x770510: location_name.ripple_field_4_s45,
0x770511: location_name.ripple_field_4_s46,
0x770512: location_name.ripple_field_4_s47,
0x770513: location_name.ripple_field_4_s48,
0x770514: location_name.ripple_field_4_s49,
0x770515: location_name.ripple_field_4_s50,
0x770516: location_name.ripple_field_4_s51,
0x770517: location_name.ripple_field_5_s1,
0x770518: location_name.ripple_field_5_s2,
0x770519: location_name.ripple_field_5_s3,
0x77051a: location_name.ripple_field_5_s4,
0x77051b: location_name.ripple_field_5_s5,
0x77051c: location_name.ripple_field_5_s6,
0x77051d: location_name.ripple_field_5_s7,
0x77051e: location_name.ripple_field_5_s8,
0x77051f: location_name.ripple_field_5_s9,
0x770520: location_name.ripple_field_5_s10,
0x770521: location_name.ripple_field_5_s11,
0x770522: location_name.ripple_field_5_s12,
0x770523: location_name.ripple_field_5_s13,
0x770524: location_name.ripple_field_5_s14,
0x770525: location_name.ripple_field_5_s15,
0x770526: location_name.ripple_field_5_s16,
0x770527: location_name.ripple_field_5_s17,
0x770528: location_name.ripple_field_5_s18,
0x770529: location_name.ripple_field_5_s19,
0x77052a: location_name.ripple_field_5_s20,
0x77052b: location_name.ripple_field_5_s21,
0x77052c: location_name.ripple_field_5_s22,
0x77052d: location_name.ripple_field_5_s23,
0x77052e: location_name.ripple_field_5_s24,
0x77052f: location_name.ripple_field_5_s25,
0x770530: location_name.ripple_field_5_s26,
0x770531: location_name.ripple_field_5_s27,
0x770532: location_name.ripple_field_5_s28,
0x770533: location_name.ripple_field_5_s29,
0x770534: location_name.ripple_field_5_s30,
0x770535: location_name.ripple_field_5_s31,
0x770536: location_name.ripple_field_5_s32,
0x770537: location_name.ripple_field_5_s33,
0x770538: location_name.ripple_field_5_s34,
0x770539: location_name.ripple_field_5_s35,
0x77053a: location_name.ripple_field_5_s36,
0x77053b: location_name.ripple_field_5_s37,
0x77053c: location_name.ripple_field_5_s38,
0x77053d: location_name.ripple_field_5_s39,
0x77053e: location_name.ripple_field_5_s40,
0x77053f: location_name.ripple_field_5_s41,
0x770540: location_name.ripple_field_5_s42,
0x770541: location_name.ripple_field_5_s43,
0x770542: location_name.ripple_field_5_s44,
0x770543: location_name.ripple_field_5_s45,
0x770544: location_name.ripple_field_5_s46,
0x770545: location_name.ripple_field_5_s47,
0x770546: location_name.ripple_field_5_s48,
0x770547: location_name.ripple_field_5_s49,
0x770548: location_name.ripple_field_5_s50,
0x770549: location_name.ripple_field_5_s51,
0x77054a: location_name.ripple_field_6_s1,
0x77054b: location_name.ripple_field_6_s2,
0x77054c: location_name.ripple_field_6_s3,
0x77054d: location_name.ripple_field_6_s4,
0x77054e: location_name.ripple_field_6_s5,
0x77054f: location_name.ripple_field_6_s6,
0x770550: location_name.ripple_field_6_s7,
0x770551: location_name.ripple_field_6_s8,
0x770552: location_name.ripple_field_6_s9,
0x770553: location_name.ripple_field_6_s10,
0x770554: location_name.ripple_field_6_s11,
0x770555: location_name.ripple_field_6_s12,
0x770556: location_name.ripple_field_6_s13,
0x770557: location_name.ripple_field_6_s14,
0x770558: location_name.ripple_field_6_s15,
0x770559: location_name.ripple_field_6_s16,
0x77055a: location_name.ripple_field_6_s17,
0x77055b: location_name.ripple_field_6_s18,
0x77055c: location_name.ripple_field_6_s19,
0x77055d: location_name.ripple_field_6_s20,
0x77055e: location_name.ripple_field_6_s21,
0x77055f: location_name.ripple_field_6_s22,
0x770560: location_name.ripple_field_6_s23,
0x770561: location_name.sand_canyon_1_s1,
0x770562: location_name.sand_canyon_1_s2,
0x770563: location_name.sand_canyon_1_s3,
0x770564: location_name.sand_canyon_1_s4,
0x770565: location_name.sand_canyon_1_s5,
0x770566: location_name.sand_canyon_1_s6,
0x770567: location_name.sand_canyon_1_s7,
0x770568: location_name.sand_canyon_1_s8,
0x770569: location_name.sand_canyon_1_s9,
0x77056a: location_name.sand_canyon_1_s10,
0x77056b: location_name.sand_canyon_1_s11,
0x77056c: location_name.sand_canyon_1_s12,
0x77056d: location_name.sand_canyon_1_s13,
0x77056e: location_name.sand_canyon_1_s14,
0x77056f: location_name.sand_canyon_1_s15,
0x770570: location_name.sand_canyon_1_s16,
0x770571: location_name.sand_canyon_1_s17,
0x770572: location_name.sand_canyon_1_s18,
0x770573: location_name.sand_canyon_1_s19,
0x770574: location_name.sand_canyon_1_s20,
0x770575: location_name.sand_canyon_1_s21,
0x770576: location_name.sand_canyon_1_s22,
0x770577: location_name.sand_canyon_2_s1,
0x770578: location_name.sand_canyon_2_s2,
0x770579: location_name.sand_canyon_2_s3,
0x77057a: location_name.sand_canyon_2_s4,
0x77057b: location_name.sand_canyon_2_s5,
0x77057c: location_name.sand_canyon_2_s6,
0x77057d: location_name.sand_canyon_2_s7,
0x77057e: location_name.sand_canyon_2_s8,
0x77057f: location_name.sand_canyon_2_s9,
0x770580: location_name.sand_canyon_2_s10,
0x770581: location_name.sand_canyon_2_s11,
0x770582: location_name.sand_canyon_2_s12,
0x770583: location_name.sand_canyon_2_s13,
0x770584: location_name.sand_canyon_2_s14,
0x770585: location_name.sand_canyon_2_s15,
0x770586: location_name.sand_canyon_2_s16,
0x770587: location_name.sand_canyon_2_s17,
0x770588: location_name.sand_canyon_2_s18,
0x770589: location_name.sand_canyon_2_s19,
0x77058a: location_name.sand_canyon_2_s20,
0x77058b: location_name.sand_canyon_2_s21,
0x77058c: location_name.sand_canyon_2_s22,
0x77058d: location_name.sand_canyon_2_s23,
0x77058e: location_name.sand_canyon_2_s24,
0x77058f: location_name.sand_canyon_2_s25,
0x770590: location_name.sand_canyon_2_s26,
0x770591: location_name.sand_canyon_2_s27,
0x770592: location_name.sand_canyon_2_s28,
0x770593: location_name.sand_canyon_2_s29,
0x770594: location_name.sand_canyon_2_s30,
0x770595: location_name.sand_canyon_2_s31,
0x770596: location_name.sand_canyon_2_s32,
0x770597: location_name.sand_canyon_2_s33,
0x770598: location_name.sand_canyon_2_s34,
0x770599: location_name.sand_canyon_2_s35,
0x77059a: location_name.sand_canyon_2_s36,
0x77059b: location_name.sand_canyon_2_s37,
0x77059c: location_name.sand_canyon_2_s38,
0x77059d: location_name.sand_canyon_2_s39,
0x77059e: location_name.sand_canyon_2_s40,
0x77059f: location_name.sand_canyon_2_s41,
0x7705a0: location_name.sand_canyon_2_s42,
0x7705a1: location_name.sand_canyon_2_s43,
0x7705a2: location_name.sand_canyon_2_s44,
0x7705a3: location_name.sand_canyon_2_s45,
0x7705a4: location_name.sand_canyon_2_s46,
0x7705a5: location_name.sand_canyon_2_s47,
0x7705a6: location_name.sand_canyon_2_s48,
0x7705a7: location_name.sand_canyon_3_s1,
0x7705a8: location_name.sand_canyon_3_s2,
0x7705a9: location_name.sand_canyon_3_s3,
0x7705aa: location_name.sand_canyon_3_s4,
0x7705ab: location_name.sand_canyon_3_s5,
0x7705ac: location_name.sand_canyon_3_s6,
0x7705ad: location_name.sand_canyon_3_s7,
0x7705ae: location_name.sand_canyon_3_s8,
0x7705af: location_name.sand_canyon_3_s9,
0x7705b0: location_name.sand_canyon_3_s10,
0x7705b1: location_name.sand_canyon_4_s1,
0x7705b2: location_name.sand_canyon_4_s2,
0x7705b3: location_name.sand_canyon_4_s3,
0x7705b4: location_name.sand_canyon_4_s4,
0x7705b5: location_name.sand_canyon_4_s5,
0x7705b6: location_name.sand_canyon_4_s6,
0x7705b7: location_name.sand_canyon_4_s7,
0x7705b8: location_name.sand_canyon_4_s8,
0x7705b9: location_name.sand_canyon_4_s9,
0x7705ba: location_name.sand_canyon_4_s10,
0x7705bb: location_name.sand_canyon_4_s11,
0x7705bc: location_name.sand_canyon_4_s12,
0x7705bd: location_name.sand_canyon_4_s13,
0x7705be: location_name.sand_canyon_4_s14,
0x7705bf: location_name.sand_canyon_4_s15,
0x7705c0: location_name.sand_canyon_4_s16,
0x7705c1: location_name.sand_canyon_4_s17,
0x7705c2: location_name.sand_canyon_4_s18,
0x7705c3: location_name.sand_canyon_4_s19,
0x7705c4: location_name.sand_canyon_4_s20,
0x7705c5: location_name.sand_canyon_4_s21,
0x7705c6: location_name.sand_canyon_4_s22,
0x7705c7: location_name.sand_canyon_4_s23,
0x7705c8: location_name.sand_canyon_5_s1,
0x7705c9: location_name.sand_canyon_5_s2,
0x7705ca: location_name.sand_canyon_5_s3,
0x7705cb: location_name.sand_canyon_5_s4,
0x7705cc: location_name.sand_canyon_5_s5,
0x7705cd: location_name.sand_canyon_5_s6,
0x7705ce: location_name.sand_canyon_5_s7,
0x7705cf: location_name.sand_canyon_5_s8,
0x7705d0: location_name.sand_canyon_5_s9,
0x7705d1: location_name.sand_canyon_5_s10,
0x7705d2: location_name.sand_canyon_5_s11,
0x7705d3: location_name.sand_canyon_5_s12,
0x7705d4: location_name.sand_canyon_5_s13,
0x7705d5: location_name.sand_canyon_5_s14,
0x7705d6: location_name.sand_canyon_5_s15,
0x7705d7: location_name.sand_canyon_5_s16,
0x7705d8: location_name.sand_canyon_5_s17,
0x7705d9: location_name.sand_canyon_5_s18,
0x7705da: location_name.sand_canyon_5_s19,
0x7705db: location_name.sand_canyon_5_s20,
0x7705dc: location_name.sand_canyon_5_s21,
0x7705dd: location_name.sand_canyon_5_s22,
0x7705de: location_name.sand_canyon_5_s23,
0x7705df: location_name.sand_canyon_5_s24,
0x7705e0: location_name.sand_canyon_5_s25,
0x7705e1: location_name.sand_canyon_5_s26,
0x7705e2: location_name.sand_canyon_5_s27,
0x7705e3: location_name.sand_canyon_5_s28,
0x7705e4: location_name.sand_canyon_5_s29,
0x7705e5: location_name.sand_canyon_5_s30,
0x7705e6: location_name.sand_canyon_5_s31,
0x7705e7: location_name.sand_canyon_5_s32,
0x7705e8: location_name.sand_canyon_5_s33,
0x7705e9: location_name.sand_canyon_5_s34,
0x7705ea: location_name.sand_canyon_5_s35,
0x7705eb: location_name.sand_canyon_5_s36,
0x7705ec: location_name.sand_canyon_5_s37,
0x7705ed: location_name.sand_canyon_5_s38,
0x7705ee: location_name.sand_canyon_5_s39,
0x7705ef: location_name.sand_canyon_5_s40,
0x7705f0: location_name.cloudy_park_1_s1,
0x7705f1: location_name.cloudy_park_1_s2,
0x7705f2: location_name.cloudy_park_1_s3,
0x7705f3: location_name.cloudy_park_1_s4,
0x7705f4: location_name.cloudy_park_1_s5,
0x7705f5: location_name.cloudy_park_1_s6,
0x7705f6: location_name.cloudy_park_1_s7,
0x7705f7: location_name.cloudy_park_1_s8,
0x7705f8: location_name.cloudy_park_1_s9,
0x7705f9: location_name.cloudy_park_1_s10,
0x7705fa: location_name.cloudy_park_1_s11,
0x7705fb: location_name.cloudy_park_1_s12,
0x7705fc: location_name.cloudy_park_1_s13,
0x7705fd: location_name.cloudy_park_1_s14,
0x7705fe: location_name.cloudy_park_1_s15,
0x7705ff: location_name.cloudy_park_1_s16,
0x770600: location_name.cloudy_park_1_s17,
0x770601: location_name.cloudy_park_1_s18,
0x770602: location_name.cloudy_park_1_s19,
0x770603: location_name.cloudy_park_1_s20,
0x770604: location_name.cloudy_park_1_s21,
0x770605: location_name.cloudy_park_1_s22,
0x770606: location_name.cloudy_park_1_s23,
0x770607: location_name.cloudy_park_2_s1,
0x770608: location_name.cloudy_park_2_s2,
0x770609: location_name.cloudy_park_2_s3,
0x77060a: location_name.cloudy_park_2_s4,
0x77060b: location_name.cloudy_park_2_s5,
0x77060c: location_name.cloudy_park_2_s6,
0x77060d: location_name.cloudy_park_2_s7,
0x77060e: location_name.cloudy_park_2_s8,
0x77060f: location_name.cloudy_park_2_s9,
0x770610: location_name.cloudy_park_2_s10,
0x770611: location_name.cloudy_park_2_s11,
0x770612: location_name.cloudy_park_2_s12,
0x770613: location_name.cloudy_park_2_s13,
0x770614: location_name.cloudy_park_2_s14,
0x770615: location_name.cloudy_park_2_s15,
0x770616: location_name.cloudy_park_2_s16,
0x770617: location_name.cloudy_park_2_s17,
0x770618: location_name.cloudy_park_2_s18,
0x770619: location_name.cloudy_park_2_s19,
0x77061a: location_name.cloudy_park_2_s20,
0x77061b: location_name.cloudy_park_2_s21,
0x77061c: location_name.cloudy_park_2_s22,
0x77061d: location_name.cloudy_park_2_s23,
0x77061e: location_name.cloudy_park_2_s24,
0x77061f: location_name.cloudy_park_2_s25,
0x770620: location_name.cloudy_park_2_s26,
0x770621: location_name.cloudy_park_2_s27,
0x770622: location_name.cloudy_park_2_s28,
0x770623: location_name.cloudy_park_2_s29,
0x770624: location_name.cloudy_park_2_s30,
0x770625: location_name.cloudy_park_2_s31,
0x770626: location_name.cloudy_park_2_s32,
0x770627: location_name.cloudy_park_2_s33,
0x770628: location_name.cloudy_park_2_s34,
0x770629: location_name.cloudy_park_2_s35,
0x77062a: location_name.cloudy_park_2_s36,
0x77062b: location_name.cloudy_park_2_s37,
0x77062c: location_name.cloudy_park_2_s38,
0x77062d: location_name.cloudy_park_2_s39,
0x77062e: location_name.cloudy_park_2_s40,
0x77062f: location_name.cloudy_park_2_s41,
0x770630: location_name.cloudy_park_2_s42,
0x770631: location_name.cloudy_park_2_s43,
0x770632: location_name.cloudy_park_2_s44,
0x770633: location_name.cloudy_park_2_s45,
0x770634: location_name.cloudy_park_2_s46,
0x770635: location_name.cloudy_park_2_s47,
0x770636: location_name.cloudy_park_2_s48,
0x770637: location_name.cloudy_park_2_s49,
0x770638: location_name.cloudy_park_2_s50,
0x770639: location_name.cloudy_park_2_s51,
0x77063a: location_name.cloudy_park_2_s52,
0x77063b: location_name.cloudy_park_2_s53,
0x77063c: location_name.cloudy_park_2_s54,
0x77063d: location_name.cloudy_park_3_s1,
0x77063e: location_name.cloudy_park_3_s2,
0x77063f: location_name.cloudy_park_3_s3,
0x770640: location_name.cloudy_park_3_s4,
0x770641: location_name.cloudy_park_3_s5,
0x770642: location_name.cloudy_park_3_s6,
0x770643: location_name.cloudy_park_3_s7,
0x770644: location_name.cloudy_park_3_s8,
0x770645: location_name.cloudy_park_3_s9,
0x770646: location_name.cloudy_park_3_s10,
0x770647: location_name.cloudy_park_3_s11,
0x770648: location_name.cloudy_park_3_s12,
0x770649: location_name.cloudy_park_3_s13,
0x77064a: location_name.cloudy_park_3_s14,
0x77064b: location_name.cloudy_park_3_s15,
0x77064c: location_name.cloudy_park_3_s16,
0x77064d: location_name.cloudy_park_3_s17,
0x77064e: location_name.cloudy_park_3_s18,
0x77064f: location_name.cloudy_park_3_s19,
0x770650: location_name.cloudy_park_3_s20,
0x770651: location_name.cloudy_park_3_s21,
0x770652: location_name.cloudy_park_3_s22,
0x770653: location_name.cloudy_park_4_s1,
0x770654: location_name.cloudy_park_4_s2,
0x770655: location_name.cloudy_park_4_s3,
0x770656: location_name.cloudy_park_4_s4,
0x770657: location_name.cloudy_park_4_s5,
0x770658: location_name.cloudy_park_4_s6,
0x770659: location_name.cloudy_park_4_s7,
0x77065a: location_name.cloudy_park_4_s8,
0x77065b: location_name.cloudy_park_4_s9,
0x77065c: location_name.cloudy_park_4_s10,
0x77065d: location_name.cloudy_park_4_s11,
0x77065e: location_name.cloudy_park_4_s12,
0x77065f: location_name.cloudy_park_4_s13,
0x770660: location_name.cloudy_park_4_s14,
0x770661: location_name.cloudy_park_4_s15,
0x770662: location_name.cloudy_park_4_s16,
0x770663: location_name.cloudy_park_4_s17,
0x770664: location_name.cloudy_park_4_s18,
0x770665: location_name.cloudy_park_4_s19,
0x770666: location_name.cloudy_park_4_s20,
0x770667: location_name.cloudy_park_4_s21,
0x770668: location_name.cloudy_park_4_s22,
0x770669: location_name.cloudy_park_4_s23,
0x77066a: location_name.cloudy_park_4_s24,
0x77066b: location_name.cloudy_park_4_s25,
0x77066c: location_name.cloudy_park_4_s26,
0x77066d: location_name.cloudy_park_4_s27,
0x77066e: location_name.cloudy_park_4_s28,
0x77066f: location_name.cloudy_park_4_s29,
0x770670: location_name.cloudy_park_4_s30,
0x770671: location_name.cloudy_park_4_s31,
0x770672: location_name.cloudy_park_4_s32,
0x770673: location_name.cloudy_park_4_s33,
0x770674: location_name.cloudy_park_4_s34,
0x770675: location_name.cloudy_park_4_s35,
0x770676: location_name.cloudy_park_4_s36,
0x770677: location_name.cloudy_park_4_s37,
0x770678: location_name.cloudy_park_4_s38,
0x770679: location_name.cloudy_park_4_s39,
0x77067a: location_name.cloudy_park_4_s40,
0x77067b: location_name.cloudy_park_4_s41,
0x77067c: location_name.cloudy_park_4_s42,
0x77067d: location_name.cloudy_park_4_s43,
0x77067e: location_name.cloudy_park_4_s44,
0x77067f: location_name.cloudy_park_4_s45,
0x770680: location_name.cloudy_park_4_s46,
0x770681: location_name.cloudy_park_4_s47,
0x770682: location_name.cloudy_park_4_s48,
0x770683: location_name.cloudy_park_4_s49,
0x770684: location_name.cloudy_park_4_s50,
0x770685: location_name.cloudy_park_5_s1,
0x770686: location_name.cloudy_park_5_s2,
0x770687: location_name.cloudy_park_5_s3,
0x770688: location_name.cloudy_park_5_s4,
0x770689: location_name.cloudy_park_5_s5,
0x77068a: location_name.cloudy_park_5_s6,
0x77068b: location_name.cloudy_park_6_s1,
0x77068c: location_name.cloudy_park_6_s2,
0x77068d: location_name.cloudy_park_6_s3,
0x77068e: location_name.cloudy_park_6_s4,
0x77068f: location_name.cloudy_park_6_s5,
0x770690: location_name.cloudy_park_6_s6,
0x770691: location_name.cloudy_park_6_s7,
0x770692: location_name.cloudy_park_6_s8,
0x770693: location_name.cloudy_park_6_s9,
0x770694: location_name.cloudy_park_6_s10,
0x770695: location_name.cloudy_park_6_s11,
0x770696: location_name.cloudy_park_6_s12,
0x770697: location_name.cloudy_park_6_s13,
0x770698: location_name.cloudy_park_6_s14,
0x770699: location_name.cloudy_park_6_s15,
0x77069a: location_name.cloudy_park_6_s16,
0x77069b: location_name.cloudy_park_6_s17,
0x77069c: location_name.cloudy_park_6_s18,
0x77069d: location_name.cloudy_park_6_s19,
0x77069e: location_name.cloudy_park_6_s20,
0x77069f: location_name.cloudy_park_6_s21,
0x7706a0: location_name.cloudy_park_6_s22,
0x7706a1: location_name.cloudy_park_6_s23,
0x7706a2: location_name.cloudy_park_6_s24,
0x7706a3: location_name.cloudy_park_6_s25,
0x7706a4: location_name.cloudy_park_6_s26,
0x7706a5: location_name.cloudy_park_6_s27,
0x7706a6: location_name.cloudy_park_6_s28,
0x7706a7: location_name.cloudy_park_6_s29,
0x7706a8: location_name.cloudy_park_6_s30,
0x7706a9: location_name.cloudy_park_6_s31,
0x7706aa: location_name.cloudy_park_6_s32,
0x7706ab: location_name.cloudy_park_6_s33,
0x7706ac: location_name.iceberg_1_s1,
0x7706ad: location_name.iceberg_1_s2,
0x7706ae: location_name.iceberg_1_s3,
0x7706af: location_name.iceberg_1_s4,
0x7706b0: location_name.iceberg_1_s5,
0x7706b1: location_name.iceberg_1_s6,
0x7706b2: location_name.iceberg_2_s1,
0x7706b3: location_name.iceberg_2_s2,
0x7706b4: location_name.iceberg_2_s3,
0x7706b5: location_name.iceberg_2_s4,
0x7706b6: location_name.iceberg_2_s5,
0x7706b7: location_name.iceberg_2_s6,
0x7706b8: location_name.iceberg_2_s7,
0x7706b9: location_name.iceberg_2_s8,
0x7706ba: location_name.iceberg_2_s9,
0x7706bb: location_name.iceberg_2_s10,
0x7706bc: location_name.iceberg_2_s11,
0x7706bd: location_name.iceberg_2_s12,
0x7706be: location_name.iceberg_2_s13,
0x7706bf: location_name.iceberg_2_s14,
0x7706c0: location_name.iceberg_2_s15,
0x7706c1: location_name.iceberg_2_s16,
0x7706c2: location_name.iceberg_2_s17,
0x7706c3: location_name.iceberg_2_s18,
0x7706c4: location_name.iceberg_2_s19,
0x7706c5: location_name.iceberg_3_s1,
0x7706c6: location_name.iceberg_3_s2,
0x7706c7: location_name.iceberg_3_s3,
0x7706c8: location_name.iceberg_3_s4,
0x7706c9: location_name.iceberg_3_s5,
0x7706ca: location_name.iceberg_3_s6,
0x7706cb: location_name.iceberg_3_s7,
0x7706cc: location_name.iceberg_3_s8,
0x7706cd: location_name.iceberg_3_s9,
0x7706ce: location_name.iceberg_3_s10,
0x7706cf: location_name.iceberg_3_s11,
0x7706d0: location_name.iceberg_3_s12,
0x7706d1: location_name.iceberg_3_s13,
0x7706d2: location_name.iceberg_3_s14,
0x7706d3: location_name.iceberg_3_s15,
0x7706d4: location_name.iceberg_3_s16,
0x7706d5: location_name.iceberg_3_s17,
0x7706d6: location_name.iceberg_3_s18,
0x7706d7: location_name.iceberg_3_s19,
0x7706d8: location_name.iceberg_3_s20,
0x7706d9: location_name.iceberg_3_s21,
0x7706da: location_name.iceberg_4_s1,
0x7706db: location_name.iceberg_4_s2,
0x7706dc: location_name.iceberg_4_s3,
0x7706dd: location_name.iceberg_5_s1,
0x7706de: location_name.iceberg_5_s2,
0x7706df: location_name.iceberg_5_s3,
0x7706e0: location_name.iceberg_5_s4,
0x7706e1: location_name.iceberg_5_s5,
0x7706e2: location_name.iceberg_5_s6,
0x7706e3: location_name.iceberg_5_s7,
0x7706e4: location_name.iceberg_5_s8,
0x7706e5: location_name.iceberg_5_s9,
0x7706e6: location_name.iceberg_5_s10,
0x7706e7: location_name.iceberg_5_s11,
0x7706e8: location_name.iceberg_5_s12,
0x7706e9: location_name.iceberg_5_s13,
0x7706ea: location_name.iceberg_5_s14,
0x7706eb: location_name.iceberg_5_s15,
0x7706ec: location_name.iceberg_5_s16,
0x7706ed: location_name.iceberg_5_s17,
0x7706ee: location_name.iceberg_5_s18,
0x7706ef: location_name.iceberg_5_s19,
0x7706f0: location_name.iceberg_5_s20,
0x7706f1: location_name.iceberg_5_s21,
0x7706f2: location_name.iceberg_5_s22,
0x7706f3: location_name.iceberg_5_s23,
0x7706f4: location_name.iceberg_5_s24,
0x7706f5: location_name.iceberg_5_s25,
0x7706f6: location_name.iceberg_5_s26,
0x7706f7: location_name.iceberg_5_s27,
0x7706f8: location_name.iceberg_5_s28,
0x7706f9: location_name.iceberg_5_s29,
0x7706fa: location_name.iceberg_5_s30,
0x7706fb: location_name.iceberg_5_s31,
0x7706fc: location_name.iceberg_5_s32,
0x7706fd: location_name.iceberg_5_s33,
0x7706fe: location_name.iceberg_5_s34,
0x7706ff: location_name.iceberg_6_s1,
}
location_table = {
**stage_locations,
**heart_star_locations,
**boss_locations,
**consumable_locations,
**star_locations
}

View File

@@ -1,602 +0,0 @@
import typing
from pkgutil import get_data
import Utils
from typing import Optional, TYPE_CHECKING, Tuple, Dict, List
import hashlib
import os
import struct
import settings
from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes, APPatchExtension
from .aesthetics import get_palette_bytes, kirby_target_palettes, get_kirby_palette, gooey_target_palettes, \
get_gooey_palette
from .compression import hal_decompress
import bsdiff4
if TYPE_CHECKING:
from . import KDL3World
KDL3UHASH = "201e7658f6194458a3869dde36bf8ec2"
KDL3JHASH = "b2f2d004ea640c3db66df958fce122b2"
level_pointers = {
0x770000: 0x0084,
0x770001: 0x009C,
0x770002: 0x00B8,
0x770003: 0x00D8,
0x770004: 0x0104,
0x770005: 0x0124,
0x770006: 0x014C,
0x770007: 0x0170,
0x770008: 0x0190,
0x770009: 0x01B0,
0x77000A: 0x01E8,
0x77000B: 0x0218,
0x77000C: 0x024C,
0x77000D: 0x0270,
0x77000E: 0x02A0,
0x77000F: 0x02C4,
0x770010: 0x02EC,
0x770011: 0x0314,
0x770012: 0x03CC,
0x770013: 0x0404,
0x770014: 0x042C,
0x770015: 0x044C,
0x770016: 0x0478,
0x770017: 0x049C,
0x770018: 0x04E4,
0x770019: 0x0504,
0x77001A: 0x0530,
0x77001B: 0x0554,
0x77001C: 0x05A8,
0x77001D: 0x0640,
0x770200: 0x0148,
0x770201: 0x0248,
0x770202: 0x03C8,
0x770203: 0x04E0,
0x770204: 0x06A4,
0x770205: 0x06A8,
}
bb_bosses = {
0x770200: 0xED85F1,
0x770201: 0xF01360,
0x770202: 0xEDA3DF,
0x770203: 0xEDC2B9,
0x770204: 0xED7C3F,
0x770205: 0xEC29D2,
}
level_sprites = {
0x19B2C6: 1827,
0x1A195C: 1584,
0x19F6F3: 1679,
0x19DC8B: 1717,
0x197900: 1872
}
stage_tiles = {
0: [
0, 1, 2,
16, 17, 18,
32, 33, 34,
48, 49, 50
],
1: [
3, 4, 5,
19, 20, 21,
35, 36, 37,
51, 52, 53
],
2: [
6, 7, 8,
22, 23, 24,
38, 39, 40,
54, 55, 56
],
3: [
9, 10, 11,
25, 26, 27,
41, 42, 43,
57, 58, 59,
],
4: [
12, 13, 64,
28, 29, 65,
44, 45, 66,
60, 61, 67
],
5: [
14, 15, 68,
30, 31, 69,
46, 47, 70,
62, 63, 71
]
}
heart_star_address = 0x2D0000
heart_star_size = 456
consumable_address = 0x2F91DD
consumable_size = 698
stage_palettes = [0x60964, 0x60B64, 0x60D64, 0x60F64, 0x61164]
music_choices = [
2, # Boss 1
3, # Boss 2 (Unused)
4, # Boss 3 (Miniboss)
7, # Dedede
9, # Event 2 (used once)
10, # Field 1
11, # Field 2
12, # Field 3
13, # Field 4
14, # Field 5
15, # Field 6
16, # Field 7
17, # Field 8
18, # Field 9
19, # Field 10
20, # Field 11
21, # Field 12 (Gourmet Race)
23, # Dark Matter in the Hyper Zone
24, # Zero
25, # Level 1
26, # Level 2
27, # Level 4
28, # Level 3
29, # Heart Star Failed
30, # Level 5
31, # Minigame
38, # Animal Friend 1
39, # Animal Friend 2
40, # Animal Friend 3
]
# extra room pointers we don't want to track other than for music
room_music = {
3079990: 23, # Zero
2983409: 2, # BB Whispy
3150688: 2, # BB Acro
2991071: 2, # BB PonCon
2998969: 2, # BB Ado
2980927: 7, # BB Dedede
2894290: 23 # BB Zero
}
enemy_remap = {
"Waddle Dee": 0,
"Bronto Burt": 2,
"Rocky": 3,
"Bobo": 5,
"Chilly": 6,
"Poppy Bros Jr.": 7,
"Sparky": 8,
"Polof": 9,
"Broom Hatter": 11,
"Cappy": 12,
"Bouncy": 13,
"Nruff": 15,
"Glunk": 16,
"Togezo": 18,
"Kabu": 19,
"Mony": 20,
"Blipper": 21,
"Squishy": 22,
"Gabon": 24,
"Oro": 25,
"Galbo": 26,
"Sir Kibble": 27,
"Nidoo": 28,
"Kany": 29,
"Sasuke": 30,
"Yaban": 32,
"Boten": 33,
"Coconut": 34,
"Doka": 35,
"Icicle": 36,
"Pteran": 39,
"Loud": 40,
"Como": 41,
"Klinko": 42,
"Babut": 43,
"Wappa": 44,
"Mariel": 45,
"Tick": 48,
"Apolo": 49,
"Popon Ball": 50,
"KeKe": 51,
"Magoo": 53,
"Raft Waddle Dee": 57,
"Madoo": 58,
"Corori": 60,
"Kapar": 67,
"Batamon": 68,
"Peran": 72,
"Bobin": 73,
"Mopoo": 74,
"Gansan": 75,
"Bukiset (Burning)": 76,
"Bukiset (Stone)": 77,
"Bukiset (Ice)": 78,
"Bukiset (Needle)": 79,
"Bukiset (Clean)": 80,
"Bukiset (Parasol)": 81,
"Bukiset (Spark)": 82,
"Bukiset (Cutter)": 83,
"Waddle Dee Drawing": 84,
"Bronto Burt Drawing": 85,
"Bouncy Drawing": 86,
"Kabu (Dekabu)": 87,
"Wapod": 88,
"Propeller": 89,
"Dogon": 90,
"Joe": 91
}
miniboss_remap = {
"Captain Stitch": 0,
"Yuki": 1,
"Blocky": 2,
"Jumper Shoot": 3,
"Boboo": 4,
"Haboki": 5
}
ability_remap = {
"No Ability": 0,
"Burning Ability": 1,
"Stone Ability": 2,
"Ice Ability": 3,
"Needle Ability": 4,
"Clean Ability": 5,
"Parasol Ability": 6,
"Spark Ability": 7,
"Cutter Ability": 8,
}
class RomData:
def __init__(self, file: bytes, name: typing.Optional[str] = None):
self.file = bytearray(file)
self.name = name
def read_byte(self, offset: int) -> int:
return self.file[offset]
def read_bytes(self, offset: int, length: int) -> bytearray:
return self.file[offset:offset + length]
def write_byte(self, offset: int, value: int) -> None:
self.file[offset] = value
def write_bytes(self, offset: int, values: typing.Sequence[int]) -> None:
self.file[offset:offset + len(values)] = values
def get_bytes(self) -> bytes:
return bytes(self.file)
def handle_level_sprites(stages: List[Tuple[int, ...]], sprites: List[bytearray], palettes: List[List[bytearray]]) \
-> Tuple[List[bytearray], List[bytearray]]:
palette_by_level = list()
for palette in palettes:
palette_by_level.extend(palette[10:16])
out_palettes = list()
for i in range(5):
for j in range(6):
palettes[i][10 + j] = palette_by_level[stages[i][j]]
out_palettes.append(bytearray([x for palette in palettes[i] for x in palette]))
tiles_by_level = list()
for spritesheet in sprites:
decompressed = hal_decompress(spritesheet)
tiles = [decompressed[i:i + 32] for i in range(0, 2304, 32)]
tiles_by_level.extend([[tiles[x] for x in stage_tiles[stage]] for stage in stage_tiles])
out_sprites = list()
for world in range(5):
levels = [stages[world][x] for x in range(6)]
world_tiles: typing.List[bytes] = [bytes() for _ in range(72)]
for i in range(6):
for x in range(12):
world_tiles[stage_tiles[i][x]] = tiles_by_level[levels[i]][x]
out_sprites.append(bytearray())
for tile in world_tiles:
out_sprites[world].extend(tile)
# insert our fake compression
out_sprites[world][0:0] = [0xe3, 0xff]
out_sprites[world][1026:1026] = [0xe3, 0xff]
out_sprites[world][2052:2052] = [0xe0, 0xff]
out_sprites[world].append(0xff)
return out_sprites, out_palettes
def write_heart_star_sprites(rom: RomData) -> None:
compressed = rom.read_bytes(heart_star_address, heart_star_size)
decompressed = hal_decompress(compressed)
patch = get_data(__name__, os.path.join("data", "APHeartStar.bsdiff4"))
patched = bytearray(bsdiff4.patch(decompressed, patch))
rom.write_bytes(0x1AF7DF, patched)
patched[0:0] = [0xE3, 0xFF]
patched.append(0xFF)
rom.write_bytes(0x1CD000, patched)
rom.write_bytes(0x3F0EBF, [0x00, 0xD0, 0x39])
def write_consumable_sprites(rom: RomData, consumables: bool, stars: bool) -> None:
compressed = rom.read_bytes(consumable_address, consumable_size)
decompressed = hal_decompress(compressed)
patched = bytearray(decompressed)
if consumables:
patch = get_data(__name__, os.path.join("data", "APConsumable.bsdiff4"))
patched = bytearray(bsdiff4.patch(bytes(patched), patch))
if stars:
patch = get_data(__name__, os.path.join("data", "APStars.bsdiff4"))
patched = bytearray(bsdiff4.patch(bytes(patched), patch))
patched[0:0] = [0xE3, 0xFF]
patched.append(0xFF)
rom.write_bytes(0x1CD500, patched)
rom.write_bytes(0x3F0DAE, [0x00, 0xD5, 0x39])
class KDL3PatchExtensions(APPatchExtension):
game = "Kirby's Dream Land 3"
@staticmethod
def apply_post_patch(_: APProcedurePatch, rom: bytes) -> bytes:
rom_data = RomData(rom)
write_heart_star_sprites(rom_data)
if rom_data.read_bytes(0x3D014, 1)[0] > 0:
stages = [struct.unpack("HHHHHHH", rom_data.read_bytes(0x3D020 + x * 14, 14)) for x in range(5)]
palettes = [rom_data.read_bytes(full_pal, 512) for full_pal in stage_palettes]
read_palettes = [[palette[i:i + 32] for i in range(0, 512, 32)] for palette in palettes]
sprites = [rom_data.read_bytes(offset, level_sprites[offset]) for offset in level_sprites]
sprites, palettes = handle_level_sprites(stages, sprites, read_palettes)
for addr, palette in zip(stage_palettes, palettes):
rom_data.write_bytes(addr, palette)
for addr, level_sprite in zip([0x1CA000, 0x1CA920, 0x1CB230, 0x1CBB40, 0x1CC450], sprites):
rom_data.write_bytes(addr, level_sprite)
rom_data.write_bytes(0x460A, [0x00, 0xA0, 0x39, 0x20, 0xA9, 0x39, 0x30, 0xB2, 0x39, 0x40, 0xBB, 0x39,
0x50, 0xC4, 0x39])
write_consumable_sprites(rom_data, rom_data.read_byte(0x3D018) > 0, rom_data.read_byte(0x3D01A) > 0)
return rom_data.get_bytes()
class KDL3ProcedurePatch(APProcedurePatch, APTokenMixin):
hash = [KDL3UHASH, KDL3JHASH]
game = "Kirby's Dream Land 3"
patch_file_ending = ".apkdl3"
procedure = [
("apply_bsdiff4", ["kdl3_basepatch.bsdiff4"]),
("apply_tokens", ["token_patch.bin"]),
("apply_post_patch", []),
("calc_snes_crc", [])
]
name: bytes # used to pass to __init__
@classmethod
def get_source_data(cls) -> bytes:
return get_base_rom_bytes()
def patch_rom(world: "KDL3World", patch: KDL3ProcedurePatch) -> None:
patch.write_file("kdl3_basepatch.bsdiff4",
get_data(__name__, os.path.join("data", "kdl3_basepatch.bsdiff4")))
# Write open world patch
if world.options.open_world:
patch.write_token(APTokenTypes.WRITE, 0x143C7, bytes([0xAD, 0xC1, 0x5A, 0xCD, 0xC1, 0x5A, ]))
# changes the stage flag function to compare $5AC1 to $5AC1,
# always running the "new stage" function
# This has further checks present for bosses already, so we just
# need to handle regular stages
# write check for boss to be unlocked
if world.options.consumables:
# reroute maxim tomatoes to use the 1-UP function, then null out the function
patch.write_token(APTokenTypes.WRITE, 0x3002F, bytes([0x37, 0x00]))
patch.write_token(APTokenTypes.WRITE, 0x30037, bytes([0xA9, 0x26, 0x00, # LDA #$0026
0x22, 0x27, 0xD9, 0x00, # JSL $00D927
0xA4, 0xD2, # LDY $D2
0x6B, # RTL
0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA,
0xEA, # NOP #10
]))
# stars handling is built into the rom, so no changes there
rooms = world.rooms
if world.options.music_shuffle > 0:
if world.options.music_shuffle == 1:
shuffled_music = music_choices.copy()
world.random.shuffle(shuffled_music)
music_map = dict(zip(music_choices, shuffled_music))
# Avoid putting star twinkle in the pool
music_map[5] = world.random.choice(music_choices)
# Heart Star music doesn't work on regular stages
music_map[8] = world.random.choice(music_choices)
for room in rooms:
room.music = music_map[room.music]
for room_ptr in room_music:
patch.write_token(APTokenTypes.WRITE, room_ptr + 2, bytes([music_map[room_music[room_ptr]]]))
for i, old_music in zip(range(5), [25, 26, 28, 27, 30]):
# level themes
patch.write_token(APTokenTypes.WRITE, 0x133F2 + i, bytes([music_map[old_music]]))
# Zero
patch.write_token(APTokenTypes.WRITE, 0x9AE79, music_map[0x18].to_bytes(1, "little"))
# Heart Star success and fail
patch.write_token(APTokenTypes.WRITE, 0x4A388, music_map[0x08].to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x4A38D, music_map[0x1D].to_bytes(1, "little"))
elif world.options.music_shuffle == 2:
for room in rooms:
room.music = world.random.choice(music_choices)
for room_ptr in room_music:
patch.write_token(APTokenTypes.WRITE, room_ptr + 2,
world.random.choice(music_choices).to_bytes(1, "little"))
for i in range(5):
# level themes
patch.write_token(APTokenTypes.WRITE, 0x133F2 + i,
world.random.choice(music_choices).to_bytes(1, "little"))
# Zero
patch.write_token(APTokenTypes.WRITE, 0x9AE79, world.random.choice(music_choices).to_bytes(1, "little"))
# Heart Star success and fail
patch.write_token(APTokenTypes.WRITE, 0x4A388, world.random.choice(music_choices).to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x4A38D, world.random.choice(music_choices).to_bytes(1, "little"))
for room in rooms:
room.patch(patch, bool(world.options.consumables.value), not bool(world.options.remote_items.value))
if world.options.virtual_console in [1, 3]:
# Flash Reduction
patch.write_token(APTokenTypes.WRITE, 0x9AE68, b"\x10")
patch.write_token(APTokenTypes.WRITE, 0x9AE8E, bytes([0x08, 0x00, 0x22, 0x5D, 0xF7, 0x00, 0xA2, 0x08, ]))
patch.write_token(APTokenTypes.WRITE, 0x9AEA1, b"\x08")
patch.write_token(APTokenTypes.WRITE, 0x9AEC9, b"\x01")
patch.write_token(APTokenTypes.WRITE, 0x9AED2, bytes([0xA9, 0x1F]))
patch.write_token(APTokenTypes.WRITE, 0x9AEE1, b"\x08")
if world.options.virtual_console in [2, 3]:
# Hyper Zone BB colors
patch.write_token(APTokenTypes.WRITE, 0x2C5E16, bytes([0xEE, 0x1B, 0x18, 0x5B, 0xD3, 0x4A, 0xF4, 0x3B, ]))
patch.write_token(APTokenTypes.WRITE, 0x2C8217, bytes([0xFF, 0x1E, ]))
# boss requirements
patch.write_token(APTokenTypes.WRITE, 0x3D000,
struct.pack("HHHHH", world.boss_requirements[0], world.boss_requirements[1],
world.boss_requirements[2], world.boss_requirements[3],
world.boss_requirements[4]))
patch.write_token(APTokenTypes.WRITE, 0x3D00A,
struct.pack("H", world.required_heart_stars if world.options.goal_speed == 1 else 0xFFFF))
patch.write_token(APTokenTypes.WRITE, 0x3D00C, world.options.goal_speed.value.to_bytes(2, "little"))
patch.write_token(APTokenTypes.WRITE, 0x3D00E, world.options.open_world.value.to_bytes(2, "little"))
patch.write_token(APTokenTypes.WRITE, 0x3D010, ((world.options.remote_items.value << 1) +
world.options.death_link.value).to_bytes(2, "little"))
patch.write_token(APTokenTypes.WRITE, 0x3D012, world.options.goal.value.to_bytes(2, "little"))
patch.write_token(APTokenTypes.WRITE, 0x3D014, world.options.stage_shuffle.value.to_bytes(2, "little"))
patch.write_token(APTokenTypes.WRITE, 0x3D016, world.options.ow_boss_requirement.value.to_bytes(2, "little"))
patch.write_token(APTokenTypes.WRITE, 0x3D018, world.options.consumables.value.to_bytes(2, "little"))
patch.write_token(APTokenTypes.WRITE, 0x3D01A, world.options.starsanity.value.to_bytes(2, "little"))
patch.write_token(APTokenTypes.WRITE, 0x3D01C, world.options.gifting.value.to_bytes(2, "little")
if world.multiworld.players > 1 else bytes([0, 0]))
patch.write_token(APTokenTypes.WRITE, 0x3D01E, world.options.strict_bosses.value.to_bytes(2, "little"))
# don't write gifting for solo game, since there's no one to send anything to
for level in world.player_levels:
for i in range(len(world.player_levels[level])):
patch.write_token(APTokenTypes.WRITE, 0x3F002E + ((level - 1) * 14) + (i * 2),
struct.pack("H", level_pointers[world.player_levels[level][i]]))
patch.write_token(APTokenTypes.WRITE, 0x3D020 + (level - 1) * 14 + (i * 2),
struct.pack("H", world.player_levels[level][i] & 0x00FFFF))
if (i == 0) or (i > 0 and i % 6 != 0):
patch.write_token(APTokenTypes.WRITE, 0x3D080 + (level - 1) * 12 + (i * 2),
struct.pack("H", (world.player_levels[level][i] & 0x00FFFF) % 6))
for i in range(6):
if world.boss_butch_bosses[i]:
patch.write_token(APTokenTypes.WRITE, 0x3F0000 + (level_pointers[0x770200 + i]),
struct.pack("I", bb_bosses[0x770200 + i]))
# copy ability shuffle
if world.options.copy_ability_randomization.value > 0:
for enemy in world.copy_abilities:
if enemy in miniboss_remap:
patch.write_token(APTokenTypes.WRITE, 0xB417E + (miniboss_remap[enemy] << 1),
struct.pack("H", ability_remap[world.copy_abilities[enemy]]))
else:
patch.write_token(APTokenTypes.WRITE, 0xB3CAC + (enemy_remap[enemy] << 1),
struct.pack("H", ability_remap[world.copy_abilities[enemy]]))
# following only needs done on non-door rando
# incredibly lucky this follows the same order (including 5E == star block)
patch.write_token(APTokenTypes.WRITE, 0x2F77EA,
(0x5E + (ability_remap[world.copy_abilities["Sparky"]] << 1)).to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x2F7811,
(0x5E + (ability_remap[world.copy_abilities["Sparky"]] << 1)).to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x2F9BC4,
(0x5E + (ability_remap[world.copy_abilities["Blocky"]] << 1)).to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x2F9BEB,
(0x5E + (ability_remap[world.copy_abilities["Blocky"]] << 1)).to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x2FAC06,
(0x5E + (ability_remap[world.copy_abilities["Jumper Shoot"]] << 1)).to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x2FAC2D,
(0x5E + (ability_remap[world.copy_abilities["Jumper Shoot"]] << 1)).to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x2F9E7B,
(0x5E + (ability_remap[world.copy_abilities["Yuki"]] << 1)).to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x2F9EA2,
(0x5E + (ability_remap[world.copy_abilities["Yuki"]] << 1)).to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x2FA951,
(0x5E + (ability_remap[world.copy_abilities["Sir Kibble"]] << 1)).to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x2FA978,
(0x5E + (ability_remap[world.copy_abilities["Sir Kibble"]] << 1)).to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x2FA132,
(0x5E + (ability_remap[world.copy_abilities["Haboki"]] << 1)).to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x2FA159,
(0x5E + (ability_remap[world.copy_abilities["Haboki"]] << 1)).to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x2FA3E8,
(0x5E + (ability_remap[world.copy_abilities["Boboo"]] << 1)).to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x2FA40F,
(0x5E + (ability_remap[world.copy_abilities["Boboo"]] << 1)).to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x2F90E2,
(0x5E + (ability_remap[world.copy_abilities["Captain Stitch"]] << 1)).to_bytes(1, "little"))
patch.write_token(APTokenTypes.WRITE, 0x2F9109,
(0x5E + (ability_remap[world.copy_abilities["Captain Stitch"]] << 1)).to_bytes(1, "little"))
if world.options.copy_ability_randomization == 2:
for enemy in enemy_remap:
# we just won't include it for minibosses
patch.write_token(APTokenTypes.WRITE, 0xB3E40 + (enemy_remap[enemy] << 1),
struct.pack("h", world.random.randint(-1, 2)))
# write jumping goal
patch.write_token(APTokenTypes.WRITE, 0x94F8, struct.pack("H", world.options.jumping_target))
patch.write_token(APTokenTypes.WRITE, 0x944E, struct.pack("H", world.options.jumping_target))
from Utils import __version__
patch_name = bytearray(
f'KDL3{__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0', 'utf8')[:21]
patch_name.extend([0] * (21 - len(patch_name)))
patch.name = bytes(patch_name)
patch.write_token(APTokenTypes.WRITE, 0x3C000, patch.name)
patch.write_token(APTokenTypes.WRITE, 0x3C020, world.options.game_language.value.to_bytes(1, "little"))
patch.write_token(APTokenTypes.COPY, 0x7FC0, (21, 0x3C000))
patch.write_token(APTokenTypes.COPY, 0x7FD9, (1, 0x3C020))
# handle palette
if world.options.kirby_flavor_preset.value != 0:
for addr in kirby_target_palettes:
target = kirby_target_palettes[addr]
palette = get_kirby_palette(world)
if palette is not None:
patch.write_token(APTokenTypes.WRITE, addr, get_palette_bytes(palette, target[0], target[1], target[2]))
if world.options.gooey_flavor_preset.value != 0:
for addr in gooey_target_palettes:
target = gooey_target_palettes[addr]
palette = get_gooey_palette(world)
if palette is not None:
patch.write_token(APTokenTypes.WRITE, addr, get_palette_bytes(palette, target[0], target[1], target[2]))
patch.write_file("token_patch.bin", patch.get_token_binary())
def get_base_rom_bytes() -> bytes:
rom_file: str = get_base_rom_path()
base_rom_bytes: Optional[bytes] = getattr(get_base_rom_bytes, "base_rom_bytes", None)
if not base_rom_bytes:
base_rom_bytes = bytes(Utils.read_snes_rom(open(rom_file, "rb")))
basemd5 = hashlib.md5()
basemd5.update(base_rom_bytes)
if basemd5.hexdigest() not in {KDL3UHASH, KDL3JHASH}:
raise Exception("Supplied Base Rom does not match known MD5 for US or JP release. "
"Get the correct game and version, then dump it")
get_base_rom_bytes.base_rom_bytes = base_rom_bytes
return base_rom_bytes
def get_base_rom_path(file_name: str = "") -> str:
options: settings.Settings = settings.get_settings()
if not file_name:
file_name = options["kdl3_options"]["rom_file"]
if not os.path.exists(file_name):
file_name = Utils.user_path(file_name)
return file_name

View File

@@ -1,133 +0,0 @@
import struct
from typing import Optional, Dict, TYPE_CHECKING, List, Union
from BaseClasses import Region, ItemClassification, MultiWorld
from worlds.Files import APTokenTypes
from .client_addrs import consumable_addrs, star_addrs
if TYPE_CHECKING:
from .rom import KDL3ProcedurePatch
animal_map = {
"Rick Spawn": 0,
"Kine Spawn": 1,
"Coo Spawn": 2,
"Nago Spawn": 3,
"ChuChu Spawn": 4,
"Pitch Spawn": 5
}
class KDL3Room(Region):
pointer: int = 0
level: int = 0
stage: int = 0
room: int = 0
music: int = 0
default_exits: List[Dict[str, Union[int, List[str]]]]
animal_pointers: List[int]
enemies: List[str]
entity_load: List[List[int]]
consumables: List[Dict[str, Union[int, str]]]
def __init__(self, name: str, player: int, multiworld: MultiWorld, hint: Optional[str], level: int,
stage: int, room: int, pointer: int, music: int,
default_exits: List[Dict[str, List[str]]],
animal_pointers: List[int], enemies: List[str],
entity_load: List[List[int]],
consumables: List[Dict[str, Union[int, str]]], consumable_pointer: int) -> None:
super().__init__(name, player, multiworld, hint)
self.level = level
self.stage = stage
self.room = room
self.pointer = pointer
self.music = music
self.default_exits = default_exits
self.animal_pointers = animal_pointers
self.enemies = enemies
self.entity_load = entity_load
self.consumables = consumables
self.consumable_pointer = consumable_pointer
def patch(self, patch: "KDL3ProcedurePatch", consumables: bool, local_items: bool) -> None:
patch.write_token(APTokenTypes.WRITE, self.pointer + 2, self.music.to_bytes(1, "little"))
animals = [x.item.name for x in self.locations if "Animal" in x.name and x.item]
if len(animals) > 0:
for current_animal, address in zip(animals, self.animal_pointers):
patch.write_token(APTokenTypes.WRITE, self.pointer + address + 7,
animal_map[current_animal].to_bytes(1, "little"))
if local_items:
for location in self.get_locations():
if location.item is None or location.item.player != self.player:
continue
item = location.item.code
if item is None:
continue
item_idx = item & 0x00000F
location_idx = location.address & 0xFFFF
if location_idx & 0xF00 in (0x300, 0x400, 0x500, 0x600):
# consumable or star, need remapped
location_base = location_idx & 0xF00
if location_base == 0x300:
# consumable
location_idx = consumable_addrs[location_idx & 0xFF] | 0x1000
else:
# star
location_idx = star_addrs[location.address] | 0x2000
if item & 0x000070 == 0:
patch.write_token(APTokenTypes.WRITE, 0x4B000 + location_idx, bytes([item_idx | 0x10]))
elif item & 0x000010 > 0:
patch.write_token(APTokenTypes.WRITE, 0x4B000 + location_idx, bytes([item_idx | 0x20]))
elif item & 0x000020 > 0:
patch.write_token(APTokenTypes.WRITE, 0x4B000 + location_idx, bytes([item_idx | 0x40]))
elif item & 0x000040 > 0:
patch.write_token(APTokenTypes.WRITE, 0x4B000 + location_idx, bytes([item_idx | 0x80]))
if consumables:
load_len = len(self.entity_load)
for consumable in self.consumables:
location = next(x for x in self.locations if x.name == consumable["name"])
assert location.item is not None
is_progression = location.item.classification & ItemClassification.progression
if load_len == 8:
# edge case, there is exactly 1 room with 8 entities and only 1 consumable among them
if not (any(x in self.entity_load for x in [[0, 22], [1, 22]])
and any(x in self.entity_load for x in [[2, 22], [3, 22]])):
replacement_target = self.entity_load.index(
next(x for x in self.entity_load if x in [[0, 22], [1, 22], [2, 22], [3, 22]]))
if is_progression:
vtype = 0
else:
vtype = 2
patch.write_token(APTokenTypes.WRITE, self.pointer + 88 + (replacement_target * 2),
vtype.to_bytes(1, "little"))
self.entity_load[replacement_target] = [vtype, 22]
else:
if is_progression:
# we need to see if 1-ups are in our load list
if any(x not in self.entity_load for x in [[0, 22], [1, 22]]):
self.entity_load.append([0, 22])
else:
if any(x not in self.entity_load for x in [[2, 22], [3, 22]]):
# edge case: if (1, 22) is in, we need to load (3, 22) instead
if [1, 22] in self.entity_load:
self.entity_load.append([3, 22])
else:
self.entity_load.append([2, 22])
if load_len < len(self.entity_load):
patch.write_token(APTokenTypes.WRITE, self.pointer + 88 + (load_len * 2),
bytes(self.entity_load[load_len]))
patch.write_token(APTokenTypes.WRITE, self.pointer + 104 + (load_len * 2),
bytes(struct.pack("H", self.consumable_pointer)))
if is_progression:
if [1, 22] in self.entity_load:
vtype = 1
else:
vtype = 0
else:
if [3, 22] in self.entity_load:
vtype = 3
else:
vtype = 2
assert isinstance(consumable["pointer"], int)
patch.write_token(APTokenTypes.WRITE, self.pointer + consumable["pointer"] + 7,
vtype.to_bytes(1, "little"))

View File

@@ -58,10 +58,6 @@ org $01AFC8
org $01B013
SEC ; Remove Dedede Bad Ending
org $01B050
JSL HookBossPurify
NOP
org $02B7B0 ; Zero unlock
LDA $80A0
CMP #$0001
@@ -164,6 +160,7 @@ CopyAbilityAnimalOverride:
STA $39DF, X
RTL
org $079A00
HeartStarCheck:
TXA
CMP #$0000 ; is this level 1
@@ -204,6 +201,7 @@ HeartStarCheck:
SEC
RTL
org $079A80
OpenWorldUnlock:
PHX
LDX $900E ; Are we on open world?
@@ -226,6 +224,7 @@ OpenWorldUnlock:
PLX
RTL
org $079B00
MainLoopHook:
STA $D4
INC $3524
@@ -240,18 +239,16 @@ MainLoopHook:
BEQ .Return ; return if we are
LDA $5541 ; gooey status
BPL .Slowness ; gooey is already spawned
LDA $39D1 ; is kirby alive?
BEQ .Slowness ; branch if he isn't
; maybe BMI here too?
LDA $8080
CMP #$0000 ; did we get a gooey trap
BEQ .Slowness ; branch if we did not
JSL GooeySpawn
DEC $8080
STZ $8080
.Slowness:
LDA $8082 ; slowness
BEQ .Eject ; are we under the effects of a slowness trap
DEC $8082 ; dec by 1 each frame
DEC
STA $8082 ; dec by 1 each frame
.Eject:
PHX
PHY
@@ -261,13 +258,14 @@ MainLoopHook:
BEQ .PullVars ; branch if we haven't received eject
LDA #$2000 ; select button press
STA $60C1 ; write to controller mirror
DEC $8084
STZ $8084
.PullVars:
PLY
PLX
.Return:
RTL
org $079B80
HeartStarGraphicFix:
LDA #$0000
PHX
@@ -290,7 +288,7 @@ HeartStarGraphicFix:
ASL
TAX
LDA $07D080, X ; table of original stage number
CMP #$0002 ; is the current stage a minigame stage?
CMP #$0003 ; is the current stage a minigame stage?
BEQ .ReturnTrue ; branch if so
CLC
BRA .Return
@@ -301,6 +299,7 @@ HeartStarGraphicFix:
PLX
RTL
org $079BF0
ParseItemQueue:
; Local item queue parsing
NOP
@@ -337,6 +336,8 @@ ParseItemQueue:
AND #$000F
ASL
TAY
LDA $8080,Y
BNE .LoopCheck
JSL .ApplyNegative
RTL
.ApplyAbility:
@@ -417,73 +418,35 @@ ParseItemQueue:
CPY #$0005
BCS .PlayNone
LDA $8080,Y
CPY #$0002
BNE .Increment
CLC
BNE .Return
LDA #$0384
ADC $8080, Y
BVC .PlayNegative
LDA #$FFFF
.PlayNegative:
STA $8080,Y
LDA #$00A7
BRA .PlaySFXLong
.Increment:
INC
STA $8080, Y
BRA .PlayNegative
.PlayNone:
LDA #$0000
BRA .PlaySFXLong
org $079D00
AnimalFriendSpawn:
PHA
CPX #$0002 ; is this an animal friend?
BNE .Return
XBA
PHA
PHX
PHA
LDX #$0000
.CheckSpawned:
LDA $05CA, X
BNE .Continue
LDA #$0002
CMP $074A, X
BNE .ContinueCheck
PLA
PHA
XBA
CMP $07CA, X
BEQ .AlreadySpawned
.ContinueCheck:
INX
INX
BRA .CheckSpawned
.Continue:
PLA
PLX
ASL
TAY
PLA
INC
CMP $8000, Y ; do we have this animal friend
BEQ .Return ; we have this animal friend
.False:
INX
.Return:
PLY
LDA #$9999
RTL
.AlreadySpawned:
PLA
PLX
ASL
TAY
PLA
BRA .False
org $079E00
WriteBWRAM:
LDY #$6001 ;starting addr
LDA #$1FFE ;bytes to write
@@ -516,6 +479,7 @@ WriteBWRAM:
.Return:
RTL
org $079E80
ConsumableSet:
PHA
PHX
@@ -543,6 +507,7 @@ ConsumableSet:
ASL
TAX
LDA $07D020, X ; current stage
DEC
ASL #6
TAX
PLA
@@ -554,16 +519,8 @@ ConsumableSet:
BRA .LoopHead ; return to loop head
.ApplyCheck:
LDA $A000, X ; consumables index
PHA
ORA #$0001
STA $A000, X
PLA
AND #$00FF
BNE .Return
TXA
ORA #$1000
JSL ApplyLocalCheck
.Return:
PLY
PLX
PLA
@@ -571,6 +528,7 @@ ConsumableSet:
AND #$00FF
RTL
org $079F00
NormalGoalSet:
PHX
LDA $07D012
@@ -591,6 +549,7 @@ NormalGoalSet:
STA $5AC1 ; cutscene
RTL
org $079F80
FinalIcebergFix:
PHX
PHY
@@ -613,7 +572,7 @@ FinalIcebergFix:
ASL
TAX
LDA $07D020, X
CMP #$001D
CMP #$001E
BEQ .ReturnTrue
CLC
BRA .Return
@@ -624,6 +583,7 @@ FinalIcebergFix:
PLX
RTL
org $07A000
StrictBosses:
PHX
LDA $901E ; Do we have strict bosses enabled?
@@ -650,6 +610,7 @@ StrictBosses:
LDA $53CD
RTL
org $07A030
NintenHalken:
LDX #$0005
.Halken:
@@ -667,6 +628,7 @@ NintenHalken:
LDA #$0001
RTL
org $07A080
StageCompleteSet:
PHX
LDA $5AC1 ; completed stage cutscene
@@ -694,17 +656,9 @@ StageCompleteSet:
ASL
TAX
LDA $9020, X ; load the stage we completed
DEC
ASL
TAX
PHX
LDA $8200, X
AND #$00FF
BNE .ApplyClear
TXA
LSR
JSL ApplyLocalCheck
.ApplyClear:
PLX
LDA #$0001
ORA $8200, X
STA $8200, X
@@ -714,6 +668,7 @@ StageCompleteSet:
CMP $53CB
RTL
org $07A100
OpenWorldBossUnlock:
PHX
PHY
@@ -744,6 +699,7 @@ OpenWorldBossUnlock:
.LoopStage:
PLX
LDY $9020, X ; get stage id
DEY
INX
INX
PHA
@@ -776,6 +732,7 @@ OpenWorldBossUnlock:
PLX
RTL
org $07A180
GooeySpawn:
PHY
PHX
@@ -811,6 +768,7 @@ GooeySpawn:
PLY
RTL
org $07A200
SpeedTrap:
PHX
LDX $8082 ; do we have slowness
@@ -822,6 +780,7 @@ SpeedTrap:
EOR #$FFFF
RTL
org $07A280
HeartStarVisual:
CPX #$0000
BEQ .SkipInx
@@ -885,6 +844,7 @@ HeartStarVisual:
.Return:
RTL
org $07A300
LoadFont:
JSL $00D29F ; play sfx
PHX
@@ -955,6 +915,7 @@ LoadFont:
PLX
RTL
org $07A380
HeartStarVisual2:
LDA #$2C80
STA $0000, Y
@@ -1068,12 +1029,14 @@ HeartStarVisual2:
STA $0000, Y
RTL
org $07A480
HeartStarSelectFix:
PHX
TXA
ASL
TAX
LDA $9020, X
DEC
TAX
.LoopHead:
CMP #$0006
@@ -1088,31 +1051,15 @@ HeartStarSelectFix:
AND #$00FF
RTL
org $07A500
HeartStarCutsceneFix:
TAX
LDA $53D3
DEC
STA $5AC3
LDA $53A7, X
AND #$00FF
BNE .Return
PHX
TXA
.Loop:
CMP #$0007
BCC .Continue
SEC
SBC #$0007
DEX
BRA .Loop
.Continue:
TXA
ORA #$0100
JSL ApplyLocalCheck
PLX
.Return
RTL
org $07A510
GiftGiving:
CMP #$0008
.This:
@@ -1128,6 +1075,7 @@ GiftGiving:
PLX
JML $CABC18
org $07A550
PauseMenu:
JSL $00D29F
PHX
@@ -1188,6 +1136,7 @@ PauseMenu:
PLX
RTL
org $07A600
StarsSet:
PHA
PHX
@@ -1217,6 +1166,7 @@ StarsSet:
ASL
TAX
LDA $07D020, X
DEC
ASL
ASL
ASL
@@ -1233,15 +1183,8 @@ StarsSet:
BRA .2LoopHead
.2LoopEnd:
LDA $B000, X
PHA
ORA #$0001
STA $B000, X
PLA
AND #$00FF
BNE .Return
TXA
ORA #$2000
JSL ApplyLocalCheck
.Return:
PLY
PLX
@@ -1256,48 +1199,6 @@ StarsSet:
STA $39D7
BRA .Return
ApplyLocalCheck:
; args: A-address of check following $08B000
TAX
LDA $09B000, X
AND #$00FF
TAY
LDX #$0000
.Loop:
LDA $C000, X
BEQ .Apply
INX
INX
CPX #$0010
BCC .Loop
BRA .Return ; this is dangerous, could lose a check here
.Apply:
TYA
STA $C000, X
.Return:
RTL
HookBossPurify:
ORA $B0
STA $53D5
LDA $B0
LDX #$0000
LSR
.Loop:
BIT #$0001
BNE .Apply
LSR
LSR
INX
CPX #$0005
BCS .Return
BRA .Loop
.Apply:
TXA
ORA #$0200
JSL ApplyLocalCheck
.Return:
RTL
org $07C000
db "KDL3_BASEPATCH_ARCHI"
@@ -1333,7 +1234,4 @@ org $07E040
db $3A, $01
db $3B, $05
db $3C, $05
db $3D, $05
org $07F000
incbin "APPauseIcons.dat"
db $3D, $05

View File

@@ -6,8 +6,6 @@ from test.bases import WorldTestBase
from test.general import gen_steps
from worlds import AutoWorld
from worlds.AutoWorld import call_all
# mypy: ignore-errors
# This is a copy of core code, and I'm not smart enough to solve the errors in here
class KDL3TestBase(WorldTestBase):

View File

@@ -5,12 +5,12 @@ class TestFastGoal(KDL3TestBase):
options = {
"open_world": False,
"goal_speed": "fast",
"max_heart_stars": 30,
"total_heart_stars": 30,
"heart_stars_required": 50,
"filler_percentage": 0,
}
def test_goal(self) -> None:
def test_goal(self):
self.assertBeatable(False)
heart_stars = self.get_items_by_name("Heart Star")
self.collect(heart_stars[0:14])
@@ -30,12 +30,12 @@ class TestNormalGoal(KDL3TestBase):
options = {
"open_world": False,
"goal_speed": "normal",
"max_heart_stars": 30,
"total_heart_stars": 30,
"heart_stars_required": 50,
"filler_percentage": 0,
}
def test_goal(self) -> None:
def test_goal(self):
self.assertBeatable(False)
heart_stars = self.get_items_by_name("Heart Star")
self.collect(heart_stars[0:14])
@@ -51,14 +51,14 @@ class TestNormalGoal(KDL3TestBase):
self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed))
self.assertBeatable(True)
def test_kine(self) -> None:
def test_kine(self):
self.collect_by_name(["Cutter", "Burning", "Heart Star"])
self.assertBeatable(False)
def test_cutter(self) -> None:
def test_cutter(self):
self.collect_by_name(["Kine", "Burning", "Heart Star"])
self.assertBeatable(False)
def test_burning(self) -> None:
def test_burning(self):
self.collect_by_name(["Cutter", "Kine", "Heart Star"])
self.assertBeatable(False)

View File

@@ -1,6 +1,6 @@
from . import KDL3TestBase
from ..names import location_name
from Options import PlandoConnection
from ..Names import LocationName
import typing
@@ -12,31 +12,31 @@ class TestLocations(KDL3TestBase):
# these ensure we can always reach all stages physically
}
def test_simple_heart_stars(self) -> None:
self.run_location_test(location_name.grass_land_muchi, ["ChuChu"])
self.run_location_test(location_name.grass_land_chao, ["Stone"])
self.run_location_test(location_name.grass_land_mine, ["Kine"])
self.run_location_test(location_name.ripple_field_kamuribana, ["Pitch", "Clean"])
self.run_location_test(location_name.ripple_field_bakasa, ["Kine", "Parasol"])
self.run_location_test(location_name.ripple_field_toad, ["Needle"])
self.run_location_test(location_name.ripple_field_mama_pitch, ["Pitch", "Kine", "Burning", "Stone"])
self.run_location_test(location_name.sand_canyon_auntie, ["Clean"])
self.run_location_test(location_name.sand_canyon_nyupun, ["ChuChu", "Cutter"])
self.run_location_test(location_name.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Ice"])
self.run_location_test(location_name.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Ice"])
self.run_location_test(location_name.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Needle"])
self.run_location_test(location_name.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Needle"])
self.run_location_test(location_name.cloudy_park_hibanamodoki, ["Coo", "Clean"])
self.run_location_test(location_name.cloudy_park_piyokeko, ["Needle"])
self.run_location_test(location_name.cloudy_park_mikarin, ["Coo"])
self.run_location_test(location_name.cloudy_park_pick, ["Rick"])
self.run_location_test(location_name.iceberg_kogoesou, ["Burning"])
self.run_location_test(location_name.iceberg_samus, ["Ice"])
self.run_location_test(location_name.iceberg_name, ["Burning", "Coo", "ChuChu"])
self.run_location_test(location_name.iceberg_angel, ["Cutter", "Burning", "Spark", "Parasol", "Needle", "Clean",
def test_simple_heart_stars(self):
self.run_location_test(LocationName.grass_land_muchi, ["ChuChu"])
self.run_location_test(LocationName.grass_land_chao, ["Stone"])
self.run_location_test(LocationName.grass_land_mine, ["Kine"])
self.run_location_test(LocationName.ripple_field_kamuribana, ["Pitch", "Clean"])
self.run_location_test(LocationName.ripple_field_bakasa, ["Kine", "Parasol"])
self.run_location_test(LocationName.ripple_field_toad, ["Needle"])
self.run_location_test(LocationName.ripple_field_mama_pitch, ["Pitch", "Kine", "Burning", "Stone"])
self.run_location_test(LocationName.sand_canyon_auntie, ["Clean"])
self.run_location_test(LocationName.sand_canyon_nyupun, ["ChuChu", "Cutter"])
self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Ice"])
self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Ice"]),
self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Needle"]),
self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Needle"]),
self.run_location_test(LocationName.cloudy_park_hibanamodoki, ["Coo", "Clean"])
self.run_location_test(LocationName.cloudy_park_piyokeko, ["Needle"])
self.run_location_test(LocationName.cloudy_park_mikarin, ["Coo"])
self.run_location_test(LocationName.cloudy_park_pick, ["Rick"])
self.run_location_test(LocationName.iceberg_kogoesou, ["Burning"])
self.run_location_test(LocationName.iceberg_samus, ["Ice"])
self.run_location_test(LocationName.iceberg_name, ["Burning", "Coo", "ChuChu"])
self.run_location_test(LocationName.iceberg_angel, ["Cutter", "Burning", "Spark", "Parasol", "Needle", "Clean",
"Stone", "Ice"])
def run_location_test(self, location: str, itempool: typing.List[str]) -> None:
def run_location_test(self, location: str, itempool: typing.List[str]):
items = itempool.copy()
while len(itempool) > 0:
self.assertFalse(self.can_reach_location(location), str(self.multiworld.seed))
@@ -57,7 +57,7 @@ class TestShiro(KDL3TestBase):
"plando_options": "connections"
}
def test_shiro(self) -> None:
def test_shiro(self):
self.assertFalse(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed))
self.collect_by_name("Nago")
self.assertFalse(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed))

View File

@@ -1,61 +1,47 @@
from typing import List, Tuple, Optional
from typing import List, Tuple
from . import KDL3TestBase
from ..room import KDL3Room
from ..names import animal_friend_spawns
from ..Room import KDL3Room
class TestCopyAbilityShuffle(KDL3TestBase):
options = {
"open_world": False,
"goal_speed": "normal",
"max_heart_stars": 30,
"total_heart_stars": 30,
"heart_stars_required": 50,
"filler_percentage": 0,
"copy_ability_randomization": "enabled",
}
def test_goal(self) -> None:
try:
self.assertBeatable(False)
heart_stars = self.get_items_by_name("Heart Star")
self.collect(heart_stars[0:14])
self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed))
self.assertBeatable(False)
self.collect(heart_stars[14:15])
self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed))
self.assertBeatable(False)
self.collect_by_name(["Burning", "Cutter", "Kine"])
self.assertBeatable(True)
self.remove([self.get_item_by_name("Love-Love Rod")])
self.collect(heart_stars)
self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed))
self.assertBeatable(True)
except AssertionError as ex:
# if assert beatable fails, this will catch and print the seed
raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex
def test_goal(self):
self.assertBeatable(False)
heart_stars = self.get_items_by_name("Heart Star")
self.collect(heart_stars[0:14])
self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed))
self.assertBeatable(False)
self.collect(heart_stars[14:15])
self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed))
self.assertBeatable(False)
self.collect_by_name(["Burning", "Cutter", "Kine"])
self.assertBeatable(True)
self.remove([self.get_item_by_name("Love-Love Rod")])
self.collect(heart_stars)
self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed))
self.assertBeatable(True)
def test_kine(self) -> None:
try:
self.collect_by_name(["Cutter", "Burning", "Heart Star"])
self.assertBeatable(False)
except AssertionError as ex:
raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex
def test_kine(self):
self.collect_by_name(["Cutter", "Burning", "Heart Star"])
self.assertBeatable(False)
def test_cutter(self) -> None:
try:
self.collect_by_name(["Kine", "Burning", "Heart Star"])
self.assertBeatable(False)
except AssertionError as ex:
raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex
def test_cutter(self):
self.collect_by_name(["Kine", "Burning", "Heart Star"])
self.assertBeatable(False)
def test_burning(self) -> None:
try:
self.collect_by_name(["Cutter", "Kine", "Heart Star"])
self.assertBeatable(False)
except AssertionError as ex:
raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex
def test_burning(self):
self.collect_by_name(["Cutter", "Kine", "Heart Star"])
self.assertBeatable(False)
def test_cutter_and_burning_reachable(self) -> None:
def test_cutter_and_burning_reachable(self):
rooms = self.multiworld.worlds[1].rooms
copy_abilities = self.multiworld.worlds[1].copy_abilities
sand_canyon_5 = self.multiworld.get_region("Sand Canyon 5 - 9", 1)
@@ -77,7 +63,7 @@ class TestCopyAbilityShuffle(KDL3TestBase):
else:
self.fail("Could not reach Burning Ability before Iceberg 4!")
def test_valid_abilities_for_ROB(self) -> None:
def test_valid_abilities_for_ROB(self):
# there exists a subset of 4-7 abilities that will allow us access to ROB heart star on default settings
self.collect_by_name(["Heart Star", "Kine", "Coo"]) # we will guaranteed need Coo, Kine, and Heart Stars to reach
# first we need to identify our bukiset requirements
@@ -88,13 +74,13 @@ class TestCopyAbilityShuffle(KDL3TestBase):
({"Stone Ability", "Burning Ability"}, {'Bukiset (Stone)', 'Bukiset (Burning)'}),
]
copy_abilities = self.multiworld.worlds[1].copy_abilities
required_abilities: List[List[str]] = []
required_abilities: List[Tuple[str]] = []
for abilities, bukisets in groups:
potential_abilities: List[str] = list()
for bukiset in bukisets:
if copy_abilities[bukiset] in abilities:
potential_abilities.append(copy_abilities[bukiset])
required_abilities.append(potential_abilities)
required_abilities.append(tuple(potential_abilities))
collected_abilities = list()
for group in required_abilities:
self.assertFalse(len(group) == 0, str(self.multiworld.seed))
@@ -117,147 +103,91 @@ class TestAnimalShuffle(KDL3TestBase):
options = {
"open_world": False,
"goal_speed": "normal",
"max_heart_stars": 30,
"total_heart_stars": 30,
"heart_stars_required": 50,
"filler_percentage": 0,
"animal_randomization": "full",
}
def test_goal(self) -> None:
try:
self.assertBeatable(False)
heart_stars = self.get_items_by_name("Heart Star")
self.collect(heart_stars[0:14])
self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed))
self.assertBeatable(False)
self.collect(heart_stars[14:15])
self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed))
self.assertBeatable(False)
self.collect_by_name(["Burning", "Cutter", "Kine"])
self.assertBeatable(True)
self.remove([self.get_item_by_name("Love-Love Rod")])
self.collect(heart_stars)
self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed))
self.assertBeatable(True)
except AssertionError as ex:
# if assert beatable fails, this will catch and print the seed
raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex
def test_goal(self):
self.assertBeatable(False)
heart_stars = self.get_items_by_name("Heart Star")
self.collect(heart_stars[0:14])
self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed))
self.assertBeatable(False)
self.collect(heart_stars[14:15])
self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed))
self.assertBeatable(False)
self.collect_by_name(["Burning", "Cutter", "Kine"])
self.assertBeatable(True)
self.remove([self.get_item_by_name("Love-Love Rod")])
self.collect(heart_stars)
self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed))
self.assertBeatable(True)
def test_kine(self) -> None:
try:
self.collect_by_name(["Cutter", "Burning", "Heart Star"])
self.assertBeatable(False)
except AssertionError as ex:
raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex
def test_kine(self):
self.collect_by_name(["Cutter", "Burning", "Heart Star"])
self.assertBeatable(False)
def test_cutter(self) -> None:
try:
self.collect_by_name(["Kine", "Burning", "Heart Star"])
self.assertBeatable(False)
except AssertionError as ex:
raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex
def test_cutter(self):
self.collect_by_name(["Kine", "Burning", "Heart Star"])
self.assertBeatable(False)
def test_burning(self) -> None:
try:
self.collect_by_name(["Cutter", "Kine", "Heart Star"])
self.assertBeatable(False)
except AssertionError as ex:
raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex
def test_burning(self):
self.collect_by_name(["Cutter", "Kine", "Heart Star"])
self.assertBeatable(False)
def test_locked_animals(self) -> None:
ripple_field_5 = self.multiworld.get_location("Ripple Field 5 - Animal 2", 1)
self.assertTrue(ripple_field_5.item is not None and ripple_field_5.item.name == "Pitch Spawn",
f"Multiworld did not place Pitch, Seed: {self.multiworld.seed}")
iceberg_4 = self.multiworld.get_location("Iceberg 4 - Animal 1", 1)
self.assertTrue(iceberg_4.item is not None and iceberg_4.item.name == "ChuChu Spawn",
f"Multiworld did not place ChuChu, Seed: {self.multiworld.seed}")
sand_canyon_6 = self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1)
self.assertTrue(sand_canyon_6.item is not None and sand_canyon_6.item.name in
{"Kine Spawn", "Coo Spawn"}, f"Multiworld did not place Coo/Kine, Seed: {self.multiworld.seed}")
def test_problematic(self) -> None:
for spawns in animal_friend_spawns.problematic_sets:
placed = [self.multiworld.get_location(spawn, 1).item for spawn in spawns]
placed_names = set([item.name for item in placed])
self.assertEqual(len(placed), len(placed_names),
f"Duplicate animal placed in problematic locations:"
f" {[spawn.location for spawn in placed]}, "
f"Seed: {self.multiworld.seed}")
def test_locked_animals(self):
self.assertTrue(self.multiworld.get_location("Ripple Field 5 - Animal 2", 1).item.name == "Pitch Spawn")
self.assertTrue(self.multiworld.get_location("Iceberg 4 - Animal 1", 1).item.name == "ChuChu Spawn")
self.assertTrue(self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1).item.name in {"Kine Spawn", "Coo Spawn"})
class TestAllShuffle(KDL3TestBase):
options = {
"open_world": False,
"goal_speed": "normal",
"max_heart_stars": 30,
"total_heart_stars": 30,
"heart_stars_required": 50,
"filler_percentage": 0,
"animal_randomization": "full",
"copy_ability_randomization": "enabled",
}
def test_goal(self) -> None:
try:
self.assertBeatable(False)
heart_stars = self.get_items_by_name("Heart Star")
self.collect(heart_stars[0:14])
self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed))
self.assertBeatable(False)
self.collect(heart_stars[14:15])
self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed))
self.assertBeatable(False)
self.collect_by_name(["Burning", "Cutter", "Kine"])
self.assertBeatable(True)
self.remove([self.get_item_by_name("Love-Love Rod")])
self.collect(heart_stars)
self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed))
self.assertBeatable(True)
except AssertionError as ex:
# if assert beatable fails, this will catch and print the seed
raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex
def test_goal(self):
self.assertBeatable(False)
heart_stars = self.get_items_by_name("Heart Star")
self.collect(heart_stars[0:14])
self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed))
self.assertBeatable(False)
self.collect(heart_stars[14:15])
self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed))
self.assertBeatable(False)
self.collect_by_name(["Burning", "Cutter", "Kine"])
self.assertBeatable(True)
self.remove([self.get_item_by_name("Love-Love Rod")])
self.collect(heart_stars)
self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed))
self.assertBeatable(True)
def test_kine(self) -> None:
try:
self.collect_by_name(["Cutter", "Burning", "Heart Star"])
self.assertBeatable(False)
except AssertionError as ex:
raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex
def test_kine(self):
self.collect_by_name(["Cutter", "Burning", "Heart Star"])
self.assertBeatable(False)
def test_cutter(self) -> None:
try:
self.collect_by_name(["Kine", "Burning", "Heart Star"])
self.assertBeatable(False)
except AssertionError as ex:
raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex
def test_cutter(self):
self.collect_by_name(["Kine", "Burning", "Heart Star"])
self.assertBeatable(False)
def test_burning(self) -> None:
try:
self.collect_by_name(["Cutter", "Kine", "Heart Star"])
self.assertBeatable(False)
except AssertionError as ex:
raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex
def test_burning(self):
self.collect_by_name(["Cutter", "Kine", "Heart Star"])
self.assertBeatable(False)
def test_locked_animals(self) -> None:
ripple_field_5 = self.multiworld.get_location("Ripple Field 5 - Animal 2", 1)
self.assertTrue(ripple_field_5.item is not None and ripple_field_5.item.name == "Pitch Spawn",
f"Multiworld did not place Pitch, Seed: {self.multiworld.seed}")
iceberg_4 = self.multiworld.get_location("Iceberg 4 - Animal 1", 1)
self.assertTrue(iceberg_4.item is not None and iceberg_4.item.name == "ChuChu Spawn",
f"Multiworld did not place ChuChu, Seed: {self.multiworld.seed}")
sand_canyon_6 = self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1)
self.assertTrue(sand_canyon_6.item is not None and sand_canyon_6.item.name in
{"Kine Spawn", "Coo Spawn"}, f"Multiworld did not place Coo/Kine, Seed: {self.multiworld.seed}")
def test_locked_animals(self):
self.assertTrue(self.multiworld.get_location("Ripple Field 5 - Animal 2", 1).item.name == "Pitch Spawn")
self.assertTrue(self.multiworld.get_location("Iceberg 4 - Animal 1", 1).item.name == "ChuChu Spawn")
self.assertTrue(self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1).item.name in {"Kine Spawn", "Coo Spawn"})
def test_problematic(self) -> None:
for spawns in animal_friend_spawns.problematic_sets:
placed = [self.multiworld.get_location(spawn, 1).item for spawn in spawns]
placed_names = set([item.name for item in placed])
self.assertEqual(len(placed), len(placed_names),
f"Duplicate animal placed in problematic locations:"
f" {[spawn.location for spawn in placed]}, "
f"Seed: {self.multiworld.seed}")
def test_cutter_and_burning_reachable(self) -> None:
def test_cutter_and_burning_reachable(self):
rooms = self.multiworld.worlds[1].rooms
copy_abilities = self.multiworld.worlds[1].copy_abilities
sand_canyon_5 = self.multiworld.get_region("Sand Canyon 5 - 9", 1)
@@ -279,7 +209,7 @@ class TestAllShuffle(KDL3TestBase):
else:
self.fail("Could not reach Burning Ability before Iceberg 4!")
def test_valid_abilities_for_ROB(self) -> None:
def test_valid_abilities_for_ROB(self):
# there exists a subset of 4-7 abilities that will allow us access to ROB heart star on default settings
self.collect_by_name(["Heart Star", "Kine", "Coo"]) # we will guaranteed need Coo, Kine, and Heart Stars to reach
# first we need to identify our bukiset requirements
@@ -290,13 +220,13 @@ class TestAllShuffle(KDL3TestBase):
({"Stone Ability", "Burning Ability"}, {'Bukiset (Stone)', 'Bukiset (Burning)'}),
]
copy_abilities = self.multiworld.worlds[1].copy_abilities
required_abilities: List[List[str]] = []
required_abilities: List[Tuple[str]] = []
for abilities, bukisets in groups:
potential_abilities: List[str] = list()
for bukiset in bukisets:
if copy_abilities[bukiset] in abilities:
potential_abilities.append(copy_abilities[bukiset])
required_abilities.append(potential_abilities)
required_abilities.append(tuple(potential_abilities))
collected_abilities = list()
for group in required_abilities:
self.assertFalse(len(group) == 0, str(self.multiworld.seed))
@@ -312,4 +242,4 @@ class TestAllShuffle(KDL3TestBase):
self.collect_by_name(["Cutter"])
self.assertTrue(self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B"),
f"Seed: {self.multiworld.seed}, Collected: {collected_abilities}")
''.join(str(self.multiworld.seed)).join(collected_abilities))

View File

@@ -1,258 +0,0 @@
from __future__ import annotations
import os
import json
import sys
import asyncio
import shutil
import logging
import re
import time
from calendar import timegm
import ModuleUpdate
ModuleUpdate.update()
import Utils
death_link = False
item_num = 1
logger = logging.getLogger("Client")
if __name__ == "__main__":
Utils.init_logging("KH1Client", exception_logger="Client")
from NetUtils import NetworkItem, ClientStatus
from CommonClient import gui_enabled, logger, get_base_parser, ClientCommandProcessor, \
CommonContext, server_loop
def check_stdin() -> None:
if Utils.is_windows and sys.stdin:
print("WARNING: Console input is not routed reliably on Windows, use the GUI instead.")
class KH1ClientCommandProcessor(ClientCommandProcessor):
def _cmd_deathlink(self):
"""Toggles Deathlink"""
global death_link
if death_link:
death_link = False
self.output(f"Death Link turned off")
else:
death_link = True
self.output(f"Death Link turned on")
class KH1Context(CommonContext):
command_processor: int = KH1ClientCommandProcessor
game = "Kingdom Hearts"
items_handling = 0b111 # full remote
def __init__(self, server_address, password):
super(KH1Context, self).__init__(server_address, password)
self.send_index: int = 0
self.syncing = False
self.awaiting_bridge = False
# self.game_communication_path: files go in this path to pass data between us and the actual game
if "localappdata" in os.environ:
self.game_communication_path = os.path.expandvars(r"%localappdata%/KH1FM")
else:
self.game_communication_path = os.path.expandvars(r"$HOME/KH1FM")
if not os.path.exists(self.game_communication_path):
os.makedirs(self.game_communication_path)
for root, dirs, files in os.walk(self.game_communication_path):
for file in files:
if file.find("obtain") <= -1:
os.remove(root+"/"+file)
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(KH1Context, self).server_auth(password_requested)
await self.get_username()
await self.send_connect()
async def connection_closed(self):
await super(KH1Context, self).connection_closed()
for root, dirs, files in os.walk(self.game_communication_path):
for file in files:
if file.find("obtain") <= -1:
os.remove(root + "/" + file)
global item_num
item_num = 1
@property
def endpoints(self):
if self.server:
return [self.server]
else:
return []
async def shutdown(self):
await super(KH1Context, self).shutdown()
for root, dirs, files in os.walk(self.game_communication_path):
for file in files:
if file.find("obtain") <= -1:
os.remove(root+"/"+file)
global item_num
item_num = 1
def on_package(self, cmd: str, args: dict):
if cmd in {"Connected"}:
if not os.path.exists(self.game_communication_path):
os.makedirs(self.game_communication_path)
for ss in self.checked_locations:
filename = f"send{ss}"
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
f.close()
#Handle Slot Data
for key in list(args['slot_data'].keys()):
with open(os.path.join(self.game_communication_path, key + ".cfg"), 'w') as f:
f.write(str(args['slot_data'][key]))
f.close()
###Support Legacy Games
if "Required Reports" in list(args['slot_data'].keys()) and "required_reports_eotw" not in list(args['slot_data'].keys()):
reports_required = args['slot_data']["Required Reports"]
with open(os.path.join(self.game_communication_path, "required_reports.cfg"), 'w') as f:
f.write(str(reports_required))
f.close()
###End Support Legacy Games
#End Handle Slot Data
if cmd in {"ReceivedItems"}:
start_index = args["index"]
if start_index != len(self.items_received):
global item_num
for item in args['items']:
found = False
item_filename = f"AP_{str(item_num)}.item"
for filename in os.listdir(self.game_communication_path):
if filename == item_filename:
found = True
if not found:
with open(os.path.join(self.game_communication_path, item_filename), 'w') as f:
f.write(str(NetworkItem(*item).item) + "\n" + str(NetworkItem(*item).location) + "\n" + str(NetworkItem(*item).player))
f.close()
item_num = item_num + 1
if cmd in {"RoomUpdate"}:
if "checked_locations" in args:
for ss in self.checked_locations:
filename = f"send{ss}"
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
f.close()
if cmd in {"PrintJSON"} and "type" in args:
if args["type"] == "ItemSend":
item = args["item"]
networkItem = NetworkItem(*item)
recieverID = args["receiving"]
senderID = networkItem.player
locationID = networkItem.location
if recieverID != self.slot and senderID == self.slot:
itemName = self.item_names.lookup_in_slot(networkItem.item, recieverID)
itemCategory = networkItem.flags
recieverName = self.player_names[recieverID]
filename = "sent"
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
f.write(
re.sub('[^A-Za-z0-9 ]+', '',str(itemName))[:15] + "\n"
+ re.sub('[^A-Za-z0-9 ]+', '',str(recieverName))[:6] + "\n"
+ str(itemCategory) + "\n"
+ str(locationID))
f.close()
def on_deathlink(self, data: dict[str, object]):
self.last_death_link = max(data["time"], self.last_death_link)
text = data.get("cause", "")
if text:
logger.info(f"DeathLink: {text}")
else:
logger.info(f"DeathLink: Received from {data['source']}")
with open(os.path.join(self.game_communication_path, 'dlreceive'), 'w') as f:
f.write(str(int(data["time"])))
f.close()
def run_gui(self):
"""Import kivy UI system and start running it as self.ui_task."""
from kvui import GameManager
class KH1Manager(GameManager):
logging_pairs = [
("Client", "Archipelago")
]
base_title = "Archipelago KH1 Client"
self.ui = KH1Manager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
async def game_watcher(ctx: KH1Context):
from .Locations import lookup_id_to_name
while not ctx.exit_event.is_set():
global death_link
if death_link and "DeathLink" not in ctx.tags:
await ctx.update_death_link(death_link)
if not death_link and "DeathLink" in ctx.tags:
await ctx.update_death_link(death_link)
if ctx.syncing == True:
sync_msg = [{'cmd': 'Sync'}]
if ctx.locations_checked:
sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)})
await ctx.send_msgs(sync_msg)
ctx.syncing = False
sending = []
victory = False
for root, dirs, files in os.walk(ctx.game_communication_path):
for file in files:
if file.find("send") > -1:
st = file.split("send", -1)[1]
if st != "nil":
sending = sending+[(int(st))]
if file.find("victory") > -1:
victory = True
if file.find("dlsend") > -1 and "DeathLink" in ctx.tags:
st = file.split("dlsend", -1)[1]
if st != "nil":
if timegm(time.strptime(st, '%Y%m%d%H%M%S')) > ctx.last_death_link and int(time.time()) % int(timegm(time.strptime(st, '%Y%m%d%H%M%S'))) < 10:
await ctx.send_death(death_text = "Sora was defeated!")
if file.find("insynthshop") > -1:
await ctx.send_msgs([{
"cmd": "LocationScouts",
"locations": [2656401,2656402,2656403,2656404,2656405,2656406],
"create_as_hint": 2
}])
ctx.locations_checked = sending
message = [{"cmd": 'LocationChecks', "locations": sending}]
await ctx.send_msgs(message)
if not ctx.finished_game and victory:
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True
await asyncio.sleep(0.1)
def launch():
async def main(args):
ctx = KH1Context(args.connect, args.password)
ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop")
if gui_enabled:
ctx.run_gui()
ctx.run_cli()
progression_watcher = asyncio.create_task(
game_watcher(ctx), name="KH1ProgressionWatcher")
await ctx.exit_event.wait()
ctx.server_address = None
await progression_watcher
await ctx.shutdown()
import colorama
parser = get_base_parser(description="KH1 Client, for text interfacing.")
args, rest = parser.parse_known_args()
colorama.init()
asyncio.run(main(args))
colorama.deinit()

View File

@@ -1,532 +0,0 @@
from typing import Dict, NamedTuple, Optional, Set
from BaseClasses import Item, ItemClassification
class KH1Item(Item):
game: str = "Kingdom Hearts"
class KH1ItemData(NamedTuple):
category: str
code: int
classification: ItemClassification = ItemClassification.filler
max_quantity: int = 1
weight: int = 1
def get_items_by_category(category: str) -> Dict[str, KH1ItemData]:
item_dict: Dict[str, KH1ItemData] = {}
for name, data in item_table.items():
if data.category == category:
item_dict.setdefault(name, data)
return item_dict
item_table: Dict[str, KH1ItemData] = {
"Victory": KH1ItemData("VIC", code = 264_0000, classification = ItemClassification.progression, ),
"Potion": KH1ItemData("Item", code = 264_1001, classification = ItemClassification.filler, ),
"Hi-Potion": KH1ItemData("Item", code = 264_1002, classification = ItemClassification.filler, ),
"Ether": KH1ItemData("Item", code = 264_1003, classification = ItemClassification.filler, ),
"Elixir": KH1ItemData("Item", code = 264_1004, classification = ItemClassification.filler, ),
#"B05": KH1ItemData("Item", code = 264_1005, classification = ItemClassification.filler, ),
"Mega-Potion": KH1ItemData("Item", code = 264_1006, classification = ItemClassification.filler, ),
"Mega-Ether": KH1ItemData("Item", code = 264_1007, classification = ItemClassification.filler, ),
"Megalixir": KH1ItemData("Item", code = 264_1008, classification = ItemClassification.filler, ),
#"Fury Stone": KH1ItemData("Synthesis", code = 264_1009, classification = ItemClassification.filler, ),
#"Power Stone": KH1ItemData("Synthesis", code = 264_1010, classification = ItemClassification.filler, ),
#"Energy Stone": KH1ItemData("Synthesis", code = 264_1011, classification = ItemClassification.filler, ),
#"Blazing Stone": KH1ItemData("Synthesis", code = 264_1012, classification = ItemClassification.filler, ),
#"Frost Stone": KH1ItemData("Synthesis", code = 264_1013, classification = ItemClassification.filler, ),
#"Lightning Stone": KH1ItemData("Synthesis", code = 264_1014, classification = ItemClassification.filler, ),
#"Dazzling Stone": KH1ItemData("Synthesis", code = 264_1015, classification = ItemClassification.filler, ),
#"Stormy Stone": KH1ItemData("Synthesis", code = 264_1016, classification = ItemClassification.filler, ),
"Protect Chain": KH1ItemData("Accessory", code = 264_1017, classification = ItemClassification.useful, ),
"Protera Chain": KH1ItemData("Accessory", code = 264_1018, classification = ItemClassification.useful, ),
"Protega Chain": KH1ItemData("Accessory", code = 264_1019, classification = ItemClassification.useful, ),
"Fire Ring": KH1ItemData("Accessory", code = 264_1020, classification = ItemClassification.useful, ),
"Fira Ring": KH1ItemData("Accessory", code = 264_1021, classification = ItemClassification.useful, ),
"Firaga Ring": KH1ItemData("Accessory", code = 264_1022, classification = ItemClassification.useful, ),
"Blizzard Ring": KH1ItemData("Accessory", code = 264_1023, classification = ItemClassification.useful, ),
"Blizzara Ring": KH1ItemData("Accessory", code = 264_1024, classification = ItemClassification.useful, ),
"Blizzaga Ring": KH1ItemData("Accessory", code = 264_1025, classification = ItemClassification.useful, ),
"Thunder Ring": KH1ItemData("Accessory", code = 264_1026, classification = ItemClassification.useful, ),
"Thundara Ring": KH1ItemData("Accessory", code = 264_1027, classification = ItemClassification.useful, ),
"Thundaga Ring": KH1ItemData("Accessory", code = 264_1028, classification = ItemClassification.useful, ),
"Ability Stud": KH1ItemData("Accessory", code = 264_1029, classification = ItemClassification.useful, ),
"Guard Earring": KH1ItemData("Accessory", code = 264_1030, classification = ItemClassification.useful, ),
"Master Earring": KH1ItemData("Accessory", code = 264_1031, classification = ItemClassification.useful, ),
"Chaos Ring": KH1ItemData("Accessory", code = 264_1032, classification = ItemClassification.useful, ),
"Dark Ring": KH1ItemData("Accessory", code = 264_1033, classification = ItemClassification.useful, ),
"Element Ring": KH1ItemData("Accessory", code = 264_1034, classification = ItemClassification.useful, ),
"Three Stars": KH1ItemData("Accessory", code = 264_1035, classification = ItemClassification.useful, ),
"Power Chain": KH1ItemData("Accessory", code = 264_1036, classification = ItemClassification.useful, ),
"Golem Chain": KH1ItemData("Accessory", code = 264_1037, classification = ItemClassification.useful, ),
"Titan Chain": KH1ItemData("Accessory", code = 264_1038, classification = ItemClassification.useful, ),
"Energy Bangle": KH1ItemData("Accessory", code = 264_1039, classification = ItemClassification.useful, ),
"Angel Bangle": KH1ItemData("Accessory", code = 264_1040, classification = ItemClassification.useful, ),
"Gaia Bangle": KH1ItemData("Accessory", code = 264_1041, classification = ItemClassification.useful, ),
"Magic Armlet": KH1ItemData("Accessory", code = 264_1042, classification = ItemClassification.useful, ),
"Rune Armlet": KH1ItemData("Accessory", code = 264_1043, classification = ItemClassification.useful, ),
"Atlas Armlet": KH1ItemData("Accessory", code = 264_1044, classification = ItemClassification.useful, ),
"Heartguard": KH1ItemData("Accessory", code = 264_1045, classification = ItemClassification.useful, ),
"Ribbon": KH1ItemData("Accessory", code = 264_1046, classification = ItemClassification.useful, ),
"Crystal Crown": KH1ItemData("Accessory", code = 264_1047, classification = ItemClassification.useful, ),
"Brave Warrior": KH1ItemData("Accessory", code = 264_1048, classification = ItemClassification.useful, ),
"Ifrit's Horn": KH1ItemData("Accessory", code = 264_1049, classification = ItemClassification.useful, ),
"Inferno Band": KH1ItemData("Accessory", code = 264_1050, classification = ItemClassification.useful, ),
"White Fang": KH1ItemData("Accessory", code = 264_1051, classification = ItemClassification.useful, ),
"Ray of Light": KH1ItemData("Accessory", code = 264_1052, classification = ItemClassification.useful, ),
"Holy Circlet": KH1ItemData("Accessory", code = 264_1053, classification = ItemClassification.useful, ),
"Raven's Claw": KH1ItemData("Accessory", code = 264_1054, classification = ItemClassification.useful, ),
"Omega Arts": KH1ItemData("Accessory", code = 264_1055, classification = ItemClassification.useful, ),
"EXP Earring": KH1ItemData("Accessory", code = 264_1056, classification = ItemClassification.useful, ),
#"A41": KH1ItemData("Accessory", code = 264_1057, classification = ItemClassification.useful, ),
"EXP Ring": KH1ItemData("Accessory", code = 264_1058, classification = ItemClassification.useful, ),
"EXP Bracelet": KH1ItemData("Accessory", code = 264_1059, classification = ItemClassification.useful, ),
"EXP Necklace": KH1ItemData("Accessory", code = 264_1060, classification = ItemClassification.useful, ),
"Firagun Band": KH1ItemData("Accessory", code = 264_1061, classification = ItemClassification.useful, ),
"Blizzagun Band": KH1ItemData("Accessory", code = 264_1062, classification = ItemClassification.useful, ),
"Thundagun Band": KH1ItemData("Accessory", code = 264_1063, classification = ItemClassification.useful, ),
"Ifrit Belt": KH1ItemData("Accessory", code = 264_1064, classification = ItemClassification.useful, ),
"Shiva Belt": KH1ItemData("Accessory", code = 264_1065, classification = ItemClassification.useful, ),
"Ramuh Belt": KH1ItemData("Accessory", code = 264_1066, classification = ItemClassification.useful, ),
"Moogle Badge": KH1ItemData("Accessory", code = 264_1067, classification = ItemClassification.useful, ),
"Cosmic Arts": KH1ItemData("Accessory", code = 264_1068, classification = ItemClassification.useful, ),
"Royal Crown": KH1ItemData("Accessory", code = 264_1069, classification = ItemClassification.useful, ),
"Prime Cap": KH1ItemData("Accessory", code = 264_1070, classification = ItemClassification.useful, ),
"Obsidian Ring": KH1ItemData("Accessory", code = 264_1071, classification = ItemClassification.useful, ),
#"A56": KH1ItemData("Accessory", code = 264_1072, classification = ItemClassification.filler, ),
#"A57": KH1ItemData("Accessory", code = 264_1073, classification = ItemClassification.filler, ),
#"A58": KH1ItemData("Accessory", code = 264_1074, classification = ItemClassification.filler, ),
#"A59": KH1ItemData("Accessory", code = 264_1075, classification = ItemClassification.filler, ),
#"A60": KH1ItemData("Accessory", code = 264_1076, classification = ItemClassification.filler, ),
#"A61": KH1ItemData("Accessory", code = 264_1077, classification = ItemClassification.filler, ),
#"A62": KH1ItemData("Accessory", code = 264_1078, classification = ItemClassification.filler, ),
#"A63": KH1ItemData("Accessory", code = 264_1079, classification = ItemClassification.filler, ),
#"A64": KH1ItemData("Accessory", code = 264_1080, classification = ItemClassification.filler, ),
#"Kingdom Key": KH1ItemData("Keyblades", code = 264_1081, classification = ItemClassification.useful, ),
#"Dream Sword": KH1ItemData("Keyblades", code = 264_1082, classification = ItemClassification.useful, ),
#"Dream Shield": KH1ItemData("Keyblades", code = 264_1083, classification = ItemClassification.useful, ),
#"Dream Rod": KH1ItemData("Keyblades", code = 264_1084, classification = ItemClassification.useful, ),
"Wooden Sword": KH1ItemData("Keyblades", code = 264_1085, classification = ItemClassification.useful, ),
"Jungle King": KH1ItemData("Keyblades", code = 264_1086, classification = ItemClassification.progression, ),
"Three Wishes": KH1ItemData("Keyblades", code = 264_1087, classification = ItemClassification.progression, ),
"Fairy Harp": KH1ItemData("Keyblades", code = 264_1088, classification = ItemClassification.progression, ),
"Pumpkinhead": KH1ItemData("Keyblades", code = 264_1089, classification = ItemClassification.progression, ),
"Crabclaw": KH1ItemData("Keyblades", code = 264_1090, classification = ItemClassification.useful, ),
"Divine Rose": KH1ItemData("Keyblades", code = 264_1091, classification = ItemClassification.progression, ),
"Spellbinder": KH1ItemData("Keyblades", code = 264_1092, classification = ItemClassification.useful, ),
"Olympia": KH1ItemData("Keyblades", code = 264_1093, classification = ItemClassification.progression, ),
"Lionheart": KH1ItemData("Keyblades", code = 264_1094, classification = ItemClassification.progression, ),
"Metal Chocobo": KH1ItemData("Keyblades", code = 264_1095, classification = ItemClassification.useful, ),
"Oathkeeper": KH1ItemData("Keyblades", code = 264_1096, classification = ItemClassification.progression, ),
"Oblivion": KH1ItemData("Keyblades", code = 264_1097, classification = ItemClassification.progression, ),
"Lady Luck": KH1ItemData("Keyblades", code = 264_1098, classification = ItemClassification.progression, ),
"Wishing Star": KH1ItemData("Keyblades", code = 264_1099, classification = ItemClassification.progression, ),
"Ultima Weapon": KH1ItemData("Keyblades", code = 264_1100, classification = ItemClassification.useful, ),
"Diamond Dust": KH1ItemData("Keyblades", code = 264_1101, classification = ItemClassification.useful, ),
"One-Winged Angel": KH1ItemData("Keyblades", code = 264_1102, classification = ItemClassification.useful, ),
#"Mage's Staff": KH1ItemData("Weapons", code = 264_1103, classification = ItemClassification.filler, ),
"Morning Star": KH1ItemData("Weapons", code = 264_1104, classification = ItemClassification.useful, ),
"Shooting Star": KH1ItemData("Weapons", code = 264_1105, classification = ItemClassification.useful, ),
"Magus Staff": KH1ItemData("Weapons", code = 264_1106, classification = ItemClassification.useful, ),
"Wisdom Staff": KH1ItemData("Weapons", code = 264_1107, classification = ItemClassification.useful, ),
"Warhammer": KH1ItemData("Weapons", code = 264_1108, classification = ItemClassification.useful, ),
"Silver Mallet": KH1ItemData("Weapons", code = 264_1109, classification = ItemClassification.useful, ),
"Grand Mallet": KH1ItemData("Weapons", code = 264_1110, classification = ItemClassification.useful, ),
"Lord Fortune": KH1ItemData("Weapons", code = 264_1111, classification = ItemClassification.useful, ),
"Violetta": KH1ItemData("Weapons", code = 264_1112, classification = ItemClassification.useful, ),
"Dream Rod (Donald)": KH1ItemData("Weapons", code = 264_1113, classification = ItemClassification.useful, ),
"Save the Queen": KH1ItemData("Weapons", code = 264_1114, classification = ItemClassification.useful, ),
"Wizard's Relic": KH1ItemData("Weapons", code = 264_1115, classification = ItemClassification.useful, ),
"Meteor Strike": KH1ItemData("Weapons", code = 264_1116, classification = ItemClassification.useful, ),
"Fantasista": KH1ItemData("Weapons", code = 264_1117, classification = ItemClassification.useful, ),
#"Unused (Donald)": KH1ItemData("Weapons", code = 264_1118, classification = ItemClassification.filler, ),
#"Knight's Shield": KH1ItemData("Weapons", code = 264_1119, classification = ItemClassification.filler, ),
"Mythril Shield": KH1ItemData("Weapons", code = 264_1120, classification = ItemClassification.useful, ),
"Onyx Shield": KH1ItemData("Weapons", code = 264_1121, classification = ItemClassification.useful, ),
"Stout Shield": KH1ItemData("Weapons", code = 264_1122, classification = ItemClassification.useful, ),
"Golem Shield": KH1ItemData("Weapons", code = 264_1123, classification = ItemClassification.useful, ),
"Adamant Shield": KH1ItemData("Weapons", code = 264_1124, classification = ItemClassification.useful, ),
"Smasher": KH1ItemData("Weapons", code = 264_1125, classification = ItemClassification.useful, ),
"Gigas Fist": KH1ItemData("Weapons", code = 264_1126, classification = ItemClassification.useful, ),
"Genji Shield": KH1ItemData("Weapons", code = 264_1127, classification = ItemClassification.useful, ),
"Herc's Shield": KH1ItemData("Weapons", code = 264_1128, classification = ItemClassification.useful, ),
"Dream Shield (Goofy)": KH1ItemData("Weapons", code = 264_1129, classification = ItemClassification.useful, ),
"Save the King": KH1ItemData("Weapons", code = 264_1130, classification = ItemClassification.useful, ),
"Defender": KH1ItemData("Weapons", code = 264_1131, classification = ItemClassification.useful, ),
"Mighty Shield": KH1ItemData("Weapons", code = 264_1132, classification = ItemClassification.useful, ),
"Seven Elements": KH1ItemData("Weapons", code = 264_1133, classification = ItemClassification.useful, ),
#"Unused (Goofy)": KH1ItemData("Weapons", code = 264_1134, classification = ItemClassification.filler, ),
#"Spear": KH1ItemData("Weapons", code = 264_1135, classification = ItemClassification.filler, ),
#"No Weapon": KH1ItemData("Weapons", code = 264_1136, classification = ItemClassification.filler, ),
#"Genie": KH1ItemData("Weapons", code = 264_1137, classification = ItemClassification.filler, ),
#"No Weapon": KH1ItemData("Weapons", code = 264_1138, classification = ItemClassification.filler, ),
#"No Weapon": KH1ItemData("Weapons", code = 264_1139, classification = ItemClassification.filler, ),
#"Tinker Bell": KH1ItemData("Weapons", code = 264_1140, classification = ItemClassification.filler, ),
#"Claws": KH1ItemData("Weapons", code = 264_1141, classification = ItemClassification.filler, ),
"Tent": KH1ItemData("Camping", code = 264_1142, classification = ItemClassification.filler, ),
"Camping Set": KH1ItemData("Camping", code = 264_1143, classification = ItemClassification.filler, ),
"Cottage": KH1ItemData("Camping", code = 264_1144, classification = ItemClassification.filler, ),
#"C04": KH1ItemData("Camping", code = 264_1145, classification = ItemClassification.filler, ),
#"C05": KH1ItemData("Camping", code = 264_1146, classification = ItemClassification.filler, ),
#"C06": KH1ItemData("Camping", code = 264_1147, classification = ItemClassification.filler, ),
#"C07": KH1ItemData("Camping", code = 264_1148, classification = ItemClassification.filler, ),
"Ansem's Report 11": KH1ItemData("Reports", code = 264_1149, classification = ItemClassification.progression, ),
"Ansem's Report 12": KH1ItemData("Reports", code = 264_1150, classification = ItemClassification.progression, ),
"Ansem's Report 13": KH1ItemData("Reports", code = 264_1151, classification = ItemClassification.progression, ),
"Power Up": KH1ItemData("Stat Ups", code = 264_1152, classification = ItemClassification.filler, ),
"Defense Up": KH1ItemData("Stat Ups", code = 264_1153, classification = ItemClassification.filler, ),
"AP Up": KH1ItemData("Stat Ups", code = 264_1154, classification = ItemClassification.filler, ),
#"Serenity Power": KH1ItemData("Synthesis", code = 264_1155, classification = ItemClassification.filler, ),
#"Dark Matter": KH1ItemData("Synthesis", code = 264_1156, classification = ItemClassification.filler, ),
#"Mythril Stone": KH1ItemData("Synthesis", code = 264_1157, classification = ItemClassification.filler, ),
"Fire Arts": KH1ItemData("Key", code = 264_1158, classification = ItemClassification.progression, ),
"Blizzard Arts": KH1ItemData("Key", code = 264_1159, classification = ItemClassification.progression, ),
"Thunder Arts": KH1ItemData("Key", code = 264_1160, classification = ItemClassification.progression, ),
"Cure Arts": KH1ItemData("Key", code = 264_1161, classification = ItemClassification.progression, ),
"Gravity Arts": KH1ItemData("Key", code = 264_1162, classification = ItemClassification.progression, ),
"Stop Arts": KH1ItemData("Key", code = 264_1163, classification = ItemClassification.progression, ),
"Aero Arts": KH1ItemData("Key", code = 264_1164, classification = ItemClassification.progression, ),
#"Shiitank Rank": KH1ItemData("Synthesis", code = 264_1165, classification = ItemClassification.filler, ),
#"Matsutake Rank": KH1ItemData("Synthesis", code = 264_1166, classification = ItemClassification.filler, ),
#"Mystery Mold": KH1ItemData("Synthesis", code = 264_1167, classification = ItemClassification.filler, ),
"Ansem's Report 1": KH1ItemData("Reports", code = 264_1168, classification = ItemClassification.progression, ),
"Ansem's Report 2": KH1ItemData("Reports", code = 264_1169, classification = ItemClassification.progression, ),
"Ansem's Report 3": KH1ItemData("Reports", code = 264_1170, classification = ItemClassification.progression, ),
"Ansem's Report 4": KH1ItemData("Reports", code = 264_1171, classification = ItemClassification.progression, ),
"Ansem's Report 5": KH1ItemData("Reports", code = 264_1172, classification = ItemClassification.progression, ),
"Ansem's Report 6": KH1ItemData("Reports", code = 264_1173, classification = ItemClassification.progression, ),
"Ansem's Report 7": KH1ItemData("Reports", code = 264_1174, classification = ItemClassification.progression, ),
"Ansem's Report 8": KH1ItemData("Reports", code = 264_1175, classification = ItemClassification.progression, ),
"Ansem's Report 9": KH1ItemData("Reports", code = 264_1176, classification = ItemClassification.progression, ),
"Ansem's Report 10": KH1ItemData("Reports", code = 264_1177, classification = ItemClassification.progression, ),
#"Khama Vol. 8": KH1ItemData("Key", code = 264_1178, classification = ItemClassification.progression, ),
#"Salegg Vol. 6": KH1ItemData("Key", code = 264_1179, classification = ItemClassification.progression, ),
#"Azal Vol. 3": KH1ItemData("Key", code = 264_1180, classification = ItemClassification.progression, ),
#"Mava Vol. 3": KH1ItemData("Key", code = 264_1181, classification = ItemClassification.progression, ),
#"Mava Vol. 6": KH1ItemData("Key", code = 264_1182, classification = ItemClassification.progression, ),
"Theon Vol. 6": KH1ItemData("Key", code = 264_1183, classification = ItemClassification.progression, ),
#"Nahara Vol. 5": KH1ItemData("Key", code = 264_1184, classification = ItemClassification.progression, ),
#"Hafet Vol. 4": KH1ItemData("Key", code = 264_1185, classification = ItemClassification.progression, ),
"Empty Bottle": KH1ItemData("Key", code = 264_1186, classification = ItemClassification.progression, max_quantity = 6 ),
#"Old Book": KH1ItemData("Key", code = 264_1187, classification = ItemClassification.progression, ),
"Emblem Piece (Flame)": KH1ItemData("Key", code = 264_1188, classification = ItemClassification.progression, ),
"Emblem Piece (Chest)": KH1ItemData("Key", code = 264_1189, classification = ItemClassification.progression, ),
"Emblem Piece (Statue)": KH1ItemData("Key", code = 264_1190, classification = ItemClassification.progression, ),
"Emblem Piece (Fountain)": KH1ItemData("Key", code = 264_1191, classification = ItemClassification.progression, ),
#"Log": KH1ItemData("Key", code = 264_1192, classification = ItemClassification.progression, ),
#"Cloth": KH1ItemData("Key", code = 264_1193, classification = ItemClassification.progression, ),
#"Rope": KH1ItemData("Key", code = 264_1194, classification = ItemClassification.progression, ),
#"Seagull Egg": KH1ItemData("Key", code = 264_1195, classification = ItemClassification.progression, ),
#"Fish": KH1ItemData("Key", code = 264_1196, classification = ItemClassification.progression, ),
#"Mushroom": KH1ItemData("Key", code = 264_1197, classification = ItemClassification.progression, ),
#"Coconut": KH1ItemData("Key", code = 264_1198, classification = ItemClassification.progression, ),
#"Drinking Water": KH1ItemData("Key", code = 264_1199, classification = ItemClassification.progression, ),
#"Navi-G Piece 1": KH1ItemData("Key", code = 264_1200, classification = ItemClassification.progression, ),
#"Navi-G Piece 2": KH1ItemData("Key", code = 264_1201, classification = ItemClassification.progression, ),
#"Navi-Gummi Unused": KH1ItemData("Key", code = 264_1202, classification = ItemClassification.progression, ),
#"Navi-G Piece 3": KH1ItemData("Key", code = 264_1203, classification = ItemClassification.progression, ),
#"Navi-G Piece 4": KH1ItemData("Key", code = 264_1204, classification = ItemClassification.progression, ),
#"Navi-Gummi": KH1ItemData("Key", code = 264_1205, classification = ItemClassification.progression, ),
#"Watergleam": KH1ItemData("Key", code = 264_1206, classification = ItemClassification.progression, ),
#"Naturespark": KH1ItemData("Key", code = 264_1207, classification = ItemClassification.progression, ),
#"Fireglow": KH1ItemData("Key", code = 264_1208, classification = ItemClassification.progression, ),
#"Earthshine": KH1ItemData("Key", code = 264_1209, classification = ItemClassification.progression, ),
"Crystal Trident": KH1ItemData("Key", code = 264_1210, classification = ItemClassification.progression, ),
"Postcard": KH1ItemData("Key", code = 264_1211, classification = ItemClassification.progression, max_quantity = 10),
"Torn Page 1": KH1ItemData("Torn Pages", code = 264_1212, classification = ItemClassification.progression, ),
"Torn Page 2": KH1ItemData("Torn Pages", code = 264_1213, classification = ItemClassification.progression, ),
"Torn Page 3": KH1ItemData("Torn Pages", code = 264_1214, classification = ItemClassification.progression, ),
"Torn Page 4": KH1ItemData("Torn Pages", code = 264_1215, classification = ItemClassification.progression, ),
"Torn Page 5": KH1ItemData("Torn Pages", code = 264_1216, classification = ItemClassification.progression, ),
"Slides": KH1ItemData("Key", code = 264_1217, classification = ItemClassification.progression, ),
#"Slide 2": KH1ItemData("Key", code = 264_1218, classification = ItemClassification.progression, ),
#"Slide 3": KH1ItemData("Key", code = 264_1219, classification = ItemClassification.progression, ),
#"Slide 4": KH1ItemData("Key", code = 264_1220, classification = ItemClassification.progression, ),
#"Slide 5": KH1ItemData("Key", code = 264_1221, classification = ItemClassification.progression, ),
#"Slide 6": KH1ItemData("Key", code = 264_1222, classification = ItemClassification.progression, ),
"Footprints": KH1ItemData("Key", code = 264_1223, classification = ItemClassification.progression, ),
#"Claw Marks": KH1ItemData("Key", code = 264_1224, classification = ItemClassification.progression, ),
#"Stench": KH1ItemData("Key", code = 264_1225, classification = ItemClassification.progression, ),
#"Antenna": KH1ItemData("Key", code = 264_1226, classification = ItemClassification.progression, ),
"Forget-Me-Not": KH1ItemData("Key", code = 264_1227, classification = ItemClassification.progression, ),
"Jack-In-The-Box": KH1ItemData("Key", code = 264_1228, classification = ItemClassification.progression, ),
"Entry Pass": KH1ItemData("Key", code = 264_1229, classification = ItemClassification.progression, ),
#"Hero License": KH1ItemData("Key", code = 264_1230, classification = ItemClassification.progression, ),
#"Pretty Stone": KH1ItemData("Synthesis", code = 264_1231, classification = ItemClassification.filler, ),
#"N41": KH1ItemData("Synthesis", code = 264_1232, classification = ItemClassification.filler, ),
#"Lucid Shard": KH1ItemData("Synthesis", code = 264_1233, classification = ItemClassification.filler, ),
#"Lucid Gem": KH1ItemData("Synthesis", code = 264_1234, classification = ItemClassification.filler, ),
#"Lucid Crystal": KH1ItemData("Synthesis", code = 264_1235, classification = ItemClassification.filler, ),
#"Spirit Shard": KH1ItemData("Synthesis", code = 264_1236, classification = ItemClassification.filler, ),
#"Spirit Gem": KH1ItemData("Synthesis", code = 264_1237, classification = ItemClassification.filler, ),
#"Power Shard": KH1ItemData("Synthesis", code = 264_1238, classification = ItemClassification.filler, ),
#"Power Gem": KH1ItemData("Synthesis", code = 264_1239, classification = ItemClassification.filler, ),
#"Power Crystal": KH1ItemData("Synthesis", code = 264_1240, classification = ItemClassification.filler, ),
#"Blaze Shard": KH1ItemData("Synthesis", code = 264_1241, classification = ItemClassification.filler, ),
#"Blaze Gem": KH1ItemData("Synthesis", code = 264_1242, classification = ItemClassification.filler, ),
#"Frost Shard": KH1ItemData("Synthesis", code = 264_1243, classification = ItemClassification.filler, ),
#"Frost Gem": KH1ItemData("Synthesis", code = 264_1244, classification = ItemClassification.filler, ),
#"Thunder Shard": KH1ItemData("Synthesis", code = 264_1245, classification = ItemClassification.filler, ),
#"Thunder Gem": KH1ItemData("Synthesis", code = 264_1246, classification = ItemClassification.filler, ),
#"Shiny Crystal": KH1ItemData("Synthesis", code = 264_1247, classification = ItemClassification.filler, ),
#"Bright Shard": KH1ItemData("Synthesis", code = 264_1248, classification = ItemClassification.filler, ),
#"Bright Gem": KH1ItemData("Synthesis", code = 264_1249, classification = ItemClassification.filler, ),
#"Bright Crystal": KH1ItemData("Synthesis", code = 264_1250, classification = ItemClassification.filler, ),
#"Mystery Goo": KH1ItemData("Synthesis", code = 264_1251, classification = ItemClassification.filler, ),
#"Gale": KH1ItemData("Synthesis", code = 264_1252, classification = ItemClassification.filler, ),
#"Mythril Shard": KH1ItemData("Synthesis", code = 264_1253, classification = ItemClassification.filler, ),
#"Mythril": KH1ItemData("Synthesis", code = 264_1254, classification = ItemClassification.filler, ),
#"Orichalcum": KH1ItemData("Synthesis", code = 264_1255, classification = ItemClassification.filler, ),
"High Jump": KH1ItemData("Shared Abilities", code = 264_2001, classification = ItemClassification.progression, ),
"Mermaid Kick": KH1ItemData("Shared Abilities", code = 264_2002, classification = ItemClassification.progression, ),
"Progressive Glide": KH1ItemData("Shared Abilities", code = 264_2003, classification = ItemClassification.progression, max_quantity = 2 ),
#"Superglide": KH1ItemData("Shared Abilities", code = 264_2004, classification = ItemClassification.progression, ),
"Puppy 01": KH1ItemData("Puppies", code = 264_2101, classification = ItemClassification.progression, ),
"Puppy 02": KH1ItemData("Puppies", code = 264_2102, classification = ItemClassification.progression, ),
"Puppy 03": KH1ItemData("Puppies", code = 264_2103, classification = ItemClassification.progression, ),
"Puppy 04": KH1ItemData("Puppies", code = 264_2104, classification = ItemClassification.progression, ),
"Puppy 05": KH1ItemData("Puppies", code = 264_2105, classification = ItemClassification.progression, ),
"Puppy 06": KH1ItemData("Puppies", code = 264_2106, classification = ItemClassification.progression, ),
"Puppy 07": KH1ItemData("Puppies", code = 264_2107, classification = ItemClassification.progression, ),
"Puppy 08": KH1ItemData("Puppies", code = 264_2108, classification = ItemClassification.progression, ),
"Puppy 09": KH1ItemData("Puppies", code = 264_2109, classification = ItemClassification.progression, ),
"Puppy 10": KH1ItemData("Puppies", code = 264_2110, classification = ItemClassification.progression, ),
"Puppy 11": KH1ItemData("Puppies", code = 264_2111, classification = ItemClassification.progression, ),
"Puppy 12": KH1ItemData("Puppies", code = 264_2112, classification = ItemClassification.progression, ),
"Puppy 13": KH1ItemData("Puppies", code = 264_2113, classification = ItemClassification.progression, ),
"Puppy 14": KH1ItemData("Puppies", code = 264_2114, classification = ItemClassification.progression, ),
"Puppy 15": KH1ItemData("Puppies", code = 264_2115, classification = ItemClassification.progression, ),
"Puppy 16": KH1ItemData("Puppies", code = 264_2116, classification = ItemClassification.progression, ),
"Puppy 17": KH1ItemData("Puppies", code = 264_2117, classification = ItemClassification.progression, ),
"Puppy 18": KH1ItemData("Puppies", code = 264_2118, classification = ItemClassification.progression, ),
"Puppy 19": KH1ItemData("Puppies", code = 264_2119, classification = ItemClassification.progression, ),
"Puppy 20": KH1ItemData("Puppies", code = 264_2120, classification = ItemClassification.progression, ),
"Puppy 21": KH1ItemData("Puppies", code = 264_2121, classification = ItemClassification.progression, ),
"Puppy 22": KH1ItemData("Puppies", code = 264_2122, classification = ItemClassification.progression, ),
"Puppy 23": KH1ItemData("Puppies", code = 264_2123, classification = ItemClassification.progression, ),
"Puppy 24": KH1ItemData("Puppies", code = 264_2124, classification = ItemClassification.progression, ),
"Puppy 25": KH1ItemData("Puppies", code = 264_2125, classification = ItemClassification.progression, ),
"Puppy 26": KH1ItemData("Puppies", code = 264_2126, classification = ItemClassification.progression, ),
"Puppy 27": KH1ItemData("Puppies", code = 264_2127, classification = ItemClassification.progression, ),
"Puppy 28": KH1ItemData("Puppies", code = 264_2128, classification = ItemClassification.progression, ),
"Puppy 29": KH1ItemData("Puppies", code = 264_2129, classification = ItemClassification.progression, ),
"Puppy 30": KH1ItemData("Puppies", code = 264_2130, classification = ItemClassification.progression, ),
"Puppy 31": KH1ItemData("Puppies", code = 264_2131, classification = ItemClassification.progression, ),
"Puppy 32": KH1ItemData("Puppies", code = 264_2132, classification = ItemClassification.progression, ),
"Puppy 33": KH1ItemData("Puppies", code = 264_2133, classification = ItemClassification.progression, ),
"Puppy 34": KH1ItemData("Puppies", code = 264_2134, classification = ItemClassification.progression, ),
"Puppy 35": KH1ItemData("Puppies", code = 264_2135, classification = ItemClassification.progression, ),
"Puppy 36": KH1ItemData("Puppies", code = 264_2136, classification = ItemClassification.progression, ),
"Puppy 37": KH1ItemData("Puppies", code = 264_2137, classification = ItemClassification.progression, ),
"Puppy 38": KH1ItemData("Puppies", code = 264_2138, classification = ItemClassification.progression, ),
"Puppy 39": KH1ItemData("Puppies", code = 264_2139, classification = ItemClassification.progression, ),
"Puppy 40": KH1ItemData("Puppies", code = 264_2140, classification = ItemClassification.progression, ),
"Puppy 41": KH1ItemData("Puppies", code = 264_2141, classification = ItemClassification.progression, ),
"Puppy 42": KH1ItemData("Puppies", code = 264_2142, classification = ItemClassification.progression, ),
"Puppy 43": KH1ItemData("Puppies", code = 264_2143, classification = ItemClassification.progression, ),
"Puppy 44": KH1ItemData("Puppies", code = 264_2144, classification = ItemClassification.progression, ),
"Puppy 45": KH1ItemData("Puppies", code = 264_2145, classification = ItemClassification.progression, ),
"Puppy 46": KH1ItemData("Puppies", code = 264_2146, classification = ItemClassification.progression, ),
"Puppy 47": KH1ItemData("Puppies", code = 264_2147, classification = ItemClassification.progression, ),
"Puppy 48": KH1ItemData("Puppies", code = 264_2148, classification = ItemClassification.progression, ),
"Puppy 49": KH1ItemData("Puppies", code = 264_2149, classification = ItemClassification.progression, ),
"Puppy 50": KH1ItemData("Puppies", code = 264_2150, classification = ItemClassification.progression, ),
"Puppy 51": KH1ItemData("Puppies", code = 264_2151, classification = ItemClassification.progression, ),
"Puppy 52": KH1ItemData("Puppies", code = 264_2152, classification = ItemClassification.progression, ),
"Puppy 53": KH1ItemData("Puppies", code = 264_2153, classification = ItemClassification.progression, ),
"Puppy 54": KH1ItemData("Puppies", code = 264_2154, classification = ItemClassification.progression, ),
"Puppy 55": KH1ItemData("Puppies", code = 264_2155, classification = ItemClassification.progression, ),
"Puppy 56": KH1ItemData("Puppies", code = 264_2156, classification = ItemClassification.progression, ),
"Puppy 57": KH1ItemData("Puppies", code = 264_2157, classification = ItemClassification.progression, ),
"Puppy 58": KH1ItemData("Puppies", code = 264_2158, classification = ItemClassification.progression, ),
"Puppy 59": KH1ItemData("Puppies", code = 264_2159, classification = ItemClassification.progression, ),
"Puppy 60": KH1ItemData("Puppies", code = 264_2160, classification = ItemClassification.progression, ),
"Puppy 61": KH1ItemData("Puppies", code = 264_2161, classification = ItemClassification.progression, ),
"Puppy 62": KH1ItemData("Puppies", code = 264_2162, classification = ItemClassification.progression, ),
"Puppy 63": KH1ItemData("Puppies", code = 264_2163, classification = ItemClassification.progression, ),
"Puppy 64": KH1ItemData("Puppies", code = 264_2164, classification = ItemClassification.progression, ),
"Puppy 65": KH1ItemData("Puppies", code = 264_2165, classification = ItemClassification.progression, ),
"Puppy 66": KH1ItemData("Puppies", code = 264_2166, classification = ItemClassification.progression, ),
"Puppy 67": KH1ItemData("Puppies", code = 264_2167, classification = ItemClassification.progression, ),
"Puppy 68": KH1ItemData("Puppies", code = 264_2168, classification = ItemClassification.progression, ),
"Puppy 69": KH1ItemData("Puppies", code = 264_2169, classification = ItemClassification.progression, ),
"Puppy 70": KH1ItemData("Puppies", code = 264_2170, classification = ItemClassification.progression, ),
"Puppy 71": KH1ItemData("Puppies", code = 264_2171, classification = ItemClassification.progression, ),
"Puppy 72": KH1ItemData("Puppies", code = 264_2172, classification = ItemClassification.progression, ),
"Puppy 73": KH1ItemData("Puppies", code = 264_2173, classification = ItemClassification.progression, ),
"Puppy 74": KH1ItemData("Puppies", code = 264_2174, classification = ItemClassification.progression, ),
"Puppy 75": KH1ItemData("Puppies", code = 264_2175, classification = ItemClassification.progression, ),
"Puppy 76": KH1ItemData("Puppies", code = 264_2176, classification = ItemClassification.progression, ),
"Puppy 77": KH1ItemData("Puppies", code = 264_2177, classification = ItemClassification.progression, ),
"Puppy 78": KH1ItemData("Puppies", code = 264_2178, classification = ItemClassification.progression, ),
"Puppy 79": KH1ItemData("Puppies", code = 264_2179, classification = ItemClassification.progression, ),
"Puppy 80": KH1ItemData("Puppies", code = 264_2180, classification = ItemClassification.progression, ),
"Puppy 81": KH1ItemData("Puppies", code = 264_2181, classification = ItemClassification.progression, ),
"Puppy 82": KH1ItemData("Puppies", code = 264_2182, classification = ItemClassification.progression, ),
"Puppy 83": KH1ItemData("Puppies", code = 264_2183, classification = ItemClassification.progression, ),
"Puppy 84": KH1ItemData("Puppies", code = 264_2184, classification = ItemClassification.progression, ),
"Puppy 85": KH1ItemData("Puppies", code = 264_2185, classification = ItemClassification.progression, ),
"Puppy 86": KH1ItemData("Puppies", code = 264_2186, classification = ItemClassification.progression, ),
"Puppy 87": KH1ItemData("Puppies", code = 264_2187, classification = ItemClassification.progression, ),
"Puppy 88": KH1ItemData("Puppies", code = 264_2188, classification = ItemClassification.progression, ),
"Puppy 89": KH1ItemData("Puppies", code = 264_2189, classification = ItemClassification.progression, ),
"Puppy 90": KH1ItemData("Puppies", code = 264_2190, classification = ItemClassification.progression, ),
"Puppy 91": KH1ItemData("Puppies", code = 264_2191, classification = ItemClassification.progression, ),
"Puppy 92": KH1ItemData("Puppies", code = 264_2192, classification = ItemClassification.progression, ),
"Puppy 93": KH1ItemData("Puppies", code = 264_2193, classification = ItemClassification.progression, ),
"Puppy 94": KH1ItemData("Puppies", code = 264_2194, classification = ItemClassification.progression, ),
"Puppy 95": KH1ItemData("Puppies", code = 264_2195, classification = ItemClassification.progression, ),
"Puppy 96": KH1ItemData("Puppies", code = 264_2196, classification = ItemClassification.progression, ),
"Puppy 97": KH1ItemData("Puppies", code = 264_2197, classification = ItemClassification.progression, ),
"Puppy 98": KH1ItemData("Puppies", code = 264_2198, classification = ItemClassification.progression, ),
"Puppy 99": KH1ItemData("Puppies", code = 264_2199, classification = ItemClassification.progression, ),
"Puppies 01-03": KH1ItemData("Puppies", code = 264_2201, classification = ItemClassification.progression, ),
"Puppies 04-06": KH1ItemData("Puppies", code = 264_2202, classification = ItemClassification.progression, ),
"Puppies 07-09": KH1ItemData("Puppies", code = 264_2203, classification = ItemClassification.progression, ),
"Puppies 10-12": KH1ItemData("Puppies", code = 264_2204, classification = ItemClassification.progression, ),
"Puppies 13-15": KH1ItemData("Puppies", code = 264_2205, classification = ItemClassification.progression, ),
"Puppies 16-18": KH1ItemData("Puppies", code = 264_2206, classification = ItemClassification.progression, ),
"Puppies 19-21": KH1ItemData("Puppies", code = 264_2207, classification = ItemClassification.progression, ),
"Puppies 22-24": KH1ItemData("Puppies", code = 264_2208, classification = ItemClassification.progression, ),
"Puppies 25-27": KH1ItemData("Puppies", code = 264_2209, classification = ItemClassification.progression, ),
"Puppies 28-30": KH1ItemData("Puppies", code = 264_2210, classification = ItemClassification.progression, ),
"Puppies 31-33": KH1ItemData("Puppies", code = 264_2211, classification = ItemClassification.progression, ),
"Puppies 34-36": KH1ItemData("Puppies", code = 264_2212, classification = ItemClassification.progression, ),
"Puppies 37-39": KH1ItemData("Puppies", code = 264_2213, classification = ItemClassification.progression, ),
"Puppies 40-42": KH1ItemData("Puppies", code = 264_2214, classification = ItemClassification.progression, ),
"Puppies 43-45": KH1ItemData("Puppies", code = 264_2215, classification = ItemClassification.progression, ),
"Puppies 46-48": KH1ItemData("Puppies", code = 264_2216, classification = ItemClassification.progression, ),
"Puppies 49-51": KH1ItemData("Puppies", code = 264_2217, classification = ItemClassification.progression, ),
"Puppies 52-54": KH1ItemData("Puppies", code = 264_2218, classification = ItemClassification.progression, ),
"Puppies 55-57": KH1ItemData("Puppies", code = 264_2219, classification = ItemClassification.progression, ),
"Puppies 58-60": KH1ItemData("Puppies", code = 264_2220, classification = ItemClassification.progression, ),
"Puppies 61-63": KH1ItemData("Puppies", code = 264_2221, classification = ItemClassification.progression, ),
"Puppies 64-66": KH1ItemData("Puppies", code = 264_2222, classification = ItemClassification.progression, ),
"Puppies 67-69": KH1ItemData("Puppies", code = 264_2223, classification = ItemClassification.progression, ),
"Puppies 70-72": KH1ItemData("Puppies", code = 264_2224, classification = ItemClassification.progression, ),
"Puppies 73-75": KH1ItemData("Puppies", code = 264_2225, classification = ItemClassification.progression, ),
"Puppies 76-78": KH1ItemData("Puppies", code = 264_2226, classification = ItemClassification.progression, ),
"Puppies 79-81": KH1ItemData("Puppies", code = 264_2227, classification = ItemClassification.progression, ),
"Puppies 82-84": KH1ItemData("Puppies", code = 264_2228, classification = ItemClassification.progression, ),
"Puppies 85-87": KH1ItemData("Puppies", code = 264_2229, classification = ItemClassification.progression, ),
"Puppies 88-90": KH1ItemData("Puppies", code = 264_2230, classification = ItemClassification.progression, ),
"Puppies 91-93": KH1ItemData("Puppies", code = 264_2231, classification = ItemClassification.progression, ),
"Puppies 94-96": KH1ItemData("Puppies", code = 264_2232, classification = ItemClassification.progression, ),
"Puppies 97-99": KH1ItemData("Puppies", code = 264_2233, classification = ItemClassification.progression, ),
"All Puppies": KH1ItemData("Puppies", code = 264_2240, classification = ItemClassification.progression, ),
"Treasure Magnet": KH1ItemData("Abilities", code = 264_3005, classification = ItemClassification.useful, max_quantity = 2 ),
"Combo Plus": KH1ItemData("Abilities", code = 264_3006, classification = ItemClassification.useful, max_quantity = 4 ),
"Air Combo Plus": KH1ItemData("Abilities", code = 264_3007, classification = ItemClassification.useful, max_quantity = 2 ),
"Critical Plus": KH1ItemData("Abilities", code = 264_3008, classification = ItemClassification.useful, max_quantity = 3 ),
#"Second Wind": KH1ItemData("Abilities", code = 264_3009, classification = ItemClassification.useful, ),
"Scan": KH1ItemData("Abilities", code = 264_3010, classification = ItemClassification.useful, ),
"Sonic Blade": KH1ItemData("Abilities", code = 264_3011, classification = ItemClassification.useful, ),
"Ars Arcanum": KH1ItemData("Abilities", code = 264_3012, classification = ItemClassification.useful, ),
"Strike Raid": KH1ItemData("Abilities", code = 264_3013, classification = ItemClassification.useful, ),
"Ragnarok": KH1ItemData("Abilities", code = 264_3014, classification = ItemClassification.useful, ),
"Trinity Limit": KH1ItemData("Abilities", code = 264_3015, classification = ItemClassification.useful, ),
"Cheer": KH1ItemData("Abilities", code = 264_3016, classification = ItemClassification.useful, ),
"Vortex": KH1ItemData("Abilities", code = 264_3017, classification = ItemClassification.useful, ),
"Aerial Sweep": KH1ItemData("Abilities", code = 264_3018, classification = ItemClassification.useful, ),
"Counterattack": KH1ItemData("Abilities", code = 264_3019, classification = ItemClassification.useful, ),
"Blitz": KH1ItemData("Abilities", code = 264_3020, classification = ItemClassification.useful, ),
"Guard": KH1ItemData("Abilities", code = 264_3021, classification = ItemClassification.progression, ),
"Dodge Roll": KH1ItemData("Abilities", code = 264_3022, classification = ItemClassification.progression, ),
"MP Haste": KH1ItemData("Abilities", code = 264_3023, classification = ItemClassification.useful, ),
"MP Rage": KH1ItemData("Abilities", code = 264_3024, classification = ItemClassification.progression, ),
"Second Chance": KH1ItemData("Abilities", code = 264_3025, classification = ItemClassification.progression, ),
"Berserk": KH1ItemData("Abilities", code = 264_3026, classification = ItemClassification.useful, ),
"Jackpot": KH1ItemData("Abilities", code = 264_3027, classification = ItemClassification.useful, ),
"Lucky Strike": KH1ItemData("Abilities", code = 264_3028, classification = ItemClassification.useful, ),
#"Charge": KH1ItemData("Abilities", code = 264_3029, classification = ItemClassification.useful, ),
#"Rocket": KH1ItemData("Abilities", code = 264_3030, classification = ItemClassification.useful, ),
#"Tornado": KH1ItemData("Abilities", code = 264_3031, classification = ItemClassification.useful, ),
#"MP Gift": KH1ItemData("Abilities", code = 264_3032, classification = ItemClassification.useful, ),
#"Raging Boar": KH1ItemData("Abilities", code = 264_3033, classification = ItemClassification.useful, ),
#"Asp's Bite": KH1ItemData("Abilities", code = 264_3034, classification = ItemClassification.useful, ),
#"Healing Herb": KH1ItemData("Abilities", code = 264_3035, classification = ItemClassification.useful, ),
#"Wind Armor": KH1ItemData("Abilities", code = 264_3036, classification = ItemClassification.useful, ),
#"Crescent": KH1ItemData("Abilities", code = 264_3037, classification = ItemClassification.useful, ),
#"Sandstorm": KH1ItemData("Abilities", code = 264_3038, classification = ItemClassification.useful, ),
#"Applause!": KH1ItemData("Abilities", code = 264_3039, classification = ItemClassification.useful, ),
#"Blazing Fury": KH1ItemData("Abilities", code = 264_3040, classification = ItemClassification.useful, ),
#"Icy Terror": KH1ItemData("Abilities", code = 264_3041, classification = ItemClassification.useful, ),
#"Bolts of Sorrow": KH1ItemData("Abilities", code = 264_3042, classification = ItemClassification.useful, ),
#"Ghostly Scream": KH1ItemData("Abilities", code = 264_3043, classification = ItemClassification.useful, ),
#"Humming Bird": KH1ItemData("Abilities", code = 264_3044, classification = ItemClassification.useful, ),
#"Time-Out": KH1ItemData("Abilities", code = 264_3045, classification = ItemClassification.useful, ),
#"Storm's Eye": KH1ItemData("Abilities", code = 264_3046, classification = ItemClassification.useful, ),
#"Ferocious Lunge": KH1ItemData("Abilities", code = 264_3047, classification = ItemClassification.useful, ),
#"Furious Bellow": KH1ItemData("Abilities", code = 264_3048, classification = ItemClassification.useful, ),
#"Spiral Wave": KH1ItemData("Abilities", code = 264_3049, classification = ItemClassification.useful, ),
#"Thunder Potion": KH1ItemData("Abilities", code = 264_3050, classification = ItemClassification.useful, ),
#"Cure Potion": KH1ItemData("Abilities", code = 264_3051, classification = ItemClassification.useful, ),
#"Aero Potion": KH1ItemData("Abilities", code = 264_3052, classification = ItemClassification.useful, ),
"Slapshot": KH1ItemData("Abilities", code = 264_3053, classification = ItemClassification.useful, ),
"Sliding Dash": KH1ItemData("Abilities", code = 264_3054, classification = ItemClassification.useful, ),
"Hurricane Blast": KH1ItemData("Abilities", code = 264_3055, classification = ItemClassification.useful, ),
"Ripple Drive": KH1ItemData("Abilities", code = 264_3056, classification = ItemClassification.useful, ),
"Stun Impact": KH1ItemData("Abilities", code = 264_3057, classification = ItemClassification.useful, ),
"Gravity Break": KH1ItemData("Abilities", code = 264_3058, classification = ItemClassification.useful, ),
"Zantetsuken": KH1ItemData("Abilities", code = 264_3059, classification = ItemClassification.useful, ),
"Tech Boost": KH1ItemData("Abilities", code = 264_3060, classification = ItemClassification.useful, max_quantity = 4 ),
"Encounter Plus": KH1ItemData("Abilities", code = 264_3061, classification = ItemClassification.useful, ),
"Leaf Bracer": KH1ItemData("Abilities", code = 264_3062, classification = ItemClassification.progression, ),
#"Evolution": KH1ItemData("Abilities", code = 264_3063, classification = ItemClassification.useful, ),
"EXP Zero": KH1ItemData("Abilities", code = 264_3064, classification = ItemClassification.useful, ),
"Combo Master": KH1ItemData("Abilities", code = 264_3065, classification = ItemClassification.progression, ),
"Max HP Increase": KH1ItemData("Level Up", code = 264_4001, classification = ItemClassification.useful, max_quantity = 15),
"Max MP Increase": KH1ItemData("Level Up", code = 264_4002, classification = ItemClassification.useful, max_quantity = 15),
"Max AP Increase": KH1ItemData("Level Up", code = 264_4003, classification = ItemClassification.useful, max_quantity = 15),
"Strength Increase": KH1ItemData("Level Up", code = 264_4004, classification = ItemClassification.useful, max_quantity = 15),
"Defense Increase": KH1ItemData("Level Up", code = 264_4005, classification = ItemClassification.useful, max_quantity = 15),
"Accessory Slot Increase": KH1ItemData("Limited Level Up", code = 264_4006, classification = ItemClassification.useful, max_quantity = 15),
"Item Slot Increase": KH1ItemData("Limited Level Up", code = 264_4007, classification = ItemClassification.useful, max_quantity = 15),
"Dumbo": KH1ItemData("Summons", code = 264_5000, classification = ItemClassification.progression, ),
"Bambi": KH1ItemData("Summons", code = 264_5001, classification = ItemClassification.progression, ),
"Genie": KH1ItemData("Summons", code = 264_5002, classification = ItemClassification.progression, ),
"Tinker Bell": KH1ItemData("Summons", code = 264_5003, classification = ItemClassification.progression, ),
"Mushu": KH1ItemData("Summons", code = 264_5004, classification = ItemClassification.progression, ),
"Simba": KH1ItemData("Summons", code = 264_5005, classification = ItemClassification.progression, ),
"Progressive Fire": KH1ItemData("Magic", code = 264_6001, classification = ItemClassification.progression, max_quantity = 3 ),
"Progressive Blizzard": KH1ItemData("Magic", code = 264_6002, classification = ItemClassification.progression, max_quantity = 3 ),
"Progressive Thunder": KH1ItemData("Magic", code = 264_6003, classification = ItemClassification.progression, max_quantity = 3 ),
"Progressive Cure": KH1ItemData("Magic", code = 264_6004, classification = ItemClassification.progression, max_quantity = 3 ),
"Progressive Gravity": KH1ItemData("Magic", code = 264_6005, classification = ItemClassification.progression, max_quantity = 3 ),
"Progressive Stop": KH1ItemData("Magic", code = 264_6006, classification = ItemClassification.progression, max_quantity = 3 ),
"Progressive Aero": KH1ItemData("Magic", code = 264_6007, classification = ItemClassification.progression, max_quantity = 3 ),
#"Traverse Town": KH1ItemData("Worlds", code = 264_7001, classification = ItemClassification.progression, ),
"Wonderland": KH1ItemData("Worlds", code = 264_7002, classification = ItemClassification.progression, ),
"Olympus Coliseum": KH1ItemData("Worlds", code = 264_7003, classification = ItemClassification.progression, ),
"Deep Jungle": KH1ItemData("Worlds", code = 264_7004, classification = ItemClassification.progression, ),
"Agrabah": KH1ItemData("Worlds", code = 264_7005, classification = ItemClassification.progression, ),
"Halloween Town": KH1ItemData("Worlds", code = 264_7006, classification = ItemClassification.progression, ),
"Atlantica": KH1ItemData("Worlds", code = 264_7007, classification = ItemClassification.progression, ),
"Neverland": KH1ItemData("Worlds", code = 264_7008, classification = ItemClassification.progression, ),
"Hollow Bastion": KH1ItemData("Worlds", code = 264_7009, classification = ItemClassification.progression, ),
"End of the World": KH1ItemData("Worlds", code = 264_7010, classification = ItemClassification.progression, ),
"Monstro": KH1ItemData("Worlds", code = 264_7011, classification = ItemClassification.progression, ),
"Blue Trinity": KH1ItemData("Trinities", code = 264_8001, classification = ItemClassification.progression, ),
"Red Trinity": KH1ItemData("Trinities", code = 264_8002, classification = ItemClassification.progression, ),
"Green Trinity": KH1ItemData("Trinities", code = 264_8003, classification = ItemClassification.progression, ),
"Yellow Trinity": KH1ItemData("Trinities", code = 264_8004, classification = ItemClassification.progression, ),
"White Trinity": KH1ItemData("Trinities", code = 264_8005, classification = ItemClassification.progression, ),
"Phil Cup": KH1ItemData("Cups", code = 264_9001, classification = ItemClassification.progression, ),
"Pegasus Cup": KH1ItemData("Cups", code = 264_9002, classification = ItemClassification.progression, ),
"Hercules Cup": KH1ItemData("Cups", code = 264_9003, classification = ItemClassification.progression, ),
#"Hades Cup": KH1ItemData("Cups", code = 264_9004, classification = ItemClassification.progression, ),
}
event_item_table: Dict[str, KH1ItemData] = {}
#Make item categories
item_name_groups: Dict[str, Set[str]] = {}
for item in item_table.keys():
category = item_table[item].category
if category not in item_name_groups.keys():
item_name_groups[category] = set()
item_name_groups[category].add(item)

View File

@@ -1,590 +0,0 @@
from typing import Dict, NamedTuple, Optional, Set
import typing
from BaseClasses import Location
class KH1Location(Location):
game: str = "Kingdom Hearts"
class KH1LocationData(NamedTuple):
category: str
code: int
def get_locations_by_category(category: str) -> Dict[str, KH1LocationData]:
location_dict: Dict[str, KH1LocationData] = {}
for name, data in location_table.items():
if data.category == category:
location_dict.setdefault(name, data)
return location_dict
location_table: Dict[str, KH1LocationData] = {
#"Destiny Islands Chest": KH1LocationData("Destiny Islands", 265_0011), missable
"Traverse Town 1st District Candle Puzzle Chest": KH1LocationData("Traverse Town", 265_0211),
"Traverse Town 1st District Accessory Shop Roof Chest": KH1LocationData("Traverse Town", 265_0212),
"Traverse Town 2nd District Boots and Shoes Awning Chest": KH1LocationData("Traverse Town", 265_0213),
"Traverse Town 2nd District Rooftop Chest": KH1LocationData("Traverse Town", 265_0214),
"Traverse Town 2nd District Gizmo Shop Facade Chest": KH1LocationData("Traverse Town", 265_0251),
"Traverse Town Alleyway Balcony Chest": KH1LocationData("Traverse Town", 265_0252),
"Traverse Town Alleyway Blue Room Awning Chest": KH1LocationData("Traverse Town", 265_0253),
"Traverse Town Alleyway Corner Chest": KH1LocationData("Traverse Town", 265_0254),
"Traverse Town Green Room Clock Puzzle Chest": KH1LocationData("Traverse Town", 265_0292),
"Traverse Town Green Room Table Chest": KH1LocationData("Traverse Town", 265_0293),
"Traverse Town Red Room Chest": KH1LocationData("Traverse Town", 265_0294),
"Traverse Town Mystical House Yellow Trinity Chest": KH1LocationData("Traverse Town", 265_0331),
"Traverse Town Accessory Shop Chest": KH1LocationData("Traverse Town", 265_0332),
"Traverse Town Secret Waterway White Trinity Chest": KH1LocationData("Traverse Town", 265_0333),
"Traverse Town Geppetto's House Chest": KH1LocationData("Traverse Town", 265_0334),
"Traverse Town Item Workshop Right Chest": KH1LocationData("Traverse Town", 265_0371),
"Traverse Town 1st District Blue Trinity Balcony Chest": KH1LocationData("Traverse Town", 265_0411),
"Traverse Town Mystical House Glide Chest": KH1LocationData("Traverse Town", 265_0891),
"Traverse Town Alleyway Behind Crates Chest": KH1LocationData("Traverse Town", 265_0892),
"Traverse Town Item Workshop Left Chest": KH1LocationData("Traverse Town", 265_0893),
"Traverse Town Secret Waterway Near Stairs Chest": KH1LocationData("Traverse Town", 265_0894),
"Wonderland Rabbit Hole Green Trinity Chest": KH1LocationData("Wonderland", 265_0931),
"Wonderland Rabbit Hole Defeat Heartless 1 Chest": KH1LocationData("Wonderland", 265_0932),
"Wonderland Rabbit Hole Defeat Heartless 2 Chest": KH1LocationData("Wonderland", 265_0933),
"Wonderland Rabbit Hole Defeat Heartless 3 Chest": KH1LocationData("Wonderland", 265_0934),
"Wonderland Bizarre Room Green Trinity Chest": KH1LocationData("Wonderland", 265_0971),
"Wonderland Queen's Castle Hedge Left Red Chest": KH1LocationData("Wonderland", 265_1011),
"Wonderland Queen's Castle Hedge Right Blue Chest": KH1LocationData("Wonderland", 265_1012),
"Wonderland Queen's Castle Hedge Right Red Chest": KH1LocationData("Wonderland", 265_1013),
"Wonderland Lotus Forest Thunder Plant Chest": KH1LocationData("Wonderland", 265_1014),
"Wonderland Lotus Forest Through the Painting Thunder Plant Chest": KH1LocationData("Wonderland", 265_1051),
"Wonderland Lotus Forest Glide Chest": KH1LocationData("Wonderland", 265_1052),
"Wonderland Lotus Forest Nut Chest": KH1LocationData("Wonderland", 265_1053),
"Wonderland Lotus Forest Corner Chest": KH1LocationData("Wonderland", 265_1054),
"Wonderland Bizarre Room Lamp Chest": KH1LocationData("Wonderland", 265_1091),
"Wonderland Tea Party Garden Above Lotus Forest Entrance 2nd Chest": KH1LocationData("Wonderland", 265_1093),
"Wonderland Tea Party Garden Above Lotus Forest Entrance 1st Chest": KH1LocationData("Wonderland", 265_1094),
"Wonderland Tea Party Garden Bear and Clock Puzzle Chest": KH1LocationData("Wonderland", 265_1131),
"Wonderland Tea Party Garden Across From Bizarre Room Entrance Chest": KH1LocationData("Wonderland", 265_1132),
"Wonderland Lotus Forest Through the Painting White Trinity Chest": KH1LocationData("Wonderland", 265_1133),
"Deep Jungle Tree House Beneath Tree House Chest": KH1LocationData("Deep Jungle", 265_1213),
"Deep Jungle Tree House Rooftop Chest": KH1LocationData("Deep Jungle", 265_1214),
"Deep Jungle Hippo's Lagoon Center Chest": KH1LocationData("Deep Jungle", 265_1251),
"Deep Jungle Hippo's Lagoon Left Chest": KH1LocationData("Deep Jungle", 265_1252),
"Deep Jungle Hippo's Lagoon Right Chest": KH1LocationData("Deep Jungle", 265_1253),
"Deep Jungle Vines Chest": KH1LocationData("Deep Jungle", 265_1291),
"Deep Jungle Vines 2 Chest": KH1LocationData("Deep Jungle", 265_1292),
"Deep Jungle Climbing Trees Blue Trinity Chest": KH1LocationData("Deep Jungle", 265_1293),
"Deep Jungle Tunnel Chest": KH1LocationData("Deep Jungle", 265_1331),
"Deep Jungle Cavern of Hearts White Trinity Chest": KH1LocationData("Deep Jungle", 265_1332),
"Deep Jungle Camp Blue Trinity Chest": KH1LocationData("Deep Jungle", 265_1333),
"Deep Jungle Tent Chest": KH1LocationData("Deep Jungle", 265_1334),
"Deep Jungle Waterfall Cavern Low Chest": KH1LocationData("Deep Jungle", 265_1371),
"Deep Jungle Waterfall Cavern Middle Chest": KH1LocationData("Deep Jungle", 265_1372),
"Deep Jungle Waterfall Cavern High Wall Chest": KH1LocationData("Deep Jungle", 265_1373),
"Deep Jungle Waterfall Cavern High Middle Chest": KH1LocationData("Deep Jungle", 265_1374),
"Deep Jungle Cliff Right Cliff Left Chest": KH1LocationData("Deep Jungle", 265_1411),
"Deep Jungle Cliff Right Cliff Right Chest": KH1LocationData("Deep Jungle", 265_1412),
"Deep Jungle Tree House Suspended Boat Chest": KH1LocationData("Deep Jungle", 265_1413),
"100 Acre Wood Meadow Inside Log Chest": KH1LocationData("100 Acre Wood", 265_1654),
"100 Acre Wood Bouncing Spot Left Cliff Chest": KH1LocationData("100 Acre Wood", 265_1691),
"100 Acre Wood Bouncing Spot Right Tree Alcove Chest": KH1LocationData("100 Acre Wood", 265_1692),
"100 Acre Wood Bouncing Spot Under Giant Pot Chest": KH1LocationData("100 Acre Wood", 265_1693),
"Agrabah Plaza By Storage Chest": KH1LocationData("Agrabah", 265_1972),
"Agrabah Plaza Raised Terrace Chest": KH1LocationData("Agrabah", 265_1973),
"Agrabah Plaza Top Corner Chest": KH1LocationData("Agrabah", 265_1974),
"Agrabah Alley Chest": KH1LocationData("Agrabah", 265_2011),
"Agrabah Bazaar Across Windows Chest": KH1LocationData("Agrabah", 265_2012),
"Agrabah Bazaar High Corner Chest": KH1LocationData("Agrabah", 265_2013),
"Agrabah Main Street Right Palace Entrance Chest": KH1LocationData("Agrabah", 265_2014),
"Agrabah Main Street High Above Alley Entrance Chest": KH1LocationData("Agrabah", 265_2051),
"Agrabah Main Street High Above Palace Gates Entrance Chest": KH1LocationData("Agrabah", 265_2052),
"Agrabah Palace Gates Low Chest": KH1LocationData("Agrabah", 265_2053),
"Agrabah Palace Gates High Opposite Palace Chest": KH1LocationData("Agrabah", 265_2054),
"Agrabah Palace Gates High Close to Palace Chest": KH1LocationData("Agrabah", 265_2091),
"Agrabah Storage Green Trinity Chest": KH1LocationData("Agrabah", 265_2092),
"Agrabah Storage Behind Barrel Chest": KH1LocationData("Agrabah", 265_2093),
"Agrabah Cave of Wonders Entrance Left Chest": KH1LocationData("Agrabah", 265_2094),
"Agrabah Cave of Wonders Entrance Tall Tower Chest": KH1LocationData("Agrabah", 265_2131),
"Agrabah Cave of Wonders Hall High Left Chest": KH1LocationData("Agrabah", 265_2132),
"Agrabah Cave of Wonders Hall Near Bottomless Hall Chest": KH1LocationData("Agrabah", 265_2133),
"Agrabah Cave of Wonders Bottomless Hall Raised Platform Chest": KH1LocationData("Agrabah", 265_2134),
"Agrabah Cave of Wonders Bottomless Hall Pillar Chest": KH1LocationData("Agrabah", 265_2171),
"Agrabah Cave of Wonders Bottomless Hall Across Chasm Chest": KH1LocationData("Agrabah", 265_2172),
"Agrabah Cave of Wonders Treasure Room Across Platforms Chest": KH1LocationData("Agrabah", 265_2173),
"Agrabah Cave of Wonders Treasure Room Small Treasure Pile Chest": KH1LocationData("Agrabah", 265_2174),
"Agrabah Cave of Wonders Treasure Room Large Treasure Pile Chest": KH1LocationData("Agrabah", 265_2211),
"Agrabah Cave of Wonders Treasure Room Above Fire Chest": KH1LocationData("Agrabah", 265_2212),
"Agrabah Cave of Wonders Relic Chamber Jump from Stairs Chest": KH1LocationData("Agrabah", 265_2213),
"Agrabah Cave of Wonders Relic Chamber Stairs Chest": KH1LocationData("Agrabah", 265_2214),
"Agrabah Cave of Wonders Dark Chamber Abu Gem Chest": KH1LocationData("Agrabah", 265_2251),
"Agrabah Cave of Wonders Dark Chamber Across from Relic Chamber Entrance Chest": KH1LocationData("Agrabah", 265_2252),
"Agrabah Cave of Wonders Dark Chamber Bridge Chest": KH1LocationData("Agrabah", 265_2253),
"Agrabah Cave of Wonders Dark Chamber Near Save Chest": KH1LocationData("Agrabah", 265_2254),
"Agrabah Cave of Wonders Silent Chamber Blue Trinity Chest": KH1LocationData("Agrabah", 265_2291),
"Agrabah Cave of Wonders Hidden Room Right Chest": KH1LocationData("Agrabah", 265_2292),
"Agrabah Cave of Wonders Hidden Room Left Chest": KH1LocationData("Agrabah", 265_2293),
"Agrabah Aladdin's House Main Street Entrance Chest": KH1LocationData("Agrabah", 265_2294),
"Agrabah Aladdin's House Plaza Entrance Chest": KH1LocationData("Agrabah", 265_2331),
"Agrabah Cave of Wonders Entrance White Trinity Chest": KH1LocationData("Agrabah", 265_2332),
"Monstro Chamber 6 Other Platform Chest": KH1LocationData("Monstro", 265_2413),
"Monstro Chamber 6 Platform Near Chamber 5 Entrance Chest": KH1LocationData("Monstro", 265_2414),
"Monstro Chamber 6 Raised Area Near Chamber 1 Entrance Chest": KH1LocationData("Monstro", 265_2451),
"Monstro Chamber 6 Low Chest": KH1LocationData("Monstro", 265_2452),
"Atlantica Sunken Ship In Flipped Boat Chest": KH1LocationData("Atlantica", 265_2531),
"Atlantica Sunken Ship Seabed Chest": KH1LocationData("Atlantica", 265_2532),
"Atlantica Sunken Ship Inside Ship Chest": KH1LocationData("Atlantica", 265_2533),
"Atlantica Ariel's Grotto High Chest": KH1LocationData("Atlantica", 265_2534),
"Atlantica Ariel's Grotto Middle Chest": KH1LocationData("Atlantica", 265_2571),
"Atlantica Ariel's Grotto Low Chest": KH1LocationData("Atlantica", 265_2572),
"Atlantica Ursula's Lair Use Fire on Urchin Chest": KH1LocationData("Atlantica", 265_2573),
"Atlantica Undersea Gorge Jammed by Ariel's Grotto Chest": KH1LocationData("Atlantica", 265_2574),
"Atlantica Triton's Palace White Trinity Chest": KH1LocationData("Atlantica", 265_2611),
"Halloween Town Moonlight Hill White Trinity Chest": KH1LocationData("Halloween Town", 265_3014),
"Halloween Town Bridge Under Bridge": KH1LocationData("Halloween Town", 265_3051),
"Halloween Town Boneyard Tombstone Puzzle Chest": KH1LocationData("Halloween Town", 265_3052),
"Halloween Town Bridge Right of Gate Chest": KH1LocationData("Halloween Town", 265_3053),
"Halloween Town Cemetery Behind Grave Chest": KH1LocationData("Halloween Town", 265_3054),
"Halloween Town Cemetery By Cat Shape Chest": KH1LocationData("Halloween Town", 265_3091),
"Halloween Town Cemetery Between Graves Chest": KH1LocationData("Halloween Town", 265_3092),
"Halloween Town Oogie's Manor Lower Iron Cage Chest": KH1LocationData("Halloween Town", 265_3093),
"Halloween Town Oogie's Manor Upper Iron Cage Chest": KH1LocationData("Halloween Town", 265_3094),
"Halloween Town Oogie's Manor Hollow Chest": KH1LocationData("Halloween Town", 265_3131),
"Halloween Town Oogie's Manor Grounds Red Trinity Chest": KH1LocationData("Halloween Town", 265_3132),
"Halloween Town Guillotine Square High Tower Chest": KH1LocationData("Halloween Town", 265_3133),
"Halloween Town Guillotine Square Pumpkin Structure Left Chest": KH1LocationData("Halloween Town", 265_3134),
"Halloween Town Oogie's Manor Entrance Steps Chest": KH1LocationData("Halloween Town", 265_3171),
"Halloween Town Oogie's Manor Inside Entrance Chest": KH1LocationData("Halloween Town", 265_3172),
"Halloween Town Bridge Left of Gate Chest": KH1LocationData("Halloween Town", 265_3291),
"Halloween Town Cemetery By Striped Grave Chest": KH1LocationData("Halloween Town", 265_3292),
"Halloween Town Guillotine Square Under Jack's House Stairs Chest": KH1LocationData("Halloween Town", 265_3293),
"Halloween Town Guillotine Square Pumpkin Structure Right Chest": KH1LocationData("Halloween Town", 265_3294),
"Olympus Coliseum Coliseum Gates Left Behind Columns Chest": KH1LocationData("Olympus Coliseum", 265_3332),
"Olympus Coliseum Coliseum Gates Right Blue Trinity Chest": KH1LocationData("Olympus Coliseum", 265_3333),
"Olympus Coliseum Coliseum Gates Left Blue Trinity Chest": KH1LocationData("Olympus Coliseum", 265_3334),
"Olympus Coliseum Coliseum Gates White Trinity Chest": KH1LocationData("Olympus Coliseum", 265_3371),
"Olympus Coliseum Coliseum Gates Blizzara Chest": KH1LocationData("Olympus Coliseum", 265_3372),
"Olympus Coliseum Coliseum Gates Blizzaga Chest": KH1LocationData("Olympus Coliseum", 265_3373),
"Monstro Mouth Boat Deck Chest": KH1LocationData("Monstro", 265_3454),
"Monstro Mouth High Platform Boat Side Chest": KH1LocationData("Monstro", 265_3491),
"Monstro Mouth High Platform Across from Boat Chest": KH1LocationData("Monstro", 265_3492),
"Monstro Mouth Near Ship Chest": KH1LocationData("Monstro", 265_3493),
"Monstro Mouth Green Trinity Top of Boat Chest": KH1LocationData("Monstro", 265_3494),
"Monstro Chamber 2 Ground Chest": KH1LocationData("Monstro", 265_3534),
"Monstro Chamber 2 Platform Chest": KH1LocationData("Monstro", 265_3571),
"Monstro Chamber 5 Platform Chest": KH1LocationData("Monstro", 265_3613),
"Monstro Chamber 3 Ground Chest": KH1LocationData("Monstro", 265_3614),
"Monstro Chamber 3 Platform Above Chamber 2 Entrance Chest": KH1LocationData("Monstro", 265_3651),
"Monstro Chamber 3 Near Chamber 6 Entrance Chest": KH1LocationData("Monstro", 265_3652),
"Monstro Chamber 3 Platform Near Chamber 6 Entrance Chest": KH1LocationData("Monstro", 265_3653),
"Monstro Mouth High Platform Near Teeth Chest": KH1LocationData("Monstro", 265_3732),
"Monstro Chamber 5 Atop Barrel Chest": KH1LocationData("Monstro", 265_3733),
"Monstro Chamber 5 Low 2nd Chest": KH1LocationData("Monstro", 265_3734),
"Monstro Chamber 5 Low 1st Chest": KH1LocationData("Monstro", 265_3771),
"Neverland Pirate Ship Deck White Trinity Chest": KH1LocationData("Neverland", 265_3772),
"Neverland Pirate Ship Crows Nest Chest": KH1LocationData("Neverland", 265_3773),
"Neverland Hold Yellow Trinity Right Blue Chest": KH1LocationData("Neverland", 265_3774),
"Neverland Hold Yellow Trinity Left Blue Chest": KH1LocationData("Neverland", 265_3811),
"Neverland Galley Chest": KH1LocationData("Neverland", 265_3812),
"Neverland Cabin Chest": KH1LocationData("Neverland", 265_3813),
"Neverland Hold Flight 1st Chest": KH1LocationData("Neverland", 265_3814),
"Neverland Clock Tower Chest": KH1LocationData("Neverland", 265_4014),
"Neverland Hold Flight 2nd Chest": KH1LocationData("Neverland", 265_4051),
"Neverland Hold Yellow Trinity Green Chest": KH1LocationData("Neverland", 265_4052),
"Neverland Captain's Cabin Chest": KH1LocationData("Neverland", 265_4053),
"Hollow Bastion Rising Falls Water's Surface Chest": KH1LocationData("Hollow Bastion", 265_4054),
"Hollow Bastion Rising Falls Under Water 1st Chest": KH1LocationData("Hollow Bastion", 265_4091),
"Hollow Bastion Rising Falls Under Water 2nd Chest": KH1LocationData("Hollow Bastion", 265_4092),
"Hollow Bastion Rising Falls Floating Platform Near Save Chest": KH1LocationData("Hollow Bastion", 265_4093),
"Hollow Bastion Rising Falls Floating Platform Near Bubble Chest": KH1LocationData("Hollow Bastion", 265_4094),
"Hollow Bastion Rising Falls High Platform Chest": KH1LocationData("Hollow Bastion", 265_4131),
"Hollow Bastion Castle Gates Gravity Chest": KH1LocationData("Hollow Bastion", 265_4132),
"Hollow Bastion Castle Gates Freestanding Pillar Chest": KH1LocationData("Hollow Bastion", 265_4133),
"Hollow Bastion Castle Gates High Pillar Chest": KH1LocationData("Hollow Bastion", 265_4134),
"Hollow Bastion Great Crest Lower Chest": KH1LocationData("Hollow Bastion", 265_4171),
"Hollow Bastion Great Crest After Battle Platform Chest": KH1LocationData("Hollow Bastion", 265_4172),
"Hollow Bastion High Tower 2nd Gravity Chest": KH1LocationData("Hollow Bastion", 265_4173),
"Hollow Bastion High Tower 1st Gravity Chest": KH1LocationData("Hollow Bastion", 265_4174),
"Hollow Bastion High Tower Above Sliding Blocks Chest": KH1LocationData("Hollow Bastion", 265_4211),
"Hollow Bastion Library Top of Bookshelf Chest": KH1LocationData("Hollow Bastion", 265_4213),
"Hollow Bastion Library 1st Floor Turn the Carousel Chest": KH1LocationData("Hollow Bastion", 265_4214),
"Hollow Bastion Library Top of Bookshelf Turn the Carousel Chest": KH1LocationData("Hollow Bastion", 265_4251),
"Hollow Bastion Library 2nd Floor Turn the Carousel 1st Chest": KH1LocationData("Hollow Bastion", 265_4252),
"Hollow Bastion Library 2nd Floor Turn the Carousel 2nd Chest": KH1LocationData("Hollow Bastion", 265_4253),
"Hollow Bastion Lift Stop Library Node After High Tower Switch Gravity Chest": KH1LocationData("Hollow Bastion", 265_4254),
"Hollow Bastion Lift Stop Library Node Gravity Chest": KH1LocationData("Hollow Bastion", 265_4291),
"Hollow Bastion Lift Stop Under High Tower Sliding Blocks Chest": KH1LocationData("Hollow Bastion", 265_4292),
"Hollow Bastion Lift Stop Outside Library Gravity Chest": KH1LocationData("Hollow Bastion", 265_4293),
"Hollow Bastion Lift Stop Heartless Sigil Door Gravity Chest": KH1LocationData("Hollow Bastion", 265_4294),
"Hollow Bastion Base Level Bubble Under the Wall Platform Chest": KH1LocationData("Hollow Bastion", 265_4331),
"Hollow Bastion Base Level Platform Near Entrance Chest": KH1LocationData("Hollow Bastion", 265_4332),
"Hollow Bastion Base Level Near Crystal Switch Chest": KH1LocationData("Hollow Bastion", 265_4333),
"Hollow Bastion Waterway Near Save Chest": KH1LocationData("Hollow Bastion", 265_4334),
"Hollow Bastion Waterway Blizzard on Bubble Chest": KH1LocationData("Hollow Bastion", 265_4371),
"Hollow Bastion Waterway Unlock Passage from Base Level Chest": KH1LocationData("Hollow Bastion", 265_4372),
"Hollow Bastion Dungeon By Candles Chest": KH1LocationData("Hollow Bastion", 265_4373),
"Hollow Bastion Dungeon Corner Chest": KH1LocationData("Hollow Bastion", 265_4374),
"Hollow Bastion Grand Hall Steps Right Side Chest": KH1LocationData("Hollow Bastion", 265_4454),
"Hollow Bastion Grand Hall Oblivion Chest": KH1LocationData("Hollow Bastion", 265_4491),
"Hollow Bastion Grand Hall Left of Gate Chest": KH1LocationData("Hollow Bastion", 265_4492),
#"Hollow Bastion Entrance Hall Push the Statue Chest": KH1LocationData("Hollow Bastion", 265_4493), --handled later
"Hollow Bastion Entrance Hall Left of Emblem Door Chest": KH1LocationData("Hollow Bastion", 265_4212),
"Hollow Bastion Rising Falls White Trinity Chest": KH1LocationData("Hollow Bastion", 265_4494),
"End of the World Final Dimension 1st Chest": KH1LocationData("End of the World", 265_4531),
"End of the World Final Dimension 2nd Chest": KH1LocationData("End of the World", 265_4532),
"End of the World Final Dimension 3rd Chest": KH1LocationData("End of the World", 265_4533),
"End of the World Final Dimension 4th Chest": KH1LocationData("End of the World", 265_4534),
"End of the World Final Dimension 5th Chest": KH1LocationData("End of the World", 265_4571),
"End of the World Final Dimension 6th Chest": KH1LocationData("End of the World", 265_4572),
"End of the World Final Dimension 10th Chest": KH1LocationData("End of the World", 265_4573),
"End of the World Final Dimension 9th Chest": KH1LocationData("End of the World", 265_4574),
"End of the World Final Dimension 8th Chest": KH1LocationData("End of the World", 265_4611),
"End of the World Final Dimension 7th Chest": KH1LocationData("End of the World", 265_4612),
"End of the World Giant Crevasse 3rd Chest": KH1LocationData("End of the World", 265_4613),
"End of the World Giant Crevasse 5th Chest": KH1LocationData("End of the World", 265_4614),
"End of the World Giant Crevasse 1st Chest": KH1LocationData("End of the World", 265_4651),
"End of the World Giant Crevasse 4th Chest": KH1LocationData("End of the World", 265_4652),
"End of the World Giant Crevasse 2nd Chest": KH1LocationData("End of the World", 265_4653),
"End of the World World Terminus Traverse Town Chest": KH1LocationData("End of the World", 265_4654),
"End of the World World Terminus Wonderland Chest": KH1LocationData("End of the World", 265_4691),
"End of the World World Terminus Olympus Coliseum Chest": KH1LocationData("End of the World", 265_4692),
"End of the World World Terminus Deep Jungle Chest": KH1LocationData("End of the World", 265_4693),
"End of the World World Terminus Agrabah Chest": KH1LocationData("End of the World", 265_4694),
"End of the World World Terminus Atlantica Chest": KH1LocationData("End of the World", 265_4731),
"End of the World World Terminus Halloween Town Chest": KH1LocationData("End of the World", 265_4732),
"End of the World World Terminus Neverland Chest": KH1LocationData("End of the World", 265_4733),
"End of the World World Terminus 100 Acre Wood Chest": KH1LocationData("End of the World", 265_4734),
#"End of the World World Terminus Hollow Bastion Chest": KH1LocationData("End of the World", 265_4771),
"End of the World Final Rest Chest": KH1LocationData("End of the World", 265_4772),
"Monstro Chamber 6 White Trinity Chest": KH1LocationData("End of the World", 265_5092),
#"Awakening Chest": KH1LocationData("Awakening", 265_5093), missable
"Traverse Town Defeat Guard Armor Dodge Roll Event": KH1LocationData("Traverse Town", 265_6011),
"Traverse Town Defeat Guard Armor Fire Event": KH1LocationData("Traverse Town", 265_6012),
"Traverse Town Defeat Guard Armor Blue Trinity Event": KH1LocationData("Traverse Town", 265_6013),
"Traverse Town Leon Secret Waterway Earthshine Event": KH1LocationData("Traverse Town", 265_6014),
"Traverse Town Kairi Secret Waterway Oathkeeper Event": KH1LocationData("Traverse Town", 265_6015),
"Traverse Town Defeat Guard Armor Brave Warrior Event": KH1LocationData("Traverse Town", 265_6016),
"Deep Jungle Defeat Sabor White Fang Event": KH1LocationData("Deep Jungle", 265_6021),
"Deep Jungle Defeat Clayton Cure Event": KH1LocationData("Deep Jungle", 265_6022),
"Deep Jungle Seal Keyhole Jungle King Event": KH1LocationData("Deep Jungle", 265_6023),
"Deep Jungle Seal Keyhole Red Trinity Event": KH1LocationData("Deep Jungle", 265_6024),
"Olympus Coliseum Clear Phil's Training Thunder Event": KH1LocationData("Olympus Coliseum", 265_6031),
"Olympus Coliseum Defeat Cerberus Inferno Band Event": KH1LocationData("Olympus Coliseum", 265_6033),
"Wonderland Defeat Trickmaster Blizzard Event": KH1LocationData("Wonderland", 265_6041),
"Wonderland Defeat Trickmaster Ifrit's Horn Event": KH1LocationData("Wonderland", 265_6042),
"Agrabah Defeat Pot Centipede Ray of Light Event": KH1LocationData("Agrabah", 265_6051),
"Agrabah Defeat Jafar Blizzard Event": KH1LocationData("Agrabah", 265_6052),
"Agrabah Defeat Jafar Genie Fire Event": KH1LocationData("Agrabah", 265_6053),
"Agrabah Seal Keyhole Genie Event": KH1LocationData("Agrabah", 265_6054),
"Agrabah Seal Keyhole Three Wishes Event": KH1LocationData("Agrabah", 265_6055),
"Agrabah Seal Keyhole Green Trinity Event": KH1LocationData("Agrabah", 265_6056),
"Monstro Defeat Parasite Cage I Goofy Cheer Event": KH1LocationData("Monstro", 265_6061),
"Monstro Defeat Parasite Cage II Stop Event": KH1LocationData("Monstro", 265_6062),
"Atlantica Defeat Ursula I Mermaid Kick Event": KH1LocationData("Atlantica", 265_6071),
"Atlantica Defeat Ursula II Thunder Event": KH1LocationData("Atlantica", 265_6072),
"Atlantica Seal Keyhole Crabclaw Event": KH1LocationData("Atlantica", 265_6073),
"Halloween Town Defeat Oogie Boogie Holy Circlet Event": KH1LocationData("Halloween Town", 265_6081),
"Halloween Town Defeat Oogie's Manor Gravity Event": KH1LocationData("Halloween Town", 265_6082),
"Halloween Town Seal Keyhole Pumpkinhead Event": KH1LocationData("Halloween Town", 265_6083),
"Neverland Defeat Anti Sora Raven's Claw Event": KH1LocationData("Neverland", 265_6091),
"Neverland Encounter Hook Cure Event": KH1LocationData("Neverland", 265_6092),
"Neverland Seal Keyhole Fairy Harp Event": KH1LocationData("Neverland", 265_6093),
"Neverland Seal Keyhole Tinker Bell Event": KH1LocationData("Neverland", 265_6094),
"Neverland Seal Keyhole Glide Event": KH1LocationData("Neverland", 265_6095),
"Neverland Defeat Phantom Stop Event": KH1LocationData("Neverland", 265_6096),
"Neverland Defeat Captain Hook Ars Arcanum Event": KH1LocationData("Neverland", 265_6097),
"Hollow Bastion Defeat Riku I White Trinity Event": KH1LocationData("Hollow Bastion", 265_6101),
"Hollow Bastion Defeat Maleficent Donald Cheer Event": KH1LocationData("Hollow Bastion", 265_6102),
"Hollow Bastion Defeat Dragon Maleficent Fireglow Event": KH1LocationData("Hollow Bastion", 265_6103),
"Hollow Bastion Defeat Riku II Ragnarok Event": KH1LocationData("Hollow Bastion", 265_6104),
"Hollow Bastion Defeat Behemoth Omega Arts Event": KH1LocationData("Hollow Bastion", 265_6105),
"Hollow Bastion Speak to Princesses Fire Event": KH1LocationData("Hollow Bastion", 265_6106),
"End of the World Defeat Chernabog Superglide Event": KH1LocationData("End of the World", 265_6111),
"Traverse Town Mail Postcard 01 Event": KH1LocationData("Traverse Town", 265_6120),
"Traverse Town Mail Postcard 02 Event": KH1LocationData("Traverse Town", 265_6121),
"Traverse Town Mail Postcard 03 Event": KH1LocationData("Traverse Town", 265_6122),
"Traverse Town Mail Postcard 04 Event": KH1LocationData("Traverse Town", 265_6123),
"Traverse Town Mail Postcard 05 Event": KH1LocationData("Traverse Town", 265_6124),
"Traverse Town Mail Postcard 06 Event": KH1LocationData("Traverse Town", 265_6125),
"Traverse Town Mail Postcard 07 Event": KH1LocationData("Traverse Town", 265_6126),
"Traverse Town Mail Postcard 08 Event": KH1LocationData("Traverse Town", 265_6127),
"Traverse Town Mail Postcard 09 Event": KH1LocationData("Traverse Town", 265_6128),
"Traverse Town Mail Postcard 10 Event": KH1LocationData("Traverse Town", 265_6129),
"Traverse Town Defeat Opposite Armor Aero Event": KH1LocationData("Traverse Town", 265_6131),
"Atlantica Undersea Gorge Blizzard Clam": KH1LocationData("Atlantica", 265_6201),
"Atlantica Undersea Gorge Ocean Floor Clam": KH1LocationData("Atlantica", 265_6202),
"Atlantica Undersea Valley Higher Cave Clam": KH1LocationData("Atlantica", 265_6203),
"Atlantica Undersea Valley Lower Cave Clam": KH1LocationData("Atlantica", 265_6204),
"Atlantica Undersea Valley Fire Clam": KH1LocationData("Atlantica", 265_6205),
"Atlantica Undersea Valley Wall Clam": KH1LocationData("Atlantica", 265_6206),
"Atlantica Undersea Valley Pillar Clam": KH1LocationData("Atlantica", 265_6207),
"Atlantica Undersea Valley Ocean Floor Clam": KH1LocationData("Atlantica", 265_6208),
"Atlantica Triton's Palace Thunder Clam": KH1LocationData("Atlantica", 265_6209),
"Atlantica Triton's Palace Wall Right Clam": KH1LocationData("Atlantica", 265_6210),
"Atlantica Triton's Palace Near Path Clam": KH1LocationData("Atlantica", 265_6211),
"Atlantica Triton's Palace Wall Left Clam": KH1LocationData("Atlantica", 265_6212),
"Atlantica Cavern Nook Clam": KH1LocationData("Atlantica", 265_6213),
"Atlantica Below Deck Clam": KH1LocationData("Atlantica", 265_6214),
"Atlantica Undersea Garden Clam": KH1LocationData("Atlantica", 265_6215),
"Atlantica Undersea Cave Clam": KH1LocationData("Atlantica", 265_6216),
#"Traverse Town Magician's Study Turn in Naturespark": KH1LocationData("Traverse Town", 265_6300),
#"Traverse Town Magician's Study Turn in Watergleam": KH1LocationData("Traverse Town", 265_6301),
#"Traverse Town Magician's Study Turn in Fireglow": KH1LocationData("Traverse Town", 265_6302),
#"Traverse Town Magician's Study Turn in all Summon Gems": KH1LocationData("Traverse Town", 265_6303),
"Traverse Town Geppetto's House Geppetto Reward 1": KH1LocationData("Traverse Town", 265_6304),
"Traverse Town Geppetto's House Geppetto Reward 2": KH1LocationData("Traverse Town", 265_6305),
"Traverse Town Geppetto's House Geppetto Reward 3": KH1LocationData("Traverse Town", 265_6306),
"Traverse Town Geppetto's House Geppetto Reward 4": KH1LocationData("Traverse Town", 265_6307),
"Traverse Town Geppetto's House Geppetto Reward 5": KH1LocationData("Traverse Town", 265_6308),
"Traverse Town Geppetto's House Geppetto All Summons Reward": KH1LocationData("Traverse Town", 265_6309),
"Traverse Town Geppetto's House Talk to Pinocchio": KH1LocationData("Traverse Town", 265_6310),
"Traverse Town Magician's Study Obtained All Arts Items": KH1LocationData("Traverse Town", 265_6311),
"Traverse Town Magician's Study Obtained All LV1 Magic": KH1LocationData("Traverse Town", 265_6312),
"Traverse Town Magician's Study Obtained All LV3 Magic": KH1LocationData("Traverse Town", 265_6313),
"Traverse Town Piano Room Return 10 Puppies": KH1LocationData("Traverse Town", 265_6314),
"Traverse Town Piano Room Return 20 Puppies": KH1LocationData("Traverse Town", 265_6315),
"Traverse Town Piano Room Return 30 Puppies": KH1LocationData("Traverse Town", 265_6316),
"Traverse Town Piano Room Return 40 Puppies": KH1LocationData("Traverse Town", 265_6317),
"Traverse Town Piano Room Return 50 Puppies Reward 1": KH1LocationData("Traverse Town", 265_6318),
"Traverse Town Piano Room Return 50 Puppies Reward 2": KH1LocationData("Traverse Town", 265_6319),
"Traverse Town Piano Room Return 60 Puppies": KH1LocationData("Traverse Town", 265_6320),
"Traverse Town Piano Room Return 70 Puppies": KH1LocationData("Traverse Town", 265_6321),
"Traverse Town Piano Room Return 80 Puppies": KH1LocationData("Traverse Town", 265_6322),
"Traverse Town Piano Room Return 90 Puppies": KH1LocationData("Traverse Town", 265_6324),
"Traverse Town Piano Room Return 99 Puppies Reward 1": KH1LocationData("Traverse Town", 265_6326),
"Traverse Town Piano Room Return 99 Puppies Reward 2": KH1LocationData("Traverse Town", 265_6327),
"Olympus Coliseum Cloud Sonic Blade Event": KH1LocationData("Olympus Coliseum", 265_6032), #Had to change the way we send this check, not changing location_id
"Olympus Coliseum Defeat Sephiroth One-Winged Angel Event": KH1LocationData("Olympus Coliseum", 265_6328),
"Olympus Coliseum Defeat Ice Titan Diamond Dust Event": KH1LocationData("Olympus Coliseum", 265_6329),
"Olympus Coliseum Gates Purple Jar After Defeating Hades": KH1LocationData("Olympus Coliseum", 265_6330),
"Halloween Town Guillotine Square Ring Jack's Doorbell 3 Times": KH1LocationData("Halloween Town", 265_6331),
#"Neverland Clock Tower 01:00 Door": KH1LocationData("Neverland", 265_6332),
#"Neverland Clock Tower 02:00 Door": KH1LocationData("Neverland", 265_6333),
#"Neverland Clock Tower 03:00 Door": KH1LocationData("Neverland", 265_6334),
#"Neverland Clock Tower 04:00 Door": KH1LocationData("Neverland", 265_6335),
#"Neverland Clock Tower 05:00 Door": KH1LocationData("Neverland", 265_6336),
#"Neverland Clock Tower 06:00 Door": KH1LocationData("Neverland", 265_6337),
#"Neverland Clock Tower 07:00 Door": KH1LocationData("Neverland", 265_6338),
#"Neverland Clock Tower 08:00 Door": KH1LocationData("Neverland", 265_6339),
#"Neverland Clock Tower 09:00 Door": KH1LocationData("Neverland", 265_6340),
#"Neverland Clock Tower 10:00 Door": KH1LocationData("Neverland", 265_6341),
#"Neverland Clock Tower 11:00 Door": KH1LocationData("Neverland", 265_6342),
#"Neverland Clock Tower 12:00 Door": KH1LocationData("Neverland", 265_6343),
"Neverland Hold Aero Chest": KH1LocationData("Neverland", 265_6344),
"100 Acre Wood Bouncing Spot Turn in Rare Nut 1": KH1LocationData("100 Acre Wood", 265_6345),
"100 Acre Wood Bouncing Spot Turn in Rare Nut 2": KH1LocationData("100 Acre Wood", 265_6346),
"100 Acre Wood Bouncing Spot Turn in Rare Nut 3": KH1LocationData("100 Acre Wood", 265_6347),
"100 Acre Wood Bouncing Spot Turn in Rare Nut 4": KH1LocationData("100 Acre Wood", 265_6348),
"100 Acre Wood Bouncing Spot Turn in Rare Nut 5": KH1LocationData("100 Acre Wood", 265_6349),
"100 Acre Wood Pooh's House Owl Cheer": KH1LocationData("100 Acre Wood", 265_6350),
"100 Acre Wood Convert Torn Page 1": KH1LocationData("100 Acre Wood", 265_6351),
"100 Acre Wood Convert Torn Page 2": KH1LocationData("100 Acre Wood", 265_6352),
"100 Acre Wood Convert Torn Page 3": KH1LocationData("100 Acre Wood", 265_6353),
"100 Acre Wood Convert Torn Page 4": KH1LocationData("100 Acre Wood", 265_6354),
"100 Acre Wood Convert Torn Page 5": KH1LocationData("100 Acre Wood", 265_6355),
"100 Acre Wood Pooh's House Start Fire": KH1LocationData("100 Acre Wood", 265_6356),
"100 Acre Wood Pooh's Room Cabinet": KH1LocationData("100 Acre Wood", 265_6357),
"100 Acre Wood Pooh's Room Chimney": KH1LocationData("100 Acre Wood", 265_6358),
"100 Acre Wood Bouncing Spot Break Log": KH1LocationData("100 Acre Wood", 265_6359),
"100 Acre Wood Bouncing Spot Fall Through Top of Tree Next to Pooh": KH1LocationData("100 Acre Wood", 265_6360),
"Deep Jungle Camp Hi-Potion Experiment": KH1LocationData("Deep Jungle", 265_6361),
"Deep Jungle Camp Ether Experiment": KH1LocationData("Deep Jungle", 265_6362),
"Deep Jungle Camp Replication Experiment": KH1LocationData("Deep Jungle", 265_6363),
"Deep Jungle Cliff Save Gorillas": KH1LocationData("Deep Jungle", 265_6364),
"Deep Jungle Tree House Save Gorillas": KH1LocationData("Deep Jungle", 265_6365),
"Deep Jungle Camp Save Gorillas": KH1LocationData("Deep Jungle", 265_6366),
"Deep Jungle Bamboo Thicket Save Gorillas": KH1LocationData("Deep Jungle", 265_6367),
"Deep Jungle Climbing Trees Save Gorillas": KH1LocationData("Deep Jungle", 265_6368),
"Olympus Coliseum Olympia Chest": KH1LocationData("Olympus Coliseum", 265_6369),
"Deep Jungle Jungle Slider 10 Fruits": KH1LocationData("Deep Jungle", 265_6370),
"Deep Jungle Jungle Slider 20 Fruits": KH1LocationData("Deep Jungle", 265_6371),
"Deep Jungle Jungle Slider 30 Fruits": KH1LocationData("Deep Jungle", 265_6372),
"Deep Jungle Jungle Slider 40 Fruits": KH1LocationData("Deep Jungle", 265_6373),
"Deep Jungle Jungle Slider 50 Fruits": KH1LocationData("Deep Jungle", 265_6374),
"Traverse Town 1st District Speak with Cid Event": KH1LocationData("Traverse Town", 265_6375),
"Wonderland Bizarre Room Read Book": KH1LocationData("Wonderland", 265_6376),
"Olympus Coliseum Coliseum Gates Green Trinity": KH1LocationData("Olympus Coliseum", 265_6377),
"Agrabah Defeat Kurt Zisa Zantetsuken Event": KH1LocationData("Agrabah", 265_6378),
"Hollow Bastion Defeat Unknown EXP Necklace Event": KH1LocationData("Hollow Bastion", 265_6379),
"Olympus Coliseum Coliseum Gates Hero's License Event": KH1LocationData("Olympus Coliseum", 265_6380),
"Atlantica Sunken Ship Crystal Trident Event": KH1LocationData("Atlantica", 265_6381),
"Halloween Town Graveyard Forget-Me-Not Event": KH1LocationData("Halloween Town", 265_6382),
"Deep Jungle Tent Protect-G Event": KH1LocationData("Deep Jungle", 265_6383),
"Deep Jungle Cavern of Hearts Navi-G Piece Event": KH1LocationData("Deep Jungle", 265_6384),
"Wonderland Bizarre Room Navi-G Piece Event": KH1LocationData("Wonderland", 265_6385),
"Olympus Coliseum Coliseum Gates Entry Pass Event": KH1LocationData("Olympus Coliseum", 265_6386),
"Traverse Town Synth Log": KH1LocationData("Traverse Town", 265_6401),
"Traverse Town Synth Cloth": KH1LocationData("Traverse Town", 265_6402),
"Traverse Town Synth Rope": KH1LocationData("Traverse Town", 265_6403),
"Traverse Town Synth Seagull Egg": KH1LocationData("Traverse Town", 265_6404),
"Traverse Town Synth Fish": KH1LocationData("Traverse Town", 265_6405),
"Traverse Town Synth Mushroom": KH1LocationData("Traverse Town", 265_6406),
"Traverse Town Item Shop Postcard": KH1LocationData("Traverse Town", 265_6500),
"Traverse Town 1st District Safe Postcard": KH1LocationData("Traverse Town", 265_6501),
"Traverse Town Gizmo Shop Postcard 1": KH1LocationData("Traverse Town", 265_6502),
"Traverse Town Gizmo Shop Postcard 2": KH1LocationData("Traverse Town", 265_6503),
"Traverse Town Item Workshop Postcard": KH1LocationData("Traverse Town", 265_6504),
"Traverse Town 3rd District Balcony Postcard": KH1LocationData("Traverse Town", 265_6505),
"Traverse Town Geppetto's House Postcard": KH1LocationData("Traverse Town", 265_6506),
"Halloween Town Lab Torn Page": KH1LocationData("Halloween Town", 265_6508),
"Hollow Bastion Entrance Hall Emblem Piece (Flame)": KH1LocationData("Hollow Bastion", 265_6516),
"Hollow Bastion Entrance Hall Emblem Piece (Chest)": KH1LocationData("Hollow Bastion", 265_6517),
"Hollow Bastion Entrance Hall Emblem Piece (Statue)": KH1LocationData("Hollow Bastion", 265_6518),
"Hollow Bastion Entrance Hall Emblem Piece (Fountain)": KH1LocationData("Hollow Bastion", 265_6519),
#"Traverse Town 1st District Leon Gift": KH1LocationData("Traverse Town", 265_6520),
#"Traverse Town 1st District Aerith Gift": KH1LocationData("Traverse Town", 265_6521),
"Hollow Bastion Library Speak to Belle Divine Rose": KH1LocationData("Hollow Bastion", 265_6522),
"Hollow Bastion Library Speak to Aerith Cure": KH1LocationData("Hollow Bastion", 265_6523),
"Agrabah Defeat Jafar Genie Ansem's Report 1": KH1LocationData("Agrabah", 265_7018),
"Hollow Bastion Speak with Aerith Ansem's Report 2": KH1LocationData("Hollow Bastion", 265_7017),
"Atlantica Defeat Ursula II Ansem's Report 3": KH1LocationData("Atlantica", 265_7016),
"Hollow Bastion Speak with Aerith Ansem's Report 4": KH1LocationData("Hollow Bastion", 265_7015),
"Hollow Bastion Defeat Maleficent Ansem's Report 5": KH1LocationData("Hollow Bastion", 265_7014),
"Hollow Bastion Speak with Aerith Ansem's Report 6": KH1LocationData("Hollow Bastion", 265_7013),
"Halloween Town Defeat Oogie Boogie Ansem's Report 7": KH1LocationData("Halloween Town", 265_7012),
"Olympus Coliseum Defeat Hades Ansem's Report 8": KH1LocationData("Olympus Coliseum", 265_7011),
"Neverland Defeat Hook Ansem's Report 9": KH1LocationData("Neverland", 265_7028),
"Hollow Bastion Speak with Aerith Ansem's Report 10": KH1LocationData("Hollow Bastion", 265_7027),
"Agrabah Defeat Kurt Zisa Ansem's Report 11": KH1LocationData("Agrabah", 265_7026),
"Olympus Coliseum Defeat Sephiroth Ansem's Report 12": KH1LocationData("Olympus Coliseum", 265_7025),
"Hollow Bastion Defeat Unknown Ansem's Report 13": KH1LocationData("Hollow Bastion", 265_7024),
"Level 001": KH1LocationData("Levels", 265_8001),
"Level 002": KH1LocationData("Levels", 265_8002),
"Level 003": KH1LocationData("Levels", 265_8003),
"Level 004": KH1LocationData("Levels", 265_8004),
"Level 005": KH1LocationData("Levels", 265_8005),
"Level 006": KH1LocationData("Levels", 265_8006),
"Level 007": KH1LocationData("Levels", 265_8007),
"Level 008": KH1LocationData("Levels", 265_8008),
"Level 009": KH1LocationData("Levels", 265_8009),
"Level 010": KH1LocationData("Levels", 265_8010),
"Level 011": KH1LocationData("Levels", 265_8011),
"Level 012": KH1LocationData("Levels", 265_8012),
"Level 013": KH1LocationData("Levels", 265_8013),
"Level 014": KH1LocationData("Levels", 265_8014),
"Level 015": KH1LocationData("Levels", 265_8015),
"Level 016": KH1LocationData("Levels", 265_8016),
"Level 017": KH1LocationData("Levels", 265_8017),
"Level 018": KH1LocationData("Levels", 265_8018),
"Level 019": KH1LocationData("Levels", 265_8019),
"Level 020": KH1LocationData("Levels", 265_8020),
"Level 021": KH1LocationData("Levels", 265_8021),
"Level 022": KH1LocationData("Levels", 265_8022),
"Level 023": KH1LocationData("Levels", 265_8023),
"Level 024": KH1LocationData("Levels", 265_8024),
"Level 025": KH1LocationData("Levels", 265_8025),
"Level 026": KH1LocationData("Levels", 265_8026),
"Level 027": KH1LocationData("Levels", 265_8027),
"Level 028": KH1LocationData("Levels", 265_8028),
"Level 029": KH1LocationData("Levels", 265_8029),
"Level 030": KH1LocationData("Levels", 265_8030),
"Level 031": KH1LocationData("Levels", 265_8031),
"Level 032": KH1LocationData("Levels", 265_8032),
"Level 033": KH1LocationData("Levels", 265_8033),
"Level 034": KH1LocationData("Levels", 265_8034),
"Level 035": KH1LocationData("Levels", 265_8035),
"Level 036": KH1LocationData("Levels", 265_8036),
"Level 037": KH1LocationData("Levels", 265_8037),
"Level 038": KH1LocationData("Levels", 265_8038),
"Level 039": KH1LocationData("Levels", 265_8039),
"Level 040": KH1LocationData("Levels", 265_8040),
"Level 041": KH1LocationData("Levels", 265_8041),
"Level 042": KH1LocationData("Levels", 265_8042),
"Level 043": KH1LocationData("Levels", 265_8043),
"Level 044": KH1LocationData("Levels", 265_8044),
"Level 045": KH1LocationData("Levels", 265_8045),
"Level 046": KH1LocationData("Levels", 265_8046),
"Level 047": KH1LocationData("Levels", 265_8047),
"Level 048": KH1LocationData("Levels", 265_8048),
"Level 049": KH1LocationData("Levels", 265_8049),
"Level 050": KH1LocationData("Levels", 265_8050),
"Level 051": KH1LocationData("Levels", 265_8051),
"Level 052": KH1LocationData("Levels", 265_8052),
"Level 053": KH1LocationData("Levels", 265_8053),
"Level 054": KH1LocationData("Levels", 265_8054),
"Level 055": KH1LocationData("Levels", 265_8055),
"Level 056": KH1LocationData("Levels", 265_8056),
"Level 057": KH1LocationData("Levels", 265_8057),
"Level 058": KH1LocationData("Levels", 265_8058),
"Level 059": KH1LocationData("Levels", 265_8059),
"Level 060": KH1LocationData("Levels", 265_8060),
"Level 061": KH1LocationData("Levels", 265_8061),
"Level 062": KH1LocationData("Levels", 265_8062),
"Level 063": KH1LocationData("Levels", 265_8063),
"Level 064": KH1LocationData("Levels", 265_8064),
"Level 065": KH1LocationData("Levels", 265_8065),
"Level 066": KH1LocationData("Levels", 265_8066),
"Level 067": KH1LocationData("Levels", 265_8067),
"Level 068": KH1LocationData("Levels", 265_8068),
"Level 069": KH1LocationData("Levels", 265_8069),
"Level 070": KH1LocationData("Levels", 265_8070),
"Level 071": KH1LocationData("Levels", 265_8071),
"Level 072": KH1LocationData("Levels", 265_8072),
"Level 073": KH1LocationData("Levels", 265_8073),
"Level 074": KH1LocationData("Levels", 265_8074),
"Level 075": KH1LocationData("Levels", 265_8075),
"Level 076": KH1LocationData("Levels", 265_8076),
"Level 077": KH1LocationData("Levels", 265_8077),
"Level 078": KH1LocationData("Levels", 265_8078),
"Level 079": KH1LocationData("Levels", 265_8079),
"Level 080": KH1LocationData("Levels", 265_8080),
"Level 081": KH1LocationData("Levels", 265_8081),
"Level 082": KH1LocationData("Levels", 265_8082),
"Level 083": KH1LocationData("Levels", 265_8083),
"Level 084": KH1LocationData("Levels", 265_8084),
"Level 085": KH1LocationData("Levels", 265_8085),
"Level 086": KH1LocationData("Levels", 265_8086),
"Level 087": KH1LocationData("Levels", 265_8087),
"Level 088": KH1LocationData("Levels", 265_8088),
"Level 089": KH1LocationData("Levels", 265_8089),
"Level 090": KH1LocationData("Levels", 265_8090),
"Level 091": KH1LocationData("Levels", 265_8091),
"Level 092": KH1LocationData("Levels", 265_8092),
"Level 093": KH1LocationData("Levels", 265_8093),
"Level 094": KH1LocationData("Levels", 265_8094),
"Level 095": KH1LocationData("Levels", 265_8095),
"Level 096": KH1LocationData("Levels", 265_8096),
"Level 097": KH1LocationData("Levels", 265_8097),
"Level 098": KH1LocationData("Levels", 265_8098),
"Level 099": KH1LocationData("Levels", 265_8099),
"Level 100": KH1LocationData("Levels", 265_8100),
"Complete Phil Cup": KH1LocationData("Olympus Coliseum", 265_9001),
"Complete Phil Cup Solo": KH1LocationData("Olympus Coliseum", 265_9002),
"Complete Phil Cup Time Trial": KH1LocationData("Olympus Coliseum", 265_9003),
"Complete Pegasus Cup": KH1LocationData("Olympus Coliseum", 265_9004),
"Complete Pegasus Cup Solo": KH1LocationData("Olympus Coliseum", 265_9005),
"Complete Pegasus Cup Time Trial": KH1LocationData("Olympus Coliseum", 265_9006),
"Complete Hercules Cup": KH1LocationData("Olympus Coliseum", 265_9007),
"Complete Hercules Cup Solo": KH1LocationData("Olympus Coliseum", 265_9008),
"Complete Hercules Cup Time Trial": KH1LocationData("Olympus Coliseum", 265_9009),
"Complete Hades Cup": KH1LocationData("Olympus Coliseum", 265_9010),
"Complete Hades Cup Solo": KH1LocationData("Olympus Coliseum", 265_9011),
"Complete Hades Cup Time Trial": KH1LocationData("Olympus Coliseum", 265_9012),
"Hades Cup Defeat Cloud and Leon Event": KH1LocationData("Olympus Coliseum", 265_9013),
"Hades Cup Defeat Yuffie Event": KH1LocationData("Olympus Coliseum", 265_9014),
"Hades Cup Defeat Cerberus Event": KH1LocationData("Olympus Coliseum", 265_9015),
"Hades Cup Defeat Behemoth Event": KH1LocationData("Olympus Coliseum", 265_9016),
"Hades Cup Defeat Hades Event": KH1LocationData("Olympus Coliseum", 265_9017),
"Hercules Cup Defeat Cloud Event": KH1LocationData("Olympus Coliseum", 265_9018),
"Hercules Cup Yellow Trinity Event": KH1LocationData("Olympus Coliseum", 265_9019),
"Final Ansem": KH1LocationData("Final", 265_9999)
}
event_location_table: Dict[str, KH1LocationData] = {}
lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in location_table.items() if data.code}
#Make location categories
location_name_groups: Dict[str, Set[str]] = {}
for location in location_table.keys():
category = location_table[location].category
if category not in location_name_groups.keys():
location_name_groups[category] = set()
location_name_groups[category].add(location)

View File

@@ -1,445 +0,0 @@
from dataclasses import dataclass
from Options import NamedRange, Choice, Range, Toggle, DefaultOnToggle, PerGameCommonOptions, StartInventoryPool, OptionGroup
class StrengthIncrease(Range):
"""
Determines the number of Strength Increases to add to the multiworld.
The randomizer will add all stat ups defined here into a pool and choose up to 100 to add to the multiworld.
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 100 total) are chosen at random.
"""
display_name = "STR Increases"
range_start = 0
range_end = 100
default = 24
class DefenseIncrease(Range):
"""
Determines the number of Defense Increases to add to the multiworld.
The randomizer will add all stat ups defined here into a pool and choose up to 100 to add to the multiworld.
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 100 total) are chosen at random.
"""
display_name = "DEF Increases"
range_start = 0
range_end = 100
default = 24
class HPIncrease(Range):
"""
Determines the number of HP Increases to add to the multiworld.
The randomizer will add all stat ups defined here into a pool and choose up to 100 to add to the multiworld.
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 100 total) are chosen at random.
"""
display_name = "HP Increases"
range_start = 0
range_end = 100
default = 23
class APIncrease(Range):
"""
Determines the number of AP Increases to add to the multiworld.
The randomizer will add all stat ups defined here into a pool and choose up to 100 to add to the multiworld.
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 100 total) are chosen at random.
"""
display_name = "AP Increases"
range_start = 0
range_end = 100
default = 18
class MPIncrease(Range):
"""
Determines the number of MP Increases to add to the multiworld.
The randomizer will add all stat ups defined here into a pool and choose up to 100 to add to the multiworld.
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 100 total) are chosen at random.
"""
display_name = "MP Increases"
range_start = 0
range_end = 20
default = 7
class AccessorySlotIncrease(Range):
"""
Determines the number of Accessory Slot Increases to add to the multiworld.
The randomizer will add all stat ups defined here into a pool and choose up to 100 to add to the multiworld.
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 100 total) are chosen at random.
"""
display_name = "Accessory Slot Increases"
range_start = 0
range_end = 6
default = 1
class ItemSlotIncrease(Range):
"""
Determines the number of Item Slot Increases to add to the multiworld.
The randomizer will add all stat ups defined here into a pool and choose up to 100 to add to the multiworld.
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 100 total) are chosen at random.
"""
display_name = "Item Slot Increases"
range_start = 0
range_end = 5
default = 3
class Atlantica(Toggle):
"""
Toggle whether to include checks in Atlantica.
"""
display_name = "Atlantica"
class HundredAcreWood(Toggle):
"""
Toggle whether to include checks in the 100 Acre Wood.
"""
display_name = "100 Acre Wood"
class SuperBosses(Toggle):
"""
Toggle whether to include checks behind Super Bosses.
"""
display_name = "Super Bosses"
class Cups(Toggle):
"""
Toggle whether to include checks behind completing Phil, Pegasus, Hercules, or Hades cups.
Please note that the cup items will still appear in the multiworld even if toggled off, as they are required to challenge Sephiroth.
"""
display_name = "Cups"
class Goal(Choice):
"""
Determines when victory is achieved in your playthrough.
Sephiroth: Defeat Sephiroth
Unknown: Defeat Unknown
Postcards: Turn in all 10 postcards in Traverse Town
Final Ansem: Enter End of the World and defeat Ansem as normal
Puppies: Rescue and return all 99 puppies in Traverse Town
Final Rest: Open the chest in End of the World Final Rest
"""
display_name = "Goal"
option_sephiroth = 0
option_unknown = 1
option_postcards = 2
option_final_ansem = 3
option_puppies = 4
option_final_rest = 5
default = 3
class EndoftheWorldUnlock(Choice):
"""Determines how End of the World is unlocked.
Item: You can receive an item called "End of the World" which unlocks the world
Reports: A certain amount of reports are required to unlock End of the World, which is defined in your options"""
display_name = "End of the World Unlock"
option_item = 0
option_reports = 1
default = 1
class FinalRestDoor(Choice):
"""Determines what conditions need to be met to manifest the door in Final Rest, allowing the player to challenge Ansem.
Reports: A certain number of Ansem's Reports are required, determined by the "Reports to Open Final Rest Door" option
Puppies: Having all 99 puppies is required
Postcards: Turning in all 10 postcards is required
Superbosses: Defeating Sephiroth, Unknown, Kurt Zisa, and Phantom are required
"""
display_name = "Final Rest Door"
option_reports = 0
option_puppies = 1
option_postcards = 2
option_superbosses = 3
class Puppies(Choice):
"""
Determines how dalmatian puppies are shuffled into the pool.
Full: All puppies are in one location
Triplets: Puppies are found in triplets just as they are in the base game
Individual: One puppy can be found per location
"""
display_name = "Puppies"
option_full = 0
option_triplets = 1
option_individual = 2
default = 1
class EXPMultiplier(NamedRange):
"""
Determines the multiplier to apply to EXP gained.
"""
display_name = "EXP Multiplier"
default = 16
range_start = default // 4
range_end = 128
special_range_names = {
"0.25x": int(default // 4),
"0.5x": int(default // 2),
"1x": default,
"2x": default * 2,
"3x": default * 3,
"4x": default * 4,
"8x": default * 8,
}
class RequiredReportsEotW(Range):
"""
If End of the World Unlock is set to "Reports", determines the number of Ansem's Reports required to open End of the World.
"""
display_name = "Reports to Open End of the World"
default = 4
range_start = 0
range_end = 13
class RequiredReportsDoor(Range):
"""
If Final Rest Door is set to "Reports", determines the number of Ansem's Reports required to manifest the door in Final Rest to challenge Ansem.
"""
display_name = "Reports to Open Final Rest Door"
default = 4
range_start = 0
range_end = 13
class ReportsInPool(Range):
"""
Determines the number of Ansem's Reports in the item pool.
"""
display_name = "Reports in Pool"
default = 4
range_start = 0
range_end = 13
class RandomizeKeybladeStats(DefaultOnToggle):
"""
Determines whether Keyblade stats should be randomized.
"""
display_name = "Randomize Keyblade Stats"
class KeybladeMinStrength(Range):
"""
Determines the minimum STR bonus a keyblade can have.
"""
display_name = "Keyblade Minimum STR Bonus"
default = 3
range_start = 0
range_end = 20
class KeybladeMaxStrength(Range):
"""
Determines the maximum STR bonus a keyblade can have.
"""
display_name = "Keyblade Maximum STR Bonus"
default = 14
range_start = 0
range_end = 20
class KeybladeMinMP(Range):
"""
Determines the minimum MP bonus a keyblade can have.
"""
display_name = "Keyblade Minimum MP Bonus"
default = -2
range_start = -2
range_end = 5
class KeybladeMaxMP(Range):
"""
Determines the maximum MP bonus a keyblade can have.
"""
display_name = "Keyblade Maximum MP Bonus"
default = 3
range_start = -2
range_end = 5
class LevelChecks(Range):
"""
Determines the maximum level for which checks can be obtained.
"""
display_name = "Level Checks"
default = 100
range_start = 0
range_end = 100
class ForceStatsOnLevels(NamedRange):
"""
If this value is less than the value for Level Checks, this determines the minimum level from which only stat ups are obtained at level up locations.
For example, if you want to be able to find any multiworld item from levels 1-50, then just stat ups for levels 51-100, set this value to 51.
"""
display_name = "Force Stats on Level Starting From"
default = 1
range_start = 1
range_end = 101
special_range_names = {
"none": 101,
"multiworld-to-level-50": 51,
"all": 1
}
class BadStartingWeapons(Toggle):
"""
Forces Kingdom Key, Dream Sword, Dream Shield, and Dream Staff to have bad stats.
"""
display_name = "Bad Starting Weapons"
class DonaldDeathLink(Toggle):
"""
If Donald is KO'ed, so is Sora. If Death Link is toggled on in your client, this will send a death to everyone.
"""
display_name = "Donald Death Link"
class GoofyDeathLink(Toggle):
"""
If Goofy is KO'ed, so is Sora. If Death Link is toggled on in your client, this will send a death to everyone.
"""
display_name = "Goofy Death Link"
class KeybladesUnlockChests(Toggle):
"""
If toggled on, the player is required to have a certain keyblade to open chests in certain worlds.
TT - Lionheart
WL - Lady Luck
OC - Olympia
DJ - Jungle King
AG - Three Wishes
MS - Wishing Star
HT - Pumpkinhead
NL - Fairy Harp
HB - Divine Rose
EotW - Oblivion
HAW - Oathkeeper
Note: Does not apply to Atlantica, the emblem and carousel chests in Hollow Bastion, or the Aero chest in Neverland currently.
"""
display_name = "Keyblades Unlock Chests"
class InteractInBattle(Toggle):
"""
Allow Sora to talk to people, examine objects, and open chests in battle.
"""
display_name = "Interact in Battle"
class AdvancedLogic(Toggle):
"""
If on, logic may expect you to do advanced skips like using Combo Master, Dumbo, and other unusual methods to reach locations.
"""
display_name = "Advanced Logic"
class ExtraSharedAbilities(Toggle):
"""
If on, adds extra shared abilities to the pool. These can stack, so multiple high jumps make you jump higher and multiple glides make you superglide faster.
"""
display_name = "Extra Shared Abilities"
class EXPZeroInPool(Toggle):
"""
If on, adds EXP Zero ability to the item pool. This is redundant if you are planning on playing on Proud.
"""
display_name = "EXP Zero in Pool"
class VanillaEmblemPieces(DefaultOnToggle):
"""
If on, the Hollow Bastion emblem pieces are in their vanilla locations.
"""
display_name = "Vanilla Emblem Pieces"
class StartingWorlds(Range):
"""
Number of random worlds to start with in addition to Traverse Town, which is always available. Will only consider Atlantica if toggled, and will only consider End of the World if its unlock is set to "Item".
"""
display_name = "Starting Worlds"
default = 0
range_start = 0
range_end = 10
@dataclass
class KH1Options(PerGameCommonOptions):
goal: Goal
end_of_the_world_unlock: EndoftheWorldUnlock
final_rest_door: FinalRestDoor
required_reports_eotw: RequiredReportsEotW
required_reports_door: RequiredReportsDoor
reports_in_pool: ReportsInPool
super_bosses: SuperBosses
atlantica: Atlantica
hundred_acre_wood: HundredAcreWood
cups: Cups
puppies: Puppies
starting_worlds: StartingWorlds
keyblades_unlock_chests: KeybladesUnlockChests
interact_in_battle: InteractInBattle
exp_multiplier: EXPMultiplier
advanced_logic: AdvancedLogic
extra_shared_abilities: ExtraSharedAbilities
exp_zero_in_pool: EXPZeroInPool
vanilla_emblem_pieces: VanillaEmblemPieces
donald_death_link: DonaldDeathLink
goofy_death_link: GoofyDeathLink
randomize_keyblade_stats: RandomizeKeybladeStats
bad_starting_weapons: BadStartingWeapons
keyblade_min_str: KeybladeMinStrength
keyblade_max_str: KeybladeMaxStrength
keyblade_min_mp: KeybladeMinMP
keyblade_max_mp: KeybladeMaxMP
level_checks: LevelChecks
force_stats_on_levels: ForceStatsOnLevels
strength_increase: StrengthIncrease
defense_increase: DefenseIncrease
hp_increase: HPIncrease
ap_increase: APIncrease
mp_increase: MPIncrease
accessory_slot_increase: AccessorySlotIncrease
item_slot_increase: ItemSlotIncrease
start_inventory_from_pool: StartInventoryPool
kh1_option_groups = [
OptionGroup("Goal", [
Goal,
EndoftheWorldUnlock,
FinalRestDoor,
RequiredReportsDoor,
RequiredReportsEotW,
ReportsInPool,
]),
OptionGroup("Locations", [
SuperBosses,
Atlantica,
Cups,
HundredAcreWood,
VanillaEmblemPieces,
]),
OptionGroup("Levels", [
EXPMultiplier,
LevelChecks,
ForceStatsOnLevels,
StrengthIncrease,
DefenseIncrease,
HPIncrease,
APIncrease,
MPIncrease,
AccessorySlotIncrease,
ItemSlotIncrease,
]),
OptionGroup("Keyblades", [
KeybladesUnlockChests,
RandomizeKeybladeStats,
BadStartingWeapons,
KeybladeMaxStrength,
KeybladeMinStrength,
KeybladeMaxMP,
KeybladeMinMP,
]),
OptionGroup("Misc", [
StartingWorlds,
Puppies,
InteractInBattle,
AdvancedLogic,
ExtraSharedAbilities,
EXPZeroInPool,
DonaldDeathLink,
GoofyDeathLink,
])
]

View File

@@ -1,177 +0,0 @@
from typing import Any, Dict
from .Options import *
kh1_option_presets: Dict[str, Dict[str, Any]] = {
# Standard playthrough where your goal is to defeat Ansem, reaching him by acquiring enough reports.
"Final Ansem": {
"goal": Goal.option_final_ansem,
"end_of_the_world_unlock": EndoftheWorldUnlock.option_reports,
"final_rest_door": FinalRestDoor.option_reports,
"required_reports_eotw": 7,
"required_reports_door": 10,
"reports_in_pool": 13,
"super_bosses": False,
"atlantica": False,
"hundred_acre_wood": False,
"cups": False,
"vanilla_emblem_pieces": True,
"exp_multiplier": 48,
"level_checks": 100,
"force_stats_on_levels": 1,
"strength_increase": 24,
"defense_increase": 24,
"hp_increase": 23,
"ap_increase": 18,
"mp_increase": 7,
"accessory_slot_increase": 1,
"item_slot_increase": 3,
"keyblades_unlock_chests": False,
"randomize_keyblade_stats": True,
"bad_starting_weapons": False,
"keyblade_max_str": 14,
"keyblade_min_str": 3,
"keyblade_max_mp": 3,
"keyblade_min_mp": -2,
"puppies": Puppies.option_triplets,
"starting_worlds": 0,
"interact_in_battle": False,
"advanced_logic": False,
"extra_shared_abilities": False,
"exp_zero_in_pool": False,
"donald_death_link": False,
"goofy_death_link": False
},
# Puppies are found individually, and the goal is to return them all.
"Puppy Hunt": {
"goal": Goal.option_puppies,
"end_of_the_world_unlock": EndoftheWorldUnlock.option_item,
"final_rest_door": FinalRestDoor.option_puppies,
"required_reports_eotw": 13,
"required_reports_door": 13,
"reports_in_pool": 13,
"super_bosses": False,
"atlantica": False,
"hundred_acre_wood": False,
"cups": False,
"vanilla_emblem_pieces": True,
"exp_multiplier": 48,
"level_checks": 100,
"force_stats_on_levels": 1,
"strength_increase": 24,
"defense_increase": 24,
"hp_increase": 23,
"ap_increase": 18,
"mp_increase": 7,
"accessory_slot_increase": 1,
"item_slot_increase": 3,
"keyblades_unlock_chests": False,
"randomize_keyblade_stats": True,
"bad_starting_weapons": False,
"keyblade_max_str": 14,
"keyblade_min_str": 3,
"keyblade_max_mp": 3,
"keyblade_min_mp": -2,
"puppies": Puppies.option_individual,
"starting_worlds": 0,
"interact_in_battle": False,
"advanced_logic": False,
"extra_shared_abilities": False,
"exp_zero_in_pool": False,
"donald_death_link": False,
"goofy_death_link": False
},
# Advanced playthrough with most settings on.
"Advanced": {
"goal": Goal.option_final_ansem,
"end_of_the_world_unlock": EndoftheWorldUnlock.option_reports,
"final_rest_door": FinalRestDoor.option_reports,
"required_reports_eotw": 7,
"required_reports_door": 10,
"reports_in_pool": 13,
"super_bosses": True,
"atlantica": True,
"hundred_acre_wood": True,
"cups": True,
"vanilla_emblem_pieces": False,
"exp_multiplier": 48,
"level_checks": 100,
"force_stats_on_levels": 1,
"strength_increase": 24,
"defense_increase": 24,
"hp_increase": 23,
"ap_increase": 18,
"mp_increase": 7,
"accessory_slot_increase": 1,
"item_slot_increase": 3,
"keyblades_unlock_chests": True,
"randomize_keyblade_stats": True,
"bad_starting_weapons": True,
"keyblade_max_str": 14,
"keyblade_min_str": 3,
"keyblade_max_mp": 3,
"keyblade_min_mp": -2,
"puppies": Puppies.option_triplets,
"starting_worlds": 0,
"interact_in_battle": True,
"advanced_logic": True,
"extra_shared_abilities": True,
"exp_zero_in_pool": True,
"donald_death_link": False,
"goofy_death_link": False
},
# Playthrough meant to enhance the level 1 experience.
"Level 1": {
"goal": Goal.option_final_ansem,
"end_of_the_world_unlock": EndoftheWorldUnlock.option_reports,
"final_rest_door": FinalRestDoor.option_reports,
"required_reports_eotw": 7,
"required_reports_door": 10,
"reports_in_pool": 13,
"super_bosses": False,
"atlantica": False,
"hundred_acre_wood": False,
"cups": False,
"vanilla_emblem_pieces": True,
"exp_multiplier": 16,
"level_checks": 0,
"force_stats_on_levels": 101,
"strength_increase": 0,
"defense_increase": 0,
"hp_increase": 0,
"mp_increase": 0,
"accessory_slot_increase": 6,
"item_slot_increase": 5,
"keyblades_unlock_chests": False,
"randomize_keyblade_stats": True,
"bad_starting_weapons": False,
"keyblade_max_str": 14,
"keyblade_min_str": 3,
"keyblade_max_mp": 3,
"keyblade_min_mp": -2,
"puppies": Puppies.option_triplets,
"starting_worlds": 0,
"interact_in_battle": False,
"advanced_logic": False,
"extra_shared_abilities": False,
"exp_zero_in_pool": False,
"donald_death_link": False,
"goofy_death_link": False
}
}

View File

@@ -1,516 +0,0 @@
from typing import Dict, List, NamedTuple, Optional
from BaseClasses import MultiWorld, Region, Entrance
from .Locations import KH1Location, location_table
class KH1RegionData(NamedTuple):
locations: List[str]
region_exits: Optional[List[str]]
def create_regions(multiworld: MultiWorld, player: int, options):
regions: Dict[str, KH1RegionData] = {
"Menu": KH1RegionData([], ["Awakening", "Levels"]),
"Awakening": KH1RegionData([], ["Destiny Islands"]),
"Destiny Islands": KH1RegionData([], ["Traverse Town"]),
"Traverse Town": KH1RegionData([], ["World Map"]),
"Wonderland": KH1RegionData([], []),
"Olympus Coliseum": KH1RegionData([], []),
"Deep Jungle": KH1RegionData([], []),
"Agrabah": KH1RegionData([], []),
"Monstro": KH1RegionData([], []),
"Atlantica": KH1RegionData([], []),
"Halloween Town": KH1RegionData([], []),
"Neverland": KH1RegionData([], []),
"Hollow Bastion": KH1RegionData([], []),
"End of the World": KH1RegionData([], []),
"100 Acre Wood": KH1RegionData([], []),
"Levels": KH1RegionData([], []),
"World Map": KH1RegionData([], ["Wonderland", "Olympus Coliseum", "Deep Jungle",
"Agrabah", "Monstro", "Atlantica",
"Halloween Town", "Neverland", "Hollow Bastion",
"End of the World", "100 Acre Wood"])
}
# Set up locations
regions["Agrabah"].locations.append("Agrabah Aladdin's House Main Street Entrance Chest")
regions["Agrabah"].locations.append("Agrabah Aladdin's House Plaza Entrance Chest")
regions["Agrabah"].locations.append("Agrabah Alley Chest")
regions["Agrabah"].locations.append("Agrabah Bazaar Across Windows Chest")
regions["Agrabah"].locations.append("Agrabah Bazaar High Corner Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Bottomless Hall Across Chasm Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Bottomless Hall Pillar Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Bottomless Hall Raised Platform Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Dark Chamber Abu Gem Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Dark Chamber Across from Relic Chamber Entrance Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Dark Chamber Bridge Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Dark Chamber Near Save Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Entrance Left Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Entrance Tall Tower Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Entrance White Trinity Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Hall High Left Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Hall Near Bottomless Hall Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Hidden Room Left Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Hidden Room Right Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Relic Chamber Jump from Stairs Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Relic Chamber Stairs Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Silent Chamber Blue Trinity Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Treasure Room Above Fire Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Treasure Room Across Platforms Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Treasure Room Large Treasure Pile Chest")
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Treasure Room Small Treasure Pile Chest")
regions["Agrabah"].locations.append("Agrabah Defeat Jafar Blizzard Event")
regions["Agrabah"].locations.append("Agrabah Defeat Jafar Genie Ansem's Report 1")
regions["Agrabah"].locations.append("Agrabah Defeat Jafar Genie Fire Event")
regions["Agrabah"].locations.append("Agrabah Defeat Pot Centipede Ray of Light Event")
regions["Agrabah"].locations.append("Agrabah Main Street High Above Alley Entrance Chest")
regions["Agrabah"].locations.append("Agrabah Main Street High Above Palace Gates Entrance Chest")
regions["Agrabah"].locations.append("Agrabah Main Street Right Palace Entrance Chest")
regions["Agrabah"].locations.append("Agrabah Palace Gates High Close to Palace Chest")
regions["Agrabah"].locations.append("Agrabah Palace Gates High Opposite Palace Chest")
regions["Agrabah"].locations.append("Agrabah Palace Gates Low Chest")
regions["Agrabah"].locations.append("Agrabah Plaza By Storage Chest")
regions["Agrabah"].locations.append("Agrabah Plaza Raised Terrace Chest")
regions["Agrabah"].locations.append("Agrabah Plaza Top Corner Chest")
regions["Agrabah"].locations.append("Agrabah Seal Keyhole Genie Event")
regions["Agrabah"].locations.append("Agrabah Seal Keyhole Green Trinity Event")
regions["Agrabah"].locations.append("Agrabah Seal Keyhole Three Wishes Event")
regions["Agrabah"].locations.append("Agrabah Storage Behind Barrel Chest")
regions["Agrabah"].locations.append("Agrabah Storage Green Trinity Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Bamboo Thicket Save Gorillas")
regions["Deep Jungle"].locations.append("Deep Jungle Camp Blue Trinity Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Camp Ether Experiment")
regions["Deep Jungle"].locations.append("Deep Jungle Camp Hi-Potion Experiment")
regions["Deep Jungle"].locations.append("Deep Jungle Camp Replication Experiment")
regions["Deep Jungle"].locations.append("Deep Jungle Camp Save Gorillas")
regions["Deep Jungle"].locations.append("Deep Jungle Cavern of Hearts Navi-G Piece Event")
regions["Deep Jungle"].locations.append("Deep Jungle Cavern of Hearts White Trinity Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Cliff Right Cliff Left Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Cliff Right Cliff Right Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Cliff Save Gorillas")
regions["Deep Jungle"].locations.append("Deep Jungle Climbing Trees Blue Trinity Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Climbing Trees Save Gorillas")
regions["Deep Jungle"].locations.append("Deep Jungle Defeat Clayton Cure Event")
regions["Deep Jungle"].locations.append("Deep Jungle Defeat Sabor White Fang Event")
regions["Deep Jungle"].locations.append("Deep Jungle Hippo's Lagoon Center Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Hippo's Lagoon Left Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Hippo's Lagoon Right Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Jungle Slider 10 Fruits")
regions["Deep Jungle"].locations.append("Deep Jungle Jungle Slider 20 Fruits")
regions["Deep Jungle"].locations.append("Deep Jungle Jungle Slider 30 Fruits")
regions["Deep Jungle"].locations.append("Deep Jungle Jungle Slider 40 Fruits")
regions["Deep Jungle"].locations.append("Deep Jungle Jungle Slider 50 Fruits")
regions["Deep Jungle"].locations.append("Deep Jungle Seal Keyhole Jungle King Event")
regions["Deep Jungle"].locations.append("Deep Jungle Seal Keyhole Red Trinity Event")
regions["Deep Jungle"].locations.append("Deep Jungle Tent Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Tent Protect-G Event")
regions["Deep Jungle"].locations.append("Deep Jungle Tree House Beneath Tree House Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Tree House Rooftop Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Tree House Save Gorillas")
regions["Deep Jungle"].locations.append("Deep Jungle Tree House Suspended Boat Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Tunnel Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Vines 2 Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Vines Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Waterfall Cavern High Middle Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Waterfall Cavern High Wall Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Waterfall Cavern Low Chest")
regions["Deep Jungle"].locations.append("Deep Jungle Waterfall Cavern Middle Chest")
regions["End of the World"].locations.append("End of the World Defeat Chernabog Superglide Event")
regions["End of the World"].locations.append("End of the World Final Dimension 10th Chest")
regions["End of the World"].locations.append("End of the World Final Dimension 1st Chest")
regions["End of the World"].locations.append("End of the World Final Dimension 2nd Chest")
regions["End of the World"].locations.append("End of the World Final Dimension 3rd Chest")
regions["End of the World"].locations.append("End of the World Final Dimension 4th Chest")
regions["End of the World"].locations.append("End of the World Final Dimension 5th Chest")
regions["End of the World"].locations.append("End of the World Final Dimension 6th Chest")
regions["End of the World"].locations.append("End of the World Final Dimension 7th Chest")
regions["End of the World"].locations.append("End of the World Final Dimension 8th Chest")
regions["End of the World"].locations.append("End of the World Final Dimension 9th Chest")
regions["End of the World"].locations.append("End of the World Final Rest Chest")
regions["End of the World"].locations.append("End of the World Giant Crevasse 1st Chest")
regions["End of the World"].locations.append("End of the World Giant Crevasse 2nd Chest")
regions["End of the World"].locations.append("End of the World Giant Crevasse 3rd Chest")
regions["End of the World"].locations.append("End of the World Giant Crevasse 4th Chest")
regions["End of the World"].locations.append("End of the World Giant Crevasse 5th Chest")
regions["End of the World"].locations.append("End of the World World Terminus 100 Acre Wood Chest")
regions["End of the World"].locations.append("End of the World World Terminus Agrabah Chest")
regions["End of the World"].locations.append("End of the World World Terminus Atlantica Chest")
regions["End of the World"].locations.append("End of the World World Terminus Deep Jungle Chest")
regions["End of the World"].locations.append("End of the World World Terminus Halloween Town Chest")
#regions["End of the World"].locations.append("End of the World World Terminus Hollow Bastion Chest")
regions["End of the World"].locations.append("End of the World World Terminus Neverland Chest")
regions["End of the World"].locations.append("End of the World World Terminus Olympus Coliseum Chest")
regions["End of the World"].locations.append("End of the World World Terminus Traverse Town Chest")
regions["End of the World"].locations.append("End of the World World Terminus Wonderland Chest")
regions["Halloween Town"].locations.append("Halloween Town Boneyard Tombstone Puzzle Chest")
regions["Halloween Town"].locations.append("Halloween Town Bridge Left of Gate Chest")
regions["Halloween Town"].locations.append("Halloween Town Bridge Right of Gate Chest")
regions["Halloween Town"].locations.append("Halloween Town Bridge Under Bridge")
regions["Halloween Town"].locations.append("Halloween Town Cemetery Behind Grave Chest")
regions["Halloween Town"].locations.append("Halloween Town Cemetery Between Graves Chest")
regions["Halloween Town"].locations.append("Halloween Town Cemetery By Cat Shape Chest")
regions["Halloween Town"].locations.append("Halloween Town Cemetery By Striped Grave Chest")
regions["Halloween Town"].locations.append("Halloween Town Defeat Oogie Boogie Ansem's Report 7")
regions["Halloween Town"].locations.append("Halloween Town Defeat Oogie Boogie Holy Circlet Event")
regions["Halloween Town"].locations.append("Halloween Town Defeat Oogie's Manor Gravity Event")
regions["Halloween Town"].locations.append("Halloween Town Graveyard Forget-Me-Not Event")
regions["Halloween Town"].locations.append("Halloween Town Guillotine Square High Tower Chest")
regions["Halloween Town"].locations.append("Halloween Town Guillotine Square Pumpkin Structure Left Chest")
regions["Halloween Town"].locations.append("Halloween Town Guillotine Square Pumpkin Structure Right Chest")
regions["Halloween Town"].locations.append("Halloween Town Guillotine Square Ring Jack's Doorbell 3 Times")
regions["Halloween Town"].locations.append("Halloween Town Guillotine Square Under Jack's House Stairs Chest")
regions["Halloween Town"].locations.append("Halloween Town Lab Torn Page")
regions["Halloween Town"].locations.append("Halloween Town Moonlight Hill White Trinity Chest")
regions["Halloween Town"].locations.append("Halloween Town Oogie's Manor Entrance Steps Chest")
regions["Halloween Town"].locations.append("Halloween Town Oogie's Manor Grounds Red Trinity Chest")
regions["Halloween Town"].locations.append("Halloween Town Oogie's Manor Hollow Chest")
regions["Halloween Town"].locations.append("Halloween Town Oogie's Manor Inside Entrance Chest")
regions["Halloween Town"].locations.append("Halloween Town Oogie's Manor Lower Iron Cage Chest")
regions["Halloween Town"].locations.append("Halloween Town Oogie's Manor Upper Iron Cage Chest")
regions["Halloween Town"].locations.append("Halloween Town Seal Keyhole Pumpkinhead Event")
regions["Hollow Bastion"].locations.append("Hollow Bastion Base Level Bubble Under the Wall Platform Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Base Level Near Crystal Switch Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Base Level Platform Near Entrance Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Castle Gates Freestanding Pillar Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Castle Gates Gravity Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Castle Gates High Pillar Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Defeat Behemoth Omega Arts Event")
regions["Hollow Bastion"].locations.append("Hollow Bastion Defeat Dragon Maleficent Fireglow Event")
regions["Hollow Bastion"].locations.append("Hollow Bastion Defeat Maleficent Ansem's Report 5")
regions["Hollow Bastion"].locations.append("Hollow Bastion Defeat Maleficent Donald Cheer Event")
regions["Hollow Bastion"].locations.append("Hollow Bastion Defeat Riku I White Trinity Event")
regions["Hollow Bastion"].locations.append("Hollow Bastion Defeat Riku II Ragnarok Event")
regions["Hollow Bastion"].locations.append("Hollow Bastion Dungeon By Candles Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Dungeon Corner Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Entrance Hall Emblem Piece (Chest)")
regions["Hollow Bastion"].locations.append("Hollow Bastion Entrance Hall Emblem Piece (Flame)")
regions["Hollow Bastion"].locations.append("Hollow Bastion Entrance Hall Emblem Piece (Fountain)")
regions["Hollow Bastion"].locations.append("Hollow Bastion Entrance Hall Emblem Piece (Statue)")
regions["Hollow Bastion"].locations.append("Hollow Bastion Entrance Hall Left of Emblem Door Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Grand Hall Left of Gate Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Grand Hall Oblivion Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Grand Hall Steps Right Side Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Great Crest After Battle Platform Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Great Crest Lower Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion High Tower 1st Gravity Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion High Tower 2nd Gravity Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion High Tower Above Sliding Blocks Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Library 1st Floor Turn the Carousel Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Library 2nd Floor Turn the Carousel 1st Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Library 2nd Floor Turn the Carousel 2nd Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Library Speak to Aerith Cure")
regions["Hollow Bastion"].locations.append("Hollow Bastion Library Speak to Belle Divine Rose")
regions["Hollow Bastion"].locations.append("Hollow Bastion Library Top of Bookshelf Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Library Top of Bookshelf Turn the Carousel Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Lift Stop Heartless Sigil Door Gravity Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Lift Stop Library Node After High Tower Switch Gravity Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Lift Stop Library Node Gravity Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Lift Stop Outside Library Gravity Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Lift Stop Under High Tower Sliding Blocks Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Rising Falls Floating Platform Near Bubble Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Rising Falls Floating Platform Near Save Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Rising Falls High Platform Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Rising Falls Under Water 1st Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Rising Falls Under Water 2nd Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Rising Falls Water's Surface Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Rising Falls White Trinity Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Speak to Princesses Fire Event")
regions["Hollow Bastion"].locations.append("Hollow Bastion Speak with Aerith Ansem's Report 10")
regions["Hollow Bastion"].locations.append("Hollow Bastion Speak with Aerith Ansem's Report 2")
regions["Hollow Bastion"].locations.append("Hollow Bastion Speak with Aerith Ansem's Report 4")
regions["Hollow Bastion"].locations.append("Hollow Bastion Speak with Aerith Ansem's Report 6")
regions["Hollow Bastion"].locations.append("Hollow Bastion Waterway Blizzard on Bubble Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Waterway Near Save Chest")
regions["Hollow Bastion"].locations.append("Hollow Bastion Waterway Unlock Passage from Base Level Chest")
regions["Monstro"].locations.append("Monstro Chamber 2 Ground Chest")
regions["Monstro"].locations.append("Monstro Chamber 2 Platform Chest")
regions["Monstro"].locations.append("Monstro Chamber 3 Ground Chest")
regions["Monstro"].locations.append("Monstro Chamber 3 Near Chamber 6 Entrance Chest")
regions["Monstro"].locations.append("Monstro Chamber 3 Platform Above Chamber 2 Entrance Chest")
regions["Monstro"].locations.append("Monstro Chamber 3 Platform Near Chamber 6 Entrance Chest")
regions["Monstro"].locations.append("Monstro Chamber 5 Atop Barrel Chest")
regions["Monstro"].locations.append("Monstro Chamber 5 Low 1st Chest")
regions["Monstro"].locations.append("Monstro Chamber 5 Low 2nd Chest")
regions["Monstro"].locations.append("Monstro Chamber 5 Platform Chest")
regions["Monstro"].locations.append("Monstro Chamber 6 Low Chest")
regions["Monstro"].locations.append("Monstro Chamber 6 Other Platform Chest")
regions["Monstro"].locations.append("Monstro Chamber 6 Platform Near Chamber 5 Entrance Chest")
regions["Monstro"].locations.append("Monstro Chamber 6 Raised Area Near Chamber 1 Entrance Chest")
regions["Monstro"].locations.append("Monstro Chamber 6 White Trinity Chest")
regions["Monstro"].locations.append("Monstro Defeat Parasite Cage I Goofy Cheer Event")
regions["Monstro"].locations.append("Monstro Defeat Parasite Cage II Stop Event")
regions["Monstro"].locations.append("Monstro Mouth Boat Deck Chest")
regions["Monstro"].locations.append("Monstro Mouth Green Trinity Top of Boat Chest")
regions["Monstro"].locations.append("Monstro Mouth High Platform Across from Boat Chest")
regions["Monstro"].locations.append("Monstro Mouth High Platform Boat Side Chest")
regions["Monstro"].locations.append("Monstro Mouth High Platform Near Teeth Chest")
regions["Monstro"].locations.append("Monstro Mouth Near Ship Chest")
regions["Neverland"].locations.append("Neverland Cabin Chest")
regions["Neverland"].locations.append("Neverland Captain's Cabin Chest")
#regions["Neverland"].locations.append("Neverland Clock Tower 01:00 Door")
#regions["Neverland"].locations.append("Neverland Clock Tower 02:00 Door")
#regions["Neverland"].locations.append("Neverland Clock Tower 03:00 Door")
#regions["Neverland"].locations.append("Neverland Clock Tower 04:00 Door")
#regions["Neverland"].locations.append("Neverland Clock Tower 05:00 Door")
#regions["Neverland"].locations.append("Neverland Clock Tower 06:00 Door")
#regions["Neverland"].locations.append("Neverland Clock Tower 07:00 Door")
#regions["Neverland"].locations.append("Neverland Clock Tower 08:00 Door")
#regions["Neverland"].locations.append("Neverland Clock Tower 09:00 Door")
#regions["Neverland"].locations.append("Neverland Clock Tower 10:00 Door")
#regions["Neverland"].locations.append("Neverland Clock Tower 11:00 Door")
#regions["Neverland"].locations.append("Neverland Clock Tower 12:00 Door")
regions["Neverland"].locations.append("Neverland Clock Tower Chest")
regions["Neverland"].locations.append("Neverland Defeat Anti Sora Raven's Claw Event")
regions["Neverland"].locations.append("Neverland Defeat Captain Hook Ars Arcanum Event")
regions["Neverland"].locations.append("Neverland Defeat Hook Ansem's Report 9")
regions["Neverland"].locations.append("Neverland Encounter Hook Cure Event")
regions["Neverland"].locations.append("Neverland Galley Chest")
regions["Neverland"].locations.append("Neverland Hold Aero Chest")
regions["Neverland"].locations.append("Neverland Hold Flight 1st Chest")
regions["Neverland"].locations.append("Neverland Hold Flight 2nd Chest")
regions["Neverland"].locations.append("Neverland Hold Yellow Trinity Green Chest")
regions["Neverland"].locations.append("Neverland Hold Yellow Trinity Left Blue Chest")
regions["Neverland"].locations.append("Neverland Hold Yellow Trinity Right Blue Chest")
regions["Neverland"].locations.append("Neverland Pirate Ship Crows Nest Chest")
regions["Neverland"].locations.append("Neverland Pirate Ship Deck White Trinity Chest")
regions["Neverland"].locations.append("Neverland Seal Keyhole Fairy Harp Event")
regions["Neverland"].locations.append("Neverland Seal Keyhole Glide Event")
regions["Neverland"].locations.append("Neverland Seal Keyhole Tinker Bell Event")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Clear Phil's Training Thunder Event")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Cloud Sonic Blade Event")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Coliseum Gates Blizzaga Chest")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Coliseum Gates Blizzara Chest")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Coliseum Gates Entry Pass Event")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Coliseum Gates Green Trinity")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Coliseum Gates Hero's License Event")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Coliseum Gates Left Behind Columns Chest")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Coliseum Gates Left Blue Trinity Chest")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Coliseum Gates Right Blue Trinity Chest")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Coliseum Gates White Trinity Chest")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Defeat Cerberus Inferno Band Event")
regions["Traverse Town"].locations.append("Traverse Town 1st District Accessory Shop Roof Chest")
#regions["Traverse Town"].locations.append("Traverse Town 1st District Aerith Gift")
regions["Traverse Town"].locations.append("Traverse Town 1st District Blue Trinity Balcony Chest")
regions["Traverse Town"].locations.append("Traverse Town 1st District Candle Puzzle Chest")
#regions["Traverse Town"].locations.append("Traverse Town 1st District Leon Gift")
regions["Traverse Town"].locations.append("Traverse Town 1st District Safe Postcard")
regions["Traverse Town"].locations.append("Traverse Town 1st District Speak with Cid Event")
regions["Traverse Town"].locations.append("Traverse Town 2nd District Boots and Shoes Awning Chest")
regions["Traverse Town"].locations.append("Traverse Town 2nd District Gizmo Shop Facade Chest")
regions["Traverse Town"].locations.append("Traverse Town 2nd District Rooftop Chest")
regions["Traverse Town"].locations.append("Traverse Town 3rd District Balcony Postcard")
regions["Traverse Town"].locations.append("Traverse Town Accessory Shop Chest")
regions["Traverse Town"].locations.append("Traverse Town Alleyway Balcony Chest")
regions["Traverse Town"].locations.append("Traverse Town Alleyway Behind Crates Chest")
regions["Traverse Town"].locations.append("Traverse Town Alleyway Blue Room Awning Chest")
regions["Traverse Town"].locations.append("Traverse Town Alleyway Corner Chest")
regions["Traverse Town"].locations.append("Traverse Town Defeat Guard Armor Blue Trinity Event")
regions["Traverse Town"].locations.append("Traverse Town Defeat Guard Armor Brave Warrior Event")
regions["Traverse Town"].locations.append("Traverse Town Defeat Guard Armor Dodge Roll Event")
regions["Traverse Town"].locations.append("Traverse Town Defeat Guard Armor Fire Event")
regions["Traverse Town"].locations.append("Traverse Town Defeat Opposite Armor Aero Event")
regions["Traverse Town"].locations.append("Traverse Town Geppetto's House Chest")
regions["Traverse Town"].locations.append("Traverse Town Geppetto's House Geppetto All Summons Reward")
regions["Traverse Town"].locations.append("Traverse Town Geppetto's House Geppetto Reward 1")
regions["Traverse Town"].locations.append("Traverse Town Geppetto's House Geppetto Reward 2")
regions["Traverse Town"].locations.append("Traverse Town Geppetto's House Geppetto Reward 3")
regions["Traverse Town"].locations.append("Traverse Town Geppetto's House Geppetto Reward 4")
regions["Traverse Town"].locations.append("Traverse Town Geppetto's House Geppetto Reward 5")
regions["Traverse Town"].locations.append("Traverse Town Geppetto's House Postcard")
regions["Traverse Town"].locations.append("Traverse Town Geppetto's House Talk to Pinocchio")
regions["Traverse Town"].locations.append("Traverse Town Gizmo Shop Postcard 1")
regions["Traverse Town"].locations.append("Traverse Town Gizmo Shop Postcard 2")
regions["Traverse Town"].locations.append("Traverse Town Green Room Clock Puzzle Chest")
regions["Traverse Town"].locations.append("Traverse Town Green Room Table Chest")
regions["Traverse Town"].locations.append("Traverse Town Item Shop Postcard")
regions["Traverse Town"].locations.append("Traverse Town Item Workshop Left Chest")
regions["Traverse Town"].locations.append("Traverse Town Item Workshop Postcard")
regions["Traverse Town"].locations.append("Traverse Town Item Workshop Right Chest")
regions["Traverse Town"].locations.append("Traverse Town Kairi Secret Waterway Oathkeeper Event")
regions["Traverse Town"].locations.append("Traverse Town Leon Secret Waterway Earthshine Event")
regions["Traverse Town"].locations.append("Traverse Town Magician's Study Obtained All Arts Items")
regions["Traverse Town"].locations.append("Traverse Town Magician's Study Obtained All LV1 Magic")
regions["Traverse Town"].locations.append("Traverse Town Magician's Study Obtained All LV3 Magic")
regions["Traverse Town"].locations.append("Traverse Town Mail Postcard 01 Event")
regions["Traverse Town"].locations.append("Traverse Town Mail Postcard 02 Event")
regions["Traverse Town"].locations.append("Traverse Town Mail Postcard 03 Event")
regions["Traverse Town"].locations.append("Traverse Town Mail Postcard 04 Event")
regions["Traverse Town"].locations.append("Traverse Town Mail Postcard 05 Event")
regions["Traverse Town"].locations.append("Traverse Town Mail Postcard 06 Event")
regions["Traverse Town"].locations.append("Traverse Town Mail Postcard 07 Event")
regions["Traverse Town"].locations.append("Traverse Town Mail Postcard 08 Event")
regions["Traverse Town"].locations.append("Traverse Town Mail Postcard 09 Event")
regions["Traverse Town"].locations.append("Traverse Town Mail Postcard 10 Event")
regions["Traverse Town"].locations.append("Traverse Town Mystical House Glide Chest")
regions["Traverse Town"].locations.append("Traverse Town Mystical House Yellow Trinity Chest")
regions["Traverse Town"].locations.append("Traverse Town Piano Room Return 10 Puppies")
regions["Traverse Town"].locations.append("Traverse Town Piano Room Return 20 Puppies")
regions["Traverse Town"].locations.append("Traverse Town Piano Room Return 30 Puppies")
regions["Traverse Town"].locations.append("Traverse Town Piano Room Return 40 Puppies")
regions["Traverse Town"].locations.append("Traverse Town Piano Room Return 50 Puppies Reward 1")
regions["Traverse Town"].locations.append("Traverse Town Piano Room Return 50 Puppies Reward 2")
regions["Traverse Town"].locations.append("Traverse Town Piano Room Return 60 Puppies")
regions["Traverse Town"].locations.append("Traverse Town Piano Room Return 70 Puppies")
regions["Traverse Town"].locations.append("Traverse Town Piano Room Return 80 Puppies")
regions["Traverse Town"].locations.append("Traverse Town Piano Room Return 90 Puppies")
regions["Traverse Town"].locations.append("Traverse Town Piano Room Return 99 Puppies Reward 1")
regions["Traverse Town"].locations.append("Traverse Town Piano Room Return 99 Puppies Reward 2")
regions["Traverse Town"].locations.append("Traverse Town Red Room Chest")
regions["Traverse Town"].locations.append("Traverse Town Secret Waterway Near Stairs Chest")
regions["Traverse Town"].locations.append("Traverse Town Secret Waterway White Trinity Chest")
regions["Traverse Town"].locations.append("Traverse Town Synth Cloth")
regions["Traverse Town"].locations.append("Traverse Town Synth Fish")
regions["Traverse Town"].locations.append("Traverse Town Synth Log")
regions["Traverse Town"].locations.append("Traverse Town Synth Mushroom")
regions["Traverse Town"].locations.append("Traverse Town Synth Rope")
regions["Traverse Town"].locations.append("Traverse Town Synth Seagull Egg")
regions["Wonderland"].locations.append("Wonderland Bizarre Room Green Trinity Chest")
regions["Wonderland"].locations.append("Wonderland Bizarre Room Lamp Chest")
regions["Wonderland"].locations.append("Wonderland Bizarre Room Navi-G Piece Event")
regions["Wonderland"].locations.append("Wonderland Bizarre Room Read Book")
regions["Wonderland"].locations.append("Wonderland Defeat Trickmaster Blizzard Event")
regions["Wonderland"].locations.append("Wonderland Defeat Trickmaster Ifrit's Horn Event")
regions["Wonderland"].locations.append("Wonderland Lotus Forest Corner Chest")
regions["Wonderland"].locations.append("Wonderland Lotus Forest Glide Chest")
regions["Wonderland"].locations.append("Wonderland Lotus Forest Nut Chest")
regions["Wonderland"].locations.append("Wonderland Lotus Forest Through the Painting Thunder Plant Chest")
regions["Wonderland"].locations.append("Wonderland Lotus Forest Through the Painting White Trinity Chest")
regions["Wonderland"].locations.append("Wonderland Lotus Forest Thunder Plant Chest")
regions["Wonderland"].locations.append("Wonderland Queen's Castle Hedge Left Red Chest")
regions["Wonderland"].locations.append("Wonderland Queen's Castle Hedge Right Blue Chest")
regions["Wonderland"].locations.append("Wonderland Queen's Castle Hedge Right Red Chest")
regions["Wonderland"].locations.append("Wonderland Rabbit Hole Defeat Heartless 1 Chest")
regions["Wonderland"].locations.append("Wonderland Rabbit Hole Defeat Heartless 2 Chest")
regions["Wonderland"].locations.append("Wonderland Rabbit Hole Defeat Heartless 3 Chest")
regions["Wonderland"].locations.append("Wonderland Rabbit Hole Green Trinity Chest")
regions["Wonderland"].locations.append("Wonderland Tea Party Garden Above Lotus Forest Entrance 1st Chest")
regions["Wonderland"].locations.append("Wonderland Tea Party Garden Above Lotus Forest Entrance 2nd Chest")
regions["Wonderland"].locations.append("Wonderland Tea Party Garden Across From Bizarre Room Entrance Chest")
regions["Wonderland"].locations.append("Wonderland Tea Party Garden Bear and Clock Puzzle Chest")
if options.hundred_acre_wood:
regions["100 Acre Wood"].locations.append("100 Acre Wood Meadow Inside Log Chest")
regions["100 Acre Wood"].locations.append("100 Acre Wood Bouncing Spot Left Cliff Chest")
regions["100 Acre Wood"].locations.append("100 Acre Wood Bouncing Spot Right Tree Alcove Chest")
regions["100 Acre Wood"].locations.append("100 Acre Wood Bouncing Spot Under Giant Pot Chest")
regions["100 Acre Wood"].locations.append("100 Acre Wood Bouncing Spot Turn in Rare Nut 1")
regions["100 Acre Wood"].locations.append("100 Acre Wood Bouncing Spot Turn in Rare Nut 2")
regions["100 Acre Wood"].locations.append("100 Acre Wood Bouncing Spot Turn in Rare Nut 3")
regions["100 Acre Wood"].locations.append("100 Acre Wood Bouncing Spot Turn in Rare Nut 4")
regions["100 Acre Wood"].locations.append("100 Acre Wood Bouncing Spot Turn in Rare Nut 5")
regions["100 Acre Wood"].locations.append("100 Acre Wood Pooh's House Owl Cheer")
regions["100 Acre Wood"].locations.append("100 Acre Wood Convert Torn Page 1")
regions["100 Acre Wood"].locations.append("100 Acre Wood Convert Torn Page 2")
regions["100 Acre Wood"].locations.append("100 Acre Wood Convert Torn Page 3")
regions["100 Acre Wood"].locations.append("100 Acre Wood Convert Torn Page 4")
regions["100 Acre Wood"].locations.append("100 Acre Wood Convert Torn Page 5")
regions["100 Acre Wood"].locations.append("100 Acre Wood Pooh's House Start Fire")
regions["100 Acre Wood"].locations.append("100 Acre Wood Pooh's Room Cabinet")
regions["100 Acre Wood"].locations.append("100 Acre Wood Pooh's Room Chimney")
regions["100 Acre Wood"].locations.append("100 Acre Wood Bouncing Spot Break Log")
regions["100 Acre Wood"].locations.append("100 Acre Wood Bouncing Spot Fall Through Top of Tree Next to Pooh")
if options.atlantica:
regions["Atlantica"].locations.append("Atlantica Sunken Ship In Flipped Boat Chest")
regions["Atlantica"].locations.append("Atlantica Sunken Ship Seabed Chest")
regions["Atlantica"].locations.append("Atlantica Sunken Ship Inside Ship Chest")
regions["Atlantica"].locations.append("Atlantica Ariel's Grotto High Chest")
regions["Atlantica"].locations.append("Atlantica Ariel's Grotto Middle Chest")
regions["Atlantica"].locations.append("Atlantica Ariel's Grotto Low Chest")
regions["Atlantica"].locations.append("Atlantica Ursula's Lair Use Fire on Urchin Chest")
regions["Atlantica"].locations.append("Atlantica Undersea Gorge Jammed by Ariel's Grotto Chest")
regions["Atlantica"].locations.append("Atlantica Triton's Palace White Trinity Chest")
regions["Atlantica"].locations.append("Atlantica Defeat Ursula I Mermaid Kick Event")
regions["Atlantica"].locations.append("Atlantica Defeat Ursula II Thunder Event")
regions["Atlantica"].locations.append("Atlantica Seal Keyhole Crabclaw Event")
regions["Atlantica"].locations.append("Atlantica Undersea Gorge Blizzard Clam")
regions["Atlantica"].locations.append("Atlantica Undersea Gorge Ocean Floor Clam")
regions["Atlantica"].locations.append("Atlantica Undersea Valley Higher Cave Clam")
regions["Atlantica"].locations.append("Atlantica Undersea Valley Lower Cave Clam")
regions["Atlantica"].locations.append("Atlantica Undersea Valley Fire Clam")
regions["Atlantica"].locations.append("Atlantica Undersea Valley Wall Clam")
regions["Atlantica"].locations.append("Atlantica Undersea Valley Pillar Clam")
regions["Atlantica"].locations.append("Atlantica Undersea Valley Ocean Floor Clam")
regions["Atlantica"].locations.append("Atlantica Triton's Palace Thunder Clam")
regions["Atlantica"].locations.append("Atlantica Triton's Palace Wall Right Clam")
regions["Atlantica"].locations.append("Atlantica Triton's Palace Near Path Clam")
regions["Atlantica"].locations.append("Atlantica Triton's Palace Wall Left Clam")
regions["Atlantica"].locations.append("Atlantica Cavern Nook Clam")
regions["Atlantica"].locations.append("Atlantica Below Deck Clam")
regions["Atlantica"].locations.append("Atlantica Undersea Garden Clam")
regions["Atlantica"].locations.append("Atlantica Undersea Cave Clam")
regions["Atlantica"].locations.append("Atlantica Sunken Ship Crystal Trident Event")
regions["Atlantica"].locations.append("Atlantica Defeat Ursula II Ansem's Report 3")
if options.cups:
regions["Olympus Coliseum"].locations.append("Complete Phil Cup")
regions["Olympus Coliseum"].locations.append("Complete Phil Cup Solo")
regions["Olympus Coliseum"].locations.append("Complete Phil Cup Time Trial")
regions["Olympus Coliseum"].locations.append("Complete Pegasus Cup")
regions["Olympus Coliseum"].locations.append("Complete Pegasus Cup Solo")
regions["Olympus Coliseum"].locations.append("Complete Pegasus Cup Time Trial")
regions["Olympus Coliseum"].locations.append("Complete Hercules Cup")
regions["Olympus Coliseum"].locations.append("Complete Hercules Cup Solo")
regions["Olympus Coliseum"].locations.append("Complete Hercules Cup Time Trial")
regions["Olympus Coliseum"].locations.append("Complete Hades Cup")
regions["Olympus Coliseum"].locations.append("Complete Hades Cup Solo")
regions["Olympus Coliseum"].locations.append("Complete Hades Cup Time Trial")
regions["Olympus Coliseum"].locations.append("Hades Cup Defeat Cloud and Leon Event")
regions["Olympus Coliseum"].locations.append("Hades Cup Defeat Yuffie Event")
regions["Olympus Coliseum"].locations.append("Hades Cup Defeat Cerberus Event")
regions["Olympus Coliseum"].locations.append("Hades Cup Defeat Behemoth Event")
regions["Olympus Coliseum"].locations.append("Hades Cup Defeat Hades Event")
regions["Olympus Coliseum"].locations.append("Hercules Cup Defeat Cloud Event")
regions["Olympus Coliseum"].locations.append("Hercules Cup Yellow Trinity Event")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Defeat Hades Ansem's Report 8")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Olympia Chest")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Defeat Ice Titan Diamond Dust Event")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Gates Purple Jar After Defeating Hades")
if options.super_bosses:
regions["Neverland"].locations.append("Neverland Defeat Phantom Stop Event")
regions["Agrabah"].locations.append("Agrabah Defeat Kurt Zisa Zantetsuken Event")
regions["Agrabah"].locations.append("Agrabah Defeat Kurt Zisa Ansem's Report 11")
if options.super_bosses or options.goal.current_key == "sephiroth":
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Defeat Sephiroth Ansem's Report 12")
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Defeat Sephiroth One-Winged Angel Event")
if options.super_bosses or options.goal.current_key == "unknown":
regions["Hollow Bastion"].locations.append("Hollow Bastion Defeat Unknown Ansem's Report 13")
regions["Hollow Bastion"].locations.append("Hollow Bastion Defeat Unknown EXP Necklace Event")
for i in range(options.level_checks):
regions["Levels"].locations.append("Level " + str(i+1).rjust(3, '0'))
if options.goal.current_key == "final_ansem":
regions["End of the World"].locations.append("Final Ansem")
# Set up the regions correctly.
for name, data in regions.items():
multiworld.regions.append(create_region(multiworld, player, name, data))
multiworld.get_entrance("Awakening", player).connect(multiworld.get_region("Awakening", player))
multiworld.get_entrance("Destiny Islands", player).connect(multiworld.get_region("Destiny Islands", player))
multiworld.get_entrance("Traverse Town", player).connect(multiworld.get_region("Traverse Town", player))
multiworld.get_entrance("Wonderland", player).connect(multiworld.get_region("Wonderland", player))
multiworld.get_entrance("Olympus Coliseum", player).connect(multiworld.get_region("Olympus Coliseum", player))
multiworld.get_entrance("Deep Jungle", player).connect(multiworld.get_region("Deep Jungle", player))
multiworld.get_entrance("Agrabah", player).connect(multiworld.get_region("Agrabah", player))
multiworld.get_entrance("Monstro", player).connect(multiworld.get_region("Monstro", player))
multiworld.get_entrance("Atlantica", player).connect(multiworld.get_region("Atlantica", player))
multiworld.get_entrance("Halloween Town", player).connect(multiworld.get_region("Halloween Town", player))
multiworld.get_entrance("Neverland", player).connect(multiworld.get_region("Neverland", player))
multiworld.get_entrance("Hollow Bastion", player).connect(multiworld.get_region("Hollow Bastion", player))
multiworld.get_entrance("End of the World", player).connect(multiworld.get_region("End of the World", player))
multiworld.get_entrance("100 Acre Wood", player).connect(multiworld.get_region("100 Acre Wood", player))
multiworld.get_entrance("World Map", player).connect(multiworld.get_region("World Map", player))
multiworld.get_entrance("Levels", player).connect(multiworld.get_region("Levels", player))
def create_region(multiworld: MultiWorld, player: int, name: str, data: KH1RegionData):
region = Region(name, player, multiworld)
if data.locations:
for loc_name in data.locations:
loc_data = location_table.get(loc_name)
location = KH1Location(player, loc_name, loc_data.code if loc_data else None, region)
region.locations.append(location)
if data.region_exits:
for exit in data.region_exits:
entrance = Entrance(player, exit, region)
region.exits.append(entrance)
return region

File diff suppressed because it is too large Load Diff

View File

@@ -1,282 +0,0 @@
import logging
from typing import List
from BaseClasses import Tutorial
from worlds.AutoWorld import WebWorld, World
from .Items import KH1Item, KH1ItemData, event_item_table, get_items_by_category, item_table, item_name_groups
from .Locations import KH1Location, location_table, get_locations_by_category, location_name_groups
from .Options import KH1Options, kh1_option_groups
from .Regions import create_regions
from .Rules import set_rules
from .Presets import kh1_option_presets
from worlds.LauncherComponents import Component, components, Type, launch_subprocess
def launch_client():
from .Client import launch
launch_subprocess(launch, name="KH1 Client")
components.append(Component("KH1 Client", "KH1Client", func=launch_client, component_type=Type.CLIENT))
class KH1Web(WebWorld):
theme = "ocean"
tutorials = [Tutorial(
"Multiworld Setup Guide",
"A guide to setting up the Kingdom Hearts Randomizer software on your computer."
"This guide covers single-player, multiworld, and related software.",
"English",
"kh1_en.md",
"kh1/en",
["Gicu"]
)]
option_groups = kh1_option_groups
options_presets = kh1_option_presets
class KH1World(World):
"""
Kingdom Hearts is an action RPG following Sora on his journey
through many worlds to find Riku and Kairi.
"""
game = "Kingdom Hearts"
options_dataclass = KH1Options
options: KH1Options
topology_present = True
web = KH1Web()
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = {name: data.code for name, data in location_table.items()}
item_name_groups = item_name_groups
location_name_groups = location_name_groups
fillers = {}
fillers.update(get_items_by_category("Item"))
fillers.update(get_items_by_category("Camping"))
fillers.update(get_items_by_category("Stat Ups"))
def create_items(self):
self.place_predetermined_items()
# Handle starting worlds
starting_worlds = []
if self.options.starting_worlds > 0:
possible_starting_worlds = ["Wonderland", "Olympus Coliseum", "Deep Jungle", "Agrabah", "Monstro", "Halloween Town", "Neverland", "Hollow Bastion"]
if self.options.atlantica:
possible_starting_worlds.append("Atlantica")
if self.options.end_of_the_world_unlock == "item":
possible_starting_worlds.append("End of the World")
starting_worlds = self.random.sample(possible_starting_worlds, min(self.options.starting_worlds.value, len(possible_starting_worlds)))
for starting_world in starting_worlds:
self.multiworld.push_precollected(self.create_item(starting_world))
item_pool: List[KH1Item] = []
possible_level_up_item_pool = []
level_up_item_pool = []
# Calculate Level Up Items
# Fill pool with mandatory items
for _ in range(self.options.item_slot_increase):
level_up_item_pool.append("Item Slot Increase")
for _ in range(self.options.accessory_slot_increase):
level_up_item_pool.append("Accessory Slot Increase")
# Create other pool
for _ in range(self.options.strength_increase):
possible_level_up_item_pool.append("Strength Increase")
for _ in range(self.options.defense_increase):
possible_level_up_item_pool.append("Defense Increase")
for _ in range(self.options.hp_increase):
possible_level_up_item_pool.append("Max HP Increase")
for _ in range(self.options.mp_increase):
possible_level_up_item_pool.append("Max MP Increase")
for _ in range(self.options.ap_increase):
possible_level_up_item_pool.append("Max AP Increase")
# Fill remaining pool with items from other pool
self.random.shuffle(possible_level_up_item_pool)
level_up_item_pool = level_up_item_pool + possible_level_up_item_pool[:(100 - len(level_up_item_pool))]
level_up_locations = list(get_locations_by_category("Levels").keys())
self.random.shuffle(level_up_item_pool)
current_level_for_placing_stats = self.options.force_stats_on_levels.value
while len(level_up_item_pool) > 0 and current_level_for_placing_stats <= self.options.level_checks:
self.get_location(level_up_locations[current_level_for_placing_stats - 1]).place_locked_item(self.create_item(level_up_item_pool.pop()))
current_level_for_placing_stats += 1
# Calculate prefilled locations and items
prefilled_items = []
if self.options.vanilla_emblem_pieces:
prefilled_items = prefilled_items + ["Emblem Piece (Flame)", "Emblem Piece (Chest)", "Emblem Piece (Fountain)", "Emblem Piece (Statue)"]
total_locations = len(self.multiworld.get_unfilled_locations(self.player))
non_filler_item_categories = ["Key", "Magic", "Worlds", "Trinities", "Cups", "Summons", "Abilities", "Shared Abilities", "Keyblades", "Accessory", "Weapons", "Puppies"]
if self.options.hundred_acre_wood:
non_filler_item_categories.append("Torn Pages")
for name, data in item_table.items():
quantity = data.max_quantity
if data.category not in non_filler_item_categories:
continue
if name in starting_worlds:
continue
if data.category == "Puppies":
if self.options.puppies == "triplets" and "-" in name:
item_pool += [self.create_item(name) for _ in range(quantity)]
if self.options.puppies == "individual" and "Puppy" in name:
item_pool += [self.create_item(name) for _ in range(0, quantity)]
if self.options.puppies == "full" and name == "All Puppies":
item_pool += [self.create_item(name) for _ in range(0, quantity)]
elif name == "Atlantica":
if self.options.atlantica:
item_pool += [self.create_item(name) for _ in range(0, quantity)]
elif name == "Mermaid Kick":
if self.options.atlantica:
if self.options.extra_shared_abilities:
item_pool += [self.create_item(name) for _ in range(0, 2)]
else:
item_pool += [self.create_item(name) for _ in range(0, quantity)]
elif name == "Crystal Trident":
if self.options.atlantica:
item_pool += [self.create_item(name) for _ in range(0, quantity)]
elif name == "High Jump":
if self.options.extra_shared_abilities:
item_pool += [self.create_item(name) for _ in range(0, 3)]
else:
item_pool += [self.create_item(name) for _ in range(0, quantity)]
elif name == "Progressive Glide":
if self.options.extra_shared_abilities:
item_pool += [self.create_item(name) for _ in range(0, 4)]
else:
item_pool += [self.create_item(name) for _ in range(0, quantity)]
elif name == "End of the World":
if self.options.end_of_the_world_unlock.current_key == "item":
item_pool += [self.create_item(name) for _ in range(0, quantity)]
elif name == "EXP Zero":
if self.options.exp_zero_in_pool:
item_pool += [self.create_item(name) for _ in range(0, quantity)]
elif name not in prefilled_items:
item_pool += [self.create_item(name) for _ in range(0, quantity)]
for i in range(self.determine_reports_in_pool()):
item_pool += [self.create_item("Ansem's Report " + str(i+1))]
while len(item_pool) < total_locations and len(level_up_item_pool) > 0:
item_pool += [self.create_item(level_up_item_pool.pop())]
# Fill any empty locations with filler items.
while len(item_pool) < total_locations:
item_pool.append(self.create_item(self.get_filler_item_name()))
self.multiworld.itempool += item_pool
def place_predetermined_items(self) -> None:
goal_dict = {
"sephiroth": "Olympus Coliseum Defeat Sephiroth Ansem's Report 12",
"unknown": "Hollow Bastion Defeat Unknown Ansem's Report 13",
"postcards": "Traverse Town Mail Postcard 10 Event",
"final_ansem": "Final Ansem",
"puppies": "Traverse Town Piano Room Return 99 Puppies Reward 2",
"final_rest": "End of the World Final Rest Chest"
}
self.get_location(goal_dict[self.options.goal.current_key]).place_locked_item(self.create_item("Victory"))
if self.options.vanilla_emblem_pieces:
self.get_location("Hollow Bastion Entrance Hall Emblem Piece (Flame)").place_locked_item(self.create_item("Emblem Piece (Flame)"))
self.get_location("Hollow Bastion Entrance Hall Emblem Piece (Statue)").place_locked_item(self.create_item("Emblem Piece (Statue)"))
self.get_location("Hollow Bastion Entrance Hall Emblem Piece (Fountain)").place_locked_item(self.create_item("Emblem Piece (Fountain)"))
self.get_location("Hollow Bastion Entrance Hall Emblem Piece (Chest)").place_locked_item(self.create_item("Emblem Piece (Chest)"))
def get_filler_item_name(self) -> str:
weights = [data.weight for data in self.fillers.values()]
return self.random.choices([filler for filler in self.fillers.keys()], weights)[0]
def fill_slot_data(self) -> dict:
slot_data = {"xpmult": int(self.options.exp_multiplier)/16,
"required_reports_eotw": self.determine_reports_required_to_open_end_of_the_world(),
"required_reports_door": self.determine_reports_required_to_open_final_rest_door(),
"door": self.options.final_rest_door.current_key,
"seed": self.multiworld.seed_name,
"advanced_logic": bool(self.options.advanced_logic),
"hundred_acre_wood": bool(self.options.hundred_acre_wood),
"atlantica": bool(self.options.atlantica),
"goal": str(self.options.goal.current_key)}
if self.options.randomize_keyblade_stats:
min_str_bonus = min(self.options.keyblade_min_str.value, self.options.keyblade_max_str.value)
max_str_bonus = max(self.options.keyblade_min_str.value, self.options.keyblade_max_str.value)
self.options.keyblade_min_str.value = min_str_bonus
self.options.keyblade_max_str.value = max_str_bonus
min_mp_bonus = min(self.options.keyblade_min_mp.value, self.options.keyblade_max_mp.value)
max_mp_bonus = max(self.options.keyblade_min_mp.value, self.options.keyblade_max_mp.value)
self.options.keyblade_min_mp.value = min_mp_bonus
self.options.keyblade_max_mp.value = max_mp_bonus
slot_data["keyblade_stats"] = ""
for i in range(22):
if i < 4 and self.options.bad_starting_weapons:
slot_data["keyblade_stats"] = slot_data["keyblade_stats"] + "1,0,"
else:
str_bonus = int(self.random.randint(min_str_bonus, max_str_bonus))
mp_bonus = int(self.random.randint(min_mp_bonus, max_mp_bonus))
slot_data["keyblade_stats"] = slot_data["keyblade_stats"] + str(str_bonus) + "," + str(mp_bonus) + ","
slot_data["keyblade_stats"] = slot_data["keyblade_stats"][:-1]
if self.options.donald_death_link:
slot_data["donalddl"] = ""
if self.options.goofy_death_link:
slot_data["goofydl"] = ""
if self.options.keyblades_unlock_chests:
slot_data["chestslocked"] = ""
else:
slot_data["chestsunlocked"] = ""
if self.options.interact_in_battle:
slot_data["interactinbattle"] = ""
return slot_data
def create_item(self, name: str) -> KH1Item:
data = item_table[name]
return KH1Item(name, data.classification, data.code, self.player)
def create_event(self, name: str) -> KH1Item:
data = event_item_table[name]
return KH1Item(name, data.classification, data.code, self.player)
def set_rules(self):
set_rules(self)
def create_regions(self):
create_regions(self.multiworld, self.player, self.options)
def generate_early(self):
value_names = ["Reports to Open End of the World", "Reports to Open Final Rest Door", "Reports in Pool"]
initial_report_settings = [self.options.required_reports_eotw.value, self.options.required_reports_door.value, self.options.reports_in_pool.value]
self.change_numbers_of_reports_to_consider()
new_report_settings = [self.options.required_reports_eotw.value, self.options.required_reports_door.value, self.options.reports_in_pool.value]
for i in range(3):
if initial_report_settings[i] != new_report_settings[i]:
logging.info(f"{self.player_name}'s value {initial_report_settings[i]} for \"{value_names[i]}\" was invalid\n"
f"Setting \"{value_names[i]}\" value to {new_report_settings[i]}")
def change_numbers_of_reports_to_consider(self) -> None:
if self.options.end_of_the_world_unlock == "reports" and self.options.final_rest_door == "reports":
self.options.required_reports_eotw.value, self.options.required_reports_door.value, self.options.reports_in_pool.value = sorted(
[self.options.required_reports_eotw.value, self.options.required_reports_door.value, self.options.reports_in_pool.value])
elif self.options.end_of_the_world_unlock == "reports":
self.options.required_reports_eotw.value, self.options.reports_in_pool.value = sorted(
[self.options.required_reports_eotw.value, self.options.reports_in_pool.value])
elif self.options.final_rest_door == "reports":
self.options.required_reports_door.value, self.options.reports_in_pool.value = sorted(
[self.options.required_reports_door.value, self.options.reports_in_pool.value])
def determine_reports_in_pool(self) -> int:
if self.options.end_of_the_world_unlock == "reports" or self.options.final_rest_door == "reports":
return self.options.reports_in_pool.value
return 0
def determine_reports_required_to_open_end_of_the_world(self) -> int:
if self.options.end_of_the_world_unlock == "reports":
return self.options.required_reports_eotw.value
return 14
def determine_reports_required_to_open_final_rest_door(self) -> int:
if self.options.final_rest_door == "reports":
return self.options.required_reports_door.value
return 14

View File

@@ -1,88 +0,0 @@
# Kingdom Hearts (PC)
## Where is the options page?
The [player options page for this game](../player-options) contains most of the options you need to
configure and export a config file.
## What does randomization do to this game?
The Kingdom Hearts AP Randomizer randomizes most rewards in the game and adds several items which are used to unlock worlds, Olympus Coliseum cups, and world progression.
Worlds can only be accessed by finding the corresponding item. For example, you need to find the `Monstro` item to enter Monstro.
The default goal is to enter End of the World and defeat Final Ansem.
## What items and locations get shuffled?
### Items
Any weapon, accessory, spell, trinity, summon, world, key item, stat up, consumable, or ability can be found in any location.
### Locations
Locations the player can find items include chests, event rewards, Atlantica clams, level up rewards, 101 Dalmatian rewards, and postcard rewards.
## Which items can be in another player's world?
Any of the items which can be shuffled may also be placed into another player's world. It is possible to choose to limit
certain items to your own world.
## When the player receives an item, what happens?
When the player receives an item, your client will display a message displaying the item you have obtained. You will also see a notification in the "LEVEL UP" box.
## What do I do if I encounter a bug with the game?
Please reach out to Gicu#7034 on Discord.
## How do I progress in a certain world?
### The evidence boxes aren't spawning in Wonderland.
Find `Footprints` in the multiworld.
### I can't enter any cups in Olympus Coliseum.
Firstly, find `Entry Pass` in the multiworld. Additionally, `Phil Cup`, `Pegasus Cup`, and `Hercules Cup` are all multiworld items. Finding all 3 grant you access to the Hades Cup and the Platinum Match. Clearing all cups lets you challenge Ice Titan.
### The slides aren't spawning in Deep Jungle.
Find `Slides` in the multiworld.
### I can't progress in Atlantica.
Find `Crystal Trident` in the multiworld.
### I can't progress in Halloween Town.
Find `Forget-Me-Not` and `Jack-in-the-Box` in the multiworld.
### The Hollow Bastion Library is missing a book.
Find `Theon Vol. 6` in the multiworld.
## How do I enter the End of the World?
You can enter End of the World by obtaining a number of Ansem's Reports or by finding `End of the World` in the multiworld, depending on your options.
## Credits
This is a collaborative effort from several individuals in the Kingdom Hearts community, but most of all, denhonator.
Denho's original KH rando laid the foundation for the work here and makes everything here possible, so thank you Denho for such a blast of a randomizer.
Other credits include:
Sonicshadowsilver2 for their work finding many memory addresses, working to identify and resolve bugs, and converting the code base to the latest EGS update.
Shananas and the rest of the OpenKH team for providing such an amazing tool for us to utilize on this project.
TopazTK for their work on the `Show Prompt` method and Krujo for their implementation of the method in AP.
JaredWeakStrike for helping clean up my mess of code.
KSX for their `Interact in Battle` code.
RavSpect for their title screen image edit.
SunCatMC for their work on ChecksFinder, which I used as a basis for game-to-client communication.
ThePhar for their work on Rogue Legacy AP, which I used as a basis for the apworld creation.

View File

@@ -1,54 +0,0 @@
# Kingdom Hearts Randomizer Setup Guide
## Setting up the required mods
BEFORE MODDING, PLEASE INSTALL AND RUN KH1 AT LEAST ONCE.
1. Install OpenKH and the LUA Backend
Download the [latest release of OpenKH](https://github.com/OpenKH/OpenKh/releases/tag/latest)
Extract the files to a directory of your choosing.
Open `OpenKh.Tools.ModsManager.exe` and run first time set up
When prompted for game edition, choose `PC Release`, select which platform you're using (EGS or Steam), navigate to your `Kingdom Hearts I.5 + II.5` installation folder in the path box and click `Next`
When prompted, install Panacea, then click `Next`
When prompted, check KH1 plus any other AP game you play and click `Install and configure LUA backend`, then click `Next`
Extracting game data for KH1 is unnecessary, but you may want to extract data for KH2 if you plan on playing KH2 AP
Click `Finish`
2. Open `OpenKh.Tools.ModsManager.exe`
3. Click the drop-down menu at the top-right and choose `Kingdom Hearts 1`
4. Click `Mods>Install a New Mod`
5. In `Add a new mod from GitHub` paste `gaithern/KH-1FM-AP-LUA`
6. Click `Install`
7. Navigate to Mod Loader and click `Build and Run`
## Configuring your YAML file
### What is a YAML file and why do I need one?
Your YAML file contains a set of configuration options which provide the generator with information about how it should
generate your game. Each player of a multiworld will provide their own YAML file. This setup allows each player to enjoy
an experience customized for their taste, and different players in the same multiworld can all have different options.
### Where do I get a YAML file?
you can customize your settings by visiting the [Kingdom Hearts Options Page](/games/Kingdom%20Hearts/player-options).
## Connect to the MultiWorld
For first-time players, it is recommended to open your KH1 Client first before opening the game.
On the title screen, open your KH1 Client and connect to your multiworld.

View File

@@ -1,5 +0,0 @@
from test.bases import WorldTestBase
class KH1TestBase(WorldTestBase):
game = "Kingdom Hearts"

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