mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-07 15:13:52 -08:00
Compare commits
83 Commits
NewSoupVi-
...
NewSoupVi-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af4b312f54 | ||
|
|
b388572379 | ||
|
|
6ee907e631 | ||
|
|
d65863ffa2 | ||
|
|
b8d7ef24f7 | ||
|
|
b2949dfbe8 | ||
|
|
2aa0653b6d | ||
|
|
d63efa5846 | ||
|
|
765721888a | ||
|
|
73701292b5 | ||
|
|
3ab71daa8d | ||
|
|
6f46397185 | ||
|
|
1a41e1acc8 | ||
|
|
34a3b5f058 | ||
|
|
456b4adaa1 | ||
|
|
fc8462f4e9 | ||
|
|
499dad53b1 | ||
|
|
8a809be67a | ||
|
|
7e0219c214 | ||
|
|
b37bb60891 | ||
|
|
f81335d614 | ||
|
|
8ed466bf24 | ||
|
|
920cffda2d | ||
|
|
b1be597451 | ||
|
|
08dc7e522e | ||
|
|
0f64bd08e1 | ||
|
|
d52827ebd2 | ||
|
|
0e55ddc7cf | ||
|
|
ab5b986716 | ||
|
|
97c313c1c4 | ||
|
|
701a7faa71 | ||
|
|
9a4e84efdc | ||
|
|
906b23088c | ||
|
|
0fb69dce33 | ||
|
|
e99f027b42 | ||
|
|
dddffa1660 | ||
|
|
83367c6946 | ||
|
|
0fcca25870 | ||
|
|
d1a7fd7da1 | ||
|
|
5c5f2ffc94 | ||
|
|
6f617e302d | ||
|
|
35c9061c9c | ||
|
|
e61d521ba8 | ||
|
|
6efa065867 | ||
|
|
56dbba6a31 | ||
|
|
43cb9611fb | ||
|
|
64b654d42e | ||
|
|
74aab81f79 | ||
|
|
f390b33c17 | ||
|
|
31852801c9 | ||
|
|
e35addf5b2 | ||
|
|
3cdcb8c455 | ||
|
|
48c6a6fb4c | ||
|
|
eaa8156061 | ||
|
|
54a7bb5664 | ||
|
|
0e6e359747 | ||
|
|
c4e7b6ca82 | ||
|
|
f253dffc07 | ||
|
|
c010c8c938 | ||
|
|
1e8a8e7482 | ||
|
|
182f7e24e5 | ||
|
|
9277cb39ef | ||
|
|
28a9709516 | ||
|
|
49a5b52774 | ||
|
|
2b1802ccee | ||
|
|
f5218faea7 | ||
|
|
81092247c6 | ||
|
|
ca96e7e294 | ||
|
|
c014c5a54a | ||
|
|
e9c863dffd | ||
|
|
7eda4c47f8 | ||
|
|
474a3181c6 | ||
|
|
4af6927e23 | ||
|
|
06df072095 | ||
|
|
56aabe51b8 | ||
|
|
5e5f24cdd2 | ||
|
|
9fbaa6050f | ||
|
|
0af31c71e0 | ||
|
|
169da1b1e0 | ||
|
|
8e7ea06f39 | ||
|
|
96d48a923a | ||
|
|
dcaa2f7b97 | ||
|
|
50330cf32f |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
worlds/blasphemous/region_data.py linguist-generated=true
|
||||
7
.github/workflows/unittests.yml
vendored
7
.github/workflows/unittests.yml
vendored
@@ -37,12 +37,13 @@ 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.11'} # current
|
||||
- python: {version: '3.12'} # current
|
||||
os: windows-latest
|
||||
- python: {version: '3.11'} # current
|
||||
- python: {version: '3.12'} # current
|
||||
os: macos-latest
|
||||
|
||||
steps:
|
||||
@@ -70,7 +71,7 @@ jobs:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
python:
|
||||
- {version: '3.11'} # current
|
||||
- {version: '3.12'} # current
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
157
BaseClasses.py
157
BaseClasses.py
@@ -11,8 +11,10 @@ from argparse import Namespace
|
||||
from collections import Counter, deque
|
||||
from collections.abc import Collection, MutableSequence
|
||||
from enum import IntEnum, IntFlag
|
||||
from typing import Any, Callable, Dict, Iterable, Iterator, List, Mapping, NamedTuple, Optional, Set, Tuple, \
|
||||
TypedDict, Union, Type, ClassVar
|
||||
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
|
||||
|
||||
import NetUtils
|
||||
import Options
|
||||
@@ -22,16 +24,16 @@ if typing.TYPE_CHECKING:
|
||||
from worlds import AutoWorld
|
||||
|
||||
|
||||
class Group(TypedDict, total=False):
|
||||
class Group(TypedDict):
|
||||
name: str
|
||||
game: str
|
||||
world: "AutoWorld.World"
|
||||
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
|
||||
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]
|
||||
|
||||
|
||||
class ThreadBarrierProxy:
|
||||
@@ -48,6 +50,11 @@ 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]
|
||||
@@ -156,7 +163,7 @@ class MultiWorld():
|
||||
self.start_inventory_from_pool: Dict[int, Options.StartInventoryPool] = {}
|
||||
|
||||
for player in range(1, players + 1):
|
||||
def set_player_attr(attr, val):
|
||||
def set_player_attr(attr: str, val) -> None:
|
||||
self.__dict__.setdefault(attr, {})[player] = val
|
||||
set_player_attr('plando_items', [])
|
||||
set_player_attr('plando_texts', {})
|
||||
@@ -165,13 +172,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: Set[int] = frozenset()) -> Tuple[int, Group]:
|
||||
def add_group(self, name: str, game: str, players: AbstractSet[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
|
||||
@@ -195,7 +202,7 @@ class MultiWorld():
|
||||
|
||||
return new_id, new_group
|
||||
|
||||
def get_player_groups(self, player) -> Set[int]:
|
||||
def get_player_groups(self, player: int) -> 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):
|
||||
@@ -258,7 +265,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()
|
||||
@@ -388,7 +395,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) -> str:
|
||||
def get_name_string_for_object(self, obj: HasNameAndPlayer) -> 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:
|
||||
@@ -430,7 +437,7 @@ class MultiWorld():
|
||||
subworld = self.worlds[player]
|
||||
for item in subworld.get_pre_fill_items():
|
||||
subworld.collect(ret, item)
|
||||
ret.sweep_for_events()
|
||||
ret.sweep_for_advancements()
|
||||
|
||||
if use_cache:
|
||||
self._all_state = ret
|
||||
@@ -439,7 +446,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, player: int, resolve_group_locations: bool = False) -> List[Location]:
|
||||
def find_item_locations(self, item: str, player: int, resolve_group_locations: bool = False) -> List[Location]:
|
||||
if resolve_group_locations:
|
||||
player_groups = self.get_player_groups(player)
|
||||
return [location for location in self.get_locations() if
|
||||
@@ -448,7 +455,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, player: int) -> Location:
|
||||
def find_item(self, item: str, 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)
|
||||
|
||||
@@ -541,9 +548,9 @@ class MultiWorld():
|
||||
return True
|
||||
state = starting_state.copy()
|
||||
else:
|
||||
if self.has_beaten_game(self.state):
|
||||
return True
|
||||
state = CollectionState(self)
|
||||
if self.has_beaten_game(state):
|
||||
return True
|
||||
prog_locations = {location for location in self.get_locations() if location.item
|
||||
and location.item.advancement and location not in state.locations_checked}
|
||||
|
||||
@@ -616,8 +623,7 @@ class MultiWorld():
|
||||
|
||||
def location_relevant(location: Location) -> bool:
|
||||
"""Determine if this location is relevant to sweep."""
|
||||
return location.progress_type != LocationProgressType.EXCLUDED \
|
||||
and (location.player in players["full"] or location.advancement)
|
||||
return location.player in players["full"] or location.advancement
|
||||
|
||||
def all_done() -> bool:
|
||||
"""Check if all access rules are fulfilled"""
|
||||
@@ -662,7 +668,7 @@ class CollectionState():
|
||||
multiworld: MultiWorld
|
||||
reachable_regions: Dict[int, Set[Region]]
|
||||
blocked_connections: Dict[int, Set[Entrance]]
|
||||
events: Set[Location]
|
||||
advancements: Set[Location]
|
||||
path: Dict[Union[Region, Entrance], PathValue]
|
||||
locations_checked: Set[Location]
|
||||
stale: Dict[int, bool]
|
||||
@@ -674,7 +680,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.events = set()
|
||||
self.advancements = set()
|
||||
self.path = {}
|
||||
self.locations_checked = set()
|
||||
self.stale = {player: True for player in parent.get_all_ids()}
|
||||
@@ -723,7 +729,7 @@ class CollectionState():
|
||||
self.reachable_regions.items()}
|
||||
ret.blocked_connections = {player: entrance_set.copy() for player, entrance_set in
|
||||
self.blocked_connections.items()}
|
||||
ret.events = self.events.copy()
|
||||
ret.advancements = self.advancements.copy()
|
||||
ret.path = self.path.copy()
|
||||
ret.locations_checked = self.locations_checked.copy()
|
||||
for function in self.additional_copy_functions:
|
||||
@@ -756,19 +762,24 @@ 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_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}
|
||||
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}
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
# item name related
|
||||
def has(self, item: str, player: int, count: int = 1) -> bool:
|
||||
@@ -802,7 +813,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."""
|
||||
@@ -817,7 +828,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)
|
||||
@@ -863,20 +874,16 @@ class CollectionState():
|
||||
)
|
||||
|
||||
# Item related
|
||||
def collect(self, item: Item, event: bool = False, location: Optional[Location] = None) -> bool:
|
||||
def collect(self, item: Item, prevent_sweep: 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 event:
|
||||
self.sweep_for_events()
|
||||
if changed and not prevent_sweep:
|
||||
self.sweep_for_advancements()
|
||||
|
||||
return changed
|
||||
|
||||
@@ -900,7 +907,7 @@ class Entrance:
|
||||
addresses = None
|
||||
target = None
|
||||
|
||||
def __init__(self, player: int, name: str = '', parent: Region = None):
|
||||
def __init__(self, player: int, name: str = "", parent: Optional[Region] = None) -> None:
|
||||
self.name = name
|
||||
self.parent_region = parent
|
||||
self.player = player
|
||||
@@ -920,9 +927,6 @@ 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})'
|
||||
|
||||
@@ -1048,7 +1052,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_type:
|
||||
rule: Optional[Callable[[CollectionState], bool]] = None) -> Entrance:
|
||||
"""
|
||||
Connects this Region to another Region, placing the provided rule on the connection.
|
||||
|
||||
@@ -1072,7 +1076,7 @@ class Region:
|
||||
return exit_
|
||||
|
||||
def add_exits(self, exits: Union[Iterable[str], Dict[str, Optional[str]]],
|
||||
rules: Dict[str, Callable[[CollectionState], bool]] = None) -> None:
|
||||
rules: Dict[str, Callable[[CollectionState], bool]] = None) -> List[Entrance]:
|
||||
"""
|
||||
Connects current region to regions in exit dictionary. Passed region names must exist first.
|
||||
|
||||
@@ -1082,15 +1086,16 @@ class Region:
|
||||
"""
|
||||
if not isinstance(exits, Dict):
|
||||
exits = dict.fromkeys(exits)
|
||||
for connecting_region, name in exits.items():
|
||||
self.connect(self.multiworld.get_region(connecting_region, self.player),
|
||||
name,
|
||||
rules[connecting_region] if rules and connecting_region in rules else None)
|
||||
return [
|
||||
self.connect(
|
||||
self.multiworld.get_region(connecting_region, self.player),
|
||||
name,
|
||||
rules[connecting_region] if rules and connecting_region in rules else None,
|
||||
)
|
||||
for connecting_region, name in exits.items()
|
||||
]
|
||||
|
||||
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})'
|
||||
|
||||
|
||||
@@ -1109,9 +1114,9 @@ class Location:
|
||||
locked: bool = False
|
||||
show_in_spoiler: bool = True
|
||||
progress_type: LocationProgressType = LocationProgressType.DEFAULT
|
||||
always_allow = staticmethod(lambda state, item: False)
|
||||
always_allow: Callable[[CollectionState, Item], bool] = staticmethod(lambda state, item: False)
|
||||
access_rule: Callable[[CollectionState], bool] = staticmethod(lambda state: True)
|
||||
item_rule = staticmethod(lambda item: True)
|
||||
item_rule: Callable[[Item], bool] = staticmethod(lambda item: True)
|
||||
item: Optional[Item] = None
|
||||
|
||||
def __init__(self, player: int, name: str = '', address: Optional[int] = None, parent: Optional[Region] = None):
|
||||
@@ -1120,11 +1125,15 @@ class Location:
|
||||
self.address = address
|
||||
self.parent_region = parent
|
||||
|
||||
def can_fill(self, state: CollectionState, item: Item, check_access=True) -> bool:
|
||||
return ((self.always_allow(state, item) and item.name not in state.multiworld.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: 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_reach(self, state: CollectionState) -> bool:
|
||||
# Region.can_reach is just a cache lookup, so placing it first for faster abort on average
|
||||
@@ -1139,9 +1148,6 @@ 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})'
|
||||
|
||||
@@ -1163,7 +1169,7 @@ class Location:
|
||||
@property
|
||||
def native_item(self) -> bool:
|
||||
"""Returns True if the item in this location matches game."""
|
||||
return self.item and self.item.game == self.game
|
||||
return self.item is not None and self.item.game == self.game
|
||||
|
||||
@property
|
||||
def hint_text(self) -> str:
|
||||
@@ -1246,9 +1252,6 @@ 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})"
|
||||
@@ -1326,9 +1329,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 = {}
|
||||
restore_later: Dict[Location, Item] = {}
|
||||
for num, sphere in reversed(tuple(enumerate(collection_spheres))):
|
||||
to_delete = set()
|
||||
to_delete: Set[Location] = 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,
|
||||
@@ -1346,7 +1349,7 @@ class Spoiler:
|
||||
sphere -= to_delete
|
||||
|
||||
# second phase, sphere 0
|
||||
removed_precollected = []
|
||||
removed_precollected: List[Item] = []
|
||||
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)
|
||||
@@ -1427,7 +1430,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.mode[player] != 'inverted':
|
||||
if multiworld.worlds[player].options.mode != 'inverted':
|
||||
self.paths[str(multiworld.get_region('Big Bomb Shop', player))] = \
|
||||
get_path(state, multiworld.get_region('Big Bomb Shop', player))
|
||||
else:
|
||||
@@ -1499,9 +1502,9 @@ class Spoiler:
|
||||
|
||||
if self.paths:
|
||||
outfile.write('\n\nPaths:\n\n')
|
||||
path_listings = []
|
||||
path_listings: List[str] = []
|
||||
for location, path in sorted(self.paths.items()):
|
||||
path_lines = []
|
||||
path_lines: List[str] = []
|
||||
for region, exit in path:
|
||||
if exit is not None:
|
||||
path_lines.append("{} -> {}".format(region, exit))
|
||||
|
||||
@@ -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 = None
|
||||
ui: typing.Optional["kvui.GameManager"] = 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
33
Fill.py
@@ -12,7 +12,12 @@ from worlds.generic.Rules import add_item_rule
|
||||
|
||||
|
||||
class FillError(RuntimeError):
|
||||
pass
|
||||
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)
|
||||
|
||||
|
||||
def _log_fill_progress(name: str, placed: int, total_items: int) -> None:
|
||||
@@ -24,7 +29,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_events(locations=locations)
|
||||
new_state.sweep_for_advancements(locations=locations)
|
||||
return new_state
|
||||
|
||||
|
||||
@@ -212,7 +217,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)}")
|
||||
f"{', '.join(str(place) for place in placements)}", multiworld=multiworld)
|
||||
|
||||
item_pool.extend(unplaced_items)
|
||||
|
||||
@@ -299,7 +304,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)}")
|
||||
f"{', '.join(str(place) for place in placements)}", multiworld=multiworld)
|
||||
|
||||
itempool.extend(unplaced_items)
|
||||
|
||||
@@ -324,8 +329,8 @@ def accessibility_corrections(multiworld: MultiWorld, state: CollectionState, lo
|
||||
pool.append(location.item)
|
||||
state.remove(location.item)
|
||||
location.item = None
|
||||
if location in state.events:
|
||||
state.events.remove(location)
|
||||
if location in state.advancements:
|
||||
state.advancements.remove(location)
|
||||
locations.append(location)
|
||||
if pool and locations:
|
||||
locations.sort(key=lambda loc: loc.progress_type != LocationProgressType.PRIORITY)
|
||||
@@ -358,7 +363,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_events(locations=(loc for loc in multiworld.get_filled_locations() if loc.address is None))
|
||||
base_state.sweep_for_advancements(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:
|
||||
@@ -506,7 +511,8 @@ 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."
|
||||
f"There are {len(progitempool)} more progression items than there are available locations.",
|
||||
multiworld=multiworld,
|
||||
)
|
||||
accessibility_corrections(multiworld, multiworld.state, defaultlocations)
|
||||
|
||||
@@ -523,7 +529,8 @@ 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."
|
||||
f"There are {len(excludedlocations)} more excluded locations than filler or trap items.",
|
||||
multiworld=multiworld,
|
||||
)
|
||||
|
||||
restitempool = filleritempool + usefulitempool
|
||||
@@ -551,7 +558,7 @@ def flood_items(multiworld: MultiWorld) -> None:
|
||||
progress_done = False
|
||||
|
||||
# sweep once to pick up preplaced items
|
||||
multiworld.state.sweep_for_events()
|
||||
multiworld.state.sweep_for_advancements()
|
||||
|
||||
# fill multiworld from top of itempool while we can
|
||||
while not progress_done:
|
||||
@@ -589,7 +596,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.')
|
||||
raise FillError('No more progress items left to place.', multiworld=multiworld)
|
||||
|
||||
# find item to replace with progress item
|
||||
location_list = multiworld.get_reachable_locations()
|
||||
@@ -739,7 +746,7 @@ def balance_multiworld_progression(multiworld: MultiWorld) -> None:
|
||||
), items_to_test):
|
||||
reducing_state.collect(location.item, True, location)
|
||||
|
||||
reducing_state.sweep_for_events(locations=locations_to_test)
|
||||
reducing_state.sweep_for_advancements(locations=locations_to_test)
|
||||
|
||||
if multiworld.has_beaten_game(balancing_state):
|
||||
if not multiworld.has_beaten_game(reducing_state):
|
||||
@@ -822,7 +829,7 @@ def distribute_planned(multiworld: MultiWorld) -> None:
|
||||
warn(warning, force)
|
||||
|
||||
swept_state = multiworld.state.copy()
|
||||
swept_state.sweep_for_events()
|
||||
swept_state.sweep_for_advancements()
|
||||
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)
|
||||
|
||||
@@ -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 = game_weights.get("plando_items", [])
|
||||
ret.plando_items = copy.deepcopy(game_weights.get("plando_items", []))
|
||||
if ret.game == "A Link to the Past":
|
||||
roll_alttp_settings(ret, game_weights)
|
||||
|
||||
|
||||
9
KH1Client.py
Normal file
9
KH1Client.py
Normal file
@@ -0,0 +1,9 @@
|
||||
if __name__ == '__main__':
|
||||
import ModuleUpdate
|
||||
ModuleUpdate.update()
|
||||
|
||||
import Utils
|
||||
Utils.init_logging("KH1Client", exception_logger="Client")
|
||||
|
||||
from worlds.kh1.Client import launch
|
||||
launch()
|
||||
@@ -266,7 +266,7 @@ def run_gui():
|
||||
if file and component:
|
||||
run_component(component, file)
|
||||
else:
|
||||
logging.warning(f"unable to identify component for {filename}")
|
||||
logging.warning(f"unable to identify component for {file}")
|
||||
|
||||
def _stop(self, *largs):
|
||||
# ran into what appears to be https://groups.google.com/g/kivy-users/c/saWDLoYCSZ4 with PyCharm.
|
||||
|
||||
@@ -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, TOP, LabelFrame, \
|
||||
from tkinter import Tk, Frame, Label, StringVar, Entry, filedialog, messagebox, Button, Radiobutton, LEFT, X, BOTH, 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,7 +29,8 @@ 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):
|
||||
@@ -242,16 +243,17 @@ 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)
|
||||
bottomFrame2 = Frame(adjustWindow, padx=8, pady=2)
|
||||
|
||||
romFrame, romVar = get_rom_frame(adjustWindow)
|
||||
|
||||
romDialogFrame = Frame(adjustWindow)
|
||||
romDialogFrame = Frame(adjustWindow, padx=8, pady=2)
|
||||
baseRomLabel2 = Label(romDialogFrame, text='Rom to adjust')
|
||||
romVar2 = StringVar()
|
||||
romEntry2 = Entry(romDialogFrame, textvariable=romVar2)
|
||||
@@ -261,9 +263,9 @@ def adjustGUI():
|
||||
romVar2.set(rom)
|
||||
|
||||
romSelectButton2 = Button(romDialogFrame, text='Select Rom', command=RomSelect2)
|
||||
romDialogFrame.pack(side=TOP, expand=True, fill=X)
|
||||
baseRomLabel2.pack(side=LEFT)
|
||||
romEntry2.pack(side=LEFT, expand=True, fill=X)
|
||||
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)
|
||||
romSelectButton2.pack(side=LEFT)
|
||||
|
||||
def adjustRom():
|
||||
@@ -331,12 +333,11 @@ 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)
|
||||
rom_options_frame.pack(side=TOP, padx=8, pady=8, fill=BOTH, expand=True)
|
||||
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)
|
||||
@@ -576,7 +577,7 @@ class AttachTooltip(object):
|
||||
def get_rom_frame(parent=None):
|
||||
adjuster_settings = get_adjuster_settings(GAME_ALTTP)
|
||||
|
||||
romFrame = Frame(parent)
|
||||
romFrame = Frame(parent, padx=8, pady=8)
|
||||
baseRomLabel = Label(romFrame, text='LttP Base Rom: ')
|
||||
romVar = StringVar(value=adjuster_settings.baserom)
|
||||
romEntry = Entry(romFrame, textvariable=romVar)
|
||||
@@ -596,20 +597,19 @@ 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=X)
|
||||
romEntry.pack(side=LEFT, expand=True, fill=BOTH, pady=1)
|
||||
romSelectButton.pack(side=LEFT)
|
||||
romFrame.pack(side=TOP, expand=True, fill=X)
|
||||
romFrame.pack(side=TOP, 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")
|
||||
romOptionsFrame.columnconfigure(0, weight=1)
|
||||
romOptionsFrame.columnconfigure(1, weight=1)
|
||||
romOptionsFrame = LabelFrame(parent, text="Rom options", padx=8, pady=8)
|
||||
|
||||
for i in range(5):
|
||||
romOptionsFrame.rowconfigure(i, weight=1)
|
||||
romOptionsFrame.rowconfigure(i, weight=0, pad=4)
|
||||
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)
|
||||
spriteEntry.pack(side=LEFT, expand=True, fill=X)
|
||||
spriteSelectButton.pack(side=LEFT)
|
||||
|
||||
oofDialogFrame = Frame(romOptionsFrame)
|
||||
|
||||
7
Main.py
7
Main.py
@@ -11,7 +11,8 @@ from typing import Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
import worlds
|
||||
from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld, Region
|
||||
from Fill import balance_multiworld_progression, distribute_items_restrictive, distribute_planned, flood_items
|
||||
from Fill import FillError, 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
|
||||
@@ -100,7 +101,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.early_local_items[player].get(item_name, 0)
|
||||
local_early = multiworld.local_early_items[player].get(item_name, 0)
|
||||
if local_early:
|
||||
multiworld.early_items[player][item_name] = max(0, local_early - remaining_count)
|
||||
del local_early
|
||||
@@ -346,7 +347,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 Exception("Game appears as unbeatable. Aborting.")
|
||||
raise FillError("Game appears as unbeatable. Aborting.", multiworld=multiworld)
|
||||
else:
|
||||
logger.warning("Location Accessibility requirements not fulfilled.")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -67,6 +67,21 @@ 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:
|
||||
@@ -551,6 +566,9 @@ 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()
|
||||
|
||||
@@ -991,7 +1009,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[int]:
|
||||
def get_remaining(ctx: Context, team: int, slot: int) -> typing.List[typing.Tuple[int, int]]:
|
||||
return ctx.locations.get_remaining(ctx.location_checks, team, slot)
|
||||
|
||||
|
||||
@@ -1203,6 +1221,10 @@ 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
|
||||
|
||||
@@ -1350,10 +1372,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":
|
||||
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))
|
||||
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))
|
||||
else:
|
||||
self.output("No remaining items found.")
|
||||
return True
|
||||
@@ -1363,10 +1385,10 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
||||
return False
|
||||
else: # is goal
|
||||
if self.ctx.client_game_state[self.client.team, self.client.slot] == ClientStatus.CLIENT_GOAL:
|
||||
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
|
||||
if remaining_item_ids:
|
||||
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[self.client.slot]][item_id]
|
||||
for item_id in remaining_item_ids))
|
||||
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))
|
||||
else:
|
||||
self.output("No remaining items found.")
|
||||
return True
|
||||
@@ -2039,6 +2061,8 @@ 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)
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ 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
|
||||
|
||||
|
||||
@@ -397,12 +398,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[int]:
|
||||
) -> typing.List[typing.Tuple[int, int]]:
|
||||
checked = state[team, slot]
|
||||
player_locations = self[slot]
|
||||
return sorted([player_locations[location_id][0] for
|
||||
location_id in player_locations if
|
||||
location_id not in checked])
|
||||
return sorted([(player_locations[location_id][1], 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
|
||||
|
||||
28
Options.py
28
Options.py
@@ -1518,31 +1518,3 @@ def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], ge
|
||||
|
||||
with open(os.path.join(target_folder, game_name + ".yaml"), "w", encoding="utf-8-sig") as f:
|
||||
f.write(res)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
from worlds.alttp.Options import Logic
|
||||
import argparse
|
||||
|
||||
map_shuffle = Toggle
|
||||
compass_shuffle = Toggle
|
||||
key_shuffle = Toggle
|
||||
big_key_shuffle = Toggle
|
||||
hints = Toggle
|
||||
test = argparse.Namespace()
|
||||
test.logic = Logic.from_text("no_logic")
|
||||
test.map_shuffle = map_shuffle.from_text("ON")
|
||||
test.hints = hints.from_text('OFF')
|
||||
try:
|
||||
test.logic = Logic.from_text("overworld_glitches_typo")
|
||||
except KeyError as e:
|
||||
print(e)
|
||||
try:
|
||||
test.logic_owg = Logic.from_text("owg")
|
||||
except KeyError as e:
|
||||
print(e)
|
||||
if test.map_shuffle:
|
||||
print("map_shuffle is on")
|
||||
print(f"Hints are {bool(test.hints)}")
|
||||
print(test)
|
||||
|
||||
@@ -73,6 +73,9 @@ 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
|
||||
|
||||
10
WebHost.py
10
WebHost.py
@@ -1,3 +1,4 @@
|
||||
import argparse
|
||||
import os
|
||||
import multiprocessing
|
||||
import logging
|
||||
@@ -31,6 +32,15 @@ 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()
|
||||
|
||||
@@ -72,6 +72,14 @@ 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
|
||||
@@ -249,6 +257,7 @@ 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)
|
||||
@@ -279,6 +288,7 @@ 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
|
||||
|
||||
@@ -325,7 +335,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(0)
|
||||
gc.collect()
|
||||
task = asyncio.run_coroutine_threadsafe(start_room(next_room), loop)
|
||||
self._tasks.append(task)
|
||||
task.add_done_callback(self._done)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
flask>=3.0.3
|
||||
werkzeug>=3.0.3
|
||||
pony>=0.7.17
|
||||
werkzeug>=3.0.4
|
||||
pony>=0.7.19
|
||||
waitress>=3.0.0
|
||||
Flask-Caching>=2.3.0
|
||||
Flask-Compress>=1.15
|
||||
Flask-Limiter>=3.7.0
|
||||
Flask-Limiter>=3.8.0
|
||||
bokeh>=3.1.1; python_version <= '3.8'
|
||||
bokeh>=3.4.1; python_version >= '3.9'
|
||||
bokeh>=3.4.3; python_version == '3.9'
|
||||
bokeh>=3.5.2; python_version >= '3.10'
|
||||
markupsafe>=2.1.5
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
id="{{ option_name }}-{{ key }}"
|
||||
name="{{ option_name }}||{{ key }}"
|
||||
value="1"
|
||||
checked="{{ "checked" if key in option.default else "" }}"
|
||||
{{ "checked" if key in option.default }}
|
||||
/>
|
||||
<label for="{{ option_name }}-{{ key }}">
|
||||
{{ key }}
|
||||
|
||||
@@ -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[int]:
|
||||
def get_remaining(self, state: State, team: int, slot: int) -> List[Tuple[int, 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.item for
|
||||
entry in self.entries[start:start+count] if
|
||||
entry.location not in checked])
|
||||
return sorted([(entry.receiver, entry.item) for
|
||||
entry in self.entries[start:start+count] if
|
||||
entry.location not in checked])
|
||||
|
||||
|
||||
@cython.auto_pickle(False)
|
||||
|
||||
@@ -78,6 +78,9 @@
|
||||
# Kirby's Dream Land 3
|
||||
/worlds/kdl3/ @Silvris
|
||||
|
||||
# Kingdom Hearts
|
||||
/worlds/kh1/ @gaithern
|
||||
|
||||
# Kingdom Hearts 2
|
||||
/worlds/kh2/ @JaredWeakStrike
|
||||
|
||||
@@ -103,6 +106,9 @@
|
||||
# Minecraft
|
||||
/worlds/minecraft/ @KonoTyran @espeon65536
|
||||
|
||||
# Mega Man 2
|
||||
/worlds/mm2/ @Silvris
|
||||
|
||||
# MegaMan Battle Network 3
|
||||
/worlds/mmbn3/ @digiholic
|
||||
|
||||
@@ -196,6 +202,9 @@
|
||||
# The Witness
|
||||
/worlds/witness/ @NewSoupVi @blastron
|
||||
|
||||
# Yacht Dice
|
||||
/worlds/yachtdice/ @spinerak
|
||||
|
||||
# Yoshi's Island
|
||||
/worlds/yoshisisland/ @PinkSwitch
|
||||
|
||||
|
||||
@@ -702,14 +702,18 @@ 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 |
|
||||
| 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. |
|
||||
| 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.
|
||||
|
||||
### 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:
|
||||
|
||||
@@ -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 is currently unsupported**
|
||||
* Python 3.12.x is currently the newest supported version
|
||||
* 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)
|
||||
* **Python 3.12 is currently unsupported**
|
||||
* [read above](#General) which versions are supported
|
||||
|
||||
* **Optional**: Download and install Visual Studio Build Tools from
|
||||
[Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/).
|
||||
|
||||
@@ -186,6 +186,11 @@ 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: "";
|
||||
|
||||
2
kvui.py
2
kvui.py
@@ -5,6 +5,8 @@ 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
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
colorama>=0.4.6
|
||||
websockets>=12.0
|
||||
PyYAML>=6.0.1
|
||||
jellyfish>=1.0.3
|
||||
websockets>=13.0.1
|
||||
PyYAML>=6.0.2
|
||||
jellyfish>=1.1.0
|
||||
jinja2>=3.1.4
|
||||
schema>=0.7.7
|
||||
kivy>=2.3.0
|
||||
bsdiff4>=1.2.4
|
||||
platformdirs>=4.2.2
|
||||
certifi>=2024.6.2
|
||||
cython>=3.0.10
|
||||
certifi>=2024.8.30
|
||||
cython>=3.0.11
|
||||
cymem>=2.0.8
|
||||
orjson>=3.10.3
|
||||
typing_extensions>=4.12.1
|
||||
orjson>=3.10.7
|
||||
typing_extensions>=4.12.2
|
||||
|
||||
@@ -23,8 +23,8 @@ class TestBase(unittest.TestCase):
|
||||
state = CollectionState(self.multiworld)
|
||||
for item in items:
|
||||
item.classification = ItemClassification.progression
|
||||
state.collect(item, event=True)
|
||||
state.sweep_for_events()
|
||||
state.collect(item, prevent_sweep=True)
|
||||
state.sweep_for_advancements()
|
||||
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.events:
|
||||
self.multiworld.state.events.remove(item.location)
|
||||
if item.location and item.advancement and item.location in self.multiworld.state.advancements:
|
||||
self.multiworld.state.advancements.remove(item.location)
|
||||
self.multiworld.state.remove(item)
|
||||
|
||||
def can_reach_location(self, location: str) -> bool:
|
||||
@@ -293,13 +293,11 @@ 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():
|
||||
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("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)
|
||||
|
||||
@@ -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_events() # collect everything
|
||||
multiworld.state.sweep_for_advancements() # 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_events()
|
||||
multiworld.state.sweep_for_events()
|
||||
multiworld.state.sweep_for_advancements()
|
||||
multiworld.state.sweep_for_advancements()
|
||||
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")
|
||||
|
||||
|
||||
@@ -14,6 +14,18 @@ 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
|
||||
@@ -37,12 +49,10 @@ 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():
|
||||
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")
|
||||
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:
|
||||
|
||||
@@ -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_locations
|
||||
world.options.accessibility.value = Accessibility.option_full
|
||||
self.assertSteps(gen_steps)
|
||||
with self.subTest("filling multiworld", seed=self.multiworld.seed):
|
||||
distribute_items_restrictive(self.multiworld)
|
||||
@@ -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 in AutoWorldRegister.world_types.values():
|
||||
self.multiworld = setup_multiworld([world, world], ())
|
||||
for world_type in AutoWorldRegister.world_types.values():
|
||||
self.multiworld = setup_multiworld([world_type, world_type], ())
|
||||
for world in self.multiworld.worlds.values():
|
||||
world.options.accessibility.value = Accessibility.option_full
|
||||
self.assertSteps(gen_steps)
|
||||
|
||||
@@ -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), [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])
|
||||
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)])
|
||||
|
||||
def test_location_set_intersection(self) -> None:
|
||||
locations = {10, 11, 12}
|
||||
|
||||
@@ -132,7 +132,8 @@ 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.")
|
||||
"so a Launcher restart is required to use the new installation.\n"
|
||||
"If the Launcher is not open, no action needs to be taken.")
|
||||
world_source = worlds.WorldSource(str(target), is_zip=True)
|
||||
bisect.insort(worlds.world_sources, world_source)
|
||||
world_source.load()
|
||||
|
||||
@@ -968,40 +968,35 @@ 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
|
||||
count = -1
|
||||
step = 0
|
||||
old_name = ""
|
||||
|
||||
thug_location_counts: Dict[str, int] = {}
|
||||
|
||||
for key, data in shop_locations.items():
|
||||
if data.nyakuza_thug == "":
|
||||
thug_name = data.nyakuza_thug
|
||||
if thug_name == "":
|
||||
# Different shop type.
|
||||
continue
|
||||
|
||||
if old_name != "" and old_name == data.nyakuza_thug:
|
||||
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:
|
||||
continue
|
||||
|
||||
try:
|
||||
if world.nyakuza_thug_items[data.nyakuza_thug] <= 0:
|
||||
continue
|
||||
except KeyError:
|
||||
pass
|
||||
location_count = thug_location_counts.setdefault(thug_name, 0)
|
||||
if location_count >= shop_item_count:
|
||||
# Already created all the locations for this thug.
|
||||
continue
|
||||
|
||||
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
|
||||
# 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
|
||||
|
||||
|
||||
def create_events(world: "HatInTimeWorld") -> int:
|
||||
|
||||
@@ -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
|
||||
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)
|
||||
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)
|
||||
|
||||
# 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
|
||||
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)
|
||||
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)
|
||||
|
||||
# Moderate: clear Deep Sea without Ice Hat
|
||||
set_rule(world.multiworld.get_location("Act Completion (Time Rift - Deep Sea)", world.player),
|
||||
@@ -855,6 +855,9 @@ 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:
|
||||
@@ -933,6 +936,9 @@ 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:
|
||||
|
||||
@@ -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_events()
|
||||
all_state_base.sweep_for_advancements()
|
||||
|
||||
# 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.events:
|
||||
all_state_base.events.remove(loc)
|
||||
if loc in all_state_base.advancements:
|
||||
all_state_base.advancements.remove(loc)
|
||||
fill_restrictive(multiworld, all_state_base, locations, in_dungeon_items, lock=True, allow_excluded=True,
|
||||
name="LttP Dungeon Items")
|
||||
|
||||
|
||||
@@ -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_available[player].value
|
||||
treasure_hunt_total = (world.triforce_pieces_required[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
|
||||
|
||||
@@ -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] != 'locations' and not multiworld.key_drop_shuffle[player]:
|
||||
if multiworld.accessibility[player] != 'full' 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] != 'locations':
|
||||
# if world.accessibility[player] != 'full':
|
||||
# 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.
|
||||
|
||||
@@ -356,6 +356,8 @@ 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]
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
## Configuration
|
||||
|
||||
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
|
||||
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
|
||||
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,6 +66,7 @@ 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
|
||||
@@ -132,17 +133,15 @@ 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
|
||||
@@ -162,7 +161,6 @@ 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%
|
||||
|
||||
@@ -54,7 +54,7 @@ class TestDungeon(LTTPTestBase):
|
||||
|
||||
for item in items:
|
||||
item.classification = ItemClassification.progression
|
||||
state.collect(item, event=True) # event=True prevents running sweep_for_events() and picking up
|
||||
state.sweep_for_events() # key drop keys repeatedly
|
||||
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
|
||||
|
||||
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}")
|
||||
|
||||
60
worlds/alttp/test/options/test_dungeon_fill.py
Normal file
60
worlds/alttp/test/options/test_dungeon_fill.py
Normal file
@@ -0,0 +1,60 @@
|
||||
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")
|
||||
@@ -4,7 +4,7 @@ from BaseClasses import Tutorial
|
||||
from ..AutoWorld import WebWorld, World
|
||||
|
||||
class AP_SudokuWebWorld(WebWorld):
|
||||
options_page = "games/Sudoku/info/en"
|
||||
options_page = False
|
||||
theme = 'partyTime'
|
||||
|
||||
setup_en = Tutorial(
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# APSudoku Setup Guide
|
||||
|
||||
## Required Software
|
||||
- [APSudoku](https://github.com/EmilyV99/APSudoku)
|
||||
- Windows (most tested on Win10)
|
||||
- Other platforms might be able to build from source themselves; and may be included in the future.
|
||||
- [APSudoku](https://github.com/APSudoku/APSudoku)
|
||||
|
||||
## General Concept
|
||||
|
||||
@@ -13,25 +11,33 @@ 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/EmilyV99/APSudoku/releases). Download and extract the `APSudoku.zip` file.
|
||||
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.
|
||||
|
||||
## Joining a MultiWorld Game
|
||||
|
||||
1. Run APSudoku.exe
|
||||
2. Under the 'Archipelago' tab at the top-right:
|
||||
- Enter the server url & port number
|
||||
1. Run the APSudoku executable.
|
||||
2. Under `Settings` → `Connection` at the top-right:
|
||||
- Enter the server address and port number
|
||||
- Enter the name of the slot you wish to connect to
|
||||
- Enter the room password (optional)
|
||||
- Select DeathLink related settings (optional)
|
||||
- Press connect
|
||||
3. Go back to the 'Sudoku' tab
|
||||
- Click the various '?' buttons for information on how to play / control
|
||||
4. Choose puzzle difficulty
|
||||
5. Try to solve the Sudoku. Click 'Check' when done.
|
||||
- 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)
|
||||
|
||||
Info:
|
||||
- You can set various settings under `Settings` → `Sudoku`, and can change the colors used under `Settings` → `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 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.
|
||||
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.
|
||||
- On receiving a DeathLink from another player, your puzzle resets.
|
||||
|
||||
19
worlds/blasphemous/ExtractorConfig.json
Normal file
19
worlds/blasphemous/ExtractorConfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"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]"
|
||||
]
|
||||
}
|
||||
@@ -637,52 +637,35 @@ 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",
|
||||
@@ -725,14 +708,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",
|
||||
@@ -746,10 +729,17 @@ 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"]
|
||||
"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"}
|
||||
}
|
||||
|
||||
tears_set: Set[str] = [
|
||||
tears_list: List[str] = [
|
||||
"Tears of Atonement (500)",
|
||||
"Tears of Atonement (625)",
|
||||
"Tears of Atonement (750)",
|
||||
@@ -772,16 +762,16 @@ tears_set: Set[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
@@ -1,4 +1,5 @@
|
||||
from Options import Choice, Toggle, DefaultOnToggle, DeathLink, StartInventoryPool
|
||||
from dataclasses import dataclass
|
||||
from Options import Choice, Toggle, DefaultOnToggle, DeathLink, PerGameCommonOptions, OptionGroup
|
||||
import random
|
||||
|
||||
|
||||
@@ -20,23 +21,30 @@ 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
|
||||
@@ -45,15 +53,18 @@ 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
|
||||
@@ -66,10 +77,15 @@ 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
|
||||
@@ -78,14 +94,18 @@ 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
|
||||
@@ -94,50 +114,68 @@ 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 \"Blasphemous-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 "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 \"Blasphemous-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 "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
|
||||
@@ -146,43 +184,75 @@ class EnemyRando(Choice):
|
||||
|
||||
|
||||
class EnemyGroups(DefaultOnToggle):
|
||||
"""Randomized enemies will chosen from sets of specific groups.
|
||||
"""
|
||||
Randomized enemies will be 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.
|
||||
"""
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
@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
|
||||
])
|
||||
]
|
||||
|
||||
582
worlds/blasphemous/Preprocessor.py
Normal file
582
worlds/blasphemous/Preprocessor.py
Normal file
@@ -0,0 +1,582 @@
|
||||
# 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())
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
from typing import Dict, List, Set, Any
|
||||
from collections import Counter
|
||||
from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification
|
||||
from BaseClasses import Region, Location, Item, Tutorial, ItemClassification
|
||||
from Options import OptionError
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
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 .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 .Vanilla import unrandomized_dict, junk_locations, thorn_set, skill_dict
|
||||
|
||||
from .region_data import regions, locations
|
||||
|
||||
class BlasphemousWeb(WebWorld):
|
||||
theme = "stone"
|
||||
@@ -21,39 +21,33 @@ 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 it's terrible fate in your quest to break
|
||||
in an endless cycle of death and rebirth, and free the world from its terrible fate in your quest to break
|
||||
your eternal damnation!
|
||||
"""
|
||||
|
||||
game: str = "Blasphemous"
|
||||
game = "Blasphemous"
|
||||
web = BlasphemousWeb()
|
||||
|
||||
item_name_to_id = {item["name"]: (base_id + index) for index, item in enumerate(item_table)}
|
||||
location_name_to_id = {loc["name"]: (base_id + index) for index, loc in enumerate(location_table)}
|
||||
location_name_to_game_id = {loc["name"]: loc["game_id"] for loc in location_table}
|
||||
location_name_to_id = {loc: (base_id + index) for index, loc in enumerate(location_names.values())}
|
||||
|
||||
item_name_groups = group_table
|
||||
option_definitions = blasphemous_options
|
||||
options_dataclass = BlasphemousOptions
|
||||
options: BlasphemousOptions
|
||||
|
||||
required_client_version = (0, 4, 2)
|
||||
required_client_version = (0, 4, 7)
|
||||
|
||||
|
||||
def __init__(self, multiworld, player):
|
||||
super(BlasphemousWorld, self).__init__(multiworld, player)
|
||||
self.start_room: str = "D17Z01S01"
|
||||
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)
|
||||
self.disabled_locations: List[str] = []
|
||||
|
||||
|
||||
def create_item(self, name: str) -> "BlasphemousItem":
|
||||
@@ -68,64 +62,56 @@ class BlasphemousWorld(World):
|
||||
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.multiworld.random.choice(tears_set)
|
||||
return self.random.choice(tears_list)
|
||||
|
||||
|
||||
def generate_early(self):
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
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.")
|
||||
|
||||
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 == "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 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.")
|
||||
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.")
|
||||
else:
|
||||
locations: List[int] = [ 0, 1, 2, 3, 4, 5, 6 ]
|
||||
invalid: bool = False
|
||||
|
||||
if world.difficulty[player].value < 2:
|
||||
if self.options.difficulty < 2:
|
||||
locations.remove(6)
|
||||
|
||||
if world.dash_shuffle[player]:
|
||||
if self.options.dash_shuffle:
|
||||
locations.remove(0)
|
||||
if 6 in locations:
|
||||
locations.remove(6)
|
||||
|
||||
if world.wall_climb_shuffle[player]:
|
||||
if self.options.wall_climb_shuffle:
|
||||
locations.remove(3)
|
||||
|
||||
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 self.options.starting_location.value not in locations:
|
||||
self.options.starting_location.value = self.random.choice(locations)
|
||||
|
||||
|
||||
if not world.dash_shuffle[player]:
|
||||
world.push_precollected(self.create_item("Dash Ability"))
|
||||
if not self.options.dash_shuffle:
|
||||
self.multiworld.push_precollected(self.create_item("Dash Ability"))
|
||||
|
||||
if not world.wall_climb_shuffle[player]:
|
||||
world.push_precollected(self.create_item("Wall Climb Ability"))
|
||||
if not self.options.wall_climb_shuffle:
|
||||
self.multiworld.push_precollected(self.create_item("Wall Climb Ability"))
|
||||
|
||||
if world.skip_long_quests[player]:
|
||||
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:
|
||||
for loc in junk_locations:
|
||||
world.exclude_locations[player].value.add(loc)
|
||||
self.options.exclude_locations.value.add(loc)
|
||||
|
||||
start_rooms: Dict[int, str] = {
|
||||
0: "D17Z01S01",
|
||||
@@ -137,13 +123,10 @@ class BlasphemousWorld(World):
|
||||
6: "D20Z02S09"
|
||||
}
|
||||
|
||||
self.start_room = start_rooms[world.starting_location[player].value]
|
||||
self.start_room = start_rooms[self.options.starting_location.value]
|
||||
|
||||
|
||||
def create_items(self):
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
|
||||
removed: int = 0
|
||||
to_remove: List[str] = [
|
||||
"Tears of Atonement (250)",
|
||||
@@ -156,46 +139,46 @@ class BlasphemousWorld(World):
|
||||
skipped_items = []
|
||||
junk: int = 0
|
||||
|
||||
for item, count in world.start_inventory[player].value.items():
|
||||
for item, count in self.options.start_inventory.value.items():
|
||||
for _ in range(count):
|
||||
skipped_items.append(item)
|
||||
junk += 1
|
||||
|
||||
skipped_items.extend(unrandomized_dict.values())
|
||||
|
||||
if world.thorn_shuffle[player] == 2:
|
||||
for i in range(8):
|
||||
if self.options.thorn_shuffle == "vanilla":
|
||||
for _ in range(8):
|
||||
skipped_items.append("Thorn Upgrade")
|
||||
|
||||
if world.dash_shuffle[player]:
|
||||
if self.options.dash_shuffle:
|
||||
skipped_items.append(to_remove[removed])
|
||||
removed += 1
|
||||
elif not world.dash_shuffle[player]:
|
||||
elif not self.options.dash_shuffle:
|
||||
skipped_items.append("Dash Ability")
|
||||
|
||||
if world.wall_climb_shuffle[player]:
|
||||
if self.options.wall_climb_shuffle:
|
||||
skipped_items.append(to_remove[removed])
|
||||
removed += 1
|
||||
elif not world.wall_climb_shuffle[player]:
|
||||
elif not self.options.wall_climb_shuffle:
|
||||
skipped_items.append("Wall Climb Ability")
|
||||
|
||||
if not world.reliquary_shuffle[player]:
|
||||
if not self.options.reliquary_shuffle:
|
||||
skipped_items.extend(reliquary_set)
|
||||
elif world.reliquary_shuffle[player]:
|
||||
for i in range(3):
|
||||
elif self.options.reliquary_shuffle:
|
||||
for _ in range(3):
|
||||
skipped_items.append(to_remove[removed])
|
||||
removed += 1
|
||||
|
||||
if not world.boots_of_pleading[player]:
|
||||
if not self.options.boots_of_pleading:
|
||||
skipped_items.append("Boots of Pleading")
|
||||
|
||||
if not world.purified_hand[player]:
|
||||
if not self.options.purified_hand:
|
||||
skipped_items.append("Purified Hand of the Nun")
|
||||
|
||||
if world.start_wheel[player]:
|
||||
if self.options.start_wheel:
|
||||
skipped_items.append("The Young Mason's Wheel")
|
||||
|
||||
if not world.skill_randomizer[player]:
|
||||
if not self.options.skill_randomizer:
|
||||
skipped_items.extend(skill_dict.values())
|
||||
|
||||
counter = Counter(skipped_items)
|
||||
@@ -208,184 +191,140 @@ class BlasphemousWorld(World):
|
||||
if count <= 0:
|
||||
continue
|
||||
else:
|
||||
for i in range(count):
|
||||
for _ in range(count):
|
||||
pool.append(self.create_item(item["name"]))
|
||||
|
||||
for _ in range(junk):
|
||||
pool.append(self.create_item(self.get_filler_item_name()))
|
||||
|
||||
world.itempool += pool
|
||||
self.multiworld.itempool += pool
|
||||
|
||||
|
||||
def pre_fill(self):
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
|
||||
self.place_items_from_dict(unrandomized_dict)
|
||||
|
||||
if world.thorn_shuffle[player] == 2:
|
||||
if self.options.thorn_shuffle == "vanilla":
|
||||
self.place_items_from_set(thorn_set, "Thorn Upgrade")
|
||||
|
||||
if world.start_wheel[player]:
|
||||
world.get_location("Beginning gift", player)\
|
||||
.place_locked_item(self.create_item("The Young Mason's Wheel"))
|
||||
if self.options.start_wheel:
|
||||
self.get_location("Beginning gift").place_locked_item(self.create_item("The Young Mason's Wheel"))
|
||||
|
||||
if not world.skill_randomizer[player]:
|
||||
if not self.options.skill_randomizer:
|
||||
self.place_items_from_dict(skill_dict)
|
||||
|
||||
if world.thorn_shuffle[player] == 1:
|
||||
world.local_items[player].value.add("Thorn Upgrade")
|
||||
if self.options.thorn_shuffle == "local_only":
|
||||
self.options.local_items.value.add("Thorn Upgrade")
|
||||
|
||||
|
||||
def place_items_from_set(self, location_set: Set[str], name: str):
|
||||
for loc in location_set:
|
||||
self.multiworld.get_location(loc, self.player)\
|
||||
.place_locked_item(self.create_item(name))
|
||||
self.get_location(loc).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.multiworld.get_location(loc, self.player)\
|
||||
.place_locked_item(self.create_item(item))
|
||||
self.get_location(loc).place_locked_item(self.create_item(item))
|
||||
|
||||
|
||||
def create_regions(self) -> None:
|
||||
multiworld = self.multiworld
|
||||
player = self.player
|
||||
world = self.multiworld
|
||||
|
||||
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")
|
||||
|
||||
menu_region = Region("Menu", player, world)
|
||||
misc_region = Region("Misc", player, world)
|
||||
world.regions += [menu_region, misc_region]
|
||||
|
||||
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 = Location(player, "His Holiness Escribar", None, self.get_region("D07Z01S03[W]"))
|
||||
victory.place_locked_item(self.create_event("Victory"))
|
||||
world.get_region("D07Z01S03", player).locations.append(victory)
|
||||
self.get_region("D07Z01S03[W]").locations.append(victory)
|
||||
|
||||
if world.ending[self.player].value == 1:
|
||||
if self.options.ending == "ending_a":
|
||||
set_rule(victory, lambda state: state.has("Thorn Upgrade", player, 8))
|
||||
elif world.ending[self.player].value == 2:
|
||||
elif self.options.ending == "ending_c":
|
||||
set_rule(victory, lambda state: state.has("Thorn Upgrade", player, 8) and
|
||||
state.has("Holy Wound of Abnegation", 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)
|
||||
multiworld.completion_condition[self.player] = lambda state: state.has("Victory", 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 world.thorn_shuffle[player].value == 2:
|
||||
if self.options.thorn_shuffle == "vanilla":
|
||||
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": world.difficulty[player].value,
|
||||
"StartingLocation": world.starting_location[player].value,
|
||||
"LogicDifficulty": self.options.difficulty.value,
|
||||
"StartingLocation": self.options.starting_location.value,
|
||||
"VersionCreated": "AP",
|
||||
|
||||
"UnlockTeleportation": bool(world.prie_dieu_warp[player].value),
|
||||
"AllowHints": bool(world.corpse_hints[player].value),
|
||||
"AllowPenitence": bool(world.penitence[player].value),
|
||||
"UnlockTeleportation": bool(self.options.prie_dieu_warp.value),
|
||||
"AllowHints": bool(self.options.corpse_hints.value),
|
||||
"AllowPenitence": bool(self.options.penitence.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),
|
||||
"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),
|
||||
|
||||
"ShuffleSwordSkills": bool(world.skill_randomizer[player].value),
|
||||
"ShuffleSwordSkills": bool(self.options.wall_climb_shuffle.value),
|
||||
"ShuffleThorns": thorns,
|
||||
"JunkLongQuests": bool(world.skip_long_quests[player].value),
|
||||
"StartWithWheel": bool(world.start_wheel[player].value),
|
||||
"JunkLongQuests": bool(self.options.skip_long_quests.value),
|
||||
"StartWithWheel": bool(self.options.start_wheel.value),
|
||||
|
||||
"EnemyShuffleType": world.enemy_randomizer[player].value,
|
||||
"MaintainClass": bool(world.enemy_groups[player].value),
|
||||
"AreaScaling": bool(world.enemy_scaling[player].value),
|
||||
"EnemyShuffleType": self.options.enemy_randomizer.value,
|
||||
"MaintainClass": bool(self.options.enemy_groups.value),
|
||||
"AreaScaling": bool(self.options.enemy_scaling.value),
|
||||
|
||||
"BossShuffleType": 0,
|
||||
"DoorShuffleType": 0
|
||||
}
|
||||
|
||||
slot_data = {
|
||||
"locations": locations,
|
||||
"locationinfo": [{"gameId": loc, "apId": (base_id + index)} for index, loc in enumerate(location_names)],
|
||||
"doors": doors,
|
||||
"cfg": config,
|
||||
"ending": world.ending[self.player].value,
|
||||
"death_link": bool(world.death_link[self.player].value)
|
||||
"ending": self.options.ending.value,
|
||||
"death_link": bool(self.options.death_link.value)
|
||||
}
|
||||
|
||||
return slot_data
|
||||
|
||||
@@ -1,48 +1,17 @@
|
||||
# Blasphemous Multiworld Setup Guide
|
||||
|
||||
## Useful Links
|
||||
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.
|
||||
|
||||
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)
|
||||
You will need the [Multiworld](https://github.com/BrandenEK/Blasphemous.Randomizer.Multiworld) mod to play an Archipelago randomizer.
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
## Mod Installer (Recommended)
|
||||
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.
|
||||
|
||||
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.**
|
||||
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.
|
||||
48070
worlds/blasphemous/region_data.py
generated
Normal file
48070
worlds/blasphemous/region_data.py
generated
Normal file
File diff suppressed because it is too large
Load Diff
7
worlds/blasphemous/test/__init__.py
Normal file
7
worlds/blasphemous/test/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from test.bases import WorldTestBase
|
||||
from .. import BlasphemousWorld
|
||||
|
||||
|
||||
class BlasphemousTestBase(WorldTestBase):
|
||||
game = "Blasphemous"
|
||||
world: BlasphemousWorld
|
||||
56
worlds/blasphemous/test/test_background_zones.py
Normal file
56
worlds/blasphemous/test/test_background_zones.py
Normal file
@@ -0,0 +1,56 @@
|
||||
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)
|
||||
135
worlds/blasphemous/test/test_starting_locations.py
Normal file
135
worlds/blasphemous/test/test_starting_locations.py
Normal file
@@ -0,0 +1,135 @@
|
||||
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"
|
||||
}
|
||||
@@ -28,7 +28,7 @@ An Example `AP.json` file:
|
||||
|
||||
```
|
||||
{
|
||||
"Url": "archipelago:12345",
|
||||
"Url": "archipelago.gg:12345",
|
||||
"SlotName": "Maddy",
|
||||
"Password": ""
|
||||
}
|
||||
|
||||
@@ -44,15 +44,15 @@ class ChecksFinderWorld(World):
|
||||
self.multiworld.regions += [menu, board]
|
||||
|
||||
def create_items(self):
|
||||
# Generate item pool
|
||||
itempool = []
|
||||
# Generate list of items
|
||||
items_to_create = []
|
||||
# Add the map width and height stuff
|
||||
itempool += ["Map Width"] * 5 # 10 - 5
|
||||
itempool += ["Map Height"] * 5 # 10 - 5
|
||||
items_to_create += ["Map Width"] * 5 # 10 - 5
|
||||
items_to_create += ["Map Height"] * 5 # 10 - 5
|
||||
# Add the map bombs
|
||||
itempool += ["Map Bombs"] * 15 # 20 - 5
|
||||
# Convert itempool into real items
|
||||
itempool = [self.create_item(item) for item in itempool]
|
||||
items_to_create += ["Map Bombs"] * 15 # 20 - 5
|
||||
# Convert list into real items
|
||||
itempool = [self.create_item(item) for item in items_to_create]
|
||||
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
|
||||
@@ -238,15 +238,6 @@ 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):
|
||||
|
||||
@@ -1252,6 +1252,9 @@ 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()
|
||||
|
||||
@@ -1292,10 +1295,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: return
|
||||
if data.ngp and not self.options.enable_ngp: return
|
||||
if data.dlc and not self.options.enable_dlc: continue
|
||||
if data.ngp and not self.options.enable_ngp: continue
|
||||
|
||||
if not self._is_location_available(location): return
|
||||
if not self._is_location_available(location): continue
|
||||
if isinstance(rule, str):
|
||||
assert item_dictionary[rule].classification == ItemClassification.progression
|
||||
rule = lambda state, item=rule: state.has(item, self.player)
|
||||
@@ -1504,16 +1507,19 @@ 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.
|
||||
all_items = {
|
||||
cast(DarkSouls3Item, location.item).data
|
||||
items_by_name = {
|
||||
location.item.name: 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
|
||||
}.union(item_dictionary.values())
|
||||
}
|
||||
for item in item_dictionary.values():
|
||||
if item.name not in items_by_name:
|
||||
items_by_name[item.name] = item
|
||||
|
||||
ap_ids_to_ds3_ids: Dict[str, int] = {}
|
||||
item_counts: Dict[str, int] = {}
|
||||
for item in all_items:
|
||||
for item in items_by_name.values():
|
||||
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
|
||||
|
||||
@@ -1,940 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,577 +0,0 @@
|
||||
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
|
||||
@@ -1,95 +0,0 @@
|
||||
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)
|
||||
@@ -1,25 +1,25 @@
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from BaseClasses import Tutorial, ItemClassification, MultiWorld
|
||||
from BaseClasses import Tutorial, ItemClassification, MultiWorld, CollectionState, Item
|
||||
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
|
||||
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 .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 typing import Dict, TextIO, Optional, List
|
||||
from typing import Dict, TextIO, Optional, List, Any, Mapping, ClassVar, Type
|
||||
import os
|
||||
import math
|
||||
import threading
|
||||
@@ -53,6 +53,7 @@ class KDL3WebWorld(WebWorld):
|
||||
)
|
||||
]
|
||||
options_presets = kdl3_options_presets
|
||||
option_groups = kdl3_option_groups
|
||||
|
||||
|
||||
class KDL3World(World):
|
||||
@@ -61,35 +62,35 @@ class KDL3World(World):
|
||||
"""
|
||||
|
||||
game = "Kirby's Dream Land 3"
|
||||
options_dataclass: typing.ClassVar[typing.Type[PerGameCommonOptions]] = KDL3Options
|
||||
options_dataclass: ClassVar[Type[PerGameCommonOptions]] = KDL3Options
|
||||
options: KDL3Options
|
||||
item_name_to_id = {item: item_table[item].code for item in item_table}
|
||||
item_name_to_id = lookup_item_to_id
|
||||
location_name_to_id = {location_table[location]: location for location in location_table}
|
||||
item_name_groups = item_names
|
||||
web = KDL3WebWorld()
|
||||
settings: typing.ClassVar[KDL3Settings]
|
||||
settings: ClassVar[KDL3Settings]
|
||||
|
||||
def __init__(self, multiworld: MultiWorld, player: int):
|
||||
self.rom_name = None
|
||||
self.rom_name: bytes = bytes()
|
||||
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: Dict[int, int] = dict()
|
||||
self.boss_requirements: List[int] = []
|
||||
self.player_levels = default_levels.copy()
|
||||
self.stage_shuffle_enabled = False
|
||||
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}")
|
||||
self.boss_butch_bosses: List[Optional[bool]] = []
|
||||
self.rooms: List[KDL3Room] = []
|
||||
|
||||
create_regions = create_levels
|
||||
|
||||
def create_item(self, name: str, force_non_progression=False) -> KDL3Item:
|
||||
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:
|
||||
item = item_table[name]
|
||||
classification = ItemClassification.filler
|
||||
if item.progression and not force_non_progression:
|
||||
@@ -99,7 +100,7 @@ class KDL3World(World):
|
||||
classification = ItemClassification.trap
|
||||
return KDL3Item(name, classification, item.code, self.player)
|
||||
|
||||
def get_filler_item_name(self, include_stars=True) -> str:
|
||||
def get_filler_item_name(self, include_stars: bool = True) -> str:
|
||||
if include_stars:
|
||||
return self.random.choices(list(total_filler_weights.keys()),
|
||||
weights=list(total_filler_weights.values()))[0]
|
||||
@@ -112,8 +113,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: typing.List[str],
|
||||
level: int, stage: int):
|
||||
def get_restrictive_copy_ability_placement(self, copy_ability: str, enemies_to_set: List[str],
|
||||
level: int, stage: int) -> Optional[str]:
|
||||
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()
|
||||
@@ -124,6 +125,10 @@ 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
|
||||
@@ -196,21 +201,40 @@ 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) - 9)]
|
||||
for _ in range(len(animal_friend_spawns) - 10)]
|
||||
# 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 = [self.create_item(animal) for animal in animal_pool]
|
||||
allstate = self.multiworld.get_all_state(False)
|
||||
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))
|
||||
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:
|
||||
@@ -225,21 +249,20 @@ class KDL3World(World):
|
||||
remaining_items = len(location_table) - len(itempool)
|
||||
if not self.options.consumables:
|
||||
remaining_items -= len(consumable_locations)
|
||||
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
|
||||
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
|
||||
# ensure at least 1 heart star required per world
|
||||
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
|
||||
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
|
||||
self.required_heart_stars = required_heart_stars
|
||||
# handle boss requirements here
|
||||
requirements = [required_heart_stars]
|
||||
@@ -261,8 +284,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(False))
|
||||
for _ in range(filler_amount + (remaining_items - total_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_trap_item_name())
|
||||
for _ in range(trap_amount)])
|
||||
itempool.extend([self.create_item("Heart Star", True) for _ in range(non_required_heart_stars)])
|
||||
@@ -273,15 +296,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"{LocationName.level_names_inverse[level]} - Stage Completion",
|
||||
f"{location_name.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
|
||||
goal_location = self.multiworld.get_location(LocationName.goals[goal], self.player)
|
||||
goal = self.options.goal.value
|
||||
goal_location = self.multiworld.get_location(location_name.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) \
|
||||
@@ -300,60 +323,65 @@ class KDL3World(World):
|
||||
else:
|
||||
self.boss_butch_bosses = [False for _ in range(6)]
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
rom_path = ""
|
||||
def generate_output(self, output_directory: str) -> None:
|
||||
try:
|
||||
rom = RomData(get_base_rom_path())
|
||||
patch_rom(self, rom)
|
||||
patch = KDL3ProcedurePatch()
|
||||
patch_rom(self, patch)
|
||||
|
||||
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
|
||||
self.rom_name = patch.name
|
||||
|
||||
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()
|
||||
patch.write(os.path.join(output_directory,
|
||||
f"{self.multiworld.get_out_file_name_base(self.player)}{patch.patch_file_ending}"))
|
||||
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):
|
||||
def modify_multidata(self, multidata: Dict[str, Any]) -> None:
|
||||
# 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(bytes(self.rom_name)).decode()
|
||||
new_name = base64.b64encode(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 LocationName.level_names:
|
||||
for stage, i in zip(self.player_levels[LocationName.level_names[level]], range(1, 7)):
|
||||
for level in location_name.level_names:
|
||||
for stage, i in zip(self.player_levels[location_name.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 level in self.player_levels:
|
||||
for lvl in self.player_levels:
|
||||
for stage in range(6):
|
||||
rooms = [room for room in self.rooms if room.level == level and room.stage == stage]
|
||||
rooms = [room for room in self.rooms if room.level == lvl 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])
|
||||
spoiler_handle.write(f"{location_table[self.player_levels[level][stage]].replace(' - Complete','')}"
|
||||
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','')}"
|
||||
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]]):
|
||||
def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]) -> None:
|
||||
if self.stage_shuffle_enabled:
|
||||
regions = {LocationName.level_names[level]: level for level in LocationName.level_names}
|
||||
regions = {location_name.level_names[level]: level for level in location_name.level_names}
|
||||
level_hint_data = {}
|
||||
for level in regions:
|
||||
for stage in range(7):
|
||||
@@ -361,6 +389,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 region.locations if location.address]:
|
||||
for location in [location for location in list(region.get_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
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import struct
|
||||
from .Options import KirbyFlavorPreset, GooeyFlavorPreset
|
||||
from .options import KirbyFlavorPreset, GooeyFlavorPreset
|
||||
from typing import TYPE_CHECKING, Optional, Dict, List, Tuple
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import KDL3World
|
||||
|
||||
kirby_flavor_presets = {
|
||||
1: {
|
||||
@@ -223,6 +227,23 @@ 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 = {
|
||||
@@ -398,21 +419,21 @@ gooey_target_palettes = {
|
||||
}
|
||||
|
||||
|
||||
def get_kirby_palette(world):
|
||||
def get_kirby_palette(world: "KDL3World") -> Optional[Dict[str, str]]:
|
||||
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):
|
||||
def get_gooey_palette(world: "KDL3World") -> Optional[Dict[str, str]]:
|
||||
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, green, blue) -> bytes:
|
||||
def rgb888_to_bgr555(red: int, green: int, blue: int) -> bytes:
|
||||
red = red >> 3
|
||||
green = green >> 3
|
||||
blue = blue >> 3
|
||||
@@ -420,15 +441,15 @@ def rgb888_to_bgr555(red, green, blue) -> bytes:
|
||||
return struct.pack("H", outcol)
|
||||
|
||||
|
||||
def get_palette_bytes(palette, target, offset, factor):
|
||||
def get_palette_bytes(palette: Dict[str, str], target: List[str], offset: int, factor: float) -> bytes:
|
||||
output_data = bytearray()
|
||||
for color in target:
|
||||
hexcol = palette[color]
|
||||
if hexcol.startswith("#"):
|
||||
hexcol = hexcol.replace("#", "")
|
||||
colint = int(hexcol, 16)
|
||||
col = ((colint & 0xFF0000) >> 16, (colint & 0xFF00) >> 8, colint & 0xFF)
|
||||
col: Tuple[int, ...] = ((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 output_data
|
||||
return bytes(output_data)
|
||||
@@ -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 .ClientAddrs 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 .client_addrs import consumable_addrs, star_addrs
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from SNIClient import SNIClientCommandProcessor
|
||||
from SNIClient import SNIClientCommandProcessor, SNIContext
|
||||
|
||||
snes_logger = logging.getLogger("SNES")
|
||||
|
||||
@@ -81,17 +81,16 @@ deathlink_messages = defaultdict(lambda: " was defeated.", {
|
||||
|
||||
|
||||
@mark_raw
|
||||
def cmd_gift(self: "SNIClientCommandProcessor"):
|
||||
def cmd_gift(self: "SNIClientCommandProcessor") -> None:
|
||||
"""Toggles gifting for the current game."""
|
||||
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}")
|
||||
handler = self.ctx.client_handler
|
||||
assert isinstance(handler, KDL3SNIClient)
|
||||
handler.gifting = not handler.gifting
|
||||
self.output(f"Gifting set to {handler.gifting}")
|
||||
async_start(update_object(self.ctx, f"Giftboxes;{self.ctx.team}", {
|
||||
f"{self.ctx.slot}":
|
||||
{
|
||||
"IsOpen": self.ctx.gifting,
|
||||
"IsOpen": handler.gifting,
|
||||
**kdl3_gifting_options
|
||||
}
|
||||
}))
|
||||
@@ -100,16 +99,17 @@ def cmd_gift(self: "SNIClientCommandProcessor"):
|
||||
class KDL3SNIClient(SNIClient):
|
||||
game = "Kirby's Dream Land 3"
|
||||
patch_suffix = ".apkdl3"
|
||||
levels = None
|
||||
consumables = None
|
||||
stars = None
|
||||
item_queue: typing.List = []
|
||||
initialize_gifting = False
|
||||
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
|
||||
giftbox_key: str = ""
|
||||
motherbox_key: str = ""
|
||||
client_random: random.Random = random.Random()
|
||||
|
||||
async def deathlink_kill_player(self, ctx) -> None:
|
||||
async def deathlink_kill_player(self, ctx: "SNIContext") -> None:
|
||||
from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read
|
||||
game_state = await snes_read(ctx, 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) -> bool:
|
||||
async def validate_rom(self, ctx: "SNIContext") -> 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 = 0b111 # always remote items
|
||||
ctx.items_handling = 0b101 # default local items with remote start inventory
|
||||
ctx.allow_collect = True
|
||||
if "gift" not in ctx.command_processor.commands:
|
||||
ctx.command_processor.commands["gift"] = cmd_gift
|
||||
@@ -149,9 +149,10 @@ 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, in_stage):
|
||||
async def pop_item(self, ctx: "SNIContext", in_stage: bool) -> None:
|
||||
from SNIClient import snes_buffered_write, snes_read
|
||||
if len(self.item_queue) > 0:
|
||||
item = self.item_queue.pop()
|
||||
@@ -168,8 +169,8 @@ class KDL3SNIClient(SNIClient):
|
||||
else:
|
||||
self.item_queue.append(item) # no more slots, get it next go around
|
||||
|
||||
async def pop_gift(self, ctx):
|
||||
if ctx.stored_data[self.giftbox_key]:
|
||||
async def pop_gift(self, ctx: "SNIContext") -> None:
|
||||
if self.giftbox_key in ctx.stored_data and 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)
|
||||
@@ -214,7 +215,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), {0: 75, 1: 25})[0]
|
||||
quality = self.client_random.choices(range(0, 2), [75, 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]))
|
||||
@@ -224,7 +225,8 @@ 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, gift):
|
||||
async def pick_gift_recipient(self, ctx: "SNIContext", gift: int) -> None:
|
||||
assert ctx.slot
|
||||
if gift != 4:
|
||||
gift_base = kdl3_gifts[gift]
|
||||
else:
|
||||
@@ -238,7 +240,7 @@ class KDL3SNIClient(SNIClient):
|
||||
if desire > most_applicable:
|
||||
most_applicable = desire
|
||||
most_applicable_slot = int(slot)
|
||||
elif most_applicable_slot == ctx.slot and info["AcceptsAnyGift"]:
|
||||
elif most_applicable_slot != ctx.slot and most_applicable == -1 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)
|
||||
@@ -257,7 +259,7 @@ class KDL3SNIClient(SNIClient):
|
||||
item_uuid: item,
|
||||
})
|
||||
|
||||
async def game_watcher(self, ctx) -> None:
|
||||
async def game_watcher(self, ctx: "SNIContext") -> None:
|
||||
try:
|
||||
from SNIClient import snes_buffered_write, snes_flush_writes, snes_read
|
||||
rom = await snes_read(ctx, KDL3_ROMNAME, 0x15)
|
||||
@@ -278,11 +280,12 @@ 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 self.levels is None:
|
||||
if not self.levels:
|
||||
self.levels = dict()
|
||||
for i in range(5):
|
||||
level_data = await snes_read(ctx, KDL3_LEVEL_ADDR + (14 * i), 14)
|
||||
self.levels[i] = unpack("HHHHHHH", level_data)
|
||||
self.levels[i] = [int.from_bytes(level_data[idx:idx+1], "little")
|
||||
for idx in range(0, len(level_data), 2)]
|
||||
self.levels[5] = [0x0205, # Hyper Zone
|
||||
0, # MG-5, can't send from here
|
||||
0x0300, # Boss Butch
|
||||
@@ -371,7 +374,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 + 1
|
||||
loc_id = 0x770000 + i
|
||||
if stages[i] == 1 and loc_id not in ctx.checked_locations:
|
||||
new_checks.append(loc_id)
|
||||
elif loc_id in ctx.checked_locations:
|
||||
@@ -381,8 +384,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(1, 7):
|
||||
level_ind = start_ind + j - 1
|
||||
for j in range(6):
|
||||
level_ind = start_ind + j
|
||||
loc_id = 0x770100 + (6 * i) + j
|
||||
if heart_stars[level_ind] and loc_id not in ctx.checked_locations:
|
||||
new_checks.append(loc_id)
|
||||
@@ -401,6 +404,9 @@ 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)
|
||||
@@ -408,7 +414,7 @@ class KDL3SNIClient(SNIClient):
|
||||
|
||||
# boss status
|
||||
boss_flag_bytes = await snes_read(ctx, KDL3_BOSS_STATUS, 2)
|
||||
boss_flag = unpack("H", boss_flag_bytes)[0]
|
||||
boss_flag = int.from_bytes(boss_flag_bytes, "little")
|
||||
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)
|
||||
Binary file not shown.
@@ -1,8 +1,11 @@
|
||||
# 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, key: str, value: typing.Dict):
|
||||
|
||||
async def update_object(ctx: "SNIContext", key: str, value: typing.Dict[str, typing.Any]) -> None:
|
||||
await ctx.send_msgs([
|
||||
{
|
||||
"cmd": "Set",
|
||||
@@ -16,7 +19,7 @@ async def update_object(ctx, key: str, value: typing.Dict):
|
||||
])
|
||||
|
||||
|
||||
async def pop_object(ctx, key: str, value: str):
|
||||
async def pop_object(ctx: "SNIContext", key: str, value: str) -> None:
|
||||
await ctx.send_msgs([
|
||||
{
|
||||
"cmd": "Set",
|
||||
@@ -30,14 +33,14 @@ async def pop_object(ctx, key: str, value: str):
|
||||
])
|
||||
|
||||
|
||||
async def initialize_giftboxes(ctx, giftbox_key: str, motherbox_key: str, is_open: bool):
|
||||
async def initialize_giftboxes(ctx: "SNIContext", giftbox_key: str, motherbox_key: str, is_open: bool) -> None:
|
||||
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.gifting = is_open
|
||||
{
|
||||
"IsOpen": is_open,
|
||||
**kdl3_gifting_options
|
||||
}})
|
||||
ctx.client_handler.gifting = is_open
|
||||
|
||||
|
||||
kdl3_gifting_options = {
|
||||
@@ -77,9 +77,9 @@ filler_item_weights = {
|
||||
}
|
||||
|
||||
star_item_weights = {
|
||||
"Little Star": 4,
|
||||
"Medium Star": 2,
|
||||
"Big Star": 1
|
||||
"Little Star": 16,
|
||||
"Medium Star": 8,
|
||||
"Big Star": 4
|
||||
}
|
||||
|
||||
total_filler_weights = {
|
||||
@@ -102,4 +102,4 @@ item_names = {
|
||||
"Animal Friend": set(animal_friend_table),
|
||||
}
|
||||
|
||||
lookup_name_to_id: typing.Dict[str, int] = {item_name: data.code for item_name, data in item_table.items() if data.code}
|
||||
lookup_item_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
940
worlds/kdl3/locations.py
Normal file
@@ -0,0 +1,940 @@
|
||||
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
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
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
|
||||
@@ -197,3 +199,12 @@ 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]
|
||||
]
|
||||
@@ -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)
|
||||
(["Burning Ability", "Stone Ability"], ["Rocky", "Sparky", "Babut", "Squishy", ]), # Ribbon Field 5 - 7
|
||||
(["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)']),
|
||||
@@ -1,13 +1,21 @@
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
from Options import DeathLink, Choice, Toggle, OptionDict, Range, PlandoBosses, DefaultOnToggle, \
|
||||
PerGameCommonOptions, PlandoConnections
|
||||
from .Names import LocationName
|
||||
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"
|
||||
|
||||
|
||||
class KDL3PlandoConnections(PlandoConnections):
|
||||
entrances = exits = {f"{i} {j}" for i in LocationName.level_names for j in range(1, 7)}
|
||||
entrances = exits = {f"{i} {j}" for i in location_name.level_names for j in range(1, 7)}
|
||||
|
||||
|
||||
class Goal(Choice):
|
||||
@@ -30,6 +38,7 @@ 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
|
||||
@@ -40,13 +49,14 @@ class GoalSpeed(Choice):
|
||||
option_fast = 1
|
||||
|
||||
|
||||
class TotalHeartStars(Range):
|
||||
class MaxHeartStars(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 = 50 # 30 default locations + 30 stage clears + 5 bosses - 14 progression items = 51, so round down
|
||||
range_end = 99 # previously set to 50, set to highest it can be should there be less locations than heart stars
|
||||
default = 30
|
||||
|
||||
|
||||
@@ -84,9 +94,9 @@ class BossShuffle(PlandoBosses):
|
||||
Singularity: All (non-Zero) bosses will be replaced with a single boss
|
||||
Supports plando placement.
|
||||
"""
|
||||
bosses = frozenset(LocationName.boss_names.keys())
|
||||
bosses = frozenset(location_name.boss_names.keys())
|
||||
|
||||
locations = frozenset(LocationName.level_names.keys())
|
||||
locations = frozenset(location_name.level_names.keys())
|
||||
|
||||
duplicate_bosses = True
|
||||
|
||||
@@ -278,7 +288,8 @@ class KirbyFlavorPreset(Choice):
|
||||
option_orange = 11
|
||||
option_lime = 12
|
||||
option_lavender = 13
|
||||
option_custom = 14
|
||||
option_miku = 14
|
||||
option_custom = 15
|
||||
default = 0
|
||||
|
||||
@classmethod
|
||||
@@ -296,6 +307,7 @@ 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",
|
||||
@@ -313,6 +325,7 @@ class KirbyFlavor(OptionDict):
|
||||
"14": "F8F8F8",
|
||||
"15": "B03830",
|
||||
}
|
||||
visibility = Visibility.template | Visibility.spoiler # likely never supported on guis
|
||||
|
||||
|
||||
class GooeyFlavorPreset(Choice):
|
||||
@@ -352,6 +365,7 @@ 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",
|
||||
@@ -363,6 +377,7 @@ class GooeyFlavor(OptionDict):
|
||||
"8": "D0C0C0",
|
||||
"9": "F8F8F8",
|
||||
}
|
||||
visibility = Visibility.template | Visibility.spoiler # likely never supported on guis
|
||||
|
||||
|
||||
class MusicShuffle(Choice):
|
||||
@@ -402,14 +417,27 @@ 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):
|
||||
class KDL3Options(PerGameCommonOptions, DeathLinkMixin):
|
||||
remote_items: RemoteItems
|
||||
plando_connections: KDL3PlandoConnections
|
||||
death_link: DeathLink
|
||||
game_language: GameLanguage
|
||||
goal: Goal
|
||||
goal_speed: GoalSpeed
|
||||
total_heart_stars: TotalHeartStars
|
||||
max_heart_stars: MaxHeartStars
|
||||
heart_stars_required: HeartStarsRequired
|
||||
filler_percentage: FillerPercentage
|
||||
trap_percentage: TrapPercentage
|
||||
@@ -435,3 +463,17 @@ class KDL3Options(PerGameCommonOptions):
|
||||
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, ]),
|
||||
]
|
||||
@@ -25,6 +25,7 @@ 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",
|
||||
@@ -1,60 +1,62 @@
|
||||
import orjson
|
||||
import os
|
||||
from pkgutil import get_data
|
||||
from copy import deepcopy
|
||||
|
||||
from typing import TYPE_CHECKING, List, Dict, Optional, Union
|
||||
from BaseClasses import Region
|
||||
from typing import TYPE_CHECKING, List, Dict, Optional, Union, Callable
|
||||
from BaseClasses import Region, CollectionState
|
||||
from worlds.generic.Rules import add_item_rule
|
||||
from .Locations import KDL3Location
|
||||
from .Names import LocationName
|
||||
from .Options import BossShuffle
|
||||
from .Room import KDL3Room
|
||||
from .locations import KDL3Location
|
||||
from .names import location_name
|
||||
from .options import BossShuffle
|
||||
from .room import KDL3Room
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import KDL3World
|
||||
|
||||
default_levels = {
|
||||
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],
|
||||
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],
|
||||
}
|
||||
|
||||
first_stage_blacklist = {
|
||||
# We want to confirm that the first stage can be completed without any items
|
||||
0x77000B, # 2-5 needs Kine
|
||||
0x770011, # 3-5 needs Cutter
|
||||
0x77001C, # 5-4 needs Burning
|
||||
0x77000A, # 2-5 needs Kine
|
||||
0x770010, # 3-5 needs Cutter
|
||||
0x77001B, # 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,
|
||||
0x770008,
|
||||
0x770013,
|
||||
0x77001E,
|
||||
0x770012,
|
||||
0x77001D,
|
||||
|
||||
}
|
||||
|
||||
|
||||
def generate_valid_level(world: "KDL3World", level: int, stage: int,
|
||||
possible_stages: List[int], placed_stages: List[int]):
|
||||
possible_stages: List[int], placed_stages: List[Optional[int]]) -> 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]):
|
||||
level_names = {LocationName.level_names[level]: level for level in LocationName.level_names}
|
||||
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}
|
||||
room_data = orjson.loads(get_data(__name__, os.path.join("data", "Rooms.json")))
|
||||
rooms: Dict[str, KDL3Room] = dict()
|
||||
for room_entry in room_data:
|
||||
@@ -63,7 +65,7 @@ def generate_rooms(world: "KDL3World", level_regions: Dict[int, Region]):
|
||||
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)},
|
||||
@@ -83,8 +85,8 @@ def generate_rooms(world: "KDL3World", level_regions: Dict[int, Region]):
|
||||
if room.stage == 7:
|
||||
first_rooms[0x770200 + room.level - 1] = room
|
||||
else:
|
||||
first_rooms[0x770000 + ((room.level - 1) * 6) + room.stage] = room
|
||||
exits = dict()
|
||||
first_rooms[0x770000 + ((room.level - 1) * 6) + room.stage - 1] = room
|
||||
exits: Dict[str, Callable[[CollectionState], bool]] = 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"])
|
||||
@@ -115,50 +117,54 @@ def generate_rooms(world: "KDL3World", level_regions: Dict[int, Region]):
|
||||
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", 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,
|
||||
}
|
||||
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,
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
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
|
||||
try:
|
||||
for level in range(1, 6):
|
||||
for stage in range(6):
|
||||
# Randomize bosses separately
|
||||
if levels[level][stage] is None:
|
||||
stage_candidates = [candidate for candidate in possible_stages
|
||||
if (enforce_world and candidate in default_levels[level])
|
||||
or (enforce_pattern and ((candidate - 1) & 0x00FFFF) % 6 == stage)
|
||||
or (enforce_pattern == enforce_world)
|
||||
if (shuffle_mode == 1 and candidate in default_levels[level])
|
||||
or (shuffle_mode == 2 and (candidate & 0x00FFFF) % 6 == stage)
|
||||
or (shuffle_mode == 3)
|
||||
]
|
||||
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
|
||||
except Exception:
|
||||
raise Exception(f"Failed to find valid stage for {level}-{stage}. Remaining Stages:{possible_stages}")
|
||||
|
||||
else:
|
||||
levels = deepcopy(default_levels)
|
||||
for level in levels:
|
||||
levels[level][6] = None
|
||||
# now handle bosses
|
||||
boss_shuffle: Union[int, str] = world.options.boss_shuffle.value
|
||||
plando_bosses = []
|
||||
@@ -168,17 +174,17 @@ def generate_valid_levels(world: "KDL3World", enforce_world: bool, enforce_patte
|
||||
boss_shuffle = BossShuffle.options[options.pop()]
|
||||
for option in options:
|
||||
if "-" in option:
|
||||
loc, boss = option.split("-")
|
||||
loc, plando_boss = option.split("-")
|
||||
loc = loc.title()
|
||||
boss = boss.title()
|
||||
levels[LocationName.level_names[loc]][6] = LocationName.boss_names[boss]
|
||||
plando_bosses.append(LocationName.boss_names[boss])
|
||||
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])
|
||||
else:
|
||||
option = option.title()
|
||||
for level in levels:
|
||||
if levels[level][6] is None:
|
||||
levels[level][6] = LocationName.boss_names[option]
|
||||
plando_bosses.append(LocationName.boss_names[option])
|
||||
levels[level][6] = location_name.boss_names[option]
|
||||
plando_bosses.append(location_name.boss_names[option])
|
||||
|
||||
if boss_shuffle > 0:
|
||||
if boss_shuffle == BossShuffle.option_full:
|
||||
@@ -223,15 +229,14 @@ def create_levels(world: "KDL3World") -> None:
|
||||
5: level5,
|
||||
}
|
||||
level_shuffle = world.options.stage_shuffle.value
|
||||
if level_shuffle != 0:
|
||||
world.player_levels = generate_valid_levels(
|
||||
world,
|
||||
level_shuffle == 1,
|
||||
level_shuffle == 2)
|
||||
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)
|
||||
|
||||
generate_rooms(world, levels)
|
||||
|
||||
level6.add_locations({LocationName.goals[world.options.goal]: None}, KDL3Location)
|
||||
level6.add_locations({location_name.goals[world.options.goal.value]: None}, KDL3Location)
|
||||
|
||||
menu.connect(level1, "Start Game")
|
||||
level1.connect(level2, "To Level 2")
|
||||
602
worlds/kdl3/rom.py
Normal file
602
worlds/kdl3/rom.py
Normal file
@@ -0,0 +1,602 @@
|
||||
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
|
||||
133
worlds/kdl3/room.py
Normal file
133
worlds/kdl3/room.py
Normal file
@@ -0,0 +1,133 @@
|
||||
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"))
|
||||
@@ -1,7 +1,7 @@
|
||||
from worlds.generic.Rules import set_rule, add_rule
|
||||
from .Names import LocationName, EnemyAbilities
|
||||
from .Locations import location_table
|
||||
from .Options import GoalSpeed
|
||||
from .names import location_name, enemy_abilities, animal_friend_spawns
|
||||
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.Dict[int, int]]):
|
||||
ow_boss_req: int, player_levels: typing.Dict[int, typing.List[int]]) -> bool:
|
||||
if open_world:
|
||||
return state.has(f"{LocationName.level_names_inverse[level]} - Stage Completion", player, ow_boss_req)
|
||||
return state.has(f"{location_name.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]):
|
||||
def can_assemble_rob(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]) -> bool:
|
||||
# check animal requirements
|
||||
if not (can_reach_coo(state, player) and can_reach_kine(state, player)):
|
||||
return False
|
||||
for abilities, bukisets in EnemyAbilities.enemy_restrictive[1:5]:
|
||||
for abilities, bukisets in enemy_abilities.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]):
|
||||
def can_fix_angel_wings(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]) -> bool:
|
||||
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(LocationName.grass_land_muchi, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.grass_land_muchi, world.player),
|
||||
lambda state: can_reach_chuchu(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.grass_land_chao, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.grass_land_chao, world.player),
|
||||
lambda state: can_reach_stone(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.grass_land_mine, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.grass_land_mine, world.player),
|
||||
lambda state: can_reach_kine(state, world.player))
|
||||
|
||||
# Level 2
|
||||
set_rule(world.multiworld.get_location(LocationName.ripple_field_5, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.ripple_field_5, world.player),
|
||||
lambda state: can_reach_kine(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.ripple_field_kamuribana, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.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(LocationName.ripple_field_bakasa, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.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(LocationName.ripple_field_toad, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.ripple_field_toad, world.player),
|
||||
lambda state: can_reach_needle(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.ripple_field_mama_pitch, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.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(LocationName.sand_canyon_5, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.sand_canyon_5, world.player),
|
||||
lambda state: can_reach_cutter(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.sand_canyon_auntie, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.sand_canyon_auntie, world.player),
|
||||
lambda state: can_reach_clean(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.sand_canyon_nyupun, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.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(LocationName.sand_canyon_rob, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.sand_canyon_rob, world.player),
|
||||
lambda state: can_assemble_rob(state, world.player, world.copy_abilities)
|
||||
)
|
||||
|
||||
# Level 4
|
||||
set_rule(world.multiworld.get_location(LocationName.cloudy_park_hibanamodoki, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.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(LocationName.cloudy_park_piyokeko, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.cloudy_park_piyokeko, world.player),
|
||||
lambda state: can_reach_needle(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.cloudy_park_mikarin, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.cloudy_park_mikarin, world.player),
|
||||
lambda state: can_reach_coo(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.cloudy_park_pick, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.cloudy_park_pick, world.player),
|
||||
lambda state: can_reach_rick(state, world.player))
|
||||
|
||||
# Level 5
|
||||
set_rule(world.multiworld.get_location(LocationName.iceberg_4, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.iceberg_4, world.player),
|
||||
lambda state: can_reach_burning(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.iceberg_kogoesou, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.iceberg_kogoesou, world.player),
|
||||
lambda state: can_reach_burning(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.iceberg_samus, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.iceberg_samus, world.player),
|
||||
lambda state: can_reach_ice(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.iceberg_name, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.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(LocationName.iceberg_shiro, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.iceberg_shiro, world.player),
|
||||
lambda state: can_reach_nago(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.iceberg_angel, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.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(LocationName.grass_land_1_u1, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.grass_land_1_u1, world.player),
|
||||
lambda state: can_reach_parasol(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.grass_land_1_m1, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.grass_land_1_m1, world.player),
|
||||
lambda state: can_reach_spark(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.grass_land_2_u1, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.grass_land_2_u1, world.player),
|
||||
lambda state: can_reach_needle(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.ripple_field_2_u1, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.ripple_field_2_u1, world.player),
|
||||
lambda state: can_reach_kine(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.ripple_field_2_m1, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.ripple_field_2_m1, world.player),
|
||||
lambda state: can_reach_kine(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.ripple_field_3_u1, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.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(LocationName.ripple_field_4_u1, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.ripple_field_4_u1, world.player),
|
||||
lambda state: can_reach_stone(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.ripple_field_4_m2, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.ripple_field_4_m2, world.player),
|
||||
lambda state: can_reach_stone(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.ripple_field_5_m1, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.ripple_field_5_m1, world.player),
|
||||
lambda state: can_reach_kine(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.ripple_field_5_u1, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.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(LocationName.ripple_field_5_m2, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.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(LocationName.sand_canyon_4_u1, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.sand_canyon_4_u1, world.player),
|
||||
lambda state: can_reach_clean(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.sand_canyon_4_m2, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.sand_canyon_4_m2, world.player),
|
||||
lambda state: can_reach_needle(state, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u2, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.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(LocationName.sand_canyon_5_u3, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.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(LocationName.sand_canyon_5_u4, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.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(LocationName.cloudy_park_6_u1, world.player),
|
||||
set_rule(world.multiworld.get_location(location_name.cloudy_park_6_u1, world.player),
|
||||
lambda state: can_reach_cutter(state, world.player))
|
||||
|
||||
if world.options.starsanity:
|
||||
@@ -274,50 +274,57 @@ 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(EnemyAbilities.Ripple_Field_2_E3, world.player),
|
||||
set_rule(world.multiworld.get_location(enemy_abilities.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(EnemyAbilities.Ripple_Field_3_E6, world.player),
|
||||
set_rule(world.multiworld.get_location(enemy_abilities.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(EnemyAbilities.Ripple_Field_4_E5, world.player),
|
||||
set_rule(world.multiworld.get_location(enemy_abilities.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(EnemyAbilities.Ripple_Field_4_E7, world.player),
|
||||
set_rule(world.multiworld.get_location(enemy_abilities.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(EnemyAbilities.Ripple_Field_4_E8, world.player),
|
||||
set_rule(world.multiworld.get_location(enemy_abilities.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(EnemyAbilities.Ripple_Field_5_E1, world.player),
|
||||
set_rule(world.multiworld.get_location(enemy_abilities.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(EnemyAbilities.Ripple_Field_5_E2, world.player),
|
||||
set_rule(world.multiworld.get_location(enemy_abilities.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(EnemyAbilities.Ripple_Field_5_E3, world.player),
|
||||
set_rule(world.multiworld.get_location(enemy_abilities.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(EnemyAbilities.Ripple_Field_5_E4, world.player),
|
||||
set_rule(world.multiworld.get_location(enemy_abilities.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(EnemyAbilities.Sand_Canyon_4_E7, world.player),
|
||||
set_rule(world.multiworld.get_location(enemy_abilities.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(EnemyAbilities.Sand_Canyon_4_E8, world.player),
|
||||
set_rule(world.multiworld.get_location(enemy_abilities.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(EnemyAbilities.Sand_Canyon_4_E9, world.player),
|
||||
set_rule(world.multiworld.get_location(enemy_abilities.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(EnemyAbilities.Sand_Canyon_4_E10, world.player),
|
||||
set_rule(world.multiworld.get_location(enemy_abilities.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"],
|
||||
[LocationName.grass_land_whispy, LocationName.ripple_field_acro,
|
||||
LocationName.sand_canyon_poncon, LocationName.cloudy_park_ado,
|
||||
LocationName.iceberg_dedede],
|
||||
[location_name.grass_land_whispy, location_name.ripple_field_acro,
|
||||
location_name.sand_canyon_poncon, location_name.cloudy_park_ado,
|
||||
location_name.iceberg_dedede],
|
||||
range(1, 6)):
|
||||
set_rule(world.multiworld.get_location(boss_flag, world.player),
|
||||
lambda state, i=i: (state.has("Heart Star", world.player, world.boss_requirements[i - 1])
|
||||
and can_reach_boss(state, world.player, i,
|
||||
lambda state, x=i: (state.has("Heart Star", world.player, world.boss_requirements[x - 1])
|
||||
and can_reach_boss(state, world.player, x,
|
||||
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, i=i: (state.has("Heart Star", world.player, world.boss_requirements[i - 1])
|
||||
and can_reach_boss(state, world.player, i,
|
||||
lambda state, x=i: (state.has("Heart Star", world.player, world.boss_requirements[x - 1])
|
||||
and can_reach_boss(state, world.player, x,
|
||||
world.options.open_world.value,
|
||||
world.options.ow_boss_requirement.value,
|
||||
world.player_levels)))
|
||||
@@ -327,12 +334,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, i=level: state.has(f"Level {i - 1} Boss Defeated", world.player))
|
||||
lambda state, x=level: state.has(f"Level {x - 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, i=level: state.has(f"Level {i - 1} Boss Purified", world.player))
|
||||
lambda state, x=level: state.has(f"Level {x - 1} Boss Purified", world.player))
|
||||
|
||||
if world.options.goal_speed == GoalSpeed.option_normal:
|
||||
add_rule(world.multiworld.get_entrance("To Level 6", world.player),
|
||||
@@ -58,6 +58,10 @@ org $01AFC8
|
||||
org $01B013
|
||||
SEC ; Remove Dedede Bad Ending
|
||||
|
||||
org $01B050
|
||||
JSL HookBossPurify
|
||||
NOP
|
||||
|
||||
org $02B7B0 ; Zero unlock
|
||||
LDA $80A0
|
||||
CMP #$0001
|
||||
@@ -160,7 +164,6 @@ CopyAbilityAnimalOverride:
|
||||
STA $39DF, X
|
||||
RTL
|
||||
|
||||
org $079A00
|
||||
HeartStarCheck:
|
||||
TXA
|
||||
CMP #$0000 ; is this level 1
|
||||
@@ -201,7 +204,6 @@ HeartStarCheck:
|
||||
SEC
|
||||
RTL
|
||||
|
||||
org $079A80
|
||||
OpenWorldUnlock:
|
||||
PHX
|
||||
LDX $900E ; Are we on open world?
|
||||
@@ -224,7 +226,6 @@ OpenWorldUnlock:
|
||||
PLX
|
||||
RTL
|
||||
|
||||
org $079B00
|
||||
MainLoopHook:
|
||||
STA $D4
|
||||
INC $3524
|
||||
@@ -239,16 +240,18 @@ 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
|
||||
STZ $8080
|
||||
DEC $8080
|
||||
.Slowness:
|
||||
LDA $8082 ; slowness
|
||||
BEQ .Eject ; are we under the effects of a slowness trap
|
||||
DEC
|
||||
STA $8082 ; dec by 1 each frame
|
||||
DEC $8082 ; dec by 1 each frame
|
||||
.Eject:
|
||||
PHX
|
||||
PHY
|
||||
@@ -258,14 +261,13 @@ MainLoopHook:
|
||||
BEQ .PullVars ; branch if we haven't received eject
|
||||
LDA #$2000 ; select button press
|
||||
STA $60C1 ; write to controller mirror
|
||||
STZ $8084
|
||||
DEC $8084
|
||||
.PullVars:
|
||||
PLY
|
||||
PLX
|
||||
.Return:
|
||||
RTL
|
||||
|
||||
org $079B80
|
||||
HeartStarGraphicFix:
|
||||
LDA #$0000
|
||||
PHX
|
||||
@@ -288,7 +290,7 @@ HeartStarGraphicFix:
|
||||
ASL
|
||||
TAX
|
||||
LDA $07D080, X ; table of original stage number
|
||||
CMP #$0003 ; is the current stage a minigame stage?
|
||||
CMP #$0002 ; is the current stage a minigame stage?
|
||||
BEQ .ReturnTrue ; branch if so
|
||||
CLC
|
||||
BRA .Return
|
||||
@@ -299,7 +301,6 @@ HeartStarGraphicFix:
|
||||
PLX
|
||||
RTL
|
||||
|
||||
org $079BF0
|
||||
ParseItemQueue:
|
||||
; Local item queue parsing
|
||||
NOP
|
||||
@@ -336,8 +337,6 @@ ParseItemQueue:
|
||||
AND #$000F
|
||||
ASL
|
||||
TAY
|
||||
LDA $8080,Y
|
||||
BNE .LoopCheck
|
||||
JSL .ApplyNegative
|
||||
RTL
|
||||
.ApplyAbility:
|
||||
@@ -418,35 +417,73 @@ ParseItemQueue:
|
||||
CPY #$0005
|
||||
BCS .PlayNone
|
||||
LDA $8080,Y
|
||||
BNE .Return
|
||||
CPY #$0002
|
||||
BNE .Increment
|
||||
CLC
|
||||
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
|
||||
@@ -479,7 +516,6 @@ WriteBWRAM:
|
||||
.Return:
|
||||
RTL
|
||||
|
||||
org $079E80
|
||||
ConsumableSet:
|
||||
PHA
|
||||
PHX
|
||||
@@ -507,7 +543,6 @@ ConsumableSet:
|
||||
ASL
|
||||
TAX
|
||||
LDA $07D020, X ; current stage
|
||||
DEC
|
||||
ASL #6
|
||||
TAX
|
||||
PLA
|
||||
@@ -519,8 +554,16 @@ 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
|
||||
@@ -528,7 +571,6 @@ ConsumableSet:
|
||||
AND #$00FF
|
||||
RTL
|
||||
|
||||
org $079F00
|
||||
NormalGoalSet:
|
||||
PHX
|
||||
LDA $07D012
|
||||
@@ -549,7 +591,6 @@ NormalGoalSet:
|
||||
STA $5AC1 ; cutscene
|
||||
RTL
|
||||
|
||||
org $079F80
|
||||
FinalIcebergFix:
|
||||
PHX
|
||||
PHY
|
||||
@@ -572,7 +613,7 @@ FinalIcebergFix:
|
||||
ASL
|
||||
TAX
|
||||
LDA $07D020, X
|
||||
CMP #$001E
|
||||
CMP #$001D
|
||||
BEQ .ReturnTrue
|
||||
CLC
|
||||
BRA .Return
|
||||
@@ -583,7 +624,6 @@ FinalIcebergFix:
|
||||
PLX
|
||||
RTL
|
||||
|
||||
org $07A000
|
||||
StrictBosses:
|
||||
PHX
|
||||
LDA $901E ; Do we have strict bosses enabled?
|
||||
@@ -610,7 +650,6 @@ StrictBosses:
|
||||
LDA $53CD
|
||||
RTL
|
||||
|
||||
org $07A030
|
||||
NintenHalken:
|
||||
LDX #$0005
|
||||
.Halken:
|
||||
@@ -628,7 +667,6 @@ NintenHalken:
|
||||
LDA #$0001
|
||||
RTL
|
||||
|
||||
org $07A080
|
||||
StageCompleteSet:
|
||||
PHX
|
||||
LDA $5AC1 ; completed stage cutscene
|
||||
@@ -656,9 +694,17 @@ 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
|
||||
@@ -668,7 +714,6 @@ StageCompleteSet:
|
||||
CMP $53CB
|
||||
RTL
|
||||
|
||||
org $07A100
|
||||
OpenWorldBossUnlock:
|
||||
PHX
|
||||
PHY
|
||||
@@ -699,7 +744,6 @@ OpenWorldBossUnlock:
|
||||
.LoopStage:
|
||||
PLX
|
||||
LDY $9020, X ; get stage id
|
||||
DEY
|
||||
INX
|
||||
INX
|
||||
PHA
|
||||
@@ -732,7 +776,6 @@ OpenWorldBossUnlock:
|
||||
PLX
|
||||
RTL
|
||||
|
||||
org $07A180
|
||||
GooeySpawn:
|
||||
PHY
|
||||
PHX
|
||||
@@ -768,7 +811,6 @@ GooeySpawn:
|
||||
PLY
|
||||
RTL
|
||||
|
||||
org $07A200
|
||||
SpeedTrap:
|
||||
PHX
|
||||
LDX $8082 ; do we have slowness
|
||||
@@ -780,7 +822,6 @@ SpeedTrap:
|
||||
EOR #$FFFF
|
||||
RTL
|
||||
|
||||
org $07A280
|
||||
HeartStarVisual:
|
||||
CPX #$0000
|
||||
BEQ .SkipInx
|
||||
@@ -844,7 +885,6 @@ HeartStarVisual:
|
||||
.Return:
|
||||
RTL
|
||||
|
||||
org $07A300
|
||||
LoadFont:
|
||||
JSL $00D29F ; play sfx
|
||||
PHX
|
||||
@@ -915,7 +955,6 @@ LoadFont:
|
||||
PLX
|
||||
RTL
|
||||
|
||||
org $07A380
|
||||
HeartStarVisual2:
|
||||
LDA #$2C80
|
||||
STA $0000, Y
|
||||
@@ -1029,14 +1068,12 @@ HeartStarVisual2:
|
||||
STA $0000, Y
|
||||
RTL
|
||||
|
||||
org $07A480
|
||||
HeartStarSelectFix:
|
||||
PHX
|
||||
TXA
|
||||
ASL
|
||||
TAX
|
||||
LDA $9020, X
|
||||
DEC
|
||||
TAX
|
||||
.LoopHead:
|
||||
CMP #$0006
|
||||
@@ -1051,15 +1088,31 @@ 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:
|
||||
@@ -1075,7 +1128,6 @@ GiftGiving:
|
||||
PLX
|
||||
JML $CABC18
|
||||
|
||||
org $07A550
|
||||
PauseMenu:
|
||||
JSL $00D29F
|
||||
PHX
|
||||
@@ -1136,7 +1188,6 @@ PauseMenu:
|
||||
PLX
|
||||
RTL
|
||||
|
||||
org $07A600
|
||||
StarsSet:
|
||||
PHA
|
||||
PHX
|
||||
@@ -1166,7 +1217,6 @@ StarsSet:
|
||||
ASL
|
||||
TAX
|
||||
LDA $07D020, X
|
||||
DEC
|
||||
ASL
|
||||
ASL
|
||||
ASL
|
||||
@@ -1183,8 +1233,15 @@ 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
|
||||
@@ -1199,6 +1256,48 @@ 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"
|
||||
@@ -1234,4 +1333,7 @@ org $07E040
|
||||
db $3A, $01
|
||||
db $3B, $05
|
||||
db $3C, $05
|
||||
db $3D, $05
|
||||
db $3D, $05
|
||||
|
||||
org $07F000
|
||||
incbin "APPauseIcons.dat"
|
||||
@@ -6,6 +6,8 @@ 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):
|
||||
|
||||
@@ -5,12 +5,12 @@ class TestFastGoal(KDL3TestBase):
|
||||
options = {
|
||||
"open_world": False,
|
||||
"goal_speed": "fast",
|
||||
"total_heart_stars": 30,
|
||||
"max_heart_stars": 30,
|
||||
"heart_stars_required": 50,
|
||||
"filler_percentage": 0,
|
||||
}
|
||||
|
||||
def test_goal(self):
|
||||
def test_goal(self) -> None:
|
||||
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",
|
||||
"total_heart_stars": 30,
|
||||
"max_heart_stars": 30,
|
||||
"heart_stars_required": 50,
|
||||
"filler_percentage": 0,
|
||||
}
|
||||
|
||||
def test_goal(self):
|
||||
def test_goal(self) -> None:
|
||||
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):
|
||||
def test_kine(self) -> None:
|
||||
self.collect_by_name(["Cutter", "Burning", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
|
||||
def test_cutter(self):
|
||||
def test_cutter(self) -> None:
|
||||
self.collect_by_name(["Kine", "Burning", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
|
||||
def test_burning(self):
|
||||
def test_burning(self) -> None:
|
||||
self.collect_by_name(["Cutter", "Kine", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
|
||||
@@ -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):
|
||||
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",
|
||||
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",
|
||||
"Stone", "Ice"])
|
||||
|
||||
def run_location_test(self, location: str, itempool: typing.List[str]):
|
||||
def run_location_test(self, location: str, itempool: typing.List[str]) -> None:
|
||||
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):
|
||||
def test_shiro(self) -> None:
|
||||
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))
|
||||
|
||||
@@ -1,47 +1,61 @@
|
||||
from typing import List, Tuple
|
||||
from typing import List, Tuple, Optional
|
||||
from . import KDL3TestBase
|
||||
from ..Room import KDL3Room
|
||||
from ..room import KDL3Room
|
||||
from ..names import animal_friend_spawns
|
||||
|
||||
|
||||
class TestCopyAbilityShuffle(KDL3TestBase):
|
||||
options = {
|
||||
"open_world": False,
|
||||
"goal_speed": "normal",
|
||||
"total_heart_stars": 30,
|
||||
"max_heart_stars": 30,
|
||||
"heart_stars_required": 50,
|
||||
"filler_percentage": 0,
|
||||
"copy_ability_randomization": "enabled",
|
||||
}
|
||||
|
||||
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_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_kine(self):
|
||||
self.collect_by_name(["Cutter", "Burning", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
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_cutter(self):
|
||||
self.collect_by_name(["Kine", "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_burning(self):
|
||||
self.collect_by_name(["Cutter", "Kine", "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_cutter_and_burning_reachable(self):
|
||||
def test_cutter_and_burning_reachable(self) -> None:
|
||||
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)
|
||||
@@ -63,7 +77,7 @@ class TestCopyAbilityShuffle(KDL3TestBase):
|
||||
else:
|
||||
self.fail("Could not reach Burning Ability before Iceberg 4!")
|
||||
|
||||
def test_valid_abilities_for_ROB(self):
|
||||
def test_valid_abilities_for_ROB(self) -> None:
|
||||
# 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
|
||||
@@ -74,13 +88,13 @@ class TestCopyAbilityShuffle(KDL3TestBase):
|
||||
({"Stone Ability", "Burning Ability"}, {'Bukiset (Stone)', 'Bukiset (Burning)'}),
|
||||
]
|
||||
copy_abilities = self.multiworld.worlds[1].copy_abilities
|
||||
required_abilities: List[Tuple[str]] = []
|
||||
required_abilities: List[List[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(tuple(potential_abilities))
|
||||
required_abilities.append(potential_abilities)
|
||||
collected_abilities = list()
|
||||
for group in required_abilities:
|
||||
self.assertFalse(len(group) == 0, str(self.multiworld.seed))
|
||||
@@ -103,91 +117,147 @@ class TestAnimalShuffle(KDL3TestBase):
|
||||
options = {
|
||||
"open_world": False,
|
||||
"goal_speed": "normal",
|
||||
"total_heart_stars": 30,
|
||||
"max_heart_stars": 30,
|
||||
"heart_stars_required": 50,
|
||||
"filler_percentage": 0,
|
||||
"animal_randomization": "full",
|
||||
}
|
||||
|
||||
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_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_kine(self):
|
||||
self.collect_by_name(["Cutter", "Burning", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
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_cutter(self):
|
||||
self.collect_by_name(["Kine", "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_burning(self):
|
||||
self.collect_by_name(["Cutter", "Kine", "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_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_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}")
|
||||
|
||||
|
||||
class TestAllShuffle(KDL3TestBase):
|
||||
options = {
|
||||
"open_world": False,
|
||||
"goal_speed": "normal",
|
||||
"total_heart_stars": 30,
|
||||
"max_heart_stars": 30,
|
||||
"heart_stars_required": 50,
|
||||
"filler_percentage": 0,
|
||||
"animal_randomization": "full",
|
||||
"copy_ability_randomization": "enabled",
|
||||
}
|
||||
|
||||
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_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_kine(self):
|
||||
self.collect_by_name(["Cutter", "Burning", "Heart Star"])
|
||||
self.assertBeatable(False)
|
||||
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_cutter(self):
|
||||
self.collect_by_name(["Kine", "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_burning(self):
|
||||
self.collect_by_name(["Cutter", "Kine", "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_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_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_cutter_and_burning_reachable(self):
|
||||
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:
|
||||
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)
|
||||
@@ -209,7 +279,7 @@ class TestAllShuffle(KDL3TestBase):
|
||||
else:
|
||||
self.fail("Could not reach Burning Ability before Iceberg 4!")
|
||||
|
||||
def test_valid_abilities_for_ROB(self):
|
||||
def test_valid_abilities_for_ROB(self) -> None:
|
||||
# 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
|
||||
@@ -220,13 +290,13 @@ class TestAllShuffle(KDL3TestBase):
|
||||
({"Stone Ability", "Burning Ability"}, {'Bukiset (Stone)', 'Bukiset (Burning)'}),
|
||||
]
|
||||
copy_abilities = self.multiworld.worlds[1].copy_abilities
|
||||
required_abilities: List[Tuple[str]] = []
|
||||
required_abilities: List[List[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(tuple(potential_abilities))
|
||||
required_abilities.append(potential_abilities)
|
||||
collected_abilities = list()
|
||||
for group in required_abilities:
|
||||
self.assertFalse(len(group) == 0, str(self.multiworld.seed))
|
||||
@@ -242,4 +312,4 @@ class TestAllShuffle(KDL3TestBase):
|
||||
self.collect_by_name(["Cutter"])
|
||||
|
||||
self.assertTrue(self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B"),
|
||||
''.join(str(self.multiworld.seed)).join(collected_abilities))
|
||||
f"Seed: {self.multiworld.seed}, Collected: {collected_abilities}")
|
||||
|
||||
258
worlds/kh1/Client.py
Normal file
258
worlds/kh1/Client.py
Normal file
@@ -0,0 +1,258 @@
|
||||
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()
|
||||
532
worlds/kh1/Items.py
Normal file
532
worlds/kh1/Items.py
Normal file
@@ -0,0 +1,532 @@
|
||||
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)
|
||||
590
worlds/kh1/Locations.py
Normal file
590
worlds/kh1/Locations.py
Normal file
@@ -0,0 +1,590 @@
|
||||
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)
|
||||
445
worlds/kh1/Options.py
Normal file
445
worlds/kh1/Options.py
Normal file
@@ -0,0 +1,445 @@
|
||||
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,
|
||||
])
|
||||
]
|
||||
177
worlds/kh1/Presets.py
Normal file
177
worlds/kh1/Presets.py
Normal file
@@ -0,0 +1,177 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
516
worlds/kh1/Regions.py
Normal file
516
worlds/kh1/Regions.py
Normal file
@@ -0,0 +1,516 @@
|
||||
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
|
||||
1948
worlds/kh1/Rules.py
Normal file
1948
worlds/kh1/Rules.py
Normal file
File diff suppressed because it is too large
Load Diff
282
worlds/kh1/__init__.py
Normal file
282
worlds/kh1/__init__.py
Normal file
@@ -0,0 +1,282 @@
|
||||
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
|
||||
88
worlds/kh1/docs/en_Kingdom Hearts.md
Normal file
88
worlds/kh1/docs/en_Kingdom Hearts.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# 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.
|
||||
54
worlds/kh1/docs/kh1_en.md
Normal file
54
worlds/kh1/docs/kh1_en.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# 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.
|
||||
5
worlds/kh1/test/__init__.py
Normal file
5
worlds/kh1/test/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
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
Reference in New Issue
Block a user