mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-24 10:53:23 -07:00
Merge branch 'ArchipelagoMW:main' into main
This commit is contained in:
230
BaseClasses.py
230
BaseClasses.py
@@ -312,7 +312,7 @@ class MultiWorld():
|
||||
|
||||
def initialize_regions(self, regions=None):
|
||||
for region in regions if regions else self.regions:
|
||||
region.world = self
|
||||
region.multiworld = self
|
||||
self._region_cache[region.player][region.name] = region
|
||||
|
||||
@functools.cached_property
|
||||
@@ -602,7 +602,7 @@ PathValue = Tuple[str, Optional["PathValue"]]
|
||||
|
||||
class CollectionState():
|
||||
prog_items: typing.Counter[Tuple[str, int]]
|
||||
world: MultiWorld
|
||||
multiworld: MultiWorld
|
||||
reachable_regions: Dict[int, Set[Region]]
|
||||
blocked_connections: Dict[int, Set[Entrance]]
|
||||
events: Set[Location]
|
||||
@@ -614,7 +614,7 @@ class CollectionState():
|
||||
|
||||
def __init__(self, parent: MultiWorld):
|
||||
self.prog_items = Counter()
|
||||
self.world = parent
|
||||
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()
|
||||
@@ -632,7 +632,7 @@ class CollectionState():
|
||||
rrp = self.reachable_regions[player]
|
||||
bc = self.blocked_connections[player]
|
||||
queue = deque(self.blocked_connections[player])
|
||||
start = self.world.get_region('Menu', player)
|
||||
start = self.multiworld.get_region('Menu', player)
|
||||
|
||||
# init on first call - this can't be done on construction since the regions don't exist yet
|
||||
if start not in rrp:
|
||||
@@ -655,12 +655,12 @@ class CollectionState():
|
||||
self.path[new_region] = (new_region.name, self.path.get(connection, None))
|
||||
|
||||
# Retry connections if the new region can unblock them
|
||||
for new_entrance in self.world.indirect_connections.get(new_region, set()):
|
||||
for new_entrance in self.multiworld.indirect_connections.get(new_region, set()):
|
||||
if new_entrance in bc and new_entrance not in queue:
|
||||
queue.append(new_entrance)
|
||||
|
||||
def copy(self) -> CollectionState:
|
||||
ret = CollectionState(self.world)
|
||||
ret = CollectionState(self.multiworld)
|
||||
ret.prog_items = self.prog_items.copy()
|
||||
ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in
|
||||
self.reachable_regions}
|
||||
@@ -681,17 +681,17 @@ class CollectionState():
|
||||
assert isinstance(player, int), "can_reach: player is required if spot is str"
|
||||
# try to resolve a name
|
||||
if resolution_hint == 'Location':
|
||||
spot = self.world.get_location(spot, player)
|
||||
spot = self.multiworld.get_location(spot, player)
|
||||
elif resolution_hint == 'Entrance':
|
||||
spot = self.world.get_entrance(spot, player)
|
||||
spot = self.multiworld.get_entrance(spot, player)
|
||||
else:
|
||||
# default to Region
|
||||
spot = self.world.get_region(spot, player)
|
||||
spot = self.multiworld.get_region(spot, player)
|
||||
return spot.can_reach(self)
|
||||
|
||||
def sweep_for_events(self, key_only: bool = False, locations: Optional[Iterable[Location]] = None) -> None:
|
||||
if locations is None:
|
||||
locations = self.world.get_filled_locations()
|
||||
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.event and location not in self.events and
|
||||
@@ -718,7 +718,7 @@ class CollectionState():
|
||||
|
||||
def has_group(self, item_name_group: str, player: int, count: int = 1) -> bool:
|
||||
found: int = 0
|
||||
for item_name in self.world.worlds[player].item_name_groups[item_name_group]:
|
||||
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]:
|
||||
found += self.prog_items[item_name, player]
|
||||
if found >= count:
|
||||
return True
|
||||
@@ -726,17 +726,17 @@ class CollectionState():
|
||||
|
||||
def count_group(self, item_name_group: str, player: int) -> int:
|
||||
found: int = 0
|
||||
for item_name in self.world.worlds[player].item_name_groups[item_name_group]:
|
||||
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]:
|
||||
found += self.prog_items[item_name, player]
|
||||
return found
|
||||
|
||||
def can_buy_unlimited(self, item: str, player: int) -> bool:
|
||||
return any(shop.region.player == player and shop.has_unlimited(item) and shop.region.can_reach(self) for
|
||||
shop in self.world.shops)
|
||||
shop in self.multiworld.shops)
|
||||
|
||||
def can_buy(self, item: str, player: int) -> bool:
|
||||
return any(shop.region.player == player and shop.has(item) and shop.region.can_reach(self) for
|
||||
shop in self.world.shops)
|
||||
shop in self.multiworld.shops)
|
||||
|
||||
def item_count(self, item: str, player: int) -> int:
|
||||
return self.prog_items[item, player]
|
||||
@@ -756,7 +756,7 @@ class CollectionState():
|
||||
return self.has('Power Glove', player) or self.has('Titans Mitts', player)
|
||||
|
||||
def bottle_count(self, player: int) -> int:
|
||||
return min(self.world.difficulty_requirements[player].progressive_bottle_limit,
|
||||
return min(self.multiworld.difficulty_requirements[player].progressive_bottle_limit,
|
||||
self.count_group("Bottles", player))
|
||||
|
||||
def has_hearts(self, player: int, count: int) -> int:
|
||||
@@ -765,7 +765,7 @@ class CollectionState():
|
||||
|
||||
def heart_count(self, player: int) -> int:
|
||||
# Warning: This only considers items that are marked as advancement items
|
||||
diff = self.world.difficulty_requirements[player]
|
||||
diff = self.multiworld.difficulty_requirements[player]
|
||||
return min(self.item_count('Boss Heart Container', player), diff.boss_heart_container_limit) \
|
||||
+ self.item_count('Sanctuary Heart Container', player) \
|
||||
+ min(self.item_count('Piece of Heart', player), diff.heart_piece_limit) // 4 \
|
||||
@@ -782,9 +782,9 @@ class CollectionState():
|
||||
elif self.has('Magic Upgrade (1/2)', player):
|
||||
basemagic = 16
|
||||
if self.can_buy_unlimited('Green Potion', player) or self.can_buy_unlimited('Blue Potion', player):
|
||||
if self.world.item_functionality[player] == 'hard' and not fullrefill:
|
||||
if self.multiworld.item_functionality[player] == 'hard' and not fullrefill:
|
||||
basemagic = basemagic + int(basemagic * 0.5 * self.bottle_count(player))
|
||||
elif self.world.item_functionality[player] == 'expert' and not fullrefill:
|
||||
elif self.multiworld.item_functionality[player] == 'expert' and not fullrefill:
|
||||
basemagic = basemagic + int(basemagic * 0.25 * self.bottle_count(player))
|
||||
else:
|
||||
basemagic = basemagic + basemagic * self.bottle_count(player)
|
||||
@@ -799,12 +799,12 @@ class CollectionState():
|
||||
or (self.has('Bombs (10)', player) and enemies < 6))
|
||||
|
||||
def can_shoot_arrows(self, player: int) -> bool:
|
||||
if self.world.retro_bow[player]:
|
||||
if self.multiworld.retro_bow[player]:
|
||||
return (self.has('Bow', player) or self.has('Silver Bow', player)) and self.can_buy('Single Arrow', player)
|
||||
return self.has('Bow', player) or self.has('Silver Bow', player)
|
||||
|
||||
def can_get_good_bee(self, player: int) -> bool:
|
||||
cave = self.world.get_region('Good Bee Cave', player)
|
||||
cave = self.multiworld.get_region('Good Bee Cave', player)
|
||||
return (
|
||||
self.has_group("Bottles", player) and
|
||||
self.has('Bug Catching Net', player) and
|
||||
@@ -815,7 +815,7 @@ class CollectionState():
|
||||
|
||||
def can_retrieve_tablet(self, player: int) -> bool:
|
||||
return self.has('Book of Mudora', player) and (self.has_beam_sword(player) or
|
||||
(self.world.swordless[player] and
|
||||
(self.multiworld.swordless[player] and
|
||||
self.has("Hammer", player)))
|
||||
|
||||
def has_sword(self, player: int) -> bool:
|
||||
@@ -837,7 +837,7 @@ class CollectionState():
|
||||
def can_melt_things(self, player: int) -> bool:
|
||||
return self.has('Fire Rod', player) or \
|
||||
(self.has('Bombos', player) and
|
||||
(self.world.swordless[player] or
|
||||
(self.multiworld.swordless[player] or
|
||||
self.has_sword(player)))
|
||||
|
||||
def can_avoid_lasers(self, player: int) -> bool:
|
||||
@@ -847,7 +847,7 @@ class CollectionState():
|
||||
if self.has('Moon Pearl', player):
|
||||
return True
|
||||
|
||||
return region.is_light_world if self.world.mode[player] != 'inverted' else region.is_dark_world
|
||||
return region.is_light_world if self.multiworld.mode[player] != 'inverted' else region.is_dark_world
|
||||
|
||||
def can_reach_light_world(self, player: int) -> bool:
|
||||
if True in [i.is_light_world for i in self.reachable_regions[player]]:
|
||||
@@ -860,24 +860,24 @@ class CollectionState():
|
||||
return False
|
||||
|
||||
def has_misery_mire_medallion(self, player: int) -> bool:
|
||||
return self.has(self.world.required_medallions[player][0], player)
|
||||
return self.has(self.multiworld.required_medallions[player][0], player)
|
||||
|
||||
def has_turtle_rock_medallion(self, player: int) -> bool:
|
||||
return self.has(self.world.required_medallions[player][1], player)
|
||||
return self.has(self.multiworld.required_medallions[player][1], player)
|
||||
|
||||
def can_boots_clip_lw(self, player: int) -> bool:
|
||||
if self.world.mode[player] == 'inverted':
|
||||
if self.multiworld.mode[player] == 'inverted':
|
||||
return self.has('Pegasus Boots', player) and self.has('Moon Pearl', player)
|
||||
return self.has('Pegasus Boots', player)
|
||||
|
||||
def can_boots_clip_dw(self, player: int) -> bool:
|
||||
if self.world.mode[player] != 'inverted':
|
||||
if self.multiworld.mode[player] != 'inverted':
|
||||
return self.has('Pegasus Boots', player) and self.has('Moon Pearl', player)
|
||||
return self.has('Pegasus Boots', player)
|
||||
|
||||
def can_get_glitched_speed_lw(self, player: int) -> bool:
|
||||
rules = [self.has('Pegasus Boots', player), any([self.has('Hookshot', player), self.has_sword(player)])]
|
||||
if self.world.mode[player] == 'inverted':
|
||||
if self.multiworld.mode[player] == 'inverted':
|
||||
rules.append(self.has('Moon Pearl', player))
|
||||
return all(rules)
|
||||
|
||||
@@ -886,7 +886,7 @@ class CollectionState():
|
||||
|
||||
def can_get_glitched_speed_dw(self, player: int) -> bool:
|
||||
rules = [self.has('Pegasus Boots', player), any([self.has('Hookshot', player), self.has_sword(player)])]
|
||||
if self.world.mode[player] != 'inverted':
|
||||
if self.multiworld.mode[player] != 'inverted':
|
||||
rules.append(self.has('Moon Pearl', player))
|
||||
return all(rules)
|
||||
|
||||
@@ -897,7 +897,7 @@ class CollectionState():
|
||||
if location:
|
||||
self.locations_checked.add(location)
|
||||
|
||||
changed = self.world.worlds[item.player].collect(self, item)
|
||||
changed = self.multiworld.worlds[item.player].collect(self, item)
|
||||
|
||||
if not changed and event:
|
||||
self.prog_items[item.name, item.player] += 1
|
||||
@@ -911,7 +911,7 @@ class CollectionState():
|
||||
return changed
|
||||
|
||||
def remove(self, item: Item):
|
||||
changed = self.world.worlds[item.player].remove(self, item)
|
||||
changed = self.multiworld.worlds[item.player].remove(self, item)
|
||||
if changed:
|
||||
# invalidate caches, nothing can be trusted anymore now
|
||||
self.reachable_regions[item.player] = set()
|
||||
@@ -938,7 +938,7 @@ class Region:
|
||||
type: RegionType
|
||||
hint_text: str
|
||||
player: int
|
||||
world: Optional[MultiWorld]
|
||||
multiworld: Optional[MultiWorld]
|
||||
entrances: List[Entrance]
|
||||
exits: List[Entrance]
|
||||
locations: List[Location]
|
||||
@@ -956,7 +956,7 @@ class Region:
|
||||
self.entrances = []
|
||||
self.exits = []
|
||||
self.locations = []
|
||||
self.world = world
|
||||
self.multiworld = world
|
||||
self.hint_text = hint
|
||||
self.player = player
|
||||
|
||||
@@ -984,7 +984,7 @@ class Region:
|
||||
return self.__str__()
|
||||
|
||||
def __str__(self):
|
||||
return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
|
||||
return self.multiworld.get_name_string_for_object(self) if self.multiworld else f'{self.name} (Player {self.player})'
|
||||
|
||||
|
||||
class Entrance:
|
||||
@@ -1021,7 +1021,7 @@ class Entrance:
|
||||
return self.__str__()
|
||||
|
||||
def __str__(self):
|
||||
world = self.parent_region.world if self.parent_region else None
|
||||
world = self.parent_region.multiworld if self.parent_region else None
|
||||
return world.get_name_string_for_object(self) if world else f'{self.name} (Player {self.player})'
|
||||
|
||||
|
||||
@@ -1035,7 +1035,7 @@ class Dungeon(object):
|
||||
self.dungeon_items = dungeon_items
|
||||
self.bosses = dict()
|
||||
self.player = player
|
||||
self.world = None
|
||||
self.multiworld = None
|
||||
|
||||
@property
|
||||
def boss(self) -> Optional[Boss]:
|
||||
@@ -1065,7 +1065,7 @@ class Dungeon(object):
|
||||
return self.__str__()
|
||||
|
||||
def __str__(self):
|
||||
return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
|
||||
return self.multiworld.get_name_string_for_object(self) if self.multiworld else f'{self.name} (Player {self.player})'
|
||||
|
||||
|
||||
class Boss():
|
||||
@@ -1130,7 +1130,7 @@ class Location:
|
||||
return self.__str__()
|
||||
|
||||
def __str__(self):
|
||||
world = self.parent_region.world if self.parent_region and self.parent_region.world else None
|
||||
world = self.parent_region.multiworld if self.parent_region and self.parent_region.multiworld else None
|
||||
return world.get_name_string_for_object(self) if world else f'{self.name} (Player {self.player})'
|
||||
|
||||
def __hash__(self):
|
||||
@@ -1227,17 +1227,17 @@ class Item:
|
||||
return self.__str__()
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.location and self.location.parent_region and self.location.parent_region.world:
|
||||
return self.location.parent_region.world.get_name_string_for_object(self)
|
||||
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})"
|
||||
|
||||
|
||||
class Spoiler():
|
||||
world: MultiWorld
|
||||
multiworld: MultiWorld
|
||||
unreachables: Set[Location]
|
||||
|
||||
def __init__(self, world):
|
||||
self.world = world
|
||||
self.multiworld = world
|
||||
self.hashes = {}
|
||||
self.entrances = OrderedDict()
|
||||
self.medallions = {}
|
||||
@@ -1249,7 +1249,7 @@ class Spoiler():
|
||||
self.bosses = OrderedDict()
|
||||
|
||||
def set_entrance(self, entrance: str, exit_: str, direction: str, player: int):
|
||||
if self.world.players == 1:
|
||||
if self.multiworld.players == 1:
|
||||
self.entrances[(entrance, direction, player)] = OrderedDict(
|
||||
[('entrance', entrance), ('exit', exit_), ('direction', direction)])
|
||||
else:
|
||||
@@ -1258,45 +1258,45 @@ class Spoiler():
|
||||
|
||||
def parse_data(self):
|
||||
self.medallions = OrderedDict()
|
||||
for player in self.world.get_game_players("A Link to the Past"):
|
||||
self.medallions[f'Misery Mire ({self.world.get_player_name(player)})'] = \
|
||||
self.world.required_medallions[player][0]
|
||||
self.medallions[f'Turtle Rock ({self.world.get_player_name(player)})'] = \
|
||||
self.world.required_medallions[player][1]
|
||||
for player in self.multiworld.get_game_players("A Link to the Past"):
|
||||
self.medallions[f'Misery Mire ({self.multiworld.get_player_name(player)})'] = \
|
||||
self.multiworld.required_medallions[player][0]
|
||||
self.medallions[f'Turtle Rock ({self.multiworld.get_player_name(player)})'] = \
|
||||
self.multiworld.required_medallions[player][1]
|
||||
|
||||
self.locations = OrderedDict()
|
||||
listed_locations = set()
|
||||
|
||||
lw_locations = [loc for loc in self.world.get_locations() if
|
||||
lw_locations = [loc for loc in self.multiworld.get_locations() if
|
||||
loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld and loc.show_in_spoiler]
|
||||
self.locations['Light World'] = OrderedDict(
|
||||
[(str(location), str(location.item) if location.item is not None else 'Nothing') for location in
|
||||
lw_locations])
|
||||
listed_locations.update(lw_locations)
|
||||
|
||||
dw_locations = [loc for loc in self.world.get_locations() if
|
||||
dw_locations = [loc for loc in self.multiworld.get_locations() if
|
||||
loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld and loc.show_in_spoiler]
|
||||
self.locations['Dark World'] = OrderedDict(
|
||||
[(str(location), str(location.item) if location.item is not None else 'Nothing') for location in
|
||||
dw_locations])
|
||||
listed_locations.update(dw_locations)
|
||||
|
||||
cave_locations = [loc for loc in self.world.get_locations() if
|
||||
cave_locations = [loc for loc in self.multiworld.get_locations() if
|
||||
loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.Cave and loc.show_in_spoiler]
|
||||
self.locations['Caves'] = OrderedDict(
|
||||
[(str(location), str(location.item) if location.item is not None else 'Nothing') for location in
|
||||
cave_locations])
|
||||
listed_locations.update(cave_locations)
|
||||
|
||||
for dungeon in self.world.dungeons.values():
|
||||
dungeon_locations = [loc for loc in self.world.get_locations() if
|
||||
for dungeon in self.multiworld.dungeons.values():
|
||||
dungeon_locations = [loc for loc in self.multiworld.get_locations() if
|
||||
loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon and loc.show_in_spoiler]
|
||||
self.locations[str(dungeon)] = OrderedDict(
|
||||
[(str(location), str(location.item) if location.item is not None else 'Nothing') for location in
|
||||
dungeon_locations])
|
||||
listed_locations.update(dungeon_locations)
|
||||
|
||||
other_locations = [loc for loc in self.world.get_locations() if
|
||||
other_locations = [loc for loc in self.multiworld.get_locations() if
|
||||
loc not in listed_locations and loc.show_in_spoiler]
|
||||
if other_locations:
|
||||
self.locations['Other Locations'] = OrderedDict(
|
||||
@@ -1306,7 +1306,7 @@ class Spoiler():
|
||||
|
||||
self.shops = []
|
||||
from worlds.alttp.Shops import ShopType, price_type_display_name, price_rate_display
|
||||
for shop in self.world.shops:
|
||||
for shop in self.multiworld.shops:
|
||||
if not shop.custom:
|
||||
continue
|
||||
shopdata = {
|
||||
@@ -1335,34 +1335,34 @@ class Spoiler():
|
||||
index)] += f", {item['replacement']} - {item['replacement_price']} {price_type_display_name[item['replacement_price_type']]}"
|
||||
self.shops.append(shopdata)
|
||||
|
||||
for player in self.world.get_game_players("A Link to the Past"):
|
||||
for player in self.multiworld.get_game_players("A Link to the Past"):
|
||||
self.bosses[str(player)] = OrderedDict()
|
||||
self.bosses[str(player)]["Eastern Palace"] = self.world.get_dungeon("Eastern Palace", player).boss.name
|
||||
self.bosses[str(player)]["Desert Palace"] = self.world.get_dungeon("Desert Palace", player).boss.name
|
||||
self.bosses[str(player)]["Tower Of Hera"] = self.world.get_dungeon("Tower of Hera", player).boss.name
|
||||
self.bosses[str(player)]["Eastern Palace"] = self.multiworld.get_dungeon("Eastern Palace", player).boss.name
|
||||
self.bosses[str(player)]["Desert Palace"] = self.multiworld.get_dungeon("Desert Palace", player).boss.name
|
||||
self.bosses[str(player)]["Tower Of Hera"] = self.multiworld.get_dungeon("Tower of Hera", player).boss.name
|
||||
self.bosses[str(player)]["Hyrule Castle"] = "Agahnim"
|
||||
self.bosses[str(player)]["Palace Of Darkness"] = self.world.get_dungeon("Palace of Darkness",
|
||||
player).boss.name
|
||||
self.bosses[str(player)]["Swamp Palace"] = self.world.get_dungeon("Swamp Palace", player).boss.name
|
||||
self.bosses[str(player)]["Skull Woods"] = self.world.get_dungeon("Skull Woods", player).boss.name
|
||||
self.bosses[str(player)]["Thieves Town"] = self.world.get_dungeon("Thieves Town", player).boss.name
|
||||
self.bosses[str(player)]["Ice Palace"] = self.world.get_dungeon("Ice Palace", player).boss.name
|
||||
self.bosses[str(player)]["Misery Mire"] = self.world.get_dungeon("Misery Mire", player).boss.name
|
||||
self.bosses[str(player)]["Turtle Rock"] = self.world.get_dungeon("Turtle Rock", player).boss.name
|
||||
if self.world.mode[player] != 'inverted':
|
||||
self.bosses[str(player)]["Palace Of Darkness"] = self.multiworld.get_dungeon("Palace of Darkness",
|
||||
player).boss.name
|
||||
self.bosses[str(player)]["Swamp Palace"] = self.multiworld.get_dungeon("Swamp Palace", player).boss.name
|
||||
self.bosses[str(player)]["Skull Woods"] = self.multiworld.get_dungeon("Skull Woods", player).boss.name
|
||||
self.bosses[str(player)]["Thieves Town"] = self.multiworld.get_dungeon("Thieves Town", player).boss.name
|
||||
self.bosses[str(player)]["Ice Palace"] = self.multiworld.get_dungeon("Ice Palace", player).boss.name
|
||||
self.bosses[str(player)]["Misery Mire"] = self.multiworld.get_dungeon("Misery Mire", player).boss.name
|
||||
self.bosses[str(player)]["Turtle Rock"] = self.multiworld.get_dungeon("Turtle Rock", player).boss.name
|
||||
if self.multiworld.mode[player] != 'inverted':
|
||||
self.bosses[str(player)]["Ganons Tower Basement"] = \
|
||||
self.world.get_dungeon('Ganons Tower', player).bosses['bottom'].name
|
||||
self.bosses[str(player)]["Ganons Tower Middle"] = self.world.get_dungeon('Ganons Tower', player).bosses[
|
||||
self.multiworld.get_dungeon('Ganons Tower', player).bosses['bottom'].name
|
||||
self.bosses[str(player)]["Ganons Tower Middle"] = self.multiworld.get_dungeon('Ganons Tower', player).bosses[
|
||||
'middle'].name
|
||||
self.bosses[str(player)]["Ganons Tower Top"] = self.world.get_dungeon('Ganons Tower', player).bosses[
|
||||
self.bosses[str(player)]["Ganons Tower Top"] = self.multiworld.get_dungeon('Ganons Tower', player).bosses[
|
||||
'top'].name
|
||||
else:
|
||||
self.bosses[str(player)]["Ganons Tower Basement"] = \
|
||||
self.world.get_dungeon('Inverted Ganons Tower', player).bosses['bottom'].name
|
||||
self.multiworld.get_dungeon('Inverted Ganons Tower', player).bosses['bottom'].name
|
||||
self.bosses[str(player)]["Ganons Tower Middle"] = \
|
||||
self.world.get_dungeon('Inverted Ganons Tower', player).bosses['middle'].name
|
||||
self.multiworld.get_dungeon('Inverted Ganons Tower', player).bosses['middle'].name
|
||||
self.bosses[str(player)]["Ganons Tower Top"] = \
|
||||
self.world.get_dungeon('Inverted Ganons Tower', player).bosses['top'].name
|
||||
self.multiworld.get_dungeon('Inverted Ganons Tower', player).bosses['top'].name
|
||||
|
||||
self.bosses[str(player)]["Ganons Tower"] = "Agahnim 2"
|
||||
self.bosses[str(player)]["Ganon"] = "Ganon"
|
||||
@@ -1392,7 +1392,7 @@ class Spoiler():
|
||||
return 'Yes' if variable else 'No'
|
||||
|
||||
def write_option(option_key: str, option_obj: type(Options.Option)):
|
||||
res = getattr(self.world, option_key)[player]
|
||||
res = getattr(self.multiworld, option_key)[player]
|
||||
display_name = getattr(option_obj, "display_name", option_key)
|
||||
try:
|
||||
outfile.write(f'{display_name + ":":33}{res.get_current_option_name()}\n')
|
||||
@@ -1402,59 +1402,59 @@ class Spoiler():
|
||||
with open(filename, 'w', encoding="utf-8-sig") as outfile:
|
||||
outfile.write(
|
||||
'Archipelago Version %s - Seed: %s\n\n' % (
|
||||
Utils.__version__, self.world.seed))
|
||||
outfile.write('Filling Algorithm: %s\n' % self.world.algorithm)
|
||||
outfile.write('Players: %d\n' % self.world.players)
|
||||
AutoWorld.call_stage(self.world, "write_spoiler_header", outfile)
|
||||
Utils.__version__, self.multiworld.seed))
|
||||
outfile.write('Filling Algorithm: %s\n' % self.multiworld.algorithm)
|
||||
outfile.write('Players: %d\n' % self.multiworld.players)
|
||||
AutoWorld.call_stage(self.multiworld, "write_spoiler_header", outfile)
|
||||
|
||||
for player in range(1, self.world.players + 1):
|
||||
if self.world.players > 1:
|
||||
outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_name(player)))
|
||||
outfile.write('Game: %s\n' % self.world.game[player])
|
||||
for player in range(1, self.multiworld.players + 1):
|
||||
if self.multiworld.players > 1:
|
||||
outfile.write('\nPlayer %d: %s\n' % (player, self.multiworld.get_player_name(player)))
|
||||
outfile.write('Game: %s\n' % self.multiworld.game[player])
|
||||
for f_option, option in Options.per_game_common_options.items():
|
||||
write_option(f_option, option)
|
||||
options = self.world.worlds[player].option_definitions
|
||||
options = self.multiworld.worlds[player].option_definitions
|
||||
if options:
|
||||
for f_option, option in options.items():
|
||||
write_option(f_option, option)
|
||||
AutoWorld.call_single(self.world, "write_spoiler_header", player, outfile)
|
||||
AutoWorld.call_single(self.multiworld, "write_spoiler_header", player, outfile)
|
||||
|
||||
if player in self.world.get_game_players("A Link to the Past"):
|
||||
if player in self.multiworld.get_game_players("A Link to the Past"):
|
||||
outfile.write('%s%s\n' % ('Hash: ', self.hashes[player]))
|
||||
|
||||
outfile.write('Logic: %s\n' % self.world.logic[player])
|
||||
outfile.write('Dark Room Logic: %s\n' % self.world.dark_room_logic[player])
|
||||
outfile.write('Mode: %s\n' % self.world.mode[player])
|
||||
outfile.write('Goal: %s\n' % self.world.goal[player])
|
||||
if "triforce" in self.world.goal[player]: # triforce hunt
|
||||
outfile.write('Logic: %s\n' % self.multiworld.logic[player])
|
||||
outfile.write('Dark Room Logic: %s\n' % self.multiworld.dark_room_logic[player])
|
||||
outfile.write('Mode: %s\n' % self.multiworld.mode[player])
|
||||
outfile.write('Goal: %s\n' % self.multiworld.goal[player])
|
||||
if "triforce" in self.multiworld.goal[player]: # triforce hunt
|
||||
outfile.write("Pieces available for Triforce: %s\n" %
|
||||
self.world.triforce_pieces_available[player])
|
||||
self.multiworld.triforce_pieces_available[player])
|
||||
outfile.write("Pieces required for Triforce: %s\n" %
|
||||
self.world.triforce_pieces_required[player])
|
||||
outfile.write('Difficulty: %s\n' % self.world.difficulty[player])
|
||||
outfile.write('Item Functionality: %s\n' % self.world.item_functionality[player])
|
||||
outfile.write('Entrance Shuffle: %s\n' % self.world.shuffle[player])
|
||||
if self.world.shuffle[player] != "vanilla":
|
||||
outfile.write('Entrance Shuffle Seed %s\n' % self.world.worlds[player].er_seed)
|
||||
self.multiworld.triforce_pieces_required[player])
|
||||
outfile.write('Difficulty: %s\n' % self.multiworld.difficulty[player])
|
||||
outfile.write('Item Functionality: %s\n' % self.multiworld.item_functionality[player])
|
||||
outfile.write('Entrance Shuffle: %s\n' % self.multiworld.shuffle[player])
|
||||
if self.multiworld.shuffle[player] != "vanilla":
|
||||
outfile.write('Entrance Shuffle Seed %s\n' % self.multiworld.worlds[player].er_seed)
|
||||
outfile.write('Shop inventory shuffle: %s\n' %
|
||||
bool_to_text("i" in self.world.shop_shuffle[player]))
|
||||
bool_to_text("i" in self.multiworld.shop_shuffle[player]))
|
||||
outfile.write('Shop price shuffle: %s\n' %
|
||||
bool_to_text("p" in self.world.shop_shuffle[player]))
|
||||
bool_to_text("p" in self.multiworld.shop_shuffle[player]))
|
||||
outfile.write('Shop upgrade shuffle: %s\n' %
|
||||
bool_to_text("u" in self.world.shop_shuffle[player]))
|
||||
bool_to_text("u" in self.multiworld.shop_shuffle[player]))
|
||||
outfile.write('New Shop inventory: %s\n' %
|
||||
bool_to_text("g" in self.world.shop_shuffle[player] or
|
||||
"f" in self.world.shop_shuffle[player]))
|
||||
bool_to_text("g" in self.multiworld.shop_shuffle[player] or
|
||||
"f" in self.multiworld.shop_shuffle[player]))
|
||||
outfile.write('Custom Potion Shop: %s\n' %
|
||||
bool_to_text("w" in self.world.shop_shuffle[player]))
|
||||
outfile.write('Enemy health: %s\n' % self.world.enemy_health[player])
|
||||
outfile.write('Enemy damage: %s\n' % self.world.enemy_damage[player])
|
||||
bool_to_text("w" in self.multiworld.shop_shuffle[player]))
|
||||
outfile.write('Enemy health: %s\n' % self.multiworld.enemy_health[player])
|
||||
outfile.write('Enemy damage: %s\n' % self.multiworld.enemy_damage[player])
|
||||
outfile.write('Prize shuffle %s\n' %
|
||||
self.world.shuffle_prizes[player])
|
||||
self.multiworld.shuffle_prizes[player])
|
||||
if self.entrances:
|
||||
outfile.write('\n\nEntrances:\n\n')
|
||||
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_name(entry["player"])}: '
|
||||
if self.world.players > 1 else '', entry['entrance'],
|
||||
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.multiworld.get_player_name(entry["player"])}: '
|
||||
if self.multiworld.players > 1 else '', entry['entrance'],
|
||||
'<=>' if entry['direction'] == 'both' else
|
||||
'<=' if entry['direction'] == 'exit' else '=>',
|
||||
entry['exit']) for entry in self.entrances.values()]))
|
||||
@@ -1464,7 +1464,7 @@ class Spoiler():
|
||||
for dungeon, medallion in self.medallions.items():
|
||||
outfile.write(f'\n{dungeon}: {medallion}')
|
||||
|
||||
AutoWorld.call_all(self.world, "write_spoiler", outfile)
|
||||
AutoWorld.call_all(self.multiworld, "write_spoiler", outfile)
|
||||
|
||||
outfile.write('\n\nLocations:\n\n')
|
||||
outfile.write('\n'.join(
|
||||
@@ -1477,11 +1477,11 @@ class Spoiler():
|
||||
item for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if
|
||||
item)) for shop in self.shops))
|
||||
|
||||
for player in self.world.get_game_players("A Link to the Past"):
|
||||
if self.world.boss_shuffle[player] != 'none':
|
||||
bossmap = self.bosses[str(player)] if self.world.players > 1 else self.bosses
|
||||
for player in self.multiworld.get_game_players("A Link to the Past"):
|
||||
if self.multiworld.boss_shuffle[player] != 'none':
|
||||
bossmap = self.bosses[str(player)] if self.multiworld.players > 1 else self.bosses
|
||||
outfile.write(
|
||||
f'\n\nBosses{(f" ({self.world.get_player_name(player)})" if self.world.players > 1 else "")}:\n')
|
||||
f'\n\nBosses{(f" ({self.multiworld.get_player_name(player)})" if self.multiworld.players > 1 else "")}:\n')
|
||||
outfile.write(' ' + '\n '.join([f'{x}: {y}' for x, y in bossmap.items()]))
|
||||
outfile.write('\n\nPlaythrough:\n\n')
|
||||
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join(
|
||||
@@ -1505,7 +1505,7 @@ class Spoiler():
|
||||
path_listings.append("{}\n {}".format(location, "\n => ".join(path_lines)))
|
||||
|
||||
outfile.write('\n'.join(path_listings))
|
||||
AutoWorld.call_all(self.world, "write_spoiler_end", outfile)
|
||||
AutoWorld.call_all(self.multiworld, "write_spoiler_end", outfile)
|
||||
|
||||
|
||||
class Tutorial(NamedTuple):
|
||||
|
||||
@@ -218,6 +218,12 @@ class CommonContext:
|
||||
# execution
|
||||
self.keep_alive_task = asyncio.create_task(keep_alive(self), name="Bouncy")
|
||||
|
||||
@property
|
||||
def suggested_address(self) -> str:
|
||||
if self.server_address:
|
||||
return self.server_address
|
||||
return Utils.persistent_load().get("client", {}).get("last_server_address", "")
|
||||
|
||||
@functools.cached_property
|
||||
def raw_text_parser(self) -> RawJSONtoTextParser:
|
||||
return RawJSONtoTextParser(self)
|
||||
@@ -229,9 +235,9 @@ class CommonContext:
|
||||
return len(self.checked_locations | self.missing_locations)
|
||||
|
||||
async def connection_closed(self):
|
||||
self.reset_server_state()
|
||||
if self.server and self.server.socket is not None:
|
||||
await self.server.socket.close()
|
||||
self.reset_server_state()
|
||||
|
||||
def reset_server_state(self):
|
||||
self.auth = None
|
||||
@@ -297,6 +303,8 @@ class CommonContext:
|
||||
await self.send_msgs([payload])
|
||||
|
||||
async def console_input(self) -> str:
|
||||
if self.ui:
|
||||
self.ui.focus_textinput()
|
||||
self.input_requests += 1
|
||||
return await self.input_queue.get()
|
||||
|
||||
@@ -665,6 +673,9 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
|
||||
ctx.checked_locations = set(args["checked_locations"])
|
||||
ctx.server_locations = ctx.missing_locations | ctx. checked_locations
|
||||
|
||||
server_url = urllib.parse.urlparse(ctx.server_address)
|
||||
Utils.persistent_store("client", "last_server_address", server_url.netloc)
|
||||
|
||||
elif cmd == 'ReceivedItems':
|
||||
start_index = args["index"]
|
||||
|
||||
@@ -775,7 +786,6 @@ if __name__ == '__main__':
|
||||
async def main(args):
|
||||
ctx = TextContext(args.connect, args.password)
|
||||
ctx.auth = args.name
|
||||
ctx.server_address = args.connect
|
||||
ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop")
|
||||
|
||||
if gui_enabled:
|
||||
|
||||
2
Fill.py
2
Fill.py
@@ -294,7 +294,7 @@ def distribute_items_restrictive(world: MultiWorld) -> None:
|
||||
{len(unplaced_early_items)} items early.")
|
||||
itempool += unplaced_early_items
|
||||
|
||||
fill_locations += early_locations + early_priority_locations
|
||||
fill_locations += early_locations
|
||||
world.random.shuffle(fill_locations)
|
||||
|
||||
for item in itempool:
|
||||
|
||||
12
Generate.py
12
Generate.py
@@ -233,8 +233,8 @@ def main(args=None, callback=ERmain):
|
||||
else:
|
||||
raise RuntimeError(f'No weights specified for player {player}')
|
||||
|
||||
if len(set(erargs.name.values())) != len(erargs.name):
|
||||
raise Exception(f"Names have to be unique. Names: {Counter(erargs.name.values())}")
|
||||
if len(set(name.lower() for name in erargs.name.values())) != len(erargs.name):
|
||||
raise Exception(f"Names have to be unique. Names: {Counter(name.lower() for name in erargs.name.values())}")
|
||||
|
||||
if args.yaml_output:
|
||||
import yaml
|
||||
@@ -317,11 +317,11 @@ class SafeDict(dict):
|
||||
|
||||
|
||||
def handle_name(name: str, player: int, name_counter: Counter):
|
||||
name_counter[name] += 1
|
||||
name_counter[name.lower()] += 1
|
||||
number = name_counter[name.lower()]
|
||||
new_name = "%".join([x.replace("%number%", "{number}").replace("%player%", "{player}") for x in name.split("%%")])
|
||||
new_name = string.Formatter().vformat(new_name, (), SafeDict(number=name_counter[name],
|
||||
NUMBER=(name_counter[name] if name_counter[
|
||||
name] > 1 else ''),
|
||||
new_name = string.Formatter().vformat(new_name, (), SafeDict(number=number,
|
||||
NUMBER=(number if number > 1 else ''),
|
||||
player=player,
|
||||
PLAYER=(player if player > 1 else '')))
|
||||
new_name = new_name.strip()[:16]
|
||||
|
||||
@@ -1802,14 +1802,33 @@ class ServerCommandProcessor(CommonCommandProcessor):
|
||||
self.output(response)
|
||||
return False
|
||||
|
||||
def resolve_player(self, input_name: str) -> typing.Optional[typing.Tuple[int, int, str]]:
|
||||
""" returns (team, slot, player name) """
|
||||
# TODO: clean up once we disallow multidata < 0.3.6, which has CI unique names
|
||||
# first match case
|
||||
for (team, slot), name in self.ctx.player_names.items():
|
||||
if name == input_name:
|
||||
return team, slot, name
|
||||
|
||||
# if no case-sensitive match, then match without case only if there's only 1 match
|
||||
input_lower = input_name.lower()
|
||||
match: typing.Optional[typing.Tuple[int, int, str]] = None
|
||||
for (team, slot), name in self.ctx.player_names.items():
|
||||
lowered = name.lower()
|
||||
if lowered == input_lower:
|
||||
if match:
|
||||
return None # ambiguous input_name
|
||||
match = (team, slot, name)
|
||||
return match
|
||||
|
||||
@mark_raw
|
||||
def _cmd_collect(self, player_name: str) -> bool:
|
||||
"""Send out the remaining items to player."""
|
||||
seeked_player = player_name.lower()
|
||||
for (team, slot), name in self.ctx.player_names.items():
|
||||
if name.lower() == seeked_player:
|
||||
collect_player(self.ctx, team, slot)
|
||||
return True
|
||||
player = self.resolve_player(player_name)
|
||||
if player:
|
||||
team, slot, _ = player
|
||||
collect_player(self.ctx, team, slot)
|
||||
return True
|
||||
|
||||
self.output(f"Could not find player {player_name} to collect")
|
||||
return False
|
||||
@@ -1822,11 +1841,11 @@ class ServerCommandProcessor(CommonCommandProcessor):
|
||||
@mark_raw
|
||||
def _cmd_forfeit(self, player_name: str) -> bool:
|
||||
"""Send out the remaining items from a player to their intended recipients."""
|
||||
seeked_player = player_name.lower()
|
||||
for (team, slot), name in self.ctx.player_names.items():
|
||||
if name.lower() == seeked_player:
|
||||
forfeit_player(self.ctx, team, slot)
|
||||
return True
|
||||
player = self.resolve_player(player_name)
|
||||
if player:
|
||||
team, slot, _ = player
|
||||
forfeit_player(self.ctx, team, slot)
|
||||
return True
|
||||
|
||||
self.output(f"Could not find player {player_name} to release")
|
||||
return False
|
||||
@@ -1834,12 +1853,12 @@ class ServerCommandProcessor(CommonCommandProcessor):
|
||||
@mark_raw
|
||||
def _cmd_allow_forfeit(self, player_name: str) -> bool:
|
||||
"""Allow the specified player to use the !release command."""
|
||||
seeked_player = player_name.lower()
|
||||
for (team, slot), name in self.ctx.player_names.items():
|
||||
if name.lower() == seeked_player:
|
||||
self.ctx.allow_forfeits[(team, slot)] = True
|
||||
self.output(f"Player {player_name} is now allowed to use the !release command at any time.")
|
||||
return True
|
||||
player = self.resolve_player(player_name)
|
||||
if player:
|
||||
team, slot, name = player
|
||||
self.ctx.allow_forfeits[(team, slot)] = True
|
||||
self.output(f"Player {name} is now allowed to use the !release command at any time.")
|
||||
return True
|
||||
|
||||
self.output(f"Could not find player {player_name} to allow the !release command for.")
|
||||
return False
|
||||
@@ -1847,13 +1866,12 @@ class ServerCommandProcessor(CommonCommandProcessor):
|
||||
@mark_raw
|
||||
def _cmd_forbid_forfeit(self, player_name: str) -> bool:
|
||||
""""Disallow the specified player from using the !release command."""
|
||||
seeked_player = player_name.lower()
|
||||
for (team, slot), name in self.ctx.player_names.items():
|
||||
if name.lower() == seeked_player:
|
||||
self.ctx.allow_forfeits[(team, slot)] = False
|
||||
self.output(
|
||||
f"Player {player_name} has to follow the server restrictions on use of the !release command.")
|
||||
return True
|
||||
player = self.resolve_player(player_name)
|
||||
if player:
|
||||
team, slot, name = player
|
||||
self.ctx.allow_forfeits[(team, slot)] = False
|
||||
self.output(f"Player {name} has to follow the server restrictions on use of the !release command.")
|
||||
return True
|
||||
|
||||
self.output(f"Could not find player {player_name} to forbid the !release command for.")
|
||||
return False
|
||||
|
||||
@@ -170,12 +170,12 @@ async def gb_sync_task(ctx: GBContext):
|
||||
data_decoded = json.loads(data.decode())
|
||||
#print(data_decoded)
|
||||
|
||||
if ctx.seed_name and ctx.seed_name != bytes(data_decoded['seedName']).decode():
|
||||
if ctx.seed_name and ctx.seed_name != ''.join([chr(i) for i in data_decoded['seedName'] if i != 0]):
|
||||
msg = "The server is running a different multiworld than your client is. (invalid seed_name)"
|
||||
logger.info(msg, extra={'compact_gui': True})
|
||||
ctx.gui_error('Error', msg)
|
||||
error_status = CONNECTION_RESET_STATUS
|
||||
ctx.seed_name = bytes(data_decoded['seedName']).decode()
|
||||
ctx.seed_name = ''.join([chr(i) for i in data_decoded['seedName'] if i != 0])
|
||||
if not ctx.auth:
|
||||
ctx.auth = ''.join([chr(i) for i in data_decoded['playerName'] if i != 0])
|
||||
if ctx.auth == '':
|
||||
@@ -260,7 +260,7 @@ async def patch_and_run_game(game_version, patch_file, ctx):
|
||||
patch = stream.read()
|
||||
patched_rom_data = bsdiff4.patch(base_patched_rom_data, patch)
|
||||
|
||||
written_hash = patched_rom_data[0xFFCC:0xFFDC]
|
||||
written_hash = patched_rom_data[0xFFCB:0xFFDB]
|
||||
if written_hash == basemd5.digest():
|
||||
with open(comp_path, "wb") as patched_rom_file:
|
||||
patched_rom_file.write(patched_rom_data)
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
<UILog>:
|
||||
viewclass: 'SelectableLabel'
|
||||
scroll_y: 0
|
||||
scroll_type: ["content", "bars"]
|
||||
bar_width: dp(12)
|
||||
effect_cls: "ScrollEffect"
|
||||
SelectableRecycleBoxLayout:
|
||||
default_size: None, dp(20)
|
||||
|
||||
@@ -161,7 +161,7 @@ function receive()
|
||||
-- Determine Message to send back
|
||||
memDomain.rom()
|
||||
newPlayerName = uRange(0xFFF0, 0x10)
|
||||
newSeedName = uRange(0xFFDC, 20)
|
||||
newSeedName = uRange(0xFFDB, 21)
|
||||
if (playerName ~= nil and not arrayEqual(playerName, newPlayerName)) or (seedName ~= nil and not arrayEqual(seedName, newSeedName)) then
|
||||
print("ROM changed, quitting")
|
||||
curstate = STATE_UNINITIALIZED
|
||||
|
||||
@@ -455,7 +455,7 @@ In addition, the following methods can be implemented and attributes can be set
|
||||
```python
|
||||
def generate_early(self) -> None:
|
||||
# read player settings to world instance
|
||||
self.final_boss_hp = self.world.final_boss_hp[self.player].value
|
||||
self.final_boss_hp = self.multiworld.final_boss_hp[self.player].value
|
||||
```
|
||||
|
||||
#### create_item
|
||||
@@ -490,19 +490,19 @@ def create_items(self) -> None:
|
||||
# If an item can't have duplicates it has to be excluded manually.
|
||||
|
||||
# List of items to exclude, as a copy since it will be destroyed below
|
||||
exclude = [item for item in self.world.precollected_items[self.player]]
|
||||
exclude = [item for item in self.multiworld.precollected_items[self.player]]
|
||||
|
||||
for item in map(self.create_item, mygame_items):
|
||||
if item in exclude:
|
||||
exclude.remove(item) # this is destructive. create unique list above
|
||||
self.world.itempool.append(self.create_item("nothing"))
|
||||
self.multiworld.itempool.append(self.create_item("nothing"))
|
||||
else:
|
||||
self.world.itempool.append(item)
|
||||
self.multiworld.itempool.append(item)
|
||||
|
||||
# itempool and number of locations should match up.
|
||||
# If this is not the case we want to fill the itempool with junk.
|
||||
junk = 0 # calculate this based on player settings
|
||||
self.world.itempool += [self.create_item("nothing") for _ in range(junk)]
|
||||
self.multiworld.itempool += [self.create_item("nothing") for _ in range(junk)]
|
||||
```
|
||||
|
||||
#### create_regions
|
||||
@@ -511,30 +511,30 @@ def create_items(self) -> None:
|
||||
def create_regions(self) -> None:
|
||||
# Add regions to the multiworld. "Menu" is the required starting point.
|
||||
# Arguments to Region() are name, type, human_readable_name, player, world
|
||||
r = Region("Menu", RegionType.Generic, "Menu", self.player, self.world)
|
||||
r = Region("Menu", RegionType.Generic, "Menu", self.player, self.multiworld)
|
||||
# Set Region.exits to a list of entrances that are reachable from region
|
||||
r.exits = [Entrance(self.player, "New game", r)] # or use r.exits.append
|
||||
# Append region to MultiWorld's regions
|
||||
self.world.regions.append(r) # or use += [r...]
|
||||
self.multiworld.regions.append(r) # or use += [r...]
|
||||
|
||||
r = Region("Main Area", RegionType.Generic, "Main Area", self.player, self.world)
|
||||
r = Region("Main Area", RegionType.Generic, "Main Area", self.player, self.multiworld)
|
||||
# Add main area's locations to main area (all but final boss)
|
||||
r.locations = [MyGameLocation(self.player, location.name,
|
||||
self.location_name_to_id[location.name], r)]
|
||||
r.exits = [Entrance(self.player, "Boss Door", r)]
|
||||
self.world.regions.append(r)
|
||||
self.multiworld.regions.append(r)
|
||||
|
||||
r = Region("Boss Room", RegionType.Generic, "Boss Room", self.player, self.world)
|
||||
r = Region("Boss Room", RegionType.Generic, "Boss Room", self.player, self.multiworld)
|
||||
# add event to Boss Room
|
||||
r.locations = [MyGameLocation(self.player, "Final Boss", None, r)]
|
||||
self.world.regions.append(r)
|
||||
self.multiworld.regions.append(r)
|
||||
|
||||
# If entrances are not randomized, they should be connected here, otherwise
|
||||
# they can also be connected at a later stage.
|
||||
self.world.get_entrance("New Game", self.player)\
|
||||
.connect(self.world.get_region("Main Area", self.player))
|
||||
self.world.get_entrance("Boss Door", self.player)\
|
||||
.connect(self.world.get_region("Boss Room", self.player))
|
||||
self.multiworld.get_entrance("New Game", self.player)
|
||||
.connect(self.multiworld.get_region("Main Area", self.player))
|
||||
self.multiworld.get_entrance("Boss Door", self.player)
|
||||
.connect(self.multiworld.get_region("Boss Room", self.player))
|
||||
|
||||
# If setting location access rules from data is easier here, set_rules can
|
||||
# possibly omitted.
|
||||
@@ -545,14 +545,14 @@ def create_regions(self) -> None:
|
||||
```python
|
||||
def generate_basic(self) -> None:
|
||||
# place "Victory" at "Final Boss" and set collection as win condition
|
||||
self.world.get_location("Final Boss", self.player)\
|
||||
self.multiworld.get_location("Final Boss", self.player)
|
||||
.place_locked_item(self.create_event("Victory"))
|
||||
self.world.completion_condition[self.player] = \
|
||||
self.multiworld.completion_condition[self.player] =
|
||||
lambda state: state.has("Victory", self.player)
|
||||
|
||||
# place item Herb into location Chest1 for some reason
|
||||
item = self.create_item("Herb")
|
||||
self.world.get_location("Chest1", self.player).place_locked_item(item)
|
||||
self.multiworld.get_location("Chest1", self.player).place_locked_item(item)
|
||||
# in most cases it's better to do this at the same time the itempool is
|
||||
# filled to avoid accidental duplicates:
|
||||
# manually placed and still in the itempool
|
||||
@@ -564,41 +564,42 @@ def generate_basic(self) -> None:
|
||||
from worlds.generic.Rules import add_rule, set_rule, forbid_item
|
||||
from Items import get_item_type
|
||||
|
||||
|
||||
def set_rules(self) -> None:
|
||||
# For some worlds this step can be omitted if either a Logic mixin
|
||||
# (see below) is used, it's easier to apply the rules from data during
|
||||
# location generation or everything is in generate_basic
|
||||
|
||||
# set a simple rule for an region
|
||||
set_rule(self.world.get_entrance("Boss Door", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Boss Door", self.player),
|
||||
lambda state: state.has("Boss Key", self.player))
|
||||
# combine rules to require two items
|
||||
add_rule(self.world.get_location("Chest2", self.player),
|
||||
add_rule(self.multiworld.get_location("Chest2", self.player),
|
||||
lambda state: state.has("Sword", self.player))
|
||||
add_rule(self.world.get_location("Chest2", self.player),
|
||||
add_rule(self.multiworld.get_location("Chest2", self.player),
|
||||
lambda state: state.has("Shield", self.player))
|
||||
# or simply combine yourself
|
||||
set_rule(self.world.get_location("Chest2", self.player),
|
||||
set_rule(self.multiworld.get_location("Chest2", self.player),
|
||||
lambda state: state.has("Sword", self.player) and
|
||||
state.has("Shield", self.player))
|
||||
# require two of an item
|
||||
set_rule(self.world.get_location("Chest3", self.player),
|
||||
set_rule(self.multiworld.get_location("Chest3", self.player),
|
||||
lambda state: state.has("Key", self.player, 2))
|
||||
# require one item from an item group
|
||||
add_rule(self.world.get_location("Chest3", self.player),
|
||||
add_rule(self.multiworld.get_location("Chest3", self.player),
|
||||
lambda state: state.has_group("weapons", self.player))
|
||||
# state also has .item_count() for items, .has_any() and.has_all() for sets
|
||||
# and .count_group() for groups
|
||||
# set_rule is likely to be a bit faster than add_rule
|
||||
|
||||
# disallow placing a specific local item at a specific location
|
||||
forbid_item(self.world.get_location("Chest4", self.player), "Sword")
|
||||
forbid_item(self.multiworld.get_location("Chest4", self.player), "Sword")
|
||||
# disallow placing items with a specific property
|
||||
add_item_rule(self.world.get_location("Chest5", self.player),
|
||||
add_item_rule(self.multiworld.get_location("Chest5", self.player),
|
||||
lambda item: get_item_type(item) == "weapon")
|
||||
# get_item_type needs to take player/world into account
|
||||
# if MyGameItem has a type property, a more direct implementation would be
|
||||
add_item_rule(self.world.get_location("Chest5", self.player),
|
||||
add_item_rule(self.multiworld.get_location("Chest5", self.player),
|
||||
lambda item: item.player != self.player or\
|
||||
item.my_type == "weapon")
|
||||
# location.item_rule = ... is likely to be a bit faster
|
||||
@@ -659,32 +660,33 @@ class MyGameWorld(World):
|
||||
```python
|
||||
from .Mod import generate_mod
|
||||
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
# How to generate the mod or ROM highly depends on the game
|
||||
# if the mod is written in Lua, Jinja can be used to fill a template
|
||||
# if the mod reads a json file, `json.dump()` can be used to generate that
|
||||
# code below is a dummy
|
||||
data = {
|
||||
"seed": self.world.seed_name, # to verify the server's multiworld
|
||||
"slot": self.world.player_name[self.player], # to connect to server
|
||||
"seed": self.multiworld.seed_name, # to verify the server's multiworld
|
||||
"slot": self.multiworld.player_name[self.player], # to connect to server
|
||||
"items": {location.name: location.item.name
|
||||
if location.item.player == self.player else "Remote"
|
||||
for location in self.world.get_filled_locations(self.player)},
|
||||
for location in self.multiworld.get_filled_locations(self.player)},
|
||||
# store start_inventory from player's .yaml
|
||||
"starter_items": [item.name for item
|
||||
in self.world.precollected_items[self.player]],
|
||||
in self.multiworld.precollected_items[self.player]],
|
||||
"final_boss_hp": self.final_boss_hp,
|
||||
# store option name "easy", "normal" or "hard" for difficuly
|
||||
"difficulty": self.world.difficulty[self.player].current_key,
|
||||
"difficulty": self.multiworld.difficulty[self.player].current_key,
|
||||
# store option value True or False for fixing a glitch
|
||||
"fix_xyz_glitch": self.world.fix_xyz_glitch[self.player].value
|
||||
"fix_xyz_glitch": self.multiworld.fix_xyz_glitch[self.player].value
|
||||
}
|
||||
# point to a ROM specified by the installation
|
||||
src = Utils.get_options()["mygame_options"]["rom_file"]
|
||||
# or point to worlds/mygame/data/mod_template
|
||||
src = os.path.join(os.path.dirname(__file__), "data", "mod_template")
|
||||
# generate output path
|
||||
mod_name = f"AP-{self.world.seed_name}-P{self.player}-{self.world.player_name[self.player]}"
|
||||
mod_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.player_name[self.player]}"
|
||||
out_file = os.path.join(output_directory, mod_name + ".zip")
|
||||
# generate the file
|
||||
generate_mod(src, out_file, data)
|
||||
|
||||
29
kvui.py
29
kvui.py
@@ -338,9 +338,12 @@ class GameManager(App):
|
||||
# top part
|
||||
server_label = ServerLabel()
|
||||
self.connect_layout.add_widget(server_label)
|
||||
self.server_connect_bar = ConnectBarTextInput(text=self.ctx.server_address or "archipelago.gg", size_hint_y=None,
|
||||
self.server_connect_bar = ConnectBarTextInput(text=self.ctx.suggested_address or "archipelago.gg:", size_hint_y=None,
|
||||
height=30, multiline=False, write_tab=False)
|
||||
self.server_connect_bar.bind(on_text_validate=self.connect_button_action)
|
||||
def connect_bar_validate(sender):
|
||||
if not self.ctx.server:
|
||||
self.connect_button_action(sender)
|
||||
self.server_connect_bar.bind(on_text_validate=connect_bar_validate)
|
||||
self.connect_layout.add_widget(self.server_connect_bar)
|
||||
self.server_connect_button = Button(text="Connect", size=(100, 30), size_hint_y=None, size_hint_x=None)
|
||||
self.server_connect_button.bind(on_press=self.connect_button_action)
|
||||
@@ -381,17 +384,19 @@ class GameManager(App):
|
||||
bottom_layout.add_widget(info_button)
|
||||
self.textinput = TextInput(size_hint_y=None, height=30, multiline=False, write_tab=False)
|
||||
self.textinput.bind(on_text_validate=self.on_message)
|
||||
|
||||
def text_focus(event):
|
||||
"""Needs to be set via delay, as unfocusing happens after on_message"""
|
||||
self.textinput.focus = True
|
||||
|
||||
self.textinput.text_focus = text_focus
|
||||
self.textinput.text_validate_unfocus = False
|
||||
bottom_layout.add_widget(self.textinput)
|
||||
self.grid.add_widget(bottom_layout)
|
||||
self.commandprocessor("/help")
|
||||
Clock.schedule_interval(self.update_texts, 1 / 30)
|
||||
self.container.add_widget(self.grid)
|
||||
|
||||
self.server_connect_bar.focus = True
|
||||
self.server_connect_bar.select_text(
|
||||
self.server_connect_bar.text.find(":") + 1,
|
||||
len(self.server_connect_bar.text)
|
||||
)
|
||||
|
||||
return self.container
|
||||
|
||||
def update_texts(self, dt):
|
||||
@@ -402,10 +407,12 @@ class GameManager(App):
|
||||
f" | Connected to: {self.ctx.server_address} " \
|
||||
f"{'.'.join(str(e) for e in self.ctx.server_version)}"
|
||||
self.server_connect_button.text = "Disconnect"
|
||||
self.server_connect_bar.readonly = True
|
||||
self.progressbar.max = len(self.ctx.checked_locations) + len(self.ctx.missing_locations)
|
||||
self.progressbar.value = len(self.ctx.checked_locations)
|
||||
else:
|
||||
self.server_connect_button.text = "Connect"
|
||||
self.server_connect_bar.readonly = False
|
||||
self.title = self.base_title + " " + Utils.__version__
|
||||
self.progressbar.value = 0
|
||||
|
||||
@@ -443,8 +450,6 @@ class GameManager(App):
|
||||
elif input_text:
|
||||
self.commandprocessor(input_text)
|
||||
|
||||
Clock.schedule_once(textinput.text_focus)
|
||||
|
||||
except Exception as e:
|
||||
logging.getLogger("Client").exception(e)
|
||||
|
||||
@@ -453,6 +458,10 @@ class GameManager(App):
|
||||
self.log_panels["Archipelago"].on_message_markup(text)
|
||||
self.log_panels["All"].on_message_markup(text)
|
||||
|
||||
def focus_textinput(self):
|
||||
if hasattr(self, "textinput"):
|
||||
self.textinput.focus = True
|
||||
|
||||
def update_address_bar(self, text: str):
|
||||
if hasattr(self, "server_connect_bar"):
|
||||
self.server_connect_bar.text = text
|
||||
|
||||
@@ -11,18 +11,18 @@ from worlds.alttp.Items import ItemFactory
|
||||
|
||||
|
||||
class TestBase(unittest.TestCase):
|
||||
world: MultiWorld
|
||||
multiworld: MultiWorld
|
||||
_state_cache = {}
|
||||
|
||||
def get_state(self, items):
|
||||
if (self.world, tuple(items)) in self._state_cache:
|
||||
return self._state_cache[self.world, tuple(items)]
|
||||
state = CollectionState(self.world)
|
||||
if (self.multiworld, tuple(items)) in self._state_cache:
|
||||
return self._state_cache[self.multiworld, tuple(items)]
|
||||
state = CollectionState(self.multiworld)
|
||||
for item in items:
|
||||
item.classification = ItemClassification.progression
|
||||
state.collect(item)
|
||||
state.sweep_for_events()
|
||||
self._state_cache[self.world, tuple(items)] = state
|
||||
self._state_cache[self.multiworld, tuple(items)] = state
|
||||
return state
|
||||
|
||||
def get_path(self, state, region):
|
||||
@@ -44,11 +44,11 @@ class TestBase(unittest.TestCase):
|
||||
items = item_pool[0]
|
||||
all_except = item_pool[1] if len(item_pool) > 1 else None
|
||||
state = self._get_items(item_pool, all_except)
|
||||
path = self.get_path(state, self.world.get_location(location, 1).parent_region)
|
||||
path = self.get_path(state, self.multiworld.get_location(location, 1).parent_region)
|
||||
with self.subTest(msg="Reach Location", location=location, access=access, items=items,
|
||||
all_except=all_except, path=path, entry=i):
|
||||
|
||||
self.assertEqual(self.world.get_location(location, 1).can_reach(state), access)
|
||||
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access)
|
||||
|
||||
# check for partial solution
|
||||
if not all_except and access: # we are not supposed to be able to reach location with partial inventory
|
||||
@@ -56,18 +56,18 @@ class TestBase(unittest.TestCase):
|
||||
with self.subTest(msg="Location reachable without required item", location=location,
|
||||
items=item_pool[0], missing_item=missing_item, entry=i):
|
||||
state = self._get_items_partial(item_pool, missing_item)
|
||||
self.assertEqual(self.world.get_location(location, 1).can_reach(state), False)
|
||||
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), False)
|
||||
|
||||
def run_entrance_tests(self, access_pool):
|
||||
for i, (entrance, access, *item_pool) in enumerate(access_pool):
|
||||
items = item_pool[0]
|
||||
all_except = item_pool[1] if len(item_pool) > 1 else None
|
||||
state = self._get_items(item_pool, all_except)
|
||||
path = self.get_path(state, self.world.get_entrance(entrance, 1).parent_region)
|
||||
path = self.get_path(state, self.multiworld.get_entrance(entrance, 1).parent_region)
|
||||
with self.subTest(msg="Reach Entrance", entrance=entrance, access=access, items=items,
|
||||
all_except=all_except, path=path, entry=i):
|
||||
|
||||
self.assertEqual(self.world.get_entrance(entrance, 1).can_reach(state), access)
|
||||
self.assertEqual(self.multiworld.get_entrance(entrance, 1).can_reach(state), access)
|
||||
|
||||
# check for partial solution
|
||||
if not all_except and access: # we are not supposed to be able to reach location with partial inventory
|
||||
@@ -75,11 +75,11 @@ class TestBase(unittest.TestCase):
|
||||
with self.subTest(msg="Entrance reachable without required item", entrance=entrance,
|
||||
items=item_pool[0], missing_item=missing_item, entry=i):
|
||||
state = self._get_items_partial(item_pool, missing_item)
|
||||
self.assertEqual(self.world.get_entrance(entrance, 1).can_reach(state), False)
|
||||
self.assertEqual(self.multiworld.get_entrance(entrance, 1).can_reach(state), False)
|
||||
|
||||
def _get_items(self, item_pool, all_except):
|
||||
if all_except and len(all_except) > 0:
|
||||
items = self.world.itempool[:]
|
||||
items = self.multiworld.itempool[:]
|
||||
items = [item for item in items if
|
||||
item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)]
|
||||
items.extend(ItemFactory(item_pool[0], 1))
|
||||
|
||||
@@ -14,46 +14,46 @@ from worlds import AutoWorld
|
||||
|
||||
class TestDungeon(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.world = MultiWorld(1)
|
||||
self.multiworld = MultiWorld(1)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
self.world.set_options(args)
|
||||
self.world.set_default_common_options()
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
self.starting_regions = [] # Where to start exploring
|
||||
self.remove_exits = [] # Block dungeon exits
|
||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||
create_regions(self.world, 1)
|
||||
create_dungeons(self.world, 1)
|
||||
create_shops(self.world, 1)
|
||||
self.multiworld.difficulty_requirements[1] = difficulties['normal']
|
||||
create_regions(self.multiworld, 1)
|
||||
create_dungeons(self.multiworld, 1)
|
||||
create_shops(self.multiworld, 1)
|
||||
for exitname, regionname in mandatory_connections:
|
||||
connect_simple(self.world, exitname, regionname, 1)
|
||||
connect_simple(self.world, 'Big Bomb Shop', 'Big Bomb Shop', 1)
|
||||
self.world.get_region('Menu', 1).exits = []
|
||||
self.world.swamp_patch_required[1] = True
|
||||
self.world.worlds[1].set_rules()
|
||||
self.world.worlds[1].create_items()
|
||||
self.world.itempool.extend(get_dungeon_item_pool(self.world))
|
||||
self.world.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
|
||||
connect_simple(self.multiworld, exitname, regionname, 1)
|
||||
connect_simple(self.multiworld, 'Big Bomb Shop', 'Big Bomb Shop', 1)
|
||||
self.multiworld.get_region('Menu', 1).exits = []
|
||||
self.multiworld.swamp_patch_required[1] = True
|
||||
self.multiworld.worlds[1].set_rules()
|
||||
self.multiworld.worlds[1].create_items()
|
||||
self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld))
|
||||
self.multiworld.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
|
||||
|
||||
def run_tests(self, access_pool):
|
||||
for exit in self.remove_exits:
|
||||
self.world.get_entrance(exit, 1).connected_region = self.world.get_region('Menu', 1)
|
||||
self.multiworld.get_entrance(exit, 1).connected_region = self.multiworld.get_region('Menu', 1)
|
||||
|
||||
for location, access, *item_pool in access_pool:
|
||||
items = item_pool[0]
|
||||
all_except = item_pool[1] if len(item_pool) > 1 else None
|
||||
with self.subTest(location=location, access=access, items=items, all_except=all_except):
|
||||
if all_except and len(all_except) > 0:
|
||||
items = self.world.itempool[:]
|
||||
items = self.multiworld.itempool[:]
|
||||
items = [item for item in items if item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)]
|
||||
items.extend(ItemFactory(item_pool[0], 1))
|
||||
else:
|
||||
items = ItemFactory(items, 1)
|
||||
state = CollectionState(self.world)
|
||||
state.reachable_regions[1].add(self.world.get_region('Menu', 1))
|
||||
state = CollectionState(self.multiworld)
|
||||
state.reachable_regions[1].add(self.multiworld.get_region('Menu', 1))
|
||||
for region_name in self.starting_regions:
|
||||
region = self.world.get_region(region_name, 1)
|
||||
region = self.multiworld.get_region(region_name, 1)
|
||||
state.reachable_regions[1].add(region)
|
||||
for exit in region.exits:
|
||||
if exit.connected_region is not None:
|
||||
@@ -63,4 +63,4 @@ class TestDungeon(unittest.TestCase):
|
||||
item.classification = ItemClassification.progression
|
||||
state.collect(item)
|
||||
|
||||
self.assertEqual(self.world.get_location(location, 1).can_reach(state), access)
|
||||
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access)
|
||||
@@ -27,7 +27,7 @@ def generate_multi_world(players: int = 1) -> MultiWorld:
|
||||
|
||||
|
||||
class PlayerDefinition(object):
|
||||
world: MultiWorld
|
||||
multiworld: MultiWorld
|
||||
id: int
|
||||
menu: Region
|
||||
locations: List[Location]
|
||||
@@ -36,7 +36,7 @@ class PlayerDefinition(object):
|
||||
regions: List[Region]
|
||||
|
||||
def __init__(self, world: MultiWorld, id: int, menu: Region, locations: List[Location] = [], prog_items: List[Item] = [], basic_items: List[Item] = []):
|
||||
self.world = world
|
||||
self.multiworld = world
|
||||
self.id = id
|
||||
self.menu = menu
|
||||
self.locations = locations
|
||||
@@ -48,7 +48,7 @@ class PlayerDefinition(object):
|
||||
region_tag = "_region" + str(len(self.regions))
|
||||
region_name = "player" + str(self.id) + region_tag
|
||||
region = Region("player" + str(self.id) + region_tag, RegionType.Generic,
|
||||
"Region Hint", self.id, self.world)
|
||||
"Region Hint", self.id, self.multiworld)
|
||||
self.locations += generate_locations(size, self.id, None, region, region_tag)
|
||||
|
||||
entrance = Entrance(self.id, region_name + "_entrance", parent)
|
||||
@@ -57,7 +57,7 @@ class PlayerDefinition(object):
|
||||
entrance.access_rule = access_rule
|
||||
|
||||
self.regions.append(region)
|
||||
self.world.regions.append(region)
|
||||
self.multiworld.regions.append(region)
|
||||
|
||||
return region
|
||||
|
||||
|
||||
@@ -7,15 +7,15 @@ gen_steps = ["generate_early", "create_regions", "create_items", "set_rules", "g
|
||||
|
||||
|
||||
def setup_default_world(world_type) -> MultiWorld:
|
||||
world = MultiWorld(1)
|
||||
world.game[1] = world_type.game
|
||||
world.player_name = {1: "Tester"}
|
||||
world.set_seed()
|
||||
multiworld = MultiWorld(1)
|
||||
multiworld.game[1] = world_type.game
|
||||
multiworld.player_name = {1: "Tester"}
|
||||
multiworld.set_seed()
|
||||
args = Namespace()
|
||||
for name, option in world_type.option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
world.set_options(args)
|
||||
world.set_default_common_options()
|
||||
multiworld.set_options(args)
|
||||
multiworld.set_default_common_options()
|
||||
for step in gen_steps:
|
||||
call_all(world, step)
|
||||
return world
|
||||
call_all(multiworld, step)
|
||||
return multiworld
|
||||
|
||||
@@ -14,23 +14,23 @@ from worlds import AutoWorld
|
||||
|
||||
class TestInverted(TestBase):
|
||||
def setUp(self):
|
||||
self.world = MultiWorld(1)
|
||||
self.multiworld = MultiWorld(1)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
self.world.set_options(args)
|
||||
self.world.set_default_common_options()
|
||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||
self.world.mode[1] = "inverted"
|
||||
create_inverted_regions(self.world, 1)
|
||||
create_dungeons(self.world, 1)
|
||||
create_shops(self.world, 1)
|
||||
link_inverted_entrances(self.world, 1)
|
||||
self.world.worlds[1].create_items()
|
||||
self.world.required_medallions[1] = ['Ether', 'Quake']
|
||||
self.world.itempool.extend(get_dungeon_item_pool(self.world))
|
||||
self.world.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
|
||||
self.world.get_location('Agahnim 1', 1).item = None
|
||||
self.world.get_location('Agahnim 2', 1).item = None
|
||||
mark_light_world_regions(self.world, 1)
|
||||
self.world.worlds[1].set_rules()
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
self.multiworld.difficulty_requirements[1] = difficulties['normal']
|
||||
self.multiworld.mode[1] = "inverted"
|
||||
create_inverted_regions(self.multiworld, 1)
|
||||
create_dungeons(self.multiworld, 1)
|
||||
create_shops(self.multiworld, 1)
|
||||
link_inverted_entrances(self.multiworld, 1)
|
||||
self.multiworld.worlds[1].create_items()
|
||||
self.multiworld.required_medallions[1] = ['Ether', 'Quake']
|
||||
self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld))
|
||||
self.multiworld.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
|
||||
self.multiworld.get_location('Agahnim 1', 1).item = None
|
||||
self.multiworld.get_location('Agahnim 2', 1).item = None
|
||||
mark_light_world_regions(self.multiworld, 1)
|
||||
self.multiworld.worlds[1].set_rules()
|
||||
|
||||
@@ -14,16 +14,16 @@ from worlds import AutoWorld
|
||||
class TestInvertedBombRules(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.world = MultiWorld(1)
|
||||
self.world.mode[1] = "inverted"
|
||||
self.multiworld = MultiWorld(1)
|
||||
self.multiworld.mode[1] = "inverted"
|
||||
args = Namespace
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
self.world.set_options(args)
|
||||
self.world.set_default_common_options()
|
||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||
create_inverted_regions(self.world, 1)
|
||||
create_dungeons(self.world, 1)
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
self.multiworld.difficulty_requirements[1] = difficulties['normal']
|
||||
create_inverted_regions(self.multiworld, 1)
|
||||
create_dungeons(self.multiworld, 1)
|
||||
|
||||
#TODO: Just making sure I haven't missed an entrance. It would be good to test the rules make sense as well.
|
||||
def testInvertedBombRulesAreComplete(self):
|
||||
@@ -31,9 +31,9 @@ class TestInvertedBombRules(unittest.TestCase):
|
||||
must_exits = list(Inverted_LW_Entrances_Must_Exit + Inverted_LW_Dungeon_Entrances_Must_Exit)
|
||||
for entrance_name in (entrances + must_exits):
|
||||
if entrance_name not in ['Desert Palace Entrance (East)', 'Spectacle Rock Cave', 'Spectacle Rock Cave (Bottom)']:
|
||||
entrance = self.world.get_entrance(entrance_name, 1)
|
||||
connect_entrance(self.world, entrance_name, 'Inverted Big Bomb Shop', 1)
|
||||
set_inverted_big_bomb_rules(self.world, 1)
|
||||
entrance = self.multiworld.get_entrance(entrance_name, 1)
|
||||
connect_entrance(self.multiworld, entrance_name, 'Inverted Big Bomb Shop', 1)
|
||||
set_inverted_big_bomb_rules(self.multiworld, 1)
|
||||
entrance.connected_region.entrances.remove(entrance)
|
||||
entrance.connected_region = None
|
||||
|
||||
@@ -45,9 +45,9 @@ class TestInvertedBombRules(unittest.TestCase):
|
||||
|
||||
def testInvalidEntrances(self):
|
||||
for entrance_name in ['Desert Palace Entrance (East)', 'Spectacle Rock Cave', 'Spectacle Rock Cave (Bottom)']:
|
||||
entrance = self.world.get_entrance(entrance_name, 1)
|
||||
connect_entrance(self.world, entrance_name, 'Inverted Big Bomb Shop', 1)
|
||||
entrance = self.multiworld.get_entrance(entrance_name, 1)
|
||||
connect_entrance(self.multiworld, entrance_name, 'Inverted Big Bomb Shop', 1)
|
||||
with self.assertRaises(Exception):
|
||||
set_inverted_big_bomb_rules(self.world, 1)
|
||||
set_inverted_big_bomb_rules(self.multiworld, 1)
|
||||
entrance.connected_region.entrances.remove(entrance)
|
||||
entrance.connected_region = None
|
||||
|
||||
@@ -15,24 +15,24 @@ from worlds import AutoWorld
|
||||
|
||||
class TestInvertedMinor(TestBase):
|
||||
def setUp(self):
|
||||
self.world = MultiWorld(1)
|
||||
self.multiworld = MultiWorld(1)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
self.world.set_options(args)
|
||||
self.world.set_default_common_options()
|
||||
self.world.mode[1] = "inverted"
|
||||
self.world.logic[1] = "minorglitches"
|
||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||
create_inverted_regions(self.world, 1)
|
||||
create_dungeons(self.world, 1)
|
||||
create_shops(self.world, 1)
|
||||
link_inverted_entrances(self.world, 1)
|
||||
self.world.worlds[1].create_items()
|
||||
self.world.required_medallions[1] = ['Ether', 'Quake']
|
||||
self.world.itempool.extend(get_dungeon_item_pool(self.world))
|
||||
self.world.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
|
||||
self.world.get_location('Agahnim 1', 1).item = None
|
||||
self.world.get_location('Agahnim 2', 1).item = None
|
||||
mark_light_world_regions(self.world, 1)
|
||||
self.world.worlds[1].set_rules()
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
self.multiworld.mode[1] = "inverted"
|
||||
self.multiworld.logic[1] = "minorglitches"
|
||||
self.multiworld.difficulty_requirements[1] = difficulties['normal']
|
||||
create_inverted_regions(self.multiworld, 1)
|
||||
create_dungeons(self.multiworld, 1)
|
||||
create_shops(self.multiworld, 1)
|
||||
link_inverted_entrances(self.multiworld, 1)
|
||||
self.multiworld.worlds[1].create_items()
|
||||
self.multiworld.required_medallions[1] = ['Ether', 'Quake']
|
||||
self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld))
|
||||
self.multiworld.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
|
||||
self.multiworld.get_location('Agahnim 1', 1).item = None
|
||||
self.multiworld.get_location('Agahnim 2', 1).item = None
|
||||
mark_light_world_regions(self.multiworld, 1)
|
||||
self.multiworld.worlds[1].set_rules()
|
||||
|
||||
@@ -16,26 +16,26 @@ from worlds import AutoWorld
|
||||
|
||||
class TestInvertedOWG(TestBase):
|
||||
def setUp(self):
|
||||
self.world = MultiWorld(1)
|
||||
self.multiworld = MultiWorld(1)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
self.world.set_options(args)
|
||||
self.world.set_default_common_options()
|
||||
self.world.logic[1] = "owglitches"
|
||||
self.world.mode[1] = "inverted"
|
||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||
create_inverted_regions(self.world, 1)
|
||||
create_dungeons(self.world, 1)
|
||||
create_shops(self.world, 1)
|
||||
link_inverted_entrances(self.world, 1)
|
||||
self.world.worlds[1].create_items()
|
||||
self.world.required_medallions[1] = ['Ether', 'Quake']
|
||||
self.world.itempool.extend(get_dungeon_item_pool(self.world))
|
||||
self.world.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
|
||||
self.world.get_location('Agahnim 1', 1).item = None
|
||||
self.world.get_location('Agahnim 2', 1).item = None
|
||||
self.world.precollected_items[1].clear()
|
||||
self.world.itempool.append(ItemFactory('Pegasus Boots', 1))
|
||||
mark_light_world_regions(self.world, 1)
|
||||
self.world.worlds[1].set_rules()
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
self.multiworld.logic[1] = "owglitches"
|
||||
self.multiworld.mode[1] = "inverted"
|
||||
self.multiworld.difficulty_requirements[1] = difficulties['normal']
|
||||
create_inverted_regions(self.multiworld, 1)
|
||||
create_dungeons(self.multiworld, 1)
|
||||
create_shops(self.multiworld, 1)
|
||||
link_inverted_entrances(self.multiworld, 1)
|
||||
self.multiworld.worlds[1].create_items()
|
||||
self.multiworld.required_medallions[1] = ['Ether', 'Quake']
|
||||
self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld))
|
||||
self.multiworld.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
|
||||
self.multiworld.get_location('Agahnim 1', 1).item = None
|
||||
self.multiworld.get_location('Agahnim 2', 1).item = None
|
||||
self.multiworld.precollected_items[1].clear()
|
||||
self.multiworld.itempool.append(ItemFactory('Pegasus Boots', 1))
|
||||
mark_light_world_regions(self.multiworld, 1)
|
||||
self.multiworld.worlds[1].set_rules()
|
||||
|
||||
@@ -30,28 +30,28 @@ def MCItemFactory(items, player: int):
|
||||
class TestMinecraft(TestBase):
|
||||
|
||||
def setUp(self):
|
||||
self.world = MultiWorld(1)
|
||||
self.world.game[1] = "Minecraft"
|
||||
self.world.worlds[1] = MinecraftWorld(self.world, 1)
|
||||
self.multiworld = MultiWorld(1)
|
||||
self.multiworld.game[1] = "Minecraft"
|
||||
self.multiworld.worlds[1] = MinecraftWorld(self.multiworld, 1)
|
||||
exclusion_pools = ['hard', 'unreasonable', 'postgame']
|
||||
for pool in exclusion_pools:
|
||||
setattr(self.world, f"include_{pool}_advancements", {1: False})
|
||||
setattr(self.world, "advancement_goal", {1: AdvancementGoal(30)})
|
||||
setattr(self.world, "egg_shards_required", {1: EggShardsRequired(0)})
|
||||
setattr(self.world, "egg_shards_available", {1: EggShardsAvailable(0)})
|
||||
setattr(self.world, "required_bosses", {1: BossGoal(1)}) # ender dragon
|
||||
setattr(self.world, "shuffle_structures", {1: ShuffleStructures(False)})
|
||||
setattr(self.world, "bee_traps", {1: BeeTraps(0)})
|
||||
setattr(self.world, "combat_difficulty", {1: CombatDifficulty(1)}) # normal
|
||||
setattr(self.world, "structure_compasses", {1: Toggle(False)})
|
||||
setattr(self.world, "death_link", {1: Toggle(False)})
|
||||
AutoWorld.call_single(self.world, "create_regions", 1)
|
||||
AutoWorld.call_single(self.world, "generate_basic", 1)
|
||||
AutoWorld.call_single(self.world, "set_rules", 1)
|
||||
setattr(self.multiworld, f"include_{pool}_advancements", {1: False})
|
||||
setattr(self.multiworld, "advancement_goal", {1: AdvancementGoal(30)})
|
||||
setattr(self.multiworld, "egg_shards_required", {1: EggShardsRequired(0)})
|
||||
setattr(self.multiworld, "egg_shards_available", {1: EggShardsAvailable(0)})
|
||||
setattr(self.multiworld, "required_bosses", {1: BossGoal(1)}) # ender dragon
|
||||
setattr(self.multiworld, "shuffle_structures", {1: ShuffleStructures(False)})
|
||||
setattr(self.multiworld, "bee_traps", {1: BeeTraps(0)})
|
||||
setattr(self.multiworld, "combat_difficulty", {1: CombatDifficulty(1)}) # normal
|
||||
setattr(self.multiworld, "structure_compasses", {1: Toggle(False)})
|
||||
setattr(self.multiworld, "death_link", {1: Toggle(False)})
|
||||
AutoWorld.call_single(self.multiworld, "create_regions", 1)
|
||||
AutoWorld.call_single(self.multiworld, "generate_basic", 1)
|
||||
AutoWorld.call_single(self.multiworld, "set_rules", 1)
|
||||
|
||||
def _get_items(self, item_pool, all_except):
|
||||
if all_except and len(all_except) > 0:
|
||||
items = self.world.itempool[:]
|
||||
items = self.multiworld.itempool[:]
|
||||
items = [item for item in items if
|
||||
item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)]
|
||||
items.extend(MCItemFactory(item_pool[0], 1))
|
||||
|
||||
@@ -15,23 +15,23 @@ from worlds import AutoWorld
|
||||
|
||||
class TestMinor(TestBase):
|
||||
def setUp(self):
|
||||
self.world = MultiWorld(1)
|
||||
self.multiworld = MultiWorld(1)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
self.world.set_options(args)
|
||||
self.world.set_default_common_options()
|
||||
self.world.logic[1] = "minorglitches"
|
||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||
self.world.worlds[1].er_seed = 0
|
||||
self.world.worlds[1].create_regions()
|
||||
self.world.worlds[1].create_items()
|
||||
self.world.required_medallions[1] = ['Ether', 'Quake']
|
||||
self.world.itempool.extend(get_dungeon_item_pool(self.world))
|
||||
self.world.itempool.extend(ItemFactory(
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
self.multiworld.logic[1] = "minorglitches"
|
||||
self.multiworld.difficulty_requirements[1] = difficulties['normal']
|
||||
self.multiworld.worlds[1].er_seed = 0
|
||||
self.multiworld.worlds[1].create_regions()
|
||||
self.multiworld.worlds[1].create_items()
|
||||
self.multiworld.required_medallions[1] = ['Ether', 'Quake']
|
||||
self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld))
|
||||
self.multiworld.itempool.extend(ItemFactory(
|
||||
['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1',
|
||||
'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
|
||||
self.world.get_location('Agahnim 1', 1).item = None
|
||||
self.world.get_location('Agahnim 2', 1).item = None
|
||||
mark_dark_world_regions(self.world, 1)
|
||||
self.world.worlds[1].set_rules()
|
||||
self.multiworld.get_location('Agahnim 1', 1).item = None
|
||||
self.multiworld.get_location('Agahnim 2', 1).item = None
|
||||
mark_dark_world_regions(self.multiworld, 1)
|
||||
self.multiworld.worlds[1].set_rules()
|
||||
|
||||
@@ -16,23 +16,23 @@ from worlds import AutoWorld
|
||||
|
||||
class TestVanillaOWG(TestBase):
|
||||
def setUp(self):
|
||||
self.world = MultiWorld(1)
|
||||
self.multiworld = MultiWorld(1)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
self.world.set_options(args)
|
||||
self.world.set_default_common_options()
|
||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||
self.world.logic[1] = "owglitches"
|
||||
self.world.worlds[1].er_seed = 0
|
||||
self.world.worlds[1].create_regions()
|
||||
self.world.worlds[1].create_items()
|
||||
self.world.required_medallions[1] = ['Ether', 'Quake']
|
||||
self.world.itempool.extend(get_dungeon_item_pool(self.world))
|
||||
self.world.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
|
||||
self.world.get_location('Agahnim 1', 1).item = None
|
||||
self.world.get_location('Agahnim 2', 1).item = None
|
||||
self.world.precollected_items[1].clear()
|
||||
self.world.itempool.append(ItemFactory('Pegasus Boots', 1))
|
||||
mark_dark_world_regions(self.world, 1)
|
||||
self.world.worlds[1].set_rules()
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
self.multiworld.difficulty_requirements[1] = difficulties['normal']
|
||||
self.multiworld.logic[1] = "owglitches"
|
||||
self.multiworld.worlds[1].er_seed = 0
|
||||
self.multiworld.worlds[1].create_regions()
|
||||
self.multiworld.worlds[1].create_items()
|
||||
self.multiworld.required_medallions[1] = ['Ether', 'Quake']
|
||||
self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld))
|
||||
self.multiworld.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
|
||||
self.multiworld.get_location('Agahnim 1', 1).item = None
|
||||
self.multiworld.get_location('Agahnim 2', 1).item = None
|
||||
self.multiworld.precollected_items[1].clear()
|
||||
self.multiworld.itempool.append(ItemFactory('Pegasus Boots', 1))
|
||||
mark_dark_world_regions(self.multiworld, 1)
|
||||
self.multiworld.worlds[1].set_rules()
|
||||
40
test/programs/TestMultiServer.py
Normal file
40
test/programs/TestMultiServer.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import unittest
|
||||
from MultiServer import Context, ServerCommandProcessor
|
||||
|
||||
|
||||
class TestResolvePlayerName(unittest.TestCase):
|
||||
def test_resolve(self) -> None:
|
||||
p = ServerCommandProcessor(Context("", 0, "", "", 0, 0, False))
|
||||
p.ctx.player_names = {
|
||||
(1, 1): "AAA",
|
||||
(1, 2): "aBc",
|
||||
(1, 3): "abC",
|
||||
}
|
||||
assert not p.resolve_player("abc"), "ambiguous name entry shouldn't resolve to player"
|
||||
assert not p.resolve_player("Abc"), "ambiguous name entry shouldn't resolve to player"
|
||||
assert p.resolve_player("aBc") == (1, 2, "aBc"), "matching case resolve"
|
||||
assert p.resolve_player("abC") == (1, 3, "abC"), "matching case resolve"
|
||||
assert not p.resolve_player("aB"), "partial name shouldn't resolve to player"
|
||||
assert not p.resolve_player("abCD"), "incorrect name shouldn't resolve to player"
|
||||
|
||||
p.ctx.player_names = {
|
||||
(1, 1): "aaa",
|
||||
(1, 2): "abc",
|
||||
(1, 3): "abC",
|
||||
}
|
||||
assert p.resolve_player("abc") == (1, 2, "abc"), "matching case resolve"
|
||||
assert not p.resolve_player("Abc"), "ambiguous name entry shouldn't resolve to player"
|
||||
assert not p.resolve_player("aBc"), "ambiguous name entry shouldn't resolve to player"
|
||||
assert p.resolve_player("abC") == (1, 3, "abC"), "matching case resolve"
|
||||
|
||||
p.ctx.player_names = {
|
||||
(1, 1): "AbcdE",
|
||||
(1, 2): "abc",
|
||||
(1, 3): "abCD",
|
||||
}
|
||||
assert p.resolve_player("abc") == (1, 2, "abc"), "matching case resolve"
|
||||
assert p.resolve_player("abC") == (1, 2, "abc"), "case insensitive resolves when 1 match"
|
||||
assert p.resolve_player("Abc") == (1, 2, "abc"), "case insensitive resolves when 1 match"
|
||||
assert p.resolve_player("ABC") == (1, 2, "abc"), "case insensitive resolves when 1 match"
|
||||
assert p.resolve_player("abcd") == (1, 3, "abCD"), "case insensitive resolves when 1 match"
|
||||
assert not p.resolve_player("aB"), "partial name shouldn't resolve to player"
|
||||
@@ -14,21 +14,21 @@ from worlds import AutoWorld
|
||||
|
||||
class TestVanilla(TestBase):
|
||||
def setUp(self):
|
||||
self.world = MultiWorld(1)
|
||||
self.multiworld = MultiWorld(1)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].option_definitions.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
self.world.set_options(args)
|
||||
self.world.set_default_common_options()
|
||||
self.world.logic[1] = "noglitches"
|
||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||
self.world.worlds[1].er_seed = 0
|
||||
self.world.worlds[1].create_regions()
|
||||
self.world.worlds[1].create_items()
|
||||
self.world.required_medallions[1] = ['Ether', 'Quake']
|
||||
self.world.itempool.extend(get_dungeon_item_pool(self.world))
|
||||
self.world.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
|
||||
self.world.get_location('Agahnim 1', 1).item = None
|
||||
self.world.get_location('Agahnim 2', 1).item = None
|
||||
mark_dark_world_regions(self.world, 1)
|
||||
self.world.worlds[1].set_rules()
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
self.multiworld.logic[1] = "noglitches"
|
||||
self.multiworld.difficulty_requirements[1] = difficulties['normal']
|
||||
self.multiworld.worlds[1].er_seed = 0
|
||||
self.multiworld.worlds[1].create_regions()
|
||||
self.multiworld.worlds[1].create_items()
|
||||
self.multiworld.required_medallions[1] = ['Ether', 'Quake']
|
||||
self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld))
|
||||
self.multiworld.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
|
||||
self.multiworld.get_location('Agahnim 1', 1).item = None
|
||||
self.multiworld.get_location('Agahnim 2', 1).item = None
|
||||
mark_dark_world_regions(self.multiworld, 1)
|
||||
self.multiworld.worlds[1].set_rules()
|
||||
23
test/worlds/rogue_legacy/TestUnique.py
Normal file
23
test/worlds/rogue_legacy/TestUnique.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from typing import Dict
|
||||
|
||||
from . import RLTestBase
|
||||
from worlds.rogue_legacy.Items import RLItemData, item_table
|
||||
from worlds.rogue_legacy.Locations import RLLocationData, location_table
|
||||
|
||||
|
||||
class UniqueTest(RLTestBase):
|
||||
@staticmethod
|
||||
def test_item_ids_are_all_unique():
|
||||
item_ids: Dict[int, str] = {}
|
||||
for name, data in item_table.items():
|
||||
assert data.code not in item_ids.keys(), f"'{name}': {data.code}, is not unique. " \
|
||||
f"'{item_ids[data.code]}' also has this identifier."
|
||||
item_ids[data.code] = name
|
||||
|
||||
@staticmethod
|
||||
def test_location_ids_are_all_unique():
|
||||
location_ids: Dict[int, str] = {}
|
||||
for name, data in location_table.items():
|
||||
assert data.code not in location_ids.keys(), f"'{name}': {data.code}, is not unique. " \
|
||||
f"'{location_ids[data.code]}' also has this identifier."
|
||||
location_ids[data.code] = name
|
||||
5
test/worlds/rogue_legacy/__init__.py
Normal file
5
test/worlds/rogue_legacy/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from test.worlds.test_base import WorldTestBase
|
||||
|
||||
|
||||
class RLTestBase(WorldTestBase):
|
||||
game = "Rogue Legacy"
|
||||
@@ -22,29 +22,29 @@ class WorldTestBase(unittest.TestCase):
|
||||
def world_setup(self, seed: typing.Optional[int] = None) -> None:
|
||||
if not hasattr(self, "game"):
|
||||
raise NotImplementedError("didn't define game name")
|
||||
self.world = MultiWorld(1)
|
||||
self.world.game[1] = self.game
|
||||
self.world.player_name = {1: "Tester"}
|
||||
self.world.set_seed(seed)
|
||||
self.multiworld = MultiWorld(1)
|
||||
self.multiworld.game[1] = self.game
|
||||
self.multiworld.player_name = {1: "Tester"}
|
||||
self.multiworld.set_seed(seed)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types[self.game].option_definitions.items():
|
||||
setattr(args, name, {
|
||||
1: option.from_any(self.options.get(name, getattr(option, "default")))
|
||||
})
|
||||
self.world.set_options(args)
|
||||
self.world.set_default_common_options()
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.set_default_common_options()
|
||||
for step in gen_steps:
|
||||
call_all(self.world, step)
|
||||
call_all(self.multiworld, step)
|
||||
|
||||
def collect_all_but(self, item_names: typing.Union[str, typing.Iterable[str]]) -> None:
|
||||
if isinstance(item_names, str):
|
||||
item_names = (item_names,)
|
||||
for item in self.world.get_items():
|
||||
for item in self.multiworld.get_items():
|
||||
if item.name not in item_names:
|
||||
self.world.state.collect(item)
|
||||
self.multiworld.state.collect(item)
|
||||
|
||||
def get_item_by_name(self, item_name: str) -> Item:
|
||||
for item in self.world.get_items():
|
||||
for item in self.multiworld.get_items():
|
||||
if item.name == item_name:
|
||||
return item
|
||||
raise ValueError("No such item")
|
||||
@@ -52,7 +52,7 @@ class WorldTestBase(unittest.TestCase):
|
||||
def get_items_by_name(self, item_names: typing.Union[str, typing.Iterable[str]]) -> typing.List[Item]:
|
||||
if isinstance(item_names, str):
|
||||
item_names = (item_names,)
|
||||
return [item for item in self.world.itempool if item.name in item_names]
|
||||
return [item for item in self.multiworld.itempool if item.name in item_names]
|
||||
|
||||
def collect_by_name(self, item_names: typing.Union[str, typing.Iterable[str]]) -> typing.List[Item]:
|
||||
""" collect all of the items in the item pool that have the given names """
|
||||
@@ -64,21 +64,21 @@ class WorldTestBase(unittest.TestCase):
|
||||
if isinstance(items, Item):
|
||||
items = (items,)
|
||||
for item in items:
|
||||
self.world.state.collect(item)
|
||||
self.multiworld.state.collect(item)
|
||||
|
||||
def remove(self, items: typing.Union[Item, typing.Iterable[Item]]) -> None:
|
||||
if isinstance(items, Item):
|
||||
items = (items,)
|
||||
for item in items:
|
||||
if item.location and item.location.event and item.location in self.world.state.events:
|
||||
self.world.state.events.remove(item.location)
|
||||
self.world.state.remove(item)
|
||||
if item.location and item.location.event and item.location in self.multiworld.state.events:
|
||||
self.multiworld.state.events.remove(item.location)
|
||||
self.multiworld.state.remove(item)
|
||||
|
||||
def can_reach_location(self, location: str) -> bool:
|
||||
return self.world.state.can_reach(location, "Location", 1)
|
||||
return self.multiworld.state.can_reach(location, "Location", 1)
|
||||
|
||||
def count(self, item_name: str) -> int:
|
||||
return self.world.state.count(item_name, 1)
|
||||
return self.multiworld.state.count(item_name, 1)
|
||||
|
||||
def assertAccessDependency(self,
|
||||
locations: typing.List[str],
|
||||
@@ -86,8 +86,8 @@ class WorldTestBase(unittest.TestCase):
|
||||
all_items = [item_name for item_names in possible_items for item_name in item_names]
|
||||
|
||||
self.collect_all_but(all_items)
|
||||
for location in self.world.get_locations():
|
||||
self.assertEqual(self.world.state.can_reach(location), location.name not in locations)
|
||||
for location in self.multiworld.get_locations():
|
||||
self.assertEqual(self.multiworld.state.can_reach(location), location.name not in locations)
|
||||
for item_names in possible_items:
|
||||
items = self.collect_by_name(item_names)
|
||||
for location in locations:
|
||||
@@ -95,4 +95,4 @@ class WorldTestBase(unittest.TestCase):
|
||||
self.remove(items)
|
||||
|
||||
def assertBeatable(self, beatable: bool):
|
||||
self.assertEqual(self.world.can_beat_game(self.world.state), beatable)
|
||||
self.assertEqual(self.multiworld.can_beat_game(self.multiworld.state), beatable)
|
||||
|
||||
@@ -9,7 +9,7 @@ class OptionsTest(ZillionTestBase):
|
||||
|
||||
def test_validate_default(self) -> None:
|
||||
self.world_setup()
|
||||
validate(self.world, 1)
|
||||
validate(self.multiworld, 1)
|
||||
|
||||
def test_vblr_ap_to_zz(self) -> None:
|
||||
""" all of the valid values for the AP options map to valid values for ZZ options """
|
||||
@@ -20,7 +20,7 @@ class OptionsTest(ZillionTestBase):
|
||||
for value in vblr_class.name_lookup.values():
|
||||
self.options = {option_name: value}
|
||||
self.world_setup()
|
||||
zz_options, _item_counts = validate(self.world, 1)
|
||||
zz_options, _item_counts = validate(self.multiworld, 1)
|
||||
assert getattr(zz_options, option_name) in VBLR_CHOICES
|
||||
|
||||
# TODO: test validate with invalid combinations of options
|
||||
|
||||
@@ -13,7 +13,7 @@ class ZillionTestBase(WorldTestBase):
|
||||
This makes sure that gun 3 is required by making all the canisters
|
||||
in O-7 (including key word canisters) require gun 3.
|
||||
"""
|
||||
zz_world = cast(ZillionWorld, self.world.worlds[1])
|
||||
zz_world = cast(ZillionWorld, self.multiworld.worlds[1])
|
||||
assert zz_world.zz_system.randomizer
|
||||
for zz_loc_name, zz_loc in zz_world.zz_system.randomizer.locations.items():
|
||||
if zz_loc_name.startswith("r15c6"):
|
||||
|
||||
@@ -182,7 +182,7 @@ class World(metaclass=AutoWorldRegister):
|
||||
web: WebWorld = WebWorld()
|
||||
|
||||
# autoset on creation:
|
||||
world: "MultiWorld"
|
||||
multiworld: "MultiWorld"
|
||||
player: int
|
||||
|
||||
# automatically generated
|
||||
@@ -196,7 +196,7 @@ class World(metaclass=AutoWorldRegister):
|
||||
__file__: str # path it was loaded from
|
||||
|
||||
def __init__(self, world: "MultiWorld", player: int):
|
||||
self.world = world
|
||||
self.multiworld = world
|
||||
self.player = player
|
||||
|
||||
# overridable methods that get called by Main.py, sorted by execution order
|
||||
@@ -287,7 +287,7 @@ class World(metaclass=AutoWorldRegister):
|
||||
def get_filler_item_name(self) -> str:
|
||||
"""Called when the item pool needs to be filled with additional items to match location count."""
|
||||
logging.warning(f"World {self} is generating a filler item without custom filler pool.")
|
||||
return self.world.random.choice(tuple(self.item_name_to_id.keys()))
|
||||
return self.multiworld.random.choice(tuple(self.item_name_to_id.keys()))
|
||||
|
||||
# decent place to implement progressive items, in most cases can stay as-is
|
||||
def collect_item(self, state: "CollectionState", item: "Item", remove: bool = False) -> Optional[str]:
|
||||
|
||||
@@ -81,7 +81,7 @@ def KholdstareDefeatRule(state, player: int) -> bool:
|
||||
state.has('Fire Rod', player) or
|
||||
(
|
||||
state.has('Bombos', player) and
|
||||
(state.has_sword(player) or state.world.swordless[player])
|
||||
(state.has_sword(player) or state.multiworld.swordless[player])
|
||||
)
|
||||
) and
|
||||
(
|
||||
@@ -90,7 +90,7 @@ def KholdstareDefeatRule(state, player: int) -> bool:
|
||||
(
|
||||
state.has('Fire Rod', player) and
|
||||
state.has('Bombos', player) and
|
||||
state.world.swordless[player] and
|
||||
state.multiworld.swordless[player] and
|
||||
state.can_extend_magic(player, 16)
|
||||
)
|
||||
)
|
||||
@@ -114,7 +114,7 @@ def AgahnimDefeatRule(state, player: int) -> bool:
|
||||
|
||||
|
||||
def GanonDefeatRule(state, player: int) -> bool:
|
||||
if state.world.swordless[player]:
|
||||
if state.multiworld.swordless[player]:
|
||||
return state.has('Hammer', player) and \
|
||||
state.has_fire_source(player) and \
|
||||
state.has('Silver Bow', player) and \
|
||||
@@ -123,7 +123,7 @@ def GanonDefeatRule(state, player: int) -> bool:
|
||||
can_hurt = state.has_beam_sword(player)
|
||||
common = can_hurt and state.has_fire_source(player)
|
||||
# silverless ganon may be needed in anything higher than no glitches
|
||||
if state.world.logic[player] != 'noglitches':
|
||||
if state.multiworld.logic[player] != 'noglitches':
|
||||
# need to light torch a sufficient amount of times
|
||||
return common and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (
|
||||
state.has('Silver Bow', player) and state.can_shoot_arrows(player)) or
|
||||
|
||||
@@ -18,7 +18,7 @@ def create_dungeons(world, player):
|
||||
dungeon.boss = BossFactory(default_boss, player) if default_boss else None
|
||||
for region in dungeon.regions:
|
||||
world.get_region(region, player).dungeon = dungeon
|
||||
dungeon.world = world
|
||||
dungeon.multiworld = world
|
||||
return dungeon
|
||||
|
||||
ES = make_dungeon('Hyrule Castle', None, ['Hyrule Castle', 'Sewers', 'Sewer Drop', 'Sewers (Dark)', 'Sanctuary'],
|
||||
|
||||
@@ -224,7 +224,7 @@ for diff in {'easy', 'normal', 'hard', 'expert'}:
|
||||
|
||||
def generate_itempool(world):
|
||||
player = world.player
|
||||
world = world.world
|
||||
world = world.multiworld
|
||||
|
||||
if world.difficulty[player] not in difficulties:
|
||||
raise NotImplementedError(f"Diffulty {world.difficulty[player]}")
|
||||
@@ -286,7 +286,7 @@ def generate_itempool(world):
|
||||
region = world.get_region('Light World', player)
|
||||
|
||||
loc = ALttPLocation(player, "Murahdahla", parent=region)
|
||||
loc.access_rule = lambda state: state.has_triforce_pieces(state.world.treasure_hunt_count[player], player)
|
||||
loc.access_rule = lambda state: state.has_triforce_pieces(state.multiworld.treasure_hunt_count[player], player)
|
||||
|
||||
region.locations.append(loc)
|
||||
world.clear_location_cache()
|
||||
|
||||
@@ -13,7 +13,7 @@ from worlds.alttp.Options import smallkey_shuffle
|
||||
|
||||
def set_rules(world):
|
||||
player = world.player
|
||||
world = world.world
|
||||
world = world.multiworld
|
||||
if world.logic[player] == 'nologic':
|
||||
if player == next(player_id for player_id in world.get_game_players("A Link to the Past")
|
||||
if world.logic[player_id] == 'nologic'): # only warn one time
|
||||
@@ -81,7 +81,7 @@ def set_rules(world):
|
||||
set_big_bomb_rules(world, player)
|
||||
if world.logic[player] in {'owglitches', 'hybridglitches', 'nologic'} and world.shuffle[player] not in {'insanity', 'insanity_legacy', 'madness'}:
|
||||
path_to_courtyard = mirrorless_path_to_castle_courtyard(world, player)
|
||||
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.world.get_entrance('Dark Death Mountain Offset Mirror', player).can_reach(state) and all(rule(state) for rule in path_to_courtyard), 'or')
|
||||
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.multiworld.get_entrance('Dark Death Mountain Offset Mirror', player).can_reach(state) and all(rule(state) for rule in path_to_courtyard), 'or')
|
||||
else:
|
||||
set_inverted_big_bomb_rules(world, player)
|
||||
|
||||
@@ -97,9 +97,9 @@ def set_rules(world):
|
||||
|
||||
set_trock_key_rules(world, player)
|
||||
|
||||
set_rule(ganons_tower, lambda state: state.has_crystals(state.world.crystals_needed_for_gt[player], player))
|
||||
set_rule(ganons_tower, lambda state: state.has_crystals(state.multiworld.crystals_needed_for_gt[player], player))
|
||||
if world.mode[player] != 'inverted' and world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']:
|
||||
add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.world.get_entrance('Ganons Tower Ascent', player).can_reach(state), 'or')
|
||||
add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.multiworld.get_entrance('Ganons Tower Ascent', player).can_reach(state), 'or')
|
||||
|
||||
set_bunny_rules(world, player, world.mode[player] == 'inverted')
|
||||
|
||||
@@ -204,7 +204,7 @@ def global_rules(world, player):
|
||||
((state.has('Cape', player) and state.can_extend_magic(player, 16, True)) or
|
||||
(state.has('Cane of Byrna', player) and
|
||||
(state.can_extend_magic(player, 12, True) or
|
||||
(state.world.can_take_damage[player] and (state.has('Pegasus Boots', player) or state.has_hearts(player, 4))))))
|
||||
(state.multiworld.can_take_damage[player] and (state.has('Pegasus Boots', player) or state.has_hearts(player, 4))))))
|
||||
)
|
||||
|
||||
set_rule(world.get_location('Hookshot Cave - Top Right', player), lambda state: state.has('Hookshot', player))
|
||||
@@ -242,12 +242,12 @@ def global_rules(world, player):
|
||||
set_rule(world.get_location('Desert Palace - Big Chest', player), lambda state: state.has('Big Key (Desert Palace)', player))
|
||||
set_rule(world.get_location('Desert Palace - Torch', player), lambda state: state.has('Pegasus Boots', player))
|
||||
set_rule(world.get_entrance('Desert Palace East Wing', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player))
|
||||
set_rule(world.get_location('Desert Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player) and state.has('Big Key (Desert Palace)', player) and state.has_fire_source(player) and state.world.get_location('Desert Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state))
|
||||
set_rule(world.get_location('Desert Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player) and state.has('Big Key (Desert Palace)', player) and state.has_fire_source(player) and state.world.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state))
|
||||
set_rule(world.get_location('Desert Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player) and state.has('Big Key (Desert Palace)', player) and state.has_fire_source(player) and state.multiworld.get_location('Desert Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state))
|
||||
set_rule(world.get_location('Desert Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player) and state.has('Big Key (Desert Palace)', player) and state.has_fire_source(player) and state.multiworld.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state))
|
||||
|
||||
# logic patch to prevent placing a crystal in Desert that's required to reach the required keys
|
||||
if not (world.smallkey_shuffle[player] and world.bigkey_shuffle[player]):
|
||||
add_rule(world.get_location('Desert Palace - Prize', player), lambda state: state.world.get_region('Desert Palace Main (Outer)', player).can_reach(state))
|
||||
add_rule(world.get_location('Desert Palace - Prize', player), lambda state: state.multiworld.get_region('Desert Palace Main (Outer)', player).can_reach(state))
|
||||
|
||||
set_rule(world.get_entrance('Tower of Hera Small Key Door', player), lambda state: state._lttp_has_key('Small Key (Tower of Hera)', player) or item_name(state, 'Tower of Hera - Big Key Chest', player) == ('Small Key (Tower of Hera)', player))
|
||||
set_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: state.has('Big Key (Tower of Hera)', player))
|
||||
@@ -286,12 +286,12 @@ def global_rules(world, player):
|
||||
set_rule(world.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player))
|
||||
set_rule(world.get_entrance('Ice Palace (Kholdstare)', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player) and state.has('Big Key (Ice Palace)', player) and (state._lttp_has_key('Small Key (Ice Palace)', player, 2) or (state.has('Cane of Somaria', player) and state._lttp_has_key('Small Key (Ice Palace)', player, 1))))
|
||||
set_rule(world.get_entrance('Ice Palace (East)', player), lambda state: (state.has('Hookshot', player) or (
|
||||
item_in_locations(state, 'Big Key (Ice Palace)', player, [('Ice Palace - Spike Room', player), ('Ice Palace - Big Key Chest', player), ('Ice Palace - Map Chest', player)]) and state._lttp_has_key('Small Key (Ice Palace)', player))) and (state.world.can_take_damage[player] or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player)))
|
||||
item_in_locations(state, 'Big Key (Ice Palace)', player, [('Ice Palace - Spike Room', player), ('Ice Palace - Big Key Chest', player), ('Ice Palace - Map Chest', player)]) and state._lttp_has_key('Small Key (Ice Palace)', player))) and (state.multiworld.can_take_damage[player] or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player)))
|
||||
set_rule(world.get_entrance('Ice Palace (East Top)', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
|
||||
|
||||
set_rule(world.get_entrance('Misery Mire Entrance Gap', player), lambda state: (state.has('Pegasus Boots', player) or state.has('Hookshot', player)) and (state.has_sword(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player))) # need to defeat wizzrobes, bombs don't work ...
|
||||
set_rule(world.get_location('Misery Mire - Big Chest', player), lambda state: state.has('Big Key (Misery Mire)', player))
|
||||
set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage[player] and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
|
||||
set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.multiworld.can_take_damage[player] and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
|
||||
set_rule(world.get_entrance('Misery Mire Big Key Door', player), lambda state: state.has('Big Key (Misery Mire)', player))
|
||||
# you can squander the free small key from the pot by opening the south door to the north west switch room, locking you out of accessing a color switch ...
|
||||
# big key gives backdoor access to that from the teleporter in the north west
|
||||
@@ -377,11 +377,11 @@ def global_rules(world, player):
|
||||
set_rule(world.get_location('Ganons Tower - Big Chest', player), lambda state: state.has('Big Key (Ganons Tower)', player))
|
||||
|
||||
set_rule(world.get_location('Ganons Tower - Big Key Room - Left', player),
|
||||
lambda state: state.world.get_location('Ganons Tower - Big Key Room - Left', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
|
||||
lambda state: state.multiworld.get_location('Ganons Tower - Big Key Room - Left', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
|
||||
set_rule(world.get_location('Ganons Tower - Big Key Chest', player),
|
||||
lambda state: state.world.get_location('Ganons Tower - Big Key Chest', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
|
||||
lambda state: state.multiworld.get_location('Ganons Tower - Big Key Chest', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
|
||||
set_rule(world.get_location('Ganons Tower - Big Key Room - Right', player),
|
||||
lambda state: state.world.get_location('Ganons Tower - Big Key Room - Right', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
|
||||
lambda state: state.multiworld.get_location('Ganons Tower - Big Key Room - Right', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
|
||||
if world.enemy_shuffle[player]:
|
||||
set_rule(world.get_entrance('Ganons Tower Big Key Door', player),
|
||||
lambda state: state.has('Big Key (Ganons Tower)', player))
|
||||
@@ -389,22 +389,22 @@ def global_rules(world, player):
|
||||
set_rule(world.get_entrance('Ganons Tower Big Key Door', player),
|
||||
lambda state: state.has('Big Key (Ganons Tower)', player) and state.can_shoot_arrows(player))
|
||||
set_rule(world.get_entrance('Ganons Tower Torch Rooms', player),
|
||||
lambda state: state.has_fire_source(player) and state.world.get_entrance('Ganons Tower Torch Rooms', player).parent_region.dungeon.bosses['middle'].can_defeat(state))
|
||||
lambda state: state.has_fire_source(player) and state.multiworld.get_entrance('Ganons Tower Torch Rooms', player).parent_region.dungeon.bosses['middle'].can_defeat(state))
|
||||
set_rule(world.get_location('Ganons Tower - Pre-Moldorm Chest', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 3))
|
||||
set_rule(world.get_entrance('Ganons Tower Moldorm Door', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 4))
|
||||
set_rule(world.get_entrance('Ganons Tower Moldorm Gap', player),
|
||||
lambda state: state.has('Hookshot', player) and state.world.get_entrance('Ganons Tower Moldorm Gap', player).parent_region.dungeon.bosses['top'].can_defeat(state))
|
||||
lambda state: state.has('Hookshot', player) and state.multiworld.get_entrance('Ganons Tower Moldorm Gap', player).parent_region.dungeon.bosses['top'].can_defeat(state))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player))
|
||||
ganon = world.get_location('Ganon', player)
|
||||
set_rule(ganon, lambda state: GanonDefeatRule(state, player))
|
||||
if world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']:
|
||||
add_rule(ganon, lambda state: state.has_triforce_pieces(state.world.treasure_hunt_count[player], player))
|
||||
add_rule(ganon, lambda state: state.has_triforce_pieces(state.multiworld.treasure_hunt_count[player], player))
|
||||
elif world.goal[player] == 'ganonpedestal':
|
||||
add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player))
|
||||
else:
|
||||
add_rule(ganon, lambda state: state.has_crystals(state.world.crystals_needed_for_ganon[player], player))
|
||||
add_rule(ganon, lambda state: state.has_crystals(state.multiworld.crystals_needed_for_ganon[player], player))
|
||||
set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has_beam_sword(player)) # need to damage ganon to get tiles to drop
|
||||
|
||||
set_rule(world.get_location('Flute Activation Spot', player), lambda state: state.has('Flute', player))
|
||||
@@ -942,7 +942,7 @@ def set_trock_key_rules(world, player):
|
||||
|
||||
if world.accessibility[player] != 'locations':
|
||||
set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player
|
||||
and state.can_reach(state.world.get_region('Turtle Rock (Second Section)', player)))
|
||||
and state.can_reach(state.multiworld.get_region('Turtle Rock (Second Section)', player)))
|
||||
|
||||
|
||||
def set_big_bomb_rules(world, player):
|
||||
|
||||
@@ -163,7 +163,7 @@ class ALTTPWorld(World):
|
||||
check_enemizer(self.enemizer_path)
|
||||
|
||||
player = self.player
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
|
||||
# system for sharing ER layouts
|
||||
self.er_seed = str(world.random.randint(0, 2 ** 64))
|
||||
@@ -195,7 +195,7 @@ class ALTTPWorld(World):
|
||||
|
||||
def create_regions(self):
|
||||
player = self.player
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
|
||||
world.triforce_pieces_available[player] = max(world.triforce_pieces_available[player],
|
||||
world.triforce_pieces_required[player])
|
||||
@@ -219,21 +219,21 @@ class ALTTPWorld(World):
|
||||
link_entrances(world, player)
|
||||
mark_light_world_regions(world, player)
|
||||
for region_name, entrance_name in indirect_connections_not_inverted.items():
|
||||
world.register_indirect_condition(self.world.get_region(region_name, player),
|
||||
self.world.get_entrance(entrance_name, player))
|
||||
world.register_indirect_condition(world.get_region(region_name, player),
|
||||
world.get_entrance(entrance_name, player))
|
||||
else:
|
||||
link_inverted_entrances(world, player)
|
||||
mark_dark_world_regions(world, player)
|
||||
for region_name, entrance_name in indirect_connections_inverted.items():
|
||||
world.register_indirect_condition(self.world.get_region(region_name, player),
|
||||
self.world.get_entrance(entrance_name, player))
|
||||
world.register_indirect_condition(world.get_region(region_name, player),
|
||||
world.get_entrance(entrance_name, player))
|
||||
|
||||
world.random = old_random
|
||||
plando_connect(world, player)
|
||||
|
||||
for region_name, entrance_name in indirect_connections.items():
|
||||
world.register_indirect_condition(self.world.get_region(region_name, player),
|
||||
self.world.get_entrance(entrance_name, player))
|
||||
world.register_indirect_condition(world.get_region(region_name, player),
|
||||
world.get_entrance(entrance_name, player))
|
||||
|
||||
|
||||
def collect_item(self, state: CollectionState, item: Item, remove=False):
|
||||
@@ -278,15 +278,15 @@ class ALTTPWorld(World):
|
||||
if 'Sword' in item_name:
|
||||
if state.has('Golden Sword', item.player):
|
||||
pass
|
||||
elif state.has('Tempered Sword', item.player) and self.world.difficulty_requirements[
|
||||
elif state.has('Tempered Sword', item.player) and self.multiworld.difficulty_requirements[
|
||||
item.player].progressive_sword_limit >= 4:
|
||||
return 'Golden Sword'
|
||||
elif state.has('Master Sword', item.player) and self.world.difficulty_requirements[
|
||||
elif state.has('Master Sword', item.player) and self.multiworld.difficulty_requirements[
|
||||
item.player].progressive_sword_limit >= 3:
|
||||
return 'Tempered Sword'
|
||||
elif state.has('Fighter Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 2:
|
||||
elif state.has('Fighter Sword', item.player) and self.multiworld.difficulty_requirements[item.player].progressive_sword_limit >= 2:
|
||||
return 'Master Sword'
|
||||
elif self.world.difficulty_requirements[item.player].progressive_sword_limit >= 1:
|
||||
elif self.multiworld.difficulty_requirements[item.player].progressive_sword_limit >= 1:
|
||||
return 'Fighter Sword'
|
||||
elif 'Glove' in item_name:
|
||||
if state.has('Titans Mitts', item.player):
|
||||
@@ -298,20 +298,20 @@ class ALTTPWorld(World):
|
||||
elif 'Shield' in item_name:
|
||||
if state.has('Mirror Shield', item.player):
|
||||
return
|
||||
elif state.has('Red Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 3:
|
||||
elif state.has('Red Shield', item.player) and self.multiworld.difficulty_requirements[item.player].progressive_shield_limit >= 3:
|
||||
return 'Mirror Shield'
|
||||
elif state.has('Blue Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 2:
|
||||
elif state.has('Blue Shield', item.player) and self.multiworld.difficulty_requirements[item.player].progressive_shield_limit >= 2:
|
||||
return 'Red Shield'
|
||||
elif self.world.difficulty_requirements[item.player].progressive_shield_limit >= 1:
|
||||
elif self.multiworld.difficulty_requirements[item.player].progressive_shield_limit >= 1:
|
||||
return 'Blue Shield'
|
||||
elif 'Bow' in item_name:
|
||||
if state.has('Silver Bow', item.player):
|
||||
return
|
||||
elif state.has('Bow', item.player) and (self.world.difficulty_requirements[item.player].progressive_bow_limit >= 2
|
||||
or self.world.logic[item.player] == 'noglitches'
|
||||
or self.world.swordless[item.player]): # modes where silver bow is always required for ganon
|
||||
elif state.has('Bow', item.player) and (self.multiworld.difficulty_requirements[item.player].progressive_bow_limit >= 2
|
||||
or self.multiworld.logic[item.player] == 'noglitches'
|
||||
or self.multiworld.swordless[item.player]): # modes where silver bow is always required for ganon
|
||||
return 'Silver Bow'
|
||||
elif self.world.difficulty_requirements[item.player].progressive_bow_limit >= 1:
|
||||
elif self.multiworld.difficulty_requirements[item.player].progressive_bow_limit >= 1:
|
||||
return 'Bow'
|
||||
elif item.advancement:
|
||||
return item_name
|
||||
@@ -319,7 +319,7 @@ class ALTTPWorld(World):
|
||||
def pre_fill(self):
|
||||
from Fill import fill_restrictive, FillError
|
||||
attempts = 5
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
all_state = world.get_all_state(use_cache=True)
|
||||
crystals = [self.create_item(name) for name in ['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6']]
|
||||
@@ -362,7 +362,7 @@ class ALTTPWorld(World):
|
||||
ShopSlotFill(world)
|
||||
|
||||
def use_enemizer(self):
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
return (world.boss_shuffle[player] or world.enemy_shuffle[player]
|
||||
or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default'
|
||||
@@ -370,7 +370,7 @@ class ALTTPWorld(World):
|
||||
or world.killable_thieves[player])
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
try:
|
||||
use_enemizer = self.use_enemizer()
|
||||
@@ -409,7 +409,7 @@ class ALTTPWorld(World):
|
||||
deathlink=world.death_link[player],
|
||||
allowcollect=world.allow_collect[player])
|
||||
|
||||
rompath = os.path.join(output_directory, f"{self.world.get_out_file_name_base(self.player)}.sfc")
|
||||
rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.sfc")
|
||||
rom.write_to_file(rompath)
|
||||
patch = LttPDeltaPatch(os.path.splitext(rompath)[0]+LttPDeltaPatch.patch_file_ending, player=player,
|
||||
player_name=world.player_name[player], patched_path=rompath)
|
||||
@@ -443,7 +443,7 @@ class ALTTPWorld(World):
|
||||
# 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()
|
||||
multidata["connect_names"][new_name] = multidata["connect_names"][self.world.player_name[self.player]]
|
||||
multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]]
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return ALttPItem(name, self.player, **item_init_table[name])
|
||||
@@ -510,16 +510,16 @@ class ALTTPWorld(World):
|
||||
logging.warning(f"Could not trash fill Ganon's Tower for player {player}.")
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
if self.world.goal[self.player] == "icerodhunt":
|
||||
if self.multiworld.goal[self.player] == "icerodhunt":
|
||||
item = "Nothing"
|
||||
else:
|
||||
item = self.world.random.choice(extras_list)
|
||||
return GetBeemizerItem(self.world, self.player, item)
|
||||
item = self.multiworld.random.choice(extras_list)
|
||||
return GetBeemizerItem(self.multiworld, self.player, item)
|
||||
|
||||
def get_pre_fill_items(self):
|
||||
res = []
|
||||
if self.dungeon_local_item_names:
|
||||
for (name, player), dungeon in self.world.dungeons.items():
|
||||
for (name, player), dungeon in self.multiworld.dungeons.items():
|
||||
if player == self.player:
|
||||
for item in dungeon.all_items:
|
||||
if item.name in self.dungeon_local_item_names:
|
||||
@@ -538,8 +538,8 @@ def get_same_seed(world, seed_def: tuple) -> str:
|
||||
|
||||
class ALttPLogic(LogicMixin):
|
||||
def _lttp_has_key(self, item, player, count: int = 1):
|
||||
if self.world.logic[player] == 'nologic':
|
||||
if self.multiworld.logic[player] == 'nologic':
|
||||
return True
|
||||
if self.world.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
|
||||
if self.multiworld.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
|
||||
return self.can_buy_unlimited('Small Key (Universal)', player)
|
||||
return self.prog_items[item, player] >= count
|
||||
|
||||
@@ -43,7 +43,7 @@ class ArchipIDLEWorld(World):
|
||||
|
||||
def generate_basic(self):
|
||||
item_table_copy = list(item_table)
|
||||
self.world.random.shuffle(item_table_copy)
|
||||
self.multiworld.random.shuffle(item_table_copy)
|
||||
|
||||
item_pool = []
|
||||
for i in range(100):
|
||||
@@ -55,31 +55,31 @@ class ArchipIDLEWorld(World):
|
||||
)
|
||||
item_pool.append(item)
|
||||
|
||||
self.world.itempool += item_pool
|
||||
self.multiworld.itempool += item_pool
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return Item(name, ItemClassification.progression, self.item_name_to_id[name], self.player)
|
||||
|
||||
def create_regions(self):
|
||||
self.world.regions += [
|
||||
create_region(self.world, self.player, 'Menu', None, ['Entrance to IDLE Zone']),
|
||||
create_region(self.world, self.player, 'IDLE Zone', self.location_name_to_id)
|
||||
self.multiworld.regions += [
|
||||
create_region(self.multiworld, self.player, 'Menu', None, ['Entrance to IDLE Zone']),
|
||||
create_region(self.multiworld, self.player, 'IDLE Zone', self.location_name_to_id)
|
||||
]
|
||||
|
||||
# link up our region with the entrance we just made
|
||||
self.world.get_entrance('Entrance to IDLE Zone', self.player)\
|
||||
.connect(self.world.get_region('IDLE Zone', self.player))
|
||||
self.multiworld.get_entrance('Entrance to IDLE Zone', self.player)\
|
||||
.connect(self.multiworld.get_region('IDLE Zone', self.player))
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice(item_table)
|
||||
return self.multiworld.random.choice(item_table)
|
||||
|
||||
|
||||
def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
region = Region(name, RegionType.Generic, name, player)
|
||||
region.world = world
|
||||
region.multiworld = world
|
||||
if locations:
|
||||
for location_name in locations.keys():
|
||||
location = ArchipIDLELocation(player, location_name, locations[location_name], region)
|
||||
|
||||
@@ -38,12 +38,12 @@ class ChecksFinderWorld(World):
|
||||
|
||||
def _get_checksfinder_data(self):
|
||||
return {
|
||||
'world_seed': self.world.slot_seeds[self.player].getrandbits(32),
|
||||
'seed_name': self.world.seed_name,
|
||||
'player_name': self.world.get_player_name(self.player),
|
||||
'world_seed': self.multiworld.slot_seeds[self.player].getrandbits(32),
|
||||
'seed_name': self.multiworld.seed_name,
|
||||
'player_name': self.multiworld.get_player_name(self.player),
|
||||
'player_id': self.player,
|
||||
'client_version': client_version,
|
||||
'race': self.world.is_race,
|
||||
'race': self.multiworld.is_race,
|
||||
}
|
||||
|
||||
def generate_basic(self):
|
||||
@@ -61,15 +61,15 @@ class ChecksFinderWorld(World):
|
||||
# Convert itempool into real items
|
||||
itempool = [item for item in map(lambda name: self.create_item(name), itempool)]
|
||||
|
||||
self.world.itempool += itempool
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_completion_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
set_completion_rules(self.multiworld, self.player)
|
||||
|
||||
def create_regions(self):
|
||||
def ChecksFinderRegion(region_name: str, exits=[]):
|
||||
ret = Region(region_name, RegionType.Generic, region_name, self.player, self.world)
|
||||
ret = Region(region_name, RegionType.Generic, region_name, self.player, self.multiworld)
|
||||
ret.locations = [ChecksFinderAdvancement(self.player, loc_name, loc_data.id, ret)
|
||||
for loc_name, loc_data in advancement_table.items()
|
||||
if loc_data.region == region_name]
|
||||
@@ -77,13 +77,13 @@ class ChecksFinderWorld(World):
|
||||
ret.exits.append(Entrance(self.player, exit, ret))
|
||||
return ret
|
||||
|
||||
self.world.regions += [ChecksFinderRegion(*r) for r in checksfinder_regions]
|
||||
link_checksfinder_structures(self.world, self.player)
|
||||
self.multiworld.regions += [ChecksFinderRegion(*r) for r in checksfinder_regions]
|
||||
link_checksfinder_structures(self.multiworld, self.player)
|
||||
|
||||
def fill_slot_data(self):
|
||||
slot_data = self._get_checksfinder_data()
|
||||
for option_name in checksfinder_options:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
if slot_data.get(option_name, None) is None and type(option.value) in {str, int}:
|
||||
slot_data[option_name] = int(option.value)
|
||||
return slot_data
|
||||
|
||||
@@ -79,7 +79,7 @@ class DarkSouls3World(World):
|
||||
|
||||
def create_regions(self):
|
||||
menu_region = Region("Menu", RegionType.Generic, "Menu", self.player)
|
||||
self.world.regions.append(menu_region)
|
||||
self.multiworld.regions.append(menu_region)
|
||||
|
||||
# Create all Vanilla regions of Dark Souls III
|
||||
firelink_shrine_region = self.create_region("Firelink Shrine", fire_link_shrine_table)
|
||||
@@ -106,71 +106,71 @@ class DarkSouls3World(World):
|
||||
|
||||
# Create the entrance to connect those regions
|
||||
menu_region.exits.append(Entrance(self.player, "New Game", menu_region))
|
||||
self.world.get_entrance("New Game", self.player).connect(firelink_shrine_region)
|
||||
self.multiworld.get_entrance("New Game", self.player).connect(firelink_shrine_region)
|
||||
firelink_shrine_region.exits.append(Entrance(self.player, "Goto High Wall of Lothric",
|
||||
firelink_shrine_region))
|
||||
firelink_shrine_region.exits.append(Entrance(self.player, "Goto Kiln Of The First Flame",
|
||||
firelink_shrine_region))
|
||||
firelink_shrine_region.exits.append(Entrance(self.player, "Goto Bell Tower",
|
||||
firelink_shrine_region))
|
||||
self.world.get_entrance("Goto High Wall of Lothric", self.player).connect(high_wall_of_lothric_region)
|
||||
self.world.get_entrance("Goto Kiln Of The First Flame", self.player).connect(kiln_of_the_first_flame_region)
|
||||
self.world.get_entrance("Goto Bell Tower", self.player).connect(firelink_shrine_bell_tower_region)
|
||||
self.multiworld.get_entrance("Goto High Wall of Lothric", self.player).connect(high_wall_of_lothric_region)
|
||||
self.multiworld.get_entrance("Goto Kiln Of The First Flame", self.player).connect(kiln_of_the_first_flame_region)
|
||||
self.multiworld.get_entrance("Goto Bell Tower", self.player).connect(firelink_shrine_bell_tower_region)
|
||||
high_wall_of_lothric_region.exits.append(Entrance(self.player, "Goto Undead Settlement",
|
||||
high_wall_of_lothric_region))
|
||||
high_wall_of_lothric_region.exits.append(Entrance(self.player, "Goto Lothric Castle",
|
||||
high_wall_of_lothric_region))
|
||||
self.world.get_entrance("Goto Undead Settlement", self.player).connect(undead_settlement_region)
|
||||
self.world.get_entrance("Goto Lothric Castle", self.player).connect(lothric_castle_region)
|
||||
self.multiworld.get_entrance("Goto Undead Settlement", self.player).connect(undead_settlement_region)
|
||||
self.multiworld.get_entrance("Goto Lothric Castle", self.player).connect(lothric_castle_region)
|
||||
undead_settlement_region.exits.append(Entrance(self.player, "Goto Road Of Sacrifices",
|
||||
undead_settlement_region))
|
||||
self.world.get_entrance("Goto Road Of Sacrifices", self.player).connect(road_of_sacrifices_region)
|
||||
self.multiworld.get_entrance("Goto Road Of Sacrifices", self.player).connect(road_of_sacrifices_region)
|
||||
road_of_sacrifices_region.exits.append(Entrance(self.player, "Goto Cathedral", road_of_sacrifices_region))
|
||||
road_of_sacrifices_region.exits.append(Entrance(self.player, "Goto Farron keep", road_of_sacrifices_region))
|
||||
self.world.get_entrance("Goto Cathedral", self.player).connect(cathedral_of_the_deep_region)
|
||||
self.world.get_entrance("Goto Farron keep", self.player).connect(farron_keep_region)
|
||||
self.multiworld.get_entrance("Goto Cathedral", self.player).connect(cathedral_of_the_deep_region)
|
||||
self.multiworld.get_entrance("Goto Farron keep", self.player).connect(farron_keep_region)
|
||||
farron_keep_region.exits.append(Entrance(self.player, "Goto Carthus catacombs", farron_keep_region))
|
||||
self.world.get_entrance("Goto Carthus catacombs", self.player).connect(catacombs_of_carthus_region)
|
||||
self.multiworld.get_entrance("Goto Carthus catacombs", self.player).connect(catacombs_of_carthus_region)
|
||||
catacombs_of_carthus_region.exits.append(Entrance(self.player, "Goto Irithyll of the boreal",
|
||||
catacombs_of_carthus_region))
|
||||
catacombs_of_carthus_region.exits.append(Entrance(self.player, "Goto Smouldering Lake",
|
||||
catacombs_of_carthus_region))
|
||||
self.world.get_entrance("Goto Irithyll of the boreal", self.player).\
|
||||
self.multiworld.get_entrance("Goto Irithyll of the boreal", self.player).\
|
||||
connect(irithyll_of_the_boreal_valley_region)
|
||||
self.world.get_entrance("Goto Smouldering Lake", self.player).connect(smouldering_lake_region)
|
||||
self.multiworld.get_entrance("Goto Smouldering Lake", self.player).connect(smouldering_lake_region)
|
||||
irithyll_of_the_boreal_valley_region.exits.append(Entrance(self.player, "Goto Irithyll dungeon",
|
||||
irithyll_of_the_boreal_valley_region))
|
||||
irithyll_of_the_boreal_valley_region.exits.append(Entrance(self.player, "Goto Anor Londo",
|
||||
irithyll_of_the_boreal_valley_region))
|
||||
self.world.get_entrance("Goto Irithyll dungeon", self.player).connect(irithyll_dungeon_region)
|
||||
self.world.get_entrance("Goto Anor Londo", self.player).connect(anor_londo_region)
|
||||
self.multiworld.get_entrance("Goto Irithyll dungeon", self.player).connect(irithyll_dungeon_region)
|
||||
self.multiworld.get_entrance("Goto Anor Londo", self.player).connect(anor_londo_region)
|
||||
irithyll_dungeon_region.exits.append(Entrance(self.player, "Goto Archdragon peak", irithyll_dungeon_region))
|
||||
irithyll_dungeon_region.exits.append(Entrance(self.player, "Goto Profaned capital", irithyll_dungeon_region))
|
||||
self.world.get_entrance("Goto Archdragon peak", self.player).connect(archdragon_peak_region)
|
||||
self.world.get_entrance("Goto Profaned capital", self.player).connect(profaned_capital_region)
|
||||
self.multiworld.get_entrance("Goto Archdragon peak", self.player).connect(archdragon_peak_region)
|
||||
self.multiworld.get_entrance("Goto Profaned capital", self.player).connect(profaned_capital_region)
|
||||
lothric_castle_region.exits.append(Entrance(self.player, "Goto Consumed King Garden", lothric_castle_region))
|
||||
lothric_castle_region.exits.append(Entrance(self.player, "Goto Grand Archives", lothric_castle_region))
|
||||
self.world.get_entrance("Goto Consumed King Garden", self.player).connect(consumed_king_garden_region)
|
||||
self.world.get_entrance("Goto Grand Archives", self.player).connect(grand_archives_region)
|
||||
self.multiworld.get_entrance("Goto Consumed King Garden", self.player).connect(consumed_king_garden_region)
|
||||
self.multiworld.get_entrance("Goto Grand Archives", self.player).connect(grand_archives_region)
|
||||
consumed_king_garden_region.exits.append(Entrance(self.player, "Goto Untended Graves",
|
||||
consumed_king_garden_region))
|
||||
self.world.get_entrance("Goto Untended Graves", self.player).connect(untended_graves_region)
|
||||
self.multiworld.get_entrance("Goto Untended Graves", self.player).connect(untended_graves_region)
|
||||
|
||||
# For each region, add the associated locations retrieved from the corresponding location_table
|
||||
def create_region(self, region_name, location_table) -> Region:
|
||||
new_region = Region(region_name, RegionType.Generic, region_name, self.player, self.world)
|
||||
new_region = Region(region_name, RegionType.Generic, region_name, self.player, self.multiworld)
|
||||
if location_table:
|
||||
for name, address in location_table.items():
|
||||
location = DarkSouls3Location(self.player, name, self.location_name_to_id[name], new_region)
|
||||
new_region.locations.append(location)
|
||||
self.world.regions.append(new_region)
|
||||
self.multiworld.regions.append(new_region)
|
||||
return new_region
|
||||
|
||||
def create_items(self):
|
||||
for name, address in self.item_name_to_id.items():
|
||||
# Specific items will be included in the item pool under certain conditions. See generate_basic
|
||||
if name != "Basin of Vows":
|
||||
self.world.itempool += [self.create_item(name)]
|
||||
self.multiworld.itempool += [self.create_item(name)]
|
||||
|
||||
def generate_early(self):
|
||||
pass
|
||||
@@ -178,46 +178,46 @@ class DarkSouls3World(World):
|
||||
def set_rules(self) -> None:
|
||||
|
||||
# Define the access rules to the entrances
|
||||
set_rule(self.world.get_entrance("Goto Bell Tower", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Goto Bell Tower", self.player),
|
||||
lambda state: state.has("Tower Key", self.player))
|
||||
set_rule(self.world.get_entrance("Goto Undead Settlement", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Goto Undead Settlement", self.player),
|
||||
lambda state: state.has("Small Lothric Banner", self.player))
|
||||
set_rule(self.world.get_entrance("Goto Lothric Castle", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Goto Lothric Castle", self.player),
|
||||
lambda state: state.has("Basin of Vows", self.player))
|
||||
set_rule(self.world.get_entrance("Goto Irithyll of the boreal", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Goto Irithyll of the boreal", self.player),
|
||||
lambda state: state.has("Small Doll", self.player))
|
||||
set_rule(self.world.get_entrance("Goto Archdragon peak", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Goto Archdragon peak", self.player),
|
||||
lambda state: state.can_reach("CKG: Soul of Consumed Oceiros", "Location", self.player))
|
||||
set_rule(self.world.get_entrance("Goto Profaned capital", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Goto Profaned capital", self.player),
|
||||
lambda state: state.has("Storm Ruler", self.player))
|
||||
set_rule(self.world.get_entrance("Goto Grand Archives", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Goto Grand Archives", self.player),
|
||||
lambda state: state.has("Grand Archives Key", self.player))
|
||||
set_rule(self.world.get_entrance("Goto Kiln Of The First Flame", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Goto Kiln Of The First Flame", self.player),
|
||||
lambda state: state.has("Cinders of a Lord - Abyss Watcher", self.player) and
|
||||
state.has("Cinders of a Lord - Yhorm the Giant", self.player) and
|
||||
state.has("Cinders of a Lord - Aldrich", self.player) and
|
||||
state.has("Cinders of a Lord - Lothric Prince", self.player))
|
||||
|
||||
# Define the access rules to some specific locations
|
||||
set_rule(self.world.get_location("HWL: Soul of the Dancer", self.player),
|
||||
set_rule(self.multiworld.get_location("HWL: Soul of the Dancer", self.player),
|
||||
lambda state: state.has("Basin of Vows", self.player))
|
||||
set_rule(self.world.get_location("HWL: Greirat's Ashes", self.player),
|
||||
set_rule(self.multiworld.get_location("HWL: Greirat's Ashes", self.player),
|
||||
lambda state: state.has("Cell Key", self.player))
|
||||
set_rule(self.world.get_location("ID: Bellowing Dragoncrest Ring", self.player),
|
||||
set_rule(self.multiworld.get_location("ID: Bellowing Dragoncrest Ring", self.player),
|
||||
lambda state: state.has("Jailbreaker's Key", self.player))
|
||||
set_rule(self.world.get_location("ID: Prisoner Chief's Ashes", self.player),
|
||||
set_rule(self.multiworld.get_location("ID: Prisoner Chief's Ashes", self.player),
|
||||
lambda state: state.has("Jailer's Key Ring", self.player))
|
||||
set_rule(self.world.get_location("ID: Covetous Gold Serpent Ring", self.player),
|
||||
set_rule(self.multiworld.get_location("ID: Covetous Gold Serpent Ring", self.player),
|
||||
lambda state: state.has("Old Cell Key", self.player))
|
||||
set_rule(self.world.get_location("ID: Karla's Ashes", self.player),
|
||||
set_rule(self.multiworld.get_location("ID: Karla's Ashes", self.player),
|
||||
lambda state: state.has("Jailer's Key Ring", self.player))
|
||||
black_hand_gotthard_corpse_rule = lambda state: \
|
||||
(state.can_reach("AL: Cinders of a Lord - Aldrich", "Location", self.player) and
|
||||
state.can_reach("PC: Cinders of a Lord - Yhorm the Giant", "Location", self.player))
|
||||
set_rule(self.world.get_location("LC: Grand Archives Key", self.player), black_hand_gotthard_corpse_rule)
|
||||
set_rule(self.world.get_location("LC: Gotthard Twinswords", self.player), black_hand_gotthard_corpse_rule)
|
||||
set_rule(self.multiworld.get_location("LC: Grand Archives Key", self.player), black_hand_gotthard_corpse_rule)
|
||||
set_rule(self.multiworld.get_location("LC: Gotthard Twinswords", self.player), black_hand_gotthard_corpse_rule)
|
||||
|
||||
self.world.completion_condition[self.player] = lambda state: \
|
||||
self.multiworld.completion_condition[self.player] = lambda state: \
|
||||
state.has("Cinders of a Lord - Abyss Watcher", self.player) and \
|
||||
state.has("Cinders of a Lord - Yhorm the Giant", self.player) and \
|
||||
state.has("Cinders of a Lord - Aldrich", self.player) and \
|
||||
@@ -226,30 +226,30 @@ class DarkSouls3World(World):
|
||||
def generate_basic(self):
|
||||
# Depending on the specified option, add the Basin of Vows to a specific location or to the item pool
|
||||
item = self.create_item("Basin of Vows")
|
||||
if self.world.late_basin_of_vows[self.player]:
|
||||
self.world.get_location("IBV: Soul of Pontiff Sulyvahn", self.player).place_locked_item(item)
|
||||
if self.multiworld.late_basin_of_vows[self.player]:
|
||||
self.multiworld.get_location("IBV: Soul of Pontiff Sulyvahn", self.player).place_locked_item(item)
|
||||
else:
|
||||
self.world.itempool += [item]
|
||||
self.multiworld.itempool += [item]
|
||||
|
||||
# Fill item pool with additional items
|
||||
item_pool_len = self.item_name_to_id.__len__()
|
||||
total_required_locations = self.location_name_to_id.__len__()
|
||||
for i in range(item_pool_len, total_required_locations):
|
||||
self.world.itempool += [self.create_item("Soul of an Intrepid Hero")]
|
||||
self.multiworld.itempool += [self.create_item("Soul of an Intrepid Hero")]
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
# Depending on the specified option, modify items hexadecimal value to add an upgrade level
|
||||
item_dictionary_copy = item_dictionary.copy()
|
||||
if self.world.randomize_weapons_level[self.player]:
|
||||
if self.multiworld.randomize_weapons_level[self.player]:
|
||||
# Randomize some weapons upgrades
|
||||
for name in weapons_upgrade_5_table.keys():
|
||||
if self.world.random.randint(0, 100) < 33:
|
||||
value = self.world.random.randint(1, 5)
|
||||
if self.multiworld.random.randint(0, 100) < 33:
|
||||
value = self.multiworld.random.randint(1, 5)
|
||||
item_dictionary_copy[name] += value
|
||||
|
||||
for name in weapons_upgrade_10_table.keys():
|
||||
if self.world.random.randint(0, 100) < 33:
|
||||
value = self.world.random.randint(1, 10)
|
||||
if self.multiworld.random.randint(0, 100) < 33:
|
||||
value = self.multiworld.random.randint(1, 10)
|
||||
item_dictionary_copy[name] += value
|
||||
|
||||
# Create the mandatory lists to generate the player's output file
|
||||
@@ -258,7 +258,7 @@ class DarkSouls3World(World):
|
||||
locations_id = []
|
||||
locations_address = []
|
||||
locations_target = []
|
||||
for location in self.world.get_filled_locations():
|
||||
for location in self.multiworld.get_filled_locations():
|
||||
if location.item.player == self.player:
|
||||
items_id.append(location.item.code)
|
||||
items_address.append(item_dictionary[location.item.name])
|
||||
@@ -273,12 +273,12 @@ class DarkSouls3World(World):
|
||||
|
||||
data = {
|
||||
"options": {
|
||||
"auto_equip": self.world.auto_equip[self.player].value,
|
||||
"lock_equip": self.world.lock_equip[self.player].value,
|
||||
"no_weapon_requirements": self.world.no_weapon_requirements[self.player].value,
|
||||
"auto_equip": self.multiworld.auto_equip[self.player].value,
|
||||
"lock_equip": self.multiworld.lock_equip[self.player].value,
|
||||
"no_weapon_requirements": self.multiworld.no_weapon_requirements[self.player].value,
|
||||
},
|
||||
"seed": self.world.seed_name, # to verify the server's multiworld
|
||||
"slot": self.world.player_name[self.player], # to connect to server
|
||||
"seed": self.multiworld.seed_name, # to verify the server's multiworld
|
||||
"slot": self.multiworld.player_name[self.player], # to connect to server
|
||||
"base_id": self.base_id, # to merge location and items lists
|
||||
"locationsId": locations_id,
|
||||
"locationsAddress": locations_address,
|
||||
@@ -288,6 +288,6 @@ class DarkSouls3World(World):
|
||||
}
|
||||
|
||||
# generate the file
|
||||
filename = f"AP-{self.world.seed_name}-P{self.player}-{self.world.player_name[self.player]}.json"
|
||||
filename = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.player_name[self.player]}.json"
|
||||
with open(os.path.join(output_directory, filename), 'w') as outfile:
|
||||
json.dump(data, outfile)
|
||||
|
||||
@@ -913,7 +913,7 @@ def connect_regions(world, player, level_list):
|
||||
def create_region(world: MultiWorld, player: int, active_locations, name: str, locations=None, exits=None):
|
||||
# Shamelessly stolen from the ROR2 definition
|
||||
ret = Region(name, None, name, player)
|
||||
ret.world = world
|
||||
ret.multiworld = world
|
||||
if locations:
|
||||
for locationName, locationData in locations.items():
|
||||
loc_id = active_locations.get(locationName, 0)
|
||||
|
||||
@@ -69,13 +69,13 @@ class DKC3World(World):
|
||||
def fill_slot_data(self) -> dict:
|
||||
slot_data = self._get_slot_data()
|
||||
for option_name in dkc3_options:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
slot_data[option_name] = option.value
|
||||
|
||||
return slot_data
|
||||
|
||||
def generate_basic(self):
|
||||
self.topology_present = self.world.level_shuffle[self.player].value
|
||||
self.topology_present = self.multiworld.level_shuffle[self.player].value
|
||||
itempool: typing.List[DKC3Item] = []
|
||||
|
||||
# Levels
|
||||
@@ -85,14 +85,14 @@ class DKC3World(World):
|
||||
# Rocket Rush Cog
|
||||
total_required_locations -= 1
|
||||
number_of_cogs = 4
|
||||
self.world.get_location(LocationName.rocket_rush_flag, self.player).place_locked_item(self.create_item(ItemName.krematoa_cog))
|
||||
self.multiworld.get_location(LocationName.rocket_rush_flag, self.player).place_locked_item(self.create_item(ItemName.krematoa_cog))
|
||||
number_of_bosses = 8
|
||||
if self.world.goal[self.player] == "knautilus":
|
||||
self.world.get_location(LocationName.kastle_kaos, self.player).place_locked_item(self.create_item(ItemName.victory))
|
||||
if self.multiworld.goal[self.player] == "knautilus":
|
||||
self.multiworld.get_location(LocationName.kastle_kaos, self.player).place_locked_item(self.create_item(ItemName.victory))
|
||||
number_of_bosses = 7
|
||||
else:
|
||||
self.world.get_location(LocationName.banana_bird_mother, self.player).place_locked_item(self.create_item(ItemName.victory))
|
||||
number_of_banana_birds = self.world.number_of_banana_birds[self.player]
|
||||
self.multiworld.get_location(LocationName.banana_bird_mother, self.player).place_locked_item(self.create_item(ItemName.victory))
|
||||
number_of_banana_birds = self.multiworld.number_of_banana_birds[self.player]
|
||||
|
||||
# Bosses
|
||||
total_required_locations += number_of_bosses
|
||||
@@ -100,15 +100,15 @@ class DKC3World(World):
|
||||
# Secret Caves
|
||||
total_required_locations += 13
|
||||
|
||||
if self.world.kongsanity[self.player]:
|
||||
if self.multiworld.kongsanity[self.player]:
|
||||
total_required_locations += 39
|
||||
|
||||
## Brothers Bear
|
||||
if False:#self.world.include_trade_sequence[self.player]:
|
||||
total_required_locations += 10
|
||||
|
||||
number_of_bonus_coins = (self.world.krematoa_bonus_coin_cost[self.player] * 5)
|
||||
number_of_bonus_coins += math.ceil((85 - number_of_bonus_coins) * self.world.percentage_of_extra_bonus_coins[self.player] / 100)
|
||||
number_of_bonus_coins = (self.multiworld.krematoa_bonus_coin_cost[self.player] * 5)
|
||||
number_of_bonus_coins += math.ceil((85 - number_of_bonus_coins) * self.multiworld.percentage_of_extra_bonus_coins[self.player] / 100)
|
||||
|
||||
itempool += [self.create_item(ItemName.bonus_coin) for _ in range(number_of_bonus_coins)]
|
||||
itempool += [self.create_item(ItemName.dk_coin) for _ in range(41)]
|
||||
@@ -119,27 +119,27 @@ class DKC3World(World):
|
||||
total_junk_count = total_required_locations - len(itempool)
|
||||
|
||||
junk_pool = []
|
||||
for item_name in self.world.random.choices(list(junk_table.keys()), k=total_junk_count):
|
||||
for item_name in self.multiworld.random.choices(list(junk_table.keys()), k=total_junk_count):
|
||||
junk_pool.append(self.create_item(item_name))
|
||||
|
||||
itempool += junk_pool
|
||||
|
||||
self.active_level_list = level_list.copy()
|
||||
|
||||
if self.world.level_shuffle[self.player]:
|
||||
self.world.random.shuffle(self.active_level_list)
|
||||
if self.multiworld.level_shuffle[self.player]:
|
||||
self.multiworld.random.shuffle(self.active_level_list)
|
||||
|
||||
connect_regions(self.world, self.player, self.active_level_list)
|
||||
connect_regions(self.multiworld, self.player, self.active_level_list)
|
||||
|
||||
self.world.itempool += itempool
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
try:
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
|
||||
rom = LocalRom(get_base_rom_path())
|
||||
patch_rom(self.world, rom, self.player, self.active_level_list)
|
||||
patch_rom(self.multiworld, rom, self.player, self.active_level_list)
|
||||
|
||||
self.active_level_list.append(LocationName.rocket_rush_region)
|
||||
|
||||
@@ -165,7 +165,7 @@ class DKC3World(World):
|
||||
# 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()
|
||||
multidata["connect_names"][new_name] = multidata["connect_names"][self.world.player_name[self.player]]
|
||||
multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]]
|
||||
|
||||
if self.topology_present:
|
||||
world_names = [
|
||||
@@ -181,14 +181,14 @@ class DKC3World(World):
|
||||
er_hint_data = {}
|
||||
for world_index in range(len(world_names)):
|
||||
for level_index in range(5):
|
||||
level_region = self.world.get_region(self.active_level_list[world_index * 5 + level_index], self.player)
|
||||
level_region = self.multiworld.get_region(self.active_level_list[world_index * 5 + level_index], self.player)
|
||||
for location in level_region.locations:
|
||||
er_hint_data[location.address] = world_names[world_index]
|
||||
multidata['er_hint_data'][self.player] = er_hint_data
|
||||
|
||||
def create_regions(self):
|
||||
location_table = setup_locations(self.world, self.player)
|
||||
create_regions(self.world, self.player, location_table)
|
||||
location_table = setup_locations(self.multiworld, self.player)
|
||||
create_regions(self.multiworld, self.player, location_table)
|
||||
|
||||
def create_item(self, name: str, force_non_progression=False) -> Item:
|
||||
data = item_table[name]
|
||||
@@ -205,4 +205,4 @@ class DKC3World(World):
|
||||
return created_item
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
@@ -77,7 +77,7 @@ class FactorioModFile(worlds.Files.APContainer):
|
||||
|
||||
def generate_mod(world: "Factorio", output_directory: str):
|
||||
player = world.player
|
||||
multiworld = world.world
|
||||
multiworld = world.multiworld
|
||||
global data_final_template, locale_template, control_template, data_template, settings_template
|
||||
with template_load_lock:
|
||||
if not data_final_template:
|
||||
|
||||
@@ -20,7 +20,7 @@ def _sorter(location: "FactorioScienceLocation"):
|
||||
|
||||
|
||||
def get_shapes(factorio_world: "Factorio") -> Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]]:
|
||||
world = factorio_world.world
|
||||
world = factorio_world.multiworld
|
||||
player = factorio_world.player
|
||||
prerequisites: Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]] = {}
|
||||
layout = world.tech_tree_layout[player].value
|
||||
|
||||
@@ -73,31 +73,32 @@ class Factorio(World):
|
||||
generate_output = generate_mod
|
||||
|
||||
def generate_early(self) -> None:
|
||||
self.world.max_tech_cost[self.player] = max(self.world.max_tech_cost[self.player],
|
||||
self.world.min_tech_cost[self.player])
|
||||
self.tech_mix = self.world.tech_cost_mix[self.player]
|
||||
self.skip_silo = self.world.silo[self.player].value == Silo.option_spawn
|
||||
self.multiworld.max_tech_cost[self.player] = max(self.multiworld.max_tech_cost[self.player],
|
||||
self.multiworld.min_tech_cost[self.player])
|
||||
self.tech_mix = self.multiworld.tech_cost_mix[self.player]
|
||||
self.skip_silo = self.multiworld.silo[self.player].value == Silo.option_spawn
|
||||
|
||||
def create_regions(self):
|
||||
player = self.player
|
||||
random = self.world.random
|
||||
menu = Region("Menu", RegionType.Generic, "Menu", player, self.world)
|
||||
random = self.multiworld.random
|
||||
menu = Region("Menu", RegionType.Generic, "Menu", player, self.multiworld)
|
||||
crash = Entrance(player, "Crash Land", menu)
|
||||
menu.exits.append(crash)
|
||||
nauvis = Region("Nauvis", RegionType.Generic, "Nauvis", player, self.world)
|
||||
nauvis = Region("Nauvis", RegionType.Generic, "Nauvis", player, self.multiworld)
|
||||
|
||||
location_count = len(base_tech_table) - len(useless_technologies) - self.skip_silo + \
|
||||
self.world.evolution_traps[player].value + self.world.attack_traps[player].value
|
||||
self.multiworld.evolution_traps[player].value + self.multiworld.attack_traps[player].value
|
||||
|
||||
location_pool = []
|
||||
|
||||
for pack in self.world.max_science_pack[self.player].get_allowed_packs():
|
||||
for pack in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()):
|
||||
location_pool.extend(location_pools[pack])
|
||||
location_names = self.world.random.sample(location_pool, location_count)
|
||||
|
||||
location_names = self.multiworld.random.sample(location_pool, location_count)
|
||||
self.locations = [FactorioScienceLocation(player, loc_name, self.location_name_to_id[loc_name], nauvis)
|
||||
for loc_name in location_names]
|
||||
rand_values = sorted(random.randint(self.world.min_tech_cost[self.player],
|
||||
self.world.max_tech_cost[self.player]) for _ in self.locations)
|
||||
rand_values = sorted(random.randint(self.multiworld.min_tech_cost[self.player],
|
||||
self.multiworld.max_tech_cost[self.player]) for _ in self.locations)
|
||||
for i, location in enumerate(sorted(self.locations, key=lambda loc: loc.rel_cost)):
|
||||
location.count = rand_values[i]
|
||||
del rand_values
|
||||
@@ -107,27 +108,27 @@ class Factorio(World):
|
||||
event = FactorioItem("Victory", ItemClassification.progression, None, player)
|
||||
location.place_locked_item(event)
|
||||
|
||||
for ingredient in self.world.max_science_pack[self.player].get_allowed_packs():
|
||||
for ingredient in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()):
|
||||
location = FactorioLocation(player, f"Automate {ingredient}", None, nauvis)
|
||||
nauvis.locations.append(location)
|
||||
event = FactorioItem(f"Automated {ingredient}", ItemClassification.progression, None, player)
|
||||
location.place_locked_item(event)
|
||||
|
||||
crash.connect(nauvis)
|
||||
self.world.regions += [menu, nauvis]
|
||||
self.multiworld.regions += [menu, nauvis]
|
||||
|
||||
def set_rules(self):
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
self.custom_technologies = self.set_custom_technologies()
|
||||
self.set_custom_recipes()
|
||||
shapes = get_shapes(self)
|
||||
if world.logic[player] != 'nologic':
|
||||
from worlds.generic import Rules
|
||||
for ingredient in self.world.max_science_pack[self.player].get_allowed_packs():
|
||||
for ingredient in self.multiworld.max_science_pack[self.player].get_allowed_packs():
|
||||
location = world.get_location(f"Automate {ingredient}", player)
|
||||
|
||||
if self.world.recipe_ingredients[self.player]:
|
||||
if self.multiworld.recipe_ingredients[self.player]:
|
||||
custom_recipe = self.custom_recipes[ingredient]
|
||||
|
||||
location.access_rule = lambda state, ingredient=ingredient, custom_recipe=custom_recipe: \
|
||||
@@ -147,12 +148,12 @@ class Factorio(World):
|
||||
prerequisites: all(state.can_reach(loc) for loc in locations))
|
||||
|
||||
silo_recipe = None
|
||||
if self.world.silo[self.player] == Silo.option_spawn:
|
||||
if self.multiworld.silo[self.player] == Silo.option_spawn:
|
||||
silo_recipe = self.custom_recipes["rocket-silo"] if "rocket-silo" in self.custom_recipes \
|
||||
else next(iter(all_product_sources.get("rocket-silo")))
|
||||
part_recipe = self.custom_recipes["rocket-part"]
|
||||
satellite_recipe = None
|
||||
if self.world.goal[self.player] == Goal.option_satellite:
|
||||
if self.multiworld.goal[self.player] == Goal.option_satellite:
|
||||
satellite_recipe = self.custom_recipes["satellite"] if "satellite" in self.custom_recipes \
|
||||
else next(iter(all_product_sources.get("satellite")))
|
||||
victory_tech_names = get_rocket_requirements(silo_recipe, part_recipe, satellite_recipe)
|
||||
@@ -164,12 +165,12 @@ class Factorio(World):
|
||||
|
||||
def generate_basic(self):
|
||||
player = self.player
|
||||
want_progressives = collections.defaultdict(lambda: self.world.progressive[player].
|
||||
want_progressives(self.world.random))
|
||||
self.world.itempool.extend(self.create_item("Evolution Trap") for _ in
|
||||
range(self.world.evolution_traps[player].value))
|
||||
self.world.itempool.extend(self.create_item("Attack Trap") for _ in
|
||||
range(self.world.attack_traps[player].value))
|
||||
want_progressives = collections.defaultdict(lambda: self.multiworld.progressive[player].
|
||||
want_progressives(self.multiworld.random))
|
||||
self.multiworld.itempool.extend(self.create_item("Evolution Trap") for _ in
|
||||
range(self.multiworld.evolution_traps[player].value))
|
||||
self.multiworld.itempool.extend(self.create_item("Attack Trap") for _ in
|
||||
range(self.multiworld.attack_traps[player].value))
|
||||
|
||||
cost_sorted_locations = sorted(self.locations, key=lambda location: location.name)
|
||||
special_index = {"automation": 0,
|
||||
@@ -188,19 +189,19 @@ class Factorio(World):
|
||||
tech_item = self.create_item(item_name)
|
||||
index = special_index.get(tech_name, None)
|
||||
if index is None:
|
||||
self.world.itempool.append(tech_item)
|
||||
self.multiworld.itempool.append(tech_item)
|
||||
else:
|
||||
loc = cost_sorted_locations[index]
|
||||
loc.place_locked_item(tech_item)
|
||||
loc.revealed = True
|
||||
|
||||
map_basic_settings = self.world.world_gen[player].value["basic"]
|
||||
map_basic_settings = self.multiworld.world_gen[player].value["basic"]
|
||||
if map_basic_settings.get("seed", None) is None: # allow seed 0
|
||||
map_basic_settings["seed"] = self.world.slot_seeds[player].randint(0, 2 ** 32 - 1) # 32 bit uint
|
||||
map_basic_settings["seed"] = self.multiworld.slot_seeds[player].randint(0, 2 ** 32 - 1) # 32 bit uint
|
||||
|
||||
if self.world.tech_tree_information[player] == TechTreeInformation.option_full:
|
||||
if self.multiworld.tech_tree_information[player] == TechTreeInformation.option_full:
|
||||
# mark all locations as pre-hinted
|
||||
self.world.start_location_hints[self.player].value.update(base_tech_table)
|
||||
self.multiworld.start_location_hints[self.player].value.update(base_tech_table)
|
||||
for loc in self.locations:
|
||||
loc.revealed = True
|
||||
|
||||
@@ -258,7 +259,7 @@ class Factorio(World):
|
||||
# have to first sort for determinism, while filtering out non-stacking items
|
||||
pool: typing.List[str] = sorted(pool & valid_ingredients)
|
||||
# then sort with random data to shuffle
|
||||
self.world.random.shuffle(pool)
|
||||
self.multiworld.random.shuffle(pool)
|
||||
target_raw = int(sum((count for ingredient, count in original.base_cost.items())) * factor)
|
||||
target_energy = original.total_energy * factor
|
||||
target_num_ingredients = len(original.ingredients)
|
||||
@@ -302,7 +303,7 @@ class Factorio(World):
|
||||
if min_num > max_num:
|
||||
fallback_pool.append(ingredient)
|
||||
continue # can't use that ingredient
|
||||
num = self.world.random.randint(min_num, max_num)
|
||||
num = self.multiworld.random.randint(min_num, max_num)
|
||||
new_ingredients[ingredient] = num
|
||||
remaining_raw -= num * ingredient_raw
|
||||
remaining_energy -= num * ingredient_energy
|
||||
@@ -346,58 +347,58 @@ class Factorio(World):
|
||||
|
||||
def set_custom_technologies(self):
|
||||
custom_technologies = {}
|
||||
allowed_packs = self.world.max_science_pack[self.player].get_allowed_packs()
|
||||
allowed_packs = self.multiworld.max_science_pack[self.player].get_allowed_packs()
|
||||
for technology_name, technology in base_technology_table.items():
|
||||
custom_technologies[technology_name] = technology.get_custom(self.world, allowed_packs, self.player)
|
||||
custom_technologies[technology_name] = technology.get_custom(self.multiworld, allowed_packs, self.player)
|
||||
return custom_technologies
|
||||
|
||||
def set_custom_recipes(self):
|
||||
original_rocket_part = recipes["rocket-part"]
|
||||
science_pack_pools = get_science_pack_pools()
|
||||
valid_pool = sorted(science_pack_pools[self.world.max_science_pack[self.player].get_max_pack()] & valid_ingredients)
|
||||
self.world.random.shuffle(valid_pool)
|
||||
valid_pool = sorted(science_pack_pools[self.multiworld.max_science_pack[self.player].get_max_pack()] & valid_ingredients)
|
||||
self.multiworld.random.shuffle(valid_pool)
|
||||
self.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category,
|
||||
{valid_pool[x]: 10 for x in range(3)},
|
||||
original_rocket_part.products,
|
||||
original_rocket_part.energy)}
|
||||
|
||||
if self.world.recipe_ingredients[self.player]:
|
||||
if self.multiworld.recipe_ingredients[self.player]:
|
||||
valid_pool = []
|
||||
for pack in self.world.max_science_pack[self.player].get_ordered_science_packs():
|
||||
for pack in self.multiworld.max_science_pack[self.player].get_ordered_science_packs():
|
||||
valid_pool += sorted(science_pack_pools[pack])
|
||||
self.world.random.shuffle(valid_pool)
|
||||
self.multiworld.random.shuffle(valid_pool)
|
||||
if pack in recipes: # skips over space science pack
|
||||
new_recipe = self.make_quick_recipe(recipes[pack], valid_pool)
|
||||
self.custom_recipes[pack] = new_recipe
|
||||
|
||||
if self.world.silo[self.player].value == Silo.option_randomize_recipe \
|
||||
or self.world.satellite[self.player].value == Satellite.option_randomize_recipe:
|
||||
if self.multiworld.silo[self.player].value == Silo.option_randomize_recipe \
|
||||
or self.multiworld.satellite[self.player].value == Satellite.option_randomize_recipe:
|
||||
valid_pool = set()
|
||||
for pack in sorted(self.world.max_science_pack[self.player].get_allowed_packs()):
|
||||
for pack in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()):
|
||||
valid_pool |= science_pack_pools[pack]
|
||||
|
||||
if self.world.silo[self.player].value == Silo.option_randomize_recipe:
|
||||
if self.multiworld.silo[self.player].value == Silo.option_randomize_recipe:
|
||||
new_recipe = self.make_balanced_recipe(recipes["rocket-silo"], valid_pool,
|
||||
factor=(self.world.max_science_pack[self.player].value + 1) / 7)
|
||||
factor=(self.multiworld.max_science_pack[self.player].value + 1) / 7)
|
||||
self.custom_recipes["rocket-silo"] = new_recipe
|
||||
|
||||
if self.world.satellite[self.player].value == Satellite.option_randomize_recipe:
|
||||
if self.multiworld.satellite[self.player].value == Satellite.option_randomize_recipe:
|
||||
new_recipe = self.make_balanced_recipe(recipes["satellite"], valid_pool,
|
||||
factor=(self.world.max_science_pack[self.player].value + 1) / 7)
|
||||
factor=(self.multiworld.max_science_pack[self.player].value + 1) / 7)
|
||||
self.custom_recipes["satellite"] = new_recipe
|
||||
bridge = "ap-energy-bridge"
|
||||
new_recipe = self.make_quick_recipe(
|
||||
Recipe(bridge, "crafting", {"replace_1": 1, "replace_2": 1, "replace_3": 1},
|
||||
{bridge: 1}, 10),
|
||||
sorted(science_pack_pools[self.world.max_science_pack[self.player].get_ordered_science_packs()[0]]))
|
||||
sorted(science_pack_pools[self.multiworld.max_science_pack[self.player].get_ordered_science_packs()[0]]))
|
||||
for ingredient_name in new_recipe.ingredients:
|
||||
new_recipe.ingredients[ingredient_name] = self.world.random.randint(10, 100)
|
||||
new_recipe.ingredients[ingredient_name] = self.multiworld.random.randint(10, 100)
|
||||
self.custom_recipes[bridge] = new_recipe
|
||||
|
||||
needed_recipes = self.world.max_science_pack[self.player].get_allowed_packs() | {"rocket-part"}
|
||||
if self.world.silo[self.player] != Silo.option_spawn:
|
||||
needed_recipes = self.multiworld.max_science_pack[self.player].get_allowed_packs() | {"rocket-part"}
|
||||
if self.multiworld.silo[self.player] != Silo.option_spawn:
|
||||
needed_recipes |= {"rocket-silo"}
|
||||
if self.world.goal[self.player].value == Goal.option_satellite:
|
||||
if self.multiworld.goal[self.player].value == Goal.option_satellite:
|
||||
needed_recipes |= {"satellite"}
|
||||
|
||||
for recipe in needed_recipes:
|
||||
@@ -447,10 +448,10 @@ class FactorioScienceLocation(FactorioLocation):
|
||||
|
||||
self.ingredients = {Factorio.ordered_science_packs[self.complexity]: 1}
|
||||
for complexity in range(self.complexity):
|
||||
if parent.world.tech_cost_mix[self.player] > parent.world.random.randint(0, 99):
|
||||
if parent.multiworld.tech_cost_mix[self.player] > parent.multiworld.random.randint(0, 99):
|
||||
self.ingredients[Factorio.ordered_science_packs[complexity]] = 1
|
||||
self.count = parent.world.random.randint(parent.world.min_tech_cost[self.player],
|
||||
parent.world.max_tech_cost[self.player])
|
||||
self.count = parent.multiworld.random.randint(parent.multiworld.min_tech_cost[self.player],
|
||||
parent.multiworld.max_tech_cost[self.player])
|
||||
|
||||
@property
|
||||
def factorio_ingredients(self) -> typing.List[typing.Tuple[str, int]]:
|
||||
|
||||
@@ -51,15 +51,15 @@ class FF1World(World):
|
||||
return
|
||||
|
||||
def create_regions(self):
|
||||
locations = get_options(self.world, 'locations', self.player)
|
||||
rules = get_options(self.world, 'rules', self.player)
|
||||
locations = get_options(self.multiworld, 'locations', self.player)
|
||||
rules = get_options(self.multiworld, 'rules', self.player)
|
||||
menu_region = self.ff1_locations.create_menu_region(self.player, locations, rules)
|
||||
menu_region.world = self.world
|
||||
menu_region.multiworld = self.multiworld
|
||||
terminated_event = Location(self.player, CHAOS_TERMINATED_EVENT, EventId, menu_region)
|
||||
terminated_item = Item(CHAOS_TERMINATED_EVENT, ItemClassification.progression, EventId, self.player)
|
||||
terminated_event.place_locked_item(terminated_item)
|
||||
|
||||
items = get_options(self.world, 'items', self.player)
|
||||
items = get_options(self.multiworld, 'items', self.player)
|
||||
goal_rule = generate_rule([[name for name in items.keys() if name in FF1_PROGRESSION_LIST and name != "Shard"]],
|
||||
self.player)
|
||||
if "Shard" in items.keys():
|
||||
@@ -71,22 +71,22 @@ class FF1World(World):
|
||||
raise Exception("FFR Noverworld seeds must be generated on an older version of FFR. Please ensure you generated the settings using "
|
||||
"4-4-0.finalfantasyrandomizer.com")
|
||||
menu_region.locations.append(terminated_event)
|
||||
self.world.regions += [menu_region]
|
||||
self.multiworld.regions += [menu_region]
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return self.ff1_items.generate_item(name, self.player)
|
||||
|
||||
def set_rules(self):
|
||||
self.world.completion_condition[self.player] = lambda state: state.has(CHAOS_TERMINATED_EVENT, self.player)
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has(CHAOS_TERMINATED_EVENT, self.player)
|
||||
|
||||
def generate_basic(self):
|
||||
items = get_options(self.world, 'items', self.player)
|
||||
items = get_options(self.multiworld, 'items', self.player)
|
||||
if FF1_BRIDGE in items.keys():
|
||||
self._place_locked_item_in_sphere0(FF1_BRIDGE)
|
||||
if items:
|
||||
possible_early_items = [name for name in FF1_STARTER_ITEMS if name in items.keys()]
|
||||
if possible_early_items:
|
||||
progression_item = self.world.random.choice(possible_early_items)
|
||||
progression_item = self.multiworld.random.choice(possible_early_items)
|
||||
self._place_locked_item_in_sphere0(progression_item)
|
||||
else:
|
||||
# Fail generation if there are no items in the pool
|
||||
@@ -96,16 +96,16 @@ class FF1World(World):
|
||||
items = [self.create_item(name) for name, data in items.items() for x in range(data['count']) if name not in
|
||||
self.locked_items]
|
||||
|
||||
self.world.itempool += items
|
||||
self.multiworld.itempool += items
|
||||
|
||||
def _place_locked_item_in_sphere0(self, progression_item: str):
|
||||
if progression_item:
|
||||
rules = get_options(self.world, 'rules', self.player)
|
||||
rules = get_options(self.multiworld, 'rules', self.player)
|
||||
sphere_0_locations = [name for name, rules in rules.items()
|
||||
if rules and len(rules[0]) == 0 and name not in self.locked_locations]
|
||||
if sphere_0_locations:
|
||||
initial_location = self.world.random.choice(sphere_0_locations)
|
||||
locked_location = self.world.get_location(initial_location, self.player)
|
||||
initial_location = self.multiworld.random.choice(sphere_0_locations)
|
||||
locked_location = self.multiworld.get_location(initial_location, self.player)
|
||||
locked_location.place_locked_item(self.create_item(progression_item))
|
||||
self.locked_items.append(progression_item)
|
||||
self.locked_locations.append(locked_location.name)
|
||||
@@ -116,7 +116,7 @@ class FF1World(World):
|
||||
return slot_data
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice(["Heal", "Pure", "Soft", "Tent", "Cabin", "House"])
|
||||
return self.multiworld.random.choice(["Heal", "Pure", "Soft", "Tent", "Cabin", "House"])
|
||||
|
||||
|
||||
def get_options(world: MultiWorld, name: str, player: int):
|
||||
|
||||
@@ -151,7 +151,7 @@ def item_in_locations(state: "BaseClasses.CollectionState", item: str, player: i
|
||||
|
||||
def item_name(state: "BaseClasses.CollectionState", location: str, player: int) -> \
|
||||
typing.Optional[typing.Tuple[str, int]]:
|
||||
location = state.world.get_location(location, player)
|
||||
location = state.multiworld.get_location(location, player)
|
||||
if location.item is None:
|
||||
return None
|
||||
return location.item.name, location.item.player
|
||||
|
||||
@@ -44,7 +44,7 @@ class GenericWorld(World):
|
||||
web = GenericWeb()
|
||||
|
||||
def generate_early(self):
|
||||
self.world.player_types[self.player] = SlotType.spectator # mark as spectator
|
||||
self.multiworld.player_types[self.player] = SlotType.spectator # mark as spectator
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
if name == "Nothing":
|
||||
|
||||
@@ -29,7 +29,7 @@ def hk_set_rule(hk_world: World, location: str, rule):
|
||||
locations = hk_world.created_multi_locations.get(location)
|
||||
if locations is None:
|
||||
try:
|
||||
locations = [hk_world.world.get_location(location, player)]
|
||||
locations = [hk_world.multiworld.get_location(location, player)]
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
@@ -39,7 +39,7 @@ def hk_set_rule(hk_world: World, location: str, rule):
|
||||
|
||||
def set_rules(hk_world: World):
|
||||
player = hk_world.player
|
||||
world = hk_world.world
|
||||
world = hk_world.multiworld
|
||||
set_generated_rules(hk_world, hk_set_rule)
|
||||
|
||||
# Shop costs
|
||||
|
||||
@@ -166,7 +166,7 @@ class HKWorld(World):
|
||||
self.vanilla_shop_costs = deepcopy(vanilla_shop_costs)
|
||||
|
||||
def generate_early(self):
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
charm_costs = world.RandomCharmCosts[self.player].get_costs(world.random)
|
||||
self.charm_costs = world.PlandoCharmCosts[self.player].get_costs(charm_costs)
|
||||
# world.exclude_locations[self.player].value.update(white_palace_locations)
|
||||
@@ -182,22 +182,22 @@ class HKWorld(World):
|
||||
|
||||
def white_palace_exclusions(self):
|
||||
exclusions = set()
|
||||
wp = self.world.WhitePalace[self.player]
|
||||
wp = self.multiworld.WhitePalace[self.player]
|
||||
if wp <= WhitePalace.option_nopathofpain:
|
||||
exclusions.update(path_of_pain_locations)
|
||||
if wp <= WhitePalace.option_kingfragment:
|
||||
exclusions.update(white_palace_checks)
|
||||
if wp == WhitePalace.option_exclude:
|
||||
exclusions.add("King_Fragment")
|
||||
if self.world.RandomizeCharms[self.player]:
|
||||
if self.multiworld.RandomizeCharms[self.player]:
|
||||
# If charms are randomized, this will be junk-filled -- so transitions and events are not progression
|
||||
exclusions.update(white_palace_transitions)
|
||||
exclusions.update(white_palace_events)
|
||||
return exclusions
|
||||
|
||||
def create_regions(self):
|
||||
menu_region: Region = create_region(self.world, self.player, 'Menu')
|
||||
self.world.regions.append(menu_region)
|
||||
menu_region: Region = create_region(self.multiworld, self.player, 'Menu')
|
||||
self.multiworld.regions.append(menu_region)
|
||||
# wp_exclusions = self.white_palace_exclusions()
|
||||
|
||||
# Link regions
|
||||
@@ -226,12 +226,12 @@ class HKWorld(World):
|
||||
pool: typing.List[HKItem] = []
|
||||
wp_exclusions = self.white_palace_exclusions()
|
||||
junk_replace: typing.Set[str] = set()
|
||||
if self.world.RemoveSpellUpgrades[self.player]:
|
||||
if self.multiworld.RemoveSpellUpgrades[self.player]:
|
||||
junk_replace.update(("Abyss_Shriek", "Shade_Soul", "Descending_Dark"))
|
||||
|
||||
randomized_starting_items = set()
|
||||
for attr, items in randomizable_starting_items.items():
|
||||
if getattr(self.world, attr)[self.player]:
|
||||
if getattr(self.multiworld, attr)[self.player]:
|
||||
randomized_starting_items.update(items)
|
||||
|
||||
# noinspection PyShadowingNames
|
||||
@@ -262,7 +262,7 @@ class HKWorld(World):
|
||||
unfilled_locations += 1
|
||||
pool.append(item)
|
||||
else:
|
||||
self.world.push_precollected(item)
|
||||
self.multiworld.push_precollected(item)
|
||||
return
|
||||
|
||||
if vanilla:
|
||||
@@ -277,49 +277,49 @@ class HKWorld(World):
|
||||
location.progress_type = LocationProgressType.EXCLUDED
|
||||
|
||||
for option_key, option in hollow_knight_randomize_options.items():
|
||||
randomized = getattr(self.world, option_key)[self.player]
|
||||
randomized = getattr(self.multiworld, option_key)[self.player]
|
||||
for item_name, location_name in zip(option.items, option.locations):
|
||||
if item_name in junk_replace:
|
||||
item_name = self.get_filler_item_name()
|
||||
|
||||
if (item_name == "Crystal_Heart" and self.world.SplitCrystalHeart[self.player]) or \
|
||||
(item_name == "Mothwing_Cloak" and self.world.SplitMothwingCloak[self.player]):
|
||||
if (item_name == "Crystal_Heart" and self.multiworld.SplitCrystalHeart[self.player]) or \
|
||||
(item_name == "Mothwing_Cloak" and self.multiworld.SplitMothwingCloak[self.player]):
|
||||
_add("Left_" + item_name, location_name)
|
||||
_add("Right_" + item_name, "Split_" + location_name)
|
||||
continue
|
||||
if item_name == "Mantis_Claw" and self.world.SplitMantisClaw[self.player]:
|
||||
if item_name == "Mantis_Claw" and self.multiworld.SplitMantisClaw[self.player]:
|
||||
_add("Left_" + item_name, "Left_" + location_name)
|
||||
_add("Right_" + item_name, "Right_" + location_name)
|
||||
continue
|
||||
if item_name == "Shade_Cloak" and self.world.SplitMothwingCloak[self.player]:
|
||||
if self.world.random.randint(0, 1):
|
||||
if item_name == "Shade_Cloak" and self.multiworld.SplitMothwingCloak[self.player]:
|
||||
if self.multiworld.random.randint(0, 1):
|
||||
item_name = "Left_Mothwing_Cloak"
|
||||
else:
|
||||
item_name = "Right_Mothwing_Cloak"
|
||||
|
||||
_add(item_name, location_name)
|
||||
|
||||
if self.world.RandomizeElevatorPass[self.player]:
|
||||
if self.multiworld.RandomizeElevatorPass[self.player]:
|
||||
randomized = True
|
||||
_add("Elevator_Pass", "Elevator_Pass")
|
||||
|
||||
for shop, locations in self.created_multi_locations.items():
|
||||
for _ in range(len(locations), getattr(self.world, shop_to_option[shop])[self.player].value):
|
||||
for _ in range(len(locations), getattr(self.multiworld, shop_to_option[shop])[self.player].value):
|
||||
loc = self.create_location(shop)
|
||||
unfilled_locations += 1
|
||||
|
||||
# Balance the pool
|
||||
item_count = len(pool)
|
||||
additional_shop_items = max(item_count - unfilled_locations, self.world.ExtraShopSlots[self.player].value)
|
||||
additional_shop_items = max(item_count - unfilled_locations, self.multiworld.ExtraShopSlots[self.player].value)
|
||||
|
||||
# Add additional shop items, as needed.
|
||||
if additional_shop_items > 0:
|
||||
shops = list(shop for shop, locations in self.created_multi_locations.items() if len(locations) < 16)
|
||||
if not self.world.EggShopSlots[self.player].value: # No eggshop, so don't place items there
|
||||
if not self.multiworld.EggShopSlots[self.player].value: # No eggshop, so don't place items there
|
||||
shops.remove('Egg_Shop')
|
||||
|
||||
for _ in range(additional_shop_items):
|
||||
shop = self.world.random.choice(shops)
|
||||
shop = self.multiworld.random.choice(shops)
|
||||
loc = self.create_location(shop)
|
||||
unfilled_locations += 1
|
||||
if len(self.created_multi_locations[shop]) >= 16:
|
||||
@@ -330,7 +330,7 @@ class HKWorld(World):
|
||||
# Create filler items, if needed
|
||||
if item_count < unfilled_locations:
|
||||
pool.extend(self.create_item(self.get_filler_item_name()) for _ in range(unfilled_locations - item_count))
|
||||
self.world.itempool += pool
|
||||
self.multiworld.itempool += pool
|
||||
self.apply_costsanity()
|
||||
self.sort_shops_by_cost()
|
||||
|
||||
@@ -345,24 +345,24 @@ class HKWorld(World):
|
||||
loc.costs = costs
|
||||
|
||||
def apply_costsanity(self):
|
||||
setting = self.world.CostSanity[self.player].value
|
||||
setting = self.multiworld.CostSanity[self.player].value
|
||||
if not setting:
|
||||
return # noop
|
||||
|
||||
def _compute_weights(weights: dict, desc: str) -> typing.Dict[str, int]:
|
||||
if all(x == 0 for x in weights.values()):
|
||||
logger.warning(
|
||||
f"All {desc} weights were zero for {self.world.player_name[self.player]}."
|
||||
f"All {desc} weights were zero for {self.multiworld.player_name[self.player]}."
|
||||
f" Setting them to one instead."
|
||||
)
|
||||
weights = {k: 1 for k in weights}
|
||||
|
||||
return {k: v for k, v in weights.items() if v}
|
||||
|
||||
random = self.world.random
|
||||
hybrid_chance = getattr(self.world, f"CostSanityHybridChance")[self.player].value
|
||||
random = self.multiworld.random
|
||||
hybrid_chance = getattr(self.multiworld, f"CostSanityHybridChance")[self.player].value
|
||||
weights = {
|
||||
data.term: getattr(self.world, f"CostSanity{data.option}Weight")[self.player].value
|
||||
data.term: getattr(self.multiworld, f"CostSanity{data.option}Weight")[self.player].value
|
||||
for data in cost_terms.values()
|
||||
}
|
||||
weights_geoless = dict(weights)
|
||||
@@ -374,16 +374,16 @@ class HKWorld(World):
|
||||
if hybrid_chance > 0:
|
||||
if len(weights) == 1:
|
||||
logger.warning(
|
||||
f"Only one cost type is available for CostSanity in {self.world.player_name[self.player]}'s world."
|
||||
f"Only one cost type is available for CostSanity in {self.multiworld.player_name[self.player]}'s world."
|
||||
f" CostSanityHybridChance will not trigger."
|
||||
)
|
||||
if len(weights_geoless) == 1:
|
||||
logger.warning(
|
||||
f"Only one cost type is available for CostSanity in {self.world.player_name[self.player]}'s world."
|
||||
f"Only one cost type is available for CostSanity in {self.multiworld.player_name[self.player]}'s world."
|
||||
f" CostSanityHybridChance will not trigger in geoless locations."
|
||||
)
|
||||
|
||||
for region in self.world.get_regions(self.player):
|
||||
for region in self.multiworld.get_regions(self.player):
|
||||
for location in region.locations:
|
||||
if location.vanilla:
|
||||
continue
|
||||
@@ -417,7 +417,7 @@ class HKWorld(World):
|
||||
location.sort_costs()
|
||||
|
||||
def set_rules(self):
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
if world.logic[player] != 'nologic':
|
||||
goal = world.Goal[player]
|
||||
@@ -436,7 +436,7 @@ class HKWorld(World):
|
||||
|
||||
options = slot_data["options"] = {}
|
||||
for option_name in self.option_definitions:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
try:
|
||||
optionvalue = int(option.value)
|
||||
except TypeError:
|
||||
@@ -445,10 +445,10 @@ class HKWorld(World):
|
||||
options[option_name] = optionvalue
|
||||
|
||||
# 32 bit int
|
||||
slot_data["seed"] = self.world.slot_seeds[self.player].randint(-2147483647, 2147483646)
|
||||
slot_data["seed"] = self.multiworld.slot_seeds[self.player].randint(-2147483647, 2147483646)
|
||||
|
||||
# Backwards compatibility for shop cost data (HKAP < 0.1.0)
|
||||
if not self.world.CostSanity[self.player]:
|
||||
if not self.multiworld.CostSanity[self.player]:
|
||||
for shop, terms in shop_cost_types.items():
|
||||
unit = cost_terms[next(iter(terms))].option
|
||||
if unit == "Geo":
|
||||
@@ -460,7 +460,7 @@ class HKWorld(World):
|
||||
|
||||
# HKAP 0.1.0 and later cost data.
|
||||
location_costs = {}
|
||||
for region in self.world.get_regions(self.player):
|
||||
for region in self.multiworld.get_regions(self.player):
|
||||
for location in region.locations:
|
||||
if location.costs:
|
||||
location_costs[location.name] = location.costs
|
||||
@@ -479,7 +479,7 @@ class HKWorld(World):
|
||||
basename = name
|
||||
if name in shop_cost_types:
|
||||
costs = {
|
||||
term: self.world.random.randint(*self.ranges[term])
|
||||
term: self.multiworld.random.randint(*self.ranges[term])
|
||||
for term in shop_cost_types[name]
|
||||
}
|
||||
elif name in vanilla_location_costs:
|
||||
@@ -491,7 +491,7 @@ class HKWorld(World):
|
||||
i = len(multi) + 1
|
||||
name = f"{name}_{i}"
|
||||
|
||||
region = self.world.get_region("Menu", self.player)
|
||||
region = self.multiworld.get_region("Menu", self.player)
|
||||
loc = HKLocation(self.player, name,
|
||||
self.location_name_to_id[name], region, costs=costs, vanilla=vanilla,
|
||||
basename=basename)
|
||||
@@ -577,16 +577,16 @@ class HKWorld(World):
|
||||
'RandomizeGeoRocks', 'RandomizeSoulTotems', 'RandomizeLoreTablets', 'RandomizeJunkPitChests',
|
||||
'RandomizeRancidEggs'
|
||||
):
|
||||
if getattr(self.world, group):
|
||||
if getattr(self.multiworld, group):
|
||||
fillers.extend(item for item in hollow_knight_randomize_options[group].items if item not in
|
||||
exclusions)
|
||||
self.cached_filler_items[self.player] = fillers
|
||||
return self.world.random.choice(self.cached_filler_items[self.player])
|
||||
return self.multiworld.random.choice(self.cached_filler_items[self.player])
|
||||
|
||||
|
||||
def create_region(world: MultiWorld, player: int, name: str, location_names=None, exits=None) -> Region:
|
||||
ret = Region(name, RegionType.Generic, name, player)
|
||||
ret.world = world
|
||||
ret.multiworld = world
|
||||
if location_names:
|
||||
for location in location_names:
|
||||
loc_id = HKWorld.location_name_to_id.get(location, None)
|
||||
@@ -654,16 +654,16 @@ class HKItem(Item):
|
||||
|
||||
|
||||
class HKLogicMixin(LogicMixin):
|
||||
world: MultiWorld
|
||||
multiworld: MultiWorld
|
||||
|
||||
def _hk_notches(self, player: int, *notches: int) -> int:
|
||||
return sum(self.world.worlds[player].charm_costs[notch] for notch in notches)
|
||||
return sum(self.multiworld.worlds[player].charm_costs[notch] for notch in notches)
|
||||
|
||||
def _hk_option(self, player: int, option_name: str) -> int:
|
||||
return getattr(self.world, option_name)[player].value
|
||||
return getattr(self.multiworld, option_name)[player].value
|
||||
|
||||
def _hk_start(self, player, start_location: str) -> bool:
|
||||
return self.world.StartLocation[player] == start_location
|
||||
return self.multiworld.StartLocation[player] == start_location
|
||||
|
||||
def _hk_nail_combat(self, player: int) -> bool:
|
||||
return self.has_any({'LFFTSLASH', 'RIGHTSLASH', 'UPSLASH'}, player)
|
||||
|
||||
@@ -89,7 +89,7 @@ class Hylics2Logic(LogicMixin):
|
||||
|
||||
|
||||
def set_rules(hylics2world):
|
||||
world = hylics2world.world
|
||||
world = hylics2world.multiworld
|
||||
player = hylics2world.player
|
||||
|
||||
# Afterlife
|
||||
|
||||
@@ -64,8 +64,8 @@ class Hylics2World(World):
|
||||
|
||||
# set random starting location if option is enabled
|
||||
def generate_early(self):
|
||||
if self.world.random_start[self.player]:
|
||||
i = self.world.random.randint(0, 3)
|
||||
if self.multiworld.random_start[self.player]:
|
||||
i = self.multiworld.random.randint(0, 3)
|
||||
if i == 0:
|
||||
self.start_location = "Waynehouse"
|
||||
elif i == 1:
|
||||
@@ -77,12 +77,12 @@ class Hylics2World(World):
|
||||
|
||||
def generate_basic(self):
|
||||
# create location for beating the game and place Victory event there
|
||||
loc = Location(self.player, "Defeat Gibby", None, self.world.get_region("Hylemxylem", self.player))
|
||||
loc = Location(self.player, "Defeat Gibby", None, self.multiworld.get_region("Hylemxylem", self.player))
|
||||
loc.place_locked_item(self.create_event("Victory"))
|
||||
set_rule(loc, lambda state: state._hylics2_has_upper_chamber_key(self.player)
|
||||
and state._hylics2_has_vessel_room_key(self.player))
|
||||
self.world.get_region("Hylemxylem", self.player).locations.append(loc)
|
||||
self.world.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
|
||||
self.multiworld.get_region("Hylemxylem", self.player).locations.append(loc)
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
|
||||
|
||||
# create item pool
|
||||
pool = []
|
||||
@@ -94,53 +94,53 @@ class Hylics2World(World):
|
||||
pool.append(self.add_item(data["name"], data["classification"], i))
|
||||
|
||||
# add party members if option is enabled
|
||||
if self.world.party_shuffle[self.player]:
|
||||
if self.multiworld.party_shuffle[self.player]:
|
||||
for i, data in Items.party_item_table.items():
|
||||
pool.append(self.add_item(data["name"], data["classification"], i))
|
||||
|
||||
# handle gesture shuffle options
|
||||
if self.world.gesture_shuffle[self.player] == 2: # vanilla locations
|
||||
if self.multiworld.gesture_shuffle[self.player] == 2: # vanilla locations
|
||||
gestures = Items.gesture_item_table
|
||||
self.world.get_location("Waynehouse: TV", self.player)\
|
||||
self.multiworld.get_location("Waynehouse: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200678]["name"], gestures[200678]["classification"], 200678))
|
||||
self.world.get_location("Afterlife: TV", self.player)\
|
||||
self.multiworld.get_location("Afterlife: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200683]["name"], gestures[200683]["classification"], 200683))
|
||||
self.world.get_location("New Muldul: TV", self.player)\
|
||||
self.multiworld.get_location("New Muldul: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200679]["name"], gestures[200679]["classification"], 200679))
|
||||
self.world.get_location("Viewax's Edifice: TV", self.player)\
|
||||
self.multiworld.get_location("Viewax's Edifice: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200680]["name"], gestures[200680]["classification"], 200680))
|
||||
self.world.get_location("TV Island: TV", self.player)\
|
||||
self.multiworld.get_location("TV Island: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200681]["name"], gestures[200681]["classification"], 200681))
|
||||
self.world.get_location("Juice Ranch: TV", self.player)\
|
||||
self.multiworld.get_location("Juice Ranch: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200682]["name"], gestures[200682]["classification"], 200682))
|
||||
self.world.get_location("Foglast: TV", self.player)\
|
||||
self.multiworld.get_location("Foglast: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200684]["name"], gestures[200684]["classification"], 200684))
|
||||
self.world.get_location("Drill Castle: TV", self.player)\
|
||||
self.multiworld.get_location("Drill Castle: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200688]["name"], gestures[200688]["classification"], 200688))
|
||||
self.world.get_location("Sage Airship: TV", self.player)\
|
||||
self.multiworld.get_location("Sage Airship: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200685]["name"], gestures[200685]["classification"], 200685))
|
||||
|
||||
elif self.world.gesture_shuffle[self.player] == 1: # TVs only
|
||||
elif self.multiworld.gesture_shuffle[self.player] == 1: # TVs only
|
||||
gestures = list(Items.gesture_item_table.items())
|
||||
tvs = list(Locations.tv_location_table.items())
|
||||
|
||||
# if Extra Items in Logic is enabled place CHARGE UP first and make sure it doesn't get
|
||||
# placed at Sage Airship: TV
|
||||
if self.world.extra_items_in_logic[self.player]:
|
||||
tv = self.world.random.choice(tvs)
|
||||
if self.multiworld.extra_items_in_logic[self.player]:
|
||||
tv = self.multiworld.random.choice(tvs)
|
||||
gest = gestures.index((200681, Items.gesture_item_table[200681]))
|
||||
while tv[1]["name"] == "Sage Airship: TV":
|
||||
tv = self.world.random.choice(tvs)
|
||||
self.world.get_location(tv[1]["name"], self.player)\
|
||||
tv = self.multiworld.random.choice(tvs)
|
||||
self.multiworld.get_location(tv[1]["name"], self.player)\
|
||||
.place_locked_item(self.add_item(gestures[gest][1]["name"], gestures[gest][1]["classification"],
|
||||
gestures[gest]))
|
||||
gestures.remove(gestures[gest])
|
||||
tvs.remove(tv)
|
||||
|
||||
for i in range(len(gestures)):
|
||||
gest = self.world.random.choice(gestures)
|
||||
tv = self.world.random.choice(tvs)
|
||||
self.world.get_location(tv[1]["name"], self.player)\
|
||||
gest = self.multiworld.random.choice(gestures)
|
||||
tv = self.multiworld.random.choice(tvs)
|
||||
self.multiworld.get_location(tv[1]["name"], self.player)\
|
||||
.place_locked_item(self.add_item(gest[1]["name"], gest[1]["classification"], gest[1]))
|
||||
gestures.remove(gest)
|
||||
tvs.remove(tv)
|
||||
@@ -150,22 +150,22 @@ class Hylics2World(World):
|
||||
pool.append(self.add_item(data["name"], data["classification"], i))
|
||||
|
||||
# add '10 Bones' items if medallion shuffle is enabled
|
||||
if self.world.medallion_shuffle[self.player]:
|
||||
if self.multiworld.medallion_shuffle[self.player]:
|
||||
for i, data in Items.medallion_item_table.items():
|
||||
for j in range(data["count"]):
|
||||
pool.append(self.add_item(data["name"], data["classification"], i))
|
||||
|
||||
# add to world's pool
|
||||
self.world.itempool += pool
|
||||
self.multiworld.itempool += pool
|
||||
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
slot_data: Dict[str, Any] = {
|
||||
"party_shuffle": self.world.party_shuffle[self.player].value,
|
||||
"medallion_shuffle": self.world.medallion_shuffle[self.player].value,
|
||||
"random_start" : self.world.random_start[self.player].value,
|
||||
"party_shuffle": self.multiworld.party_shuffle[self.player].value,
|
||||
"medallion_shuffle": self.multiworld.medallion_shuffle[self.player].value,
|
||||
"random_start" : self.multiworld.random_start[self.player].value,
|
||||
"start_location" : self.start_location,
|
||||
"death_link": self.world.death_link[self.player].value
|
||||
"death_link": self.multiworld.death_link[self.player].value
|
||||
}
|
||||
return slot_data
|
||||
|
||||
@@ -173,29 +173,29 @@ class Hylics2World(World):
|
||||
def create_regions(self) -> None:
|
||||
|
||||
region_table: Dict[int, Region] = {
|
||||
0: Region("Menu", RegionType.Generic, "Menu", self.player, self.world),
|
||||
1: Region("Afterlife", RegionType.Generic, "Afterlife", self.player, self.world),
|
||||
2: Region("Waynehouse", RegionType.Generic, "Waynehouse", self.player, self.world),
|
||||
3: Region("World", RegionType.Generic, "World", self.player, self.world),
|
||||
4: Region("New Muldul", RegionType.Generic, "New Muldul", self.player, self.world),
|
||||
5: Region("New Muldul Vault", RegionType.Generic, "New Muldul Vault", self.player, self.world),
|
||||
6: Region("Viewax", RegionType.Generic, "Viewax's Edifice", self.player, self.world),
|
||||
7: Region("Airship", RegionType.Generic, "Airship", self.player, self.world),
|
||||
8: Region("Arcade Island", RegionType.Generic, "Arcade Island", self.player, self.world),
|
||||
9: Region("TV Island", RegionType.Generic, "TV Island", self.player, self.world),
|
||||
10: Region("Juice Ranch", RegionType.Generic, "Juice Ranch", self.player, self.world),
|
||||
11: Region("Shield Facility", RegionType.Generic, "Shield Facility", self.player, self.world),
|
||||
12: Region("Worm Pod", RegionType.Generic, "Worm Pod", self.player, self.world),
|
||||
13: Region("Foglast", RegionType.Generic, "Foglast", self.player, self.world),
|
||||
14: Region("Drill Castle", RegionType.Generic, "Drill Castle", self.player, self.world),
|
||||
15: Region("Sage Labyrinth", RegionType.Generic, "Sage Labyrinth", self.player, self.world),
|
||||
16: Region("Sage Airship", RegionType.Generic, "Sage Airship", self.player, self.world),
|
||||
17: Region("Hylemxylem", RegionType.Generic, "Hylemxylem", self.player, self.world)
|
||||
0: Region("Menu", RegionType.Generic, "Menu", self.player, self.multiworld),
|
||||
1: Region("Afterlife", RegionType.Generic, "Afterlife", self.player, self.multiworld),
|
||||
2: Region("Waynehouse", RegionType.Generic, "Waynehouse", self.player, self.multiworld),
|
||||
3: Region("World", RegionType.Generic, "World", self.player, self.multiworld),
|
||||
4: Region("New Muldul", RegionType.Generic, "New Muldul", self.player, self.multiworld),
|
||||
5: Region("New Muldul Vault", RegionType.Generic, "New Muldul Vault", self.player, self.multiworld),
|
||||
6: Region("Viewax", RegionType.Generic, "Viewax's Edifice", self.player, self.multiworld),
|
||||
7: Region("Airship", RegionType.Generic, "Airship", self.player, self.multiworld),
|
||||
8: Region("Arcade Island", RegionType.Generic, "Arcade Island", self.player, self.multiworld),
|
||||
9: Region("TV Island", RegionType.Generic, "TV Island", self.player, self.multiworld),
|
||||
10: Region("Juice Ranch", RegionType.Generic, "Juice Ranch", self.player, self.multiworld),
|
||||
11: Region("Shield Facility", RegionType.Generic, "Shield Facility", self.player, self.multiworld),
|
||||
12: Region("Worm Pod", RegionType.Generic, "Worm Pod", self.player, self.multiworld),
|
||||
13: Region("Foglast", RegionType.Generic, "Foglast", self.player, self.multiworld),
|
||||
14: Region("Drill Castle", RegionType.Generic, "Drill Castle", self.player, self.multiworld),
|
||||
15: Region("Sage Labyrinth", RegionType.Generic, "Sage Labyrinth", self.player, self.multiworld),
|
||||
16: Region("Sage Airship", RegionType.Generic, "Sage Airship", self.player, self.multiworld),
|
||||
17: Region("Hylemxylem", RegionType.Generic, "Hylemxylem", self.player, self.multiworld)
|
||||
}
|
||||
|
||||
# create regions from table
|
||||
for i, reg in region_table.items():
|
||||
self.world.regions.append(reg)
|
||||
self.multiworld.regions.append(reg)
|
||||
# get all exits per region
|
||||
for j, exits in Exits.region_exit_table.items():
|
||||
if j == i:
|
||||
@@ -203,7 +203,7 @@ class Hylics2World(World):
|
||||
# create entrance and connect it to parent and destination regions
|
||||
ent = Entrance(self.player, k, reg)
|
||||
reg.exits.append(ent)
|
||||
if k == "New Game" and self.world.random_start[self.player]:
|
||||
if k == "New Game" and self.multiworld.random_start[self.player]:
|
||||
if self.start_location == "Waynehouse":
|
||||
ent.connect(region_table[2])
|
||||
elif self.start_location == "Viewax's Edifice":
|
||||
@@ -226,13 +226,13 @@ class Hylics2World(World):
|
||||
.append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]]))
|
||||
|
||||
# add party member locations if option is enabled
|
||||
if self.world.party_shuffle[self.player]:
|
||||
if self.multiworld.party_shuffle[self.player]:
|
||||
for i, data in Locations.party_location_table.items():
|
||||
region_table[data["region"]].locations\
|
||||
.append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]]))
|
||||
|
||||
# add medallion locations if option is enabled
|
||||
if self.world.medallion_shuffle[self.player]:
|
||||
if self.multiworld.medallion_shuffle[self.player]:
|
||||
for i, data in Locations.medallion_location_table.items():
|
||||
region_table[data["region"]].locations\
|
||||
.append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]]))
|
||||
|
||||
@@ -88,7 +88,7 @@ class MeritousWorld(World):
|
||||
return crystal_pool
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
rand_crystals = self.world.random.randrange(0, 32)
|
||||
rand_crystals = self.multiworld.random.randrange(0, 32)
|
||||
if rand_crystals < 16:
|
||||
return "Crystals x500"
|
||||
elif rand_crystals < 28:
|
||||
@@ -97,14 +97,14 @@ class MeritousWorld(World):
|
||||
return "Crystals x2000"
|
||||
|
||||
def generate_early(self):
|
||||
self.goal = self.world.goal[self.player].value
|
||||
self.include_evolution_traps = self.world.include_evolution_traps[self.player].value
|
||||
self.include_psi_keys = self.world.include_psi_keys[self.player].value
|
||||
self.item_cache_cost = self.world.item_cache_cost[self.player].value
|
||||
self.death_link = self.world.death_link[self.player].value
|
||||
self.goal = self.multiworld.goal[self.player].value
|
||||
self.include_evolution_traps = self.multiworld.include_evolution_traps[self.player].value
|
||||
self.include_psi_keys = self.multiworld.include_psi_keys[self.player].value
|
||||
self.item_cache_cost = self.multiworld.item_cache_cost[self.player].value
|
||||
self.death_link = self.multiworld.death_link[self.player].value
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.world, self.player)
|
||||
create_regions(self.multiworld, self.player)
|
||||
|
||||
def create_items(self):
|
||||
frequencies = [0, # Nothing [0]
|
||||
@@ -133,22 +133,22 @@ class MeritousWorld(World):
|
||||
if len(item_pool) < location_count:
|
||||
item_pool += self._make_crystals(location_count - len(item_pool))
|
||||
|
||||
self.world.itempool += item_pool
|
||||
self.multiworld.itempool += item_pool
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
def generate_basic(self):
|
||||
self.world.get_location("Place of Power", self.player).place_locked_item(
|
||||
self.multiworld.get_location("Place of Power", self.player).place_locked_item(
|
||||
self.create_item("Cursed Seal"))
|
||||
self.world.get_location("The Last Place You'll Look", self.player).place_locked_item(
|
||||
self.multiworld.get_location("The Last Place You'll Look", self.player).place_locked_item(
|
||||
self.create_item("Agate Knife"))
|
||||
self.world.get_location("Wervyn Anixil", self.player).place_locked_item(
|
||||
self.multiworld.get_location("Wervyn Anixil", self.player).place_locked_item(
|
||||
self.create_event("Victory"))
|
||||
self.world.get_location("Wervyn Anixil?", self.player).place_locked_item(
|
||||
self.multiworld.get_location("Wervyn Anixil?", self.player).place_locked_item(
|
||||
self.create_event("Full Victory"))
|
||||
for boss in ["Meridian", "Ataraxia", "Merodach"]:
|
||||
self.world.get_location(f"{boss} Defeat", self.player).place_locked_item(
|
||||
self.multiworld.get_location(f"{boss} Defeat", self.player).place_locked_item(
|
||||
self.create_event(f"{boss} Defeated"))
|
||||
|
||||
if not self.include_psi_keys:
|
||||
@@ -156,22 +156,22 @@ class MeritousWorld(World):
|
||||
psi_key_storage = []
|
||||
for i in range(0, 3):
|
||||
psi_keys += [self.create_item(f"PSI Key {i + 1}")]
|
||||
psi_key_storage += [self.world.get_location(
|
||||
psi_key_storage += [self.multiworld.get_location(
|
||||
f"PSI Key Storage {i + 1}", self.player)]
|
||||
|
||||
fill_restrictive(self.world, self.world.get_all_state(
|
||||
fill_restrictive(self.multiworld, self.multiworld.get_all_state(
|
||||
False), psi_key_storage, psi_keys)
|
||||
|
||||
if not self.include_evolution_traps:
|
||||
for boss in ["Meridian", "Ataraxia", "Merodach"]:
|
||||
self.world.get_location(boss, self.player).place_locked_item(
|
||||
self.multiworld.get_location(boss, self.player).place_locked_item(
|
||||
self.create_item("Evolution Trap"))
|
||||
|
||||
if self.goal == 0:
|
||||
self.world.completion_condition[self.player] = lambda state: state.has_any(
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has_any(
|
||||
["Victory", "Full Victory"], self.player)
|
||||
else:
|
||||
self.world.completion_condition[self.player] = lambda state: state.has(
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has(
|
||||
"Full Victory", self.player)
|
||||
|
||||
def fill_slot_data(self) -> dict:
|
||||
|
||||
@@ -45,7 +45,7 @@ class MinecraftLogic(LogicMixin):
|
||||
self.can_reach('Bastion Remnant', 'Region', player))
|
||||
|
||||
def _mc_overworld_villager(self, player: int):
|
||||
village_region = self.world.get_region('Village', player).entrances[0].parent_region.name
|
||||
village_region = self.multiworld.get_region('Village', player).entrances[0].parent_region.name
|
||||
if village_region == 'The Nether': # 2 options: cure zombie villager or build portal in village
|
||||
return (self.can_reach('Zombie Doctor', 'Location', player) or
|
||||
(self._mc_has_diamond_pickaxe(player) and self.can_reach('Village', 'Region', player)))
|
||||
@@ -58,10 +58,10 @@ class MinecraftLogic(LogicMixin):
|
||||
|
||||
# Difficulty-dependent functions
|
||||
def _mc_combat_difficulty(self, player: int):
|
||||
return self.world.combat_difficulty[player].current_key
|
||||
return self.multiworld.combat_difficulty[player].current_key
|
||||
|
||||
def _mc_can_adventure(self, player: int):
|
||||
death_link_check = not self.world.death_link[player] or self.has('Bed', player)
|
||||
death_link_check = not self.multiworld.death_link[player] or self.has('Bed', player)
|
||||
if self._mc_combat_difficulty(player) == 'easy':
|
||||
return self.has('Progressive Weapons', player, 2) and self._mc_has_iron_ingots(player) and death_link_check
|
||||
elif self._mc_combat_difficulty(player) == 'hard':
|
||||
@@ -112,9 +112,9 @@ class MinecraftLogic(LogicMixin):
|
||||
return self.has('Progressive Weapons', player, 2) and self.has('Progressive Armor', player) and self.has('Archery', player)
|
||||
|
||||
def _mc_has_structure_compass(self, entrance_name: str, player: int):
|
||||
if not self.world.structure_compasses[player]:
|
||||
if not self.multiworld.structure_compasses[player]:
|
||||
return True
|
||||
return self.has(f"Structure Compass ({self.world.get_entrance(entrance_name, player).connected_region.name})", player)
|
||||
return self.has(f"Structure Compass ({self.multiworld.get_entrance(entrance_name, player).connected_region.name})", player)
|
||||
|
||||
# Sets rules on entrances and advancements that are always applied
|
||||
def set_advancement_rules(world: MultiWorld, player: int):
|
||||
|
||||
@@ -70,21 +70,21 @@ class MinecraftWorld(World):
|
||||
def _get_mc_data(self):
|
||||
exits = [connection[0] for connection in default_connections]
|
||||
return {
|
||||
'world_seed': self.world.slot_seeds[self.player].getrandbits(32),
|
||||
'seed_name': self.world.seed_name,
|
||||
'player_name': self.world.get_player_name(self.player),
|
||||
'world_seed': self.multiworld.slot_seeds[self.player].getrandbits(32),
|
||||
'seed_name': self.multiworld.seed_name,
|
||||
'player_name': self.multiworld.get_player_name(self.player),
|
||||
'player_id': self.player,
|
||||
'client_version': client_version,
|
||||
'structures': {exit: self.world.get_entrance(exit, self.player).connected_region.name for exit in exits},
|
||||
'advancement_goal': self.world.advancement_goal[self.player].value,
|
||||
'egg_shards_required': min(self.world.egg_shards_required[self.player].value,
|
||||
self.world.egg_shards_available[self.player].value),
|
||||
'egg_shards_available': self.world.egg_shards_available[self.player].value,
|
||||
'required_bosses': self.world.required_bosses[self.player].current_key,
|
||||
'MC35': bool(self.world.send_defeated_mobs[self.player].value),
|
||||
'death_link': bool(self.world.death_link[self.player].value),
|
||||
'starting_items': str(self.world.starting_items[self.player].value),
|
||||
'race': self.world.is_race,
|
||||
'structures': {exit: self.multiworld.get_entrance(exit, self.player).connected_region.name for exit in exits},
|
||||
'advancement_goal': self.multiworld.advancement_goal[self.player].value,
|
||||
'egg_shards_required': min(self.multiworld.egg_shards_required[self.player].value,
|
||||
self.multiworld.egg_shards_available[self.player].value),
|
||||
'egg_shards_available': self.multiworld.egg_shards_available[self.player].value,
|
||||
'required_bosses': self.multiworld.required_bosses[self.player].current_key,
|
||||
'MC35': bool(self.multiworld.send_defeated_mobs[self.player].value),
|
||||
'death_link': bool(self.multiworld.death_link[self.player].value),
|
||||
'starting_items': str(self.multiworld.starting_items[self.player].value),
|
||||
'race': self.multiworld.is_race,
|
||||
}
|
||||
|
||||
def generate_basic(self):
|
||||
@@ -96,18 +96,18 @@ class MinecraftWorld(World):
|
||||
for (name, num) in required_items.items():
|
||||
itempool += [name] * num
|
||||
# Add structure compasses if desired
|
||||
if self.world.structure_compasses[self.player]:
|
||||
if self.multiworld.structure_compasses[self.player]:
|
||||
structures = [connection[1] for connection in default_connections]
|
||||
for struct_name in structures:
|
||||
itempool.append(f"Structure Compass ({struct_name})")
|
||||
# Add dragon egg shards
|
||||
if self.world.egg_shards_required[self.player] > 0:
|
||||
itempool += ["Dragon Egg Shard"] * self.world.egg_shards_available[self.player]
|
||||
if self.multiworld.egg_shards_required[self.player] > 0:
|
||||
itempool += ["Dragon Egg Shard"] * self.multiworld.egg_shards_available[self.player]
|
||||
# Add bee traps if desired
|
||||
bee_trap_quantity = ceil(self.world.bee_traps[self.player] * (len(self.location_names)-len(itempool)) * 0.01)
|
||||
bee_trap_quantity = ceil(self.multiworld.bee_traps[self.player] * (len(self.location_names) - len(itempool)) * 0.01)
|
||||
itempool += ["Bee Trap"] * bee_trap_quantity
|
||||
# Fill remaining items with randomly generated junk
|
||||
itempool += self.world.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()), k=len(self.location_names)-len(itempool))
|
||||
itempool += self.multiworld.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()), k=len(self.location_names) - len(itempool))
|
||||
# Convert itempool into real items
|
||||
itempool = [item for item in map(lambda name: self.create_item(name), itempool)]
|
||||
|
||||
@@ -115,29 +115,29 @@ class MinecraftWorld(World):
|
||||
exclusion_pool = set()
|
||||
exclusion_types = ['hard', 'unreasonable']
|
||||
for key in exclusion_types:
|
||||
if not getattr(self.world, f"include_{key}_advancements")[self.player]:
|
||||
if not getattr(self.multiworld, f"include_{key}_advancements")[self.player]:
|
||||
exclusion_pool.update(exclusion_table[key])
|
||||
# For postgame advancements, check with the boss goal
|
||||
exclusion_pool.update(get_postgame_advancements(self.world.required_bosses[self.player].current_key))
|
||||
exclusion_rules(self.world, self.player, exclusion_pool)
|
||||
exclusion_pool.update(get_postgame_advancements(self.multiworld.required_bosses[self.player].current_key))
|
||||
exclusion_rules(self.multiworld, self.player, exclusion_pool)
|
||||
|
||||
# Prefill event locations with their events
|
||||
self.world.get_location("Blaze Spawner", self.player).place_locked_item(self.create_item("Blaze Rods"))
|
||||
self.world.get_location("Ender Dragon", self.player).place_locked_item(self.create_item("Defeat Ender Dragon"))
|
||||
self.world.get_location("Wither", self.player).place_locked_item(self.create_item("Defeat Wither"))
|
||||
self.multiworld.get_location("Blaze Spawner", self.player).place_locked_item(self.create_item("Blaze Rods"))
|
||||
self.multiworld.get_location("Ender Dragon", self.player).place_locked_item(self.create_item("Defeat Ender Dragon"))
|
||||
self.multiworld.get_location("Wither", self.player).place_locked_item(self.create_item("Defeat Wither"))
|
||||
|
||||
self.world.itempool += itempool
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choices(list(junk_weights.keys()), weights=list(junk_weights.values()))[0]
|
||||
return self.multiworld.random.choices(list(junk_weights.keys()), weights=list(junk_weights.values()))[0]
|
||||
|
||||
def set_rules(self):
|
||||
set_advancement_rules(self.world, self.player)
|
||||
set_completion_rules(self.world, self.player)
|
||||
set_advancement_rules(self.multiworld, self.player)
|
||||
set_completion_rules(self.multiworld, self.player)
|
||||
|
||||
def create_regions(self):
|
||||
def MCRegion(region_name: str, exits=[]):
|
||||
ret = Region(region_name, None, region_name, self.player, self.world)
|
||||
ret = Region(region_name, None, region_name, self.player, self.multiworld)
|
||||
ret.locations = [MinecraftAdvancement(self.player, loc_name, loc_data.id, ret)
|
||||
for loc_name, loc_data in advancement_table.items()
|
||||
if loc_data.region == region_name]
|
||||
@@ -145,19 +145,19 @@ class MinecraftWorld(World):
|
||||
ret.exits.append(Entrance(self.player, exit, ret))
|
||||
return ret
|
||||
|
||||
self.world.regions += [MCRegion(*r) for r in mc_regions]
|
||||
link_minecraft_structures(self.world, self.player)
|
||||
self.multiworld.regions += [MCRegion(*r) for r in mc_regions]
|
||||
link_minecraft_structures(self.multiworld, self.player)
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
data = self._get_mc_data()
|
||||
filename = f"{self.world.get_out_file_name_base(self.player)}.apmc"
|
||||
filename = f"AP_{self.multiworld.get_out_file_name_base(self.player)}.apmc"
|
||||
with open(os.path.join(output_directory, filename), 'wb') as f:
|
||||
f.write(b64encode(bytes(json.dumps(data), 'utf-8')))
|
||||
|
||||
def fill_slot_data(self):
|
||||
slot_data = self._get_mc_data()
|
||||
for option_name in minecraft_options:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
if slot_data.get(option_name, None) is None and type(option.value) in {str, int}:
|
||||
slot_data[option_name] = int(option.value)
|
||||
return slot_data
|
||||
|
||||
@@ -768,7 +768,7 @@ patch_sets = {
|
||||
|
||||
def patch_cosmetics(ootworld, rom):
|
||||
# Use the world's slot seed for cosmetics
|
||||
random.seed(ootworld.world.slot_seeds[ootworld.player])
|
||||
random.seed(ootworld.multiworld.slot_seeds[ootworld.player])
|
||||
|
||||
# try to detect the cosmetic patch data format
|
||||
versioned_patch_set = None
|
||||
|
||||
@@ -9,7 +9,7 @@ class Dungeon(object):
|
||||
else:
|
||||
return [obj]
|
||||
|
||||
self.world = world
|
||||
self.multiworld = world
|
||||
self.name = name
|
||||
self.hint_text = hint
|
||||
self.font_color = font_color
|
||||
@@ -18,7 +18,7 @@ class Dungeon(object):
|
||||
self.small_keys = to_array(small_keys)
|
||||
self.dungeon_items = to_array(dungeon_items)
|
||||
|
||||
for region in world.world.regions:
|
||||
for region in world.multiworld.regions:
|
||||
if region.player == world.player and region.dungeon == self.name:
|
||||
region.dungeon = self
|
||||
self.regions.append(region)
|
||||
|
||||
@@ -7,7 +7,7 @@ class OOTEntrance(Entrance):
|
||||
|
||||
def __init__(self, player, world, name='', parent=None):
|
||||
super(OOTEntrance, self).__init__(player, name, parent)
|
||||
self.world = world
|
||||
self.multiworld = world
|
||||
self.access_rules = []
|
||||
self.reverse = None
|
||||
self.replaces = None
|
||||
@@ -30,8 +30,8 @@ class OOTEntrance(Entrance):
|
||||
return previously_connected
|
||||
|
||||
def get_new_target(self):
|
||||
root = self.world.get_region('Root Exits', self.player)
|
||||
target_entrance = OOTEntrance(self.player, self.world, 'Root -> ' + self.connected_region.name, root)
|
||||
root = self.multiworld.get_region('Root Exits', self.player)
|
||||
target_entrance = OOTEntrance(self.player, self.multiworld, 'Root -> ' + self.connected_region.name, root)
|
||||
target_entrance.connect(self.connected_region)
|
||||
target_entrance.replaces = self
|
||||
root.exits.append(target_entrance)
|
||||
|
||||
@@ -369,7 +369,7 @@ class EntranceShuffleError(Exception):
|
||||
|
||||
|
||||
def shuffle_random_entrances(ootworld):
|
||||
world = ootworld.world
|
||||
world = ootworld.multiworld
|
||||
player = ootworld.player
|
||||
|
||||
# Gather locations to keep reachable for validation
|
||||
@@ -593,7 +593,7 @@ def place_one_way_priority_entrance(ootworld, priority_name, allowed_regions, al
|
||||
all_state, none_state, one_way_entrance_pools, one_way_target_entrance_pools):
|
||||
|
||||
avail_pool = list(chain.from_iterable(one_way_entrance_pools[t] for t in allowed_types if t in one_way_entrance_pools))
|
||||
ootworld.world.random.shuffle(avail_pool)
|
||||
ootworld.multiworld.random.shuffle(avail_pool)
|
||||
|
||||
for entrance in avail_pool:
|
||||
if entrance.replaces:
|
||||
@@ -643,11 +643,11 @@ def shuffle_entrance_pool(ootworld, pool_type, entrance_pool, target_entrances,
|
||||
raise EntranceShuffleError(f'Entrance placement attempt count exceeded for world {ootworld.player}')
|
||||
|
||||
def shuffle_entrances(ootworld, pool_type, entrances, target_entrances, rollbacks, locations_to_ensure_reachable, all_state, none_state):
|
||||
ootworld.world.random.shuffle(entrances)
|
||||
ootworld.multiworld.random.shuffle(entrances)
|
||||
for entrance in entrances:
|
||||
if entrance.connected_region != None:
|
||||
continue
|
||||
ootworld.world.random.shuffle(target_entrances)
|
||||
ootworld.multiworld.random.shuffle(target_entrances)
|
||||
# Here we deliberately introduce bias by prioritizing certain interiors, i.e. the ones most likely to cause problems.
|
||||
# success rate over randomization
|
||||
if pool_type in {'InteriorSoft', 'MixedSoft'}:
|
||||
@@ -662,7 +662,7 @@ def shuffle_entrances(ootworld, pool_type, entrances, target_entrances, rollback
|
||||
|
||||
|
||||
def split_entrances_by_requirements(ootworld, entrances_to_split, assumed_entrances):
|
||||
world = ootworld.world
|
||||
world = ootworld.multiworld
|
||||
player = ootworld.player
|
||||
|
||||
# Disconnect all root assumed entrances and save original connections
|
||||
@@ -704,7 +704,7 @@ def split_entrances_by_requirements(ootworld, entrances_to_split, assumed_entran
|
||||
# TODO: improve this function
|
||||
def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all_state_orig, none_state_orig):
|
||||
|
||||
world = ootworld.world
|
||||
world = ootworld.multiworld
|
||||
player = ootworld.player
|
||||
|
||||
all_state = all_state_orig.copy()
|
||||
@@ -827,7 +827,7 @@ def same_hint_area(first, second):
|
||||
return False
|
||||
|
||||
def get_entrance_replacing(region, entrance_name, player):
|
||||
original_entrance = region.world.get_entrance(entrance_name, player)
|
||||
original_entrance = region.multiworld.get_entrance(entrance_name, player)
|
||||
if not original_entrance.shuffled:
|
||||
return original_entrance
|
||||
|
||||
@@ -842,14 +842,14 @@ def get_entrance_replacing(region, entrance_name, player):
|
||||
def change_connections(entrance, target):
|
||||
entrance.connect(target.disconnect())
|
||||
entrance.replaces = target.replaces
|
||||
if entrance.reverse and not entrance.world.worlds[entrance.player].decouple_entrances:
|
||||
if entrance.reverse and not entrance.multiworld.worlds[entrance.player].decouple_entrances:
|
||||
target.replaces.reverse.connect(entrance.reverse.assumed.disconnect())
|
||||
target.replaces.reverse.replaces = entrance.reverse
|
||||
|
||||
def restore_connections(entrance, target):
|
||||
target.connect(entrance.disconnect())
|
||||
entrance.replaces = None
|
||||
if entrance.reverse and not entrance.world.worlds[entrance.player].decouple_entrances:
|
||||
if entrance.reverse and not entrance.multiworld.worlds[entrance.player].decouple_entrances:
|
||||
entrance.reverse.assumed.connect(target.replaces.reverse.disconnect())
|
||||
target.replaces.reverse.replaces = None
|
||||
|
||||
@@ -866,7 +866,7 @@ def check_entrances_compatibility(entrance, target, rollbacks):
|
||||
def confirm_replacement(entrance, target):
|
||||
delete_target_entrance(target)
|
||||
logging.getLogger('').debug(f'Connected {entrance} to {entrance.connected_region}')
|
||||
if entrance.reverse and not entrance.world.worlds[entrance.player].decouple_entrances:
|
||||
if entrance.reverse and not entrance.multiworld.worlds[entrance.player].decouple_entrances:
|
||||
replaced_reverse = target.replaces.reverse
|
||||
delete_target_entrance(entrance.reverse.assumed)
|
||||
logging.getLogger('').debug(f'Connected {replaced_reverse} to {replaced_reverse.connected_region}')
|
||||
|
||||
@@ -131,13 +131,13 @@ def getItemGenericName(item):
|
||||
def isRestrictedDungeonItem(dungeon, item):
|
||||
if not isinstance(item, OOTItem):
|
||||
return False
|
||||
if (item.map or item.compass) and dungeon.world.shuffle_mapcompass == 'dungeon':
|
||||
if (item.map or item.compass) and dungeon.multiworld.shuffle_mapcompass == 'dungeon':
|
||||
return item in dungeon.dungeon_items
|
||||
if item.type == 'SmallKey' and dungeon.world.shuffle_smallkeys == 'dungeon':
|
||||
if item.type == 'SmallKey' and dungeon.multiworld.shuffle_smallkeys == 'dungeon':
|
||||
return item in dungeon.small_keys
|
||||
if item.type == 'BossKey' and dungeon.world.shuffle_bosskeys == 'dungeon':
|
||||
if item.type == 'BossKey' and dungeon.multiworld.shuffle_bosskeys == 'dungeon':
|
||||
return item in dungeon.boss_key
|
||||
if item.type == 'GanonBossKey' and dungeon.world.shuffle_ganon_bosskey == 'dungeon':
|
||||
if item.type == 'GanonBossKey' and dungeon.multiworld.shuffle_ganon_bosskey == 'dungeon':
|
||||
return item in dungeon.boss_key
|
||||
return False
|
||||
|
||||
@@ -148,7 +148,7 @@ def isRestrictedDungeonItem(dungeon, item):
|
||||
def attach_name(text, hinted_object, world):
|
||||
if hinted_object.player == world.player:
|
||||
return text
|
||||
return f"{text} for {world.world.get_player_name(hinted_object.player)}"
|
||||
return f"{text} for {world.multiworld.get_player_name(hinted_object.player)}"
|
||||
|
||||
|
||||
def add_hint(world, groups, gossip_text, count, location=None, force_reachable=False):
|
||||
@@ -439,7 +439,7 @@ def get_specific_item_hint(world, checked):
|
||||
itemname = world.named_item_pool.pop(0)
|
||||
if itemname == "Bottle" and world.hint_dist == "bingo":
|
||||
locations = [
|
||||
location for location in world.world.get_filled_locations()
|
||||
location for location in world.multiworld.get_filled_locations()
|
||||
if (is_not_checked(location, checked)
|
||||
and location.name not in world.hint_exclusions
|
||||
and location.item.name in bingoBottlesForHints
|
||||
@@ -448,7 +448,7 @@ def get_specific_item_hint(world, checked):
|
||||
]
|
||||
else:
|
||||
locations = [
|
||||
location for location in world.world.get_filled_locations()
|
||||
location for location in world.multiworld.get_filled_locations()
|
||||
if (is_not_checked(location, checked)
|
||||
and location.name not in world.hint_exclusions
|
||||
and location.item.name == itemname
|
||||
@@ -489,7 +489,7 @@ def get_random_location_hint(world, checked):
|
||||
and location.name not in world.hint_exclusions
|
||||
and location.name not in world.hint_type_overrides['item']
|
||||
and location.item.name not in world.item_hint_type_overrides['item'],
|
||||
world.world.get_filled_locations(world.player)))
|
||||
world.multiworld.get_filled_locations(world.player)))
|
||||
if not locations:
|
||||
return None
|
||||
|
||||
@@ -639,13 +639,13 @@ def buildWorldGossipHints(world, checkedLocations=None):
|
||||
world.woth_dungeon = 0
|
||||
|
||||
if checkedLocations is None:
|
||||
checkedLocations = {player: set() for player in world.world.get_all_ids()}
|
||||
checkedLocations = {player: set() for player in world.multiworld.get_all_ids()}
|
||||
|
||||
# If Ganondorf hints Light Arrows and is reachable without them, add to checkedLocations to prevent extra hinting
|
||||
# Can only be forced with vanilla bridge or trials
|
||||
if world.bridge != 'vanilla' and world.trials == 0 and world.misc_hints:
|
||||
try:
|
||||
light_arrow_location = world.world.find_item("Light Arrows", world.player)
|
||||
light_arrow_location = world.multiworld.find_item("Light Arrows", world.player)
|
||||
checkedLocations[light_arrow_location.player].add(light_arrow_location.name)
|
||||
except StopIteration: # start with them
|
||||
pass
|
||||
@@ -885,7 +885,7 @@ def buildAltarHints(world, messages, include_rewards=True, include_wincons=True)
|
||||
|
||||
# pulls text string from hintlist for reward after sending the location to hintlist.
|
||||
def buildBossString(reward, color, world):
|
||||
for location in world.world.get_filled_locations(world.player):
|
||||
for location in world.multiworld.get_filled_locations(world.player):
|
||||
if location.item.name == reward:
|
||||
item_icon = chr(location.item.special['item_id'])
|
||||
location_text = getHint(location.name, world.clearer_hints).text
|
||||
@@ -956,18 +956,18 @@ def buildGanonText(world, messages):
|
||||
text += "\x05\x42your pocket\x05\x40"
|
||||
else:
|
||||
try:
|
||||
find_light_arrows = world.world.find_item('Light Arrows', world.player)
|
||||
find_light_arrows = world.multiworld.find_item('Light Arrows', world.player)
|
||||
text = get_raw_text(getHint('Light Arrow Location', world.clearer_hints).text)
|
||||
location = find_light_arrows
|
||||
location_hint = get_hint_area(location)
|
||||
if world.player != location.player:
|
||||
text += "\x05\x42%s's\x05\x40 %s" % (world.world.get_player_name(location.player), get_raw_text(location_hint))
|
||||
text += "\x05\x42%s's\x05\x40 %s" % (world.multiworld.get_player_name(location.player), get_raw_text(location_hint))
|
||||
else:
|
||||
location_hint = location_hint.replace('Ganon\'s Castle', 'my castle')
|
||||
text += get_raw_text(location_hint)
|
||||
except StopIteration:
|
||||
text = get_raw_text(getHint('Validation Line', world.clearer_hints).text)
|
||||
for location in world.world.get_filled_locations(world.player):
|
||||
for location in world.multiworld.get_filled_locations(world.player):
|
||||
if location.name == 'Ganons Tower Boss Key Chest':
|
||||
text += get_raw_text(getHint(getItemGenericName(location.item), world.clearer_hints).text)
|
||||
break
|
||||
|
||||
@@ -748,7 +748,7 @@ def replace_max_item(items, item, max):
|
||||
|
||||
|
||||
def generate_itempool(ootworld):
|
||||
world = ootworld.world
|
||||
world = ootworld.multiworld
|
||||
player = ootworld.player
|
||||
global random
|
||||
random = world.random
|
||||
@@ -1280,32 +1280,32 @@ def get_pool_core(world):
|
||||
|
||||
if world.free_scarecrow:
|
||||
item = world.create_item('Scarecrow Song')
|
||||
world.world.push_precollected(item)
|
||||
world.multiworld.push_precollected(item)
|
||||
world.remove_from_start_inventory.append(item.name)
|
||||
|
||||
if world.no_epona_race:
|
||||
item = world.create_item('Epona')
|
||||
world.world.push_precollected(item)
|
||||
world.multiworld.push_precollected(item)
|
||||
world.remove_from_start_inventory.append(item.name)
|
||||
|
||||
if world.shuffle_mapcompass == 'remove' or world.shuffle_mapcompass == 'startwith':
|
||||
for item in [item for dungeon in world.dungeons for item in dungeon.dungeon_items]:
|
||||
world.world.push_precollected(item)
|
||||
world.multiworld.push_precollected(item)
|
||||
world.remove_from_start_inventory.append(item.name)
|
||||
pool.extend(get_junk_item())
|
||||
if world.shuffle_smallkeys == 'remove':
|
||||
for item in [item for dungeon in world.dungeons for item in dungeon.small_keys]:
|
||||
world.world.push_precollected(item)
|
||||
world.multiworld.push_precollected(item)
|
||||
world.remove_from_start_inventory.append(item.name)
|
||||
pool.extend(get_junk_item())
|
||||
if world.shuffle_bosskeys == 'remove':
|
||||
for item in [item for dungeon in world.dungeons if dungeon.name != 'Ganons Castle' for item in dungeon.boss_key]:
|
||||
world.world.push_precollected(item)
|
||||
world.multiworld.push_precollected(item)
|
||||
world.remove_from_start_inventory.append(item.name)
|
||||
pool.extend(get_junk_item())
|
||||
if world.shuffle_ganon_bosskey in ['remove', 'triforce']:
|
||||
for item in [item for dungeon in world.dungeons if dungeon.name == 'Ganons Castle' for item in dungeon.boss_key]:
|
||||
world.world.push_precollected(item)
|
||||
world.multiworld.push_precollected(item)
|
||||
world.remove_from_start_inventory.append(item.name)
|
||||
pool.extend(get_junk_item())
|
||||
|
||||
@@ -1331,7 +1331,7 @@ def get_pool_core(world):
|
||||
# Yes somehow you need 3 keys. This dungeon is bonkers
|
||||
items = [world.create_item('Small Key (Spirit Temple)') for i in range(3)]
|
||||
for item in items:
|
||||
world.world.push_precollected(item)
|
||||
world.multiworld.push_precollected(item)
|
||||
world.remove_from_start_inventory.append(item.name)
|
||||
#if not world.dungeon_mq['Fire Temple']:
|
||||
# world.state.collect(ItemFactory('Small Key (Fire Temple)'))
|
||||
@@ -1346,7 +1346,7 @@ def get_pool_core(world):
|
||||
|
||||
if not world.keysanity and not world.dungeon_mq['Fire Temple']:
|
||||
item = world.create_item('Small Key (Fire Temple)')
|
||||
world.world.push_precollected(item)
|
||||
world.multiworld.push_precollected(item)
|
||||
world.remove_from_start_inventory.append(item.name)
|
||||
|
||||
if world.triforce_hunt:
|
||||
|
||||
@@ -882,7 +882,7 @@ def make_player_message(text):
|
||||
def update_item_messages(messages, world):
|
||||
new_item_messages = {**ITEM_MESSAGES, **KEYSANITY_MESSAGES}
|
||||
for id, text in new_item_messages.items():
|
||||
if len(world.world.worlds) > 1:
|
||||
if len(world.multiworld.worlds) > 1:
|
||||
update_message_by_id(messages, id, make_player_message(text), 0x23)
|
||||
else:
|
||||
update_message_by_id(messages, id, text, 0x23)
|
||||
@@ -1020,7 +1020,7 @@ def update_warp_song_text(messages, ootworld):
|
||||
}
|
||||
|
||||
for id, entr in msg_list.items():
|
||||
destination = ootworld.world.get_entrance(entr, ootworld.player).connected_region
|
||||
destination = ootworld.multiworld.get_entrance(entr, ootworld.player).connected_region
|
||||
|
||||
if destination.pretty_name:
|
||||
destination_name = destination.pretty_name
|
||||
|
||||
@@ -1326,7 +1326,7 @@ def patch_rom(world, rom):
|
||||
override_table = get_override_table(world)
|
||||
rom.write_bytes(rom.sym('cfg_item_overrides'), get_override_table_bytes(override_table))
|
||||
rom.write_byte(rom.sym('PLAYER_ID'), min(world.player, 255)) # Write player ID
|
||||
rom.write_bytes(rom.sym('AP_PLAYER_NAME'), bytearray(world.world.get_player_name(world.player), 'ascii'))
|
||||
rom.write_bytes(rom.sym('AP_PLAYER_NAME'), bytearray(world.multiworld.get_player_name(world.player), 'ascii'))
|
||||
|
||||
if world.death_link:
|
||||
rom.write_byte(rom.sym('DEATH_LINK'), 0x01)
|
||||
@@ -1359,7 +1359,7 @@ def patch_rom(world, rom):
|
||||
rom.write_byte(rom.sym('CFG_DAMAGE_MULTIPLYER'), 3)
|
||||
|
||||
# Patch songs and boss rewards
|
||||
for location in world.world.get_filled_locations(world.player):
|
||||
for location in world.multiworld.get_filled_locations(world.player):
|
||||
item = location.item
|
||||
special = item.special if item.game == 'Ocarina of Time' else {} # this shouldn't matter hopefully
|
||||
locationaddress = location.address1
|
||||
@@ -1686,7 +1686,7 @@ def patch_rom(world, rom):
|
||||
pass
|
||||
elif dungeon in ['Bottom of the Well', 'Ice Cavern']:
|
||||
dungeon_name, boss_name, compass_id, map_id = dungeon_list[dungeon]
|
||||
if len(world.world.worlds) > 1:
|
||||
if len(world.multiworld.worlds) > 1:
|
||||
map_message = "\x13\x76\x08\x05\x42\x0F\x05\x40 found the \x05\x41Dungeon Map\x05\x40\x01for %s\x05\x40!\x09" % (dungeon_name)
|
||||
else:
|
||||
map_message = "\x13\x76\x08You found the \x05\x41Dungeon Map\x05\x40\x01for %s\x05\x40!\x01It\'s %s!\x09" % (dungeon_name, "masterful" if world.dungeon_mq[dungeon] else "ordinary")
|
||||
@@ -1696,13 +1696,13 @@ def patch_rom(world, rom):
|
||||
else:
|
||||
dungeon_name, boss_name, compass_id, map_id = dungeon_list[dungeon]
|
||||
dungeon_reward = reward_list[world.get_location(boss_name).item.name]
|
||||
if len(world.world.worlds) > 1:
|
||||
if len(world.multiworld.worlds) > 1:
|
||||
compass_message = "\x13\x75\x08\x05\x42\x0F\x05\x40 found the \x05\x41Compass\x05\x40\x01for %s\x05\x40!\x09" % (dungeon_name)
|
||||
else:
|
||||
compass_message = "\x13\x75\x08You found the \x05\x41Compass\x05\x40\x01for %s\x05\x40!\x01It holds the %s!\x09" % (dungeon_name, dungeon_reward)
|
||||
update_message_by_id(messages, compass_id, compass_message)
|
||||
if world.mq_dungeons_random or world.mq_dungeons != 0 and world.mq_dungeons != 12:
|
||||
if len(world.world.worlds) > 1:
|
||||
if len(world.multiworld.worlds) > 1:
|
||||
map_message = "\x13\x76\x08\x05\x42\x0F\x05\x40 found the \x05\x41Dungeon Map\x05\x40\x01for %s\x05\x40!\x09" % (dungeon_name)
|
||||
else:
|
||||
map_message = "\x13\x76\x08You found the \x05\x41Dungeon Map\x05\x40\x01for %s\x05\x40!\x01It\'s %s!\x09" % (dungeon_name, "masterful" if world.dungeon_mq[dungeon] else "ordinary")
|
||||
@@ -1730,7 +1730,7 @@ def patch_rom(world, rom):
|
||||
rom.write_int16(0xB6D57E, 0x0003)
|
||||
rom.write_int16(0xB6EC52, 999)
|
||||
tycoon_message = "\x08\x13\x57You got a \x05\x43Tycoon's Wallet\x05\x40!\x01Now you can hold\x01up to \x05\x46999\x05\x40 \x05\x46Rupees\x05\x40."
|
||||
if len(world.world.worlds) > 1:
|
||||
if len(world.multiworld.worlds) > 1:
|
||||
tycoon_message = make_player_message(tycoon_message)
|
||||
update_message_by_id(messages, 0x00F8, tycoon_message, 0x23)
|
||||
|
||||
@@ -1844,7 +1844,7 @@ def write_rom_item(rom, item_id, item):
|
||||
|
||||
|
||||
def get_override_table(world):
|
||||
return list(filter(lambda val: val != None, map(partial(get_override_entry, world), world.world.get_filled_locations(world.player))))
|
||||
return list(filter(lambda val: val != None, map(partial(get_override_entry, world), world.multiworld.get_filled_locations(world.player))))
|
||||
|
||||
|
||||
override_struct = struct.Struct('>xBBBHBB') # match override_t in get_items.c
|
||||
@@ -2154,8 +2154,8 @@ def place_shop_items(rom, world, shop_items, messages, locations, init_shop_id=F
|
||||
if location.item.name == 'Ice Trap':
|
||||
split_item_name[0] = create_fake_name(split_item_name[0])
|
||||
|
||||
if len(world.world.worlds) > 1: # OOTWorld.MultiWorld.AutoWorld[]
|
||||
description_text = '\x08\x05\x41%s %d Rupees\x01%s\x01\x05\x42%s\x05\x40\x01Special deal! ONE LEFT!\x09\x0A\x02' % (split_item_name[0], location.price, split_item_name[1], world.world.get_player_name(location.item.player))
|
||||
if len(world.multiworld.worlds) > 1: # OOTWorld.MultiWorld.AutoWorld[]
|
||||
description_text = '\x08\x05\x41%s %d Rupees\x01%s\x01\x05\x42%s\x05\x40\x01Special deal! ONE LEFT!\x09\x0A\x02' % (split_item_name[0], location.price, split_item_name[1], world.multiworld.get_player_name(location.item.player))
|
||||
else:
|
||||
description_text = '\x08\x05\x41%s %d Rupees\x01%s\x01\x05\x40Special deal! ONE LEFT!\x01Get it while it lasts!\x09\x0A\x02' % (split_item_name[0], location.price, split_item_name[1])
|
||||
purchase_text = '\x08%s %d Rupees\x09\x01%s\x01\x1B\x05\x42Buy\x01Don\'t buy\x05\x40\x02' % (split_item_name[0], location.price, split_item_name[1])
|
||||
@@ -2168,10 +2168,10 @@ def place_shop_items(rom, world, shop_items, messages, locations, init_shop_id=F
|
||||
if location.item.trap:
|
||||
shop_item_name = create_fake_name(shop_item_name)
|
||||
|
||||
if len(world.world.worlds) > 1:
|
||||
if len(world.multiworld.worlds) > 1:
|
||||
shop_item_name = ''.join(filter(lambda char: char in character_table, shop_item_name))
|
||||
do_line_break = sum(character_table[char] for char in f"{shop_item_name} {location.price} Rupees") > NORMAL_LINE_WIDTH
|
||||
description_text = '\x08\x05\x41%s%s%d Rupees\x01\x05\x42%s\x05\x40\x01Special deal! ONE LEFT!\x09\x0A\x02' % (shop_item_name, '\x01' if do_line_break else ' ', location.price, world.world.get_player_name(location.item.player))
|
||||
description_text = '\x08\x05\x41%s%s%d Rupees\x01\x05\x42%s\x05\x40\x01Special deal! ONE LEFT!\x09\x0A\x02' % (shop_item_name, '\x01' if do_line_break else ' ', location.price, world.multiworld.get_player_name(location.item.player))
|
||||
else:
|
||||
description_text = '\x08\x05\x41%s %d Rupees\x01\x05\x40Special deal! ONE LEFT!\x01Get it while it lasts!\x09\x0A\x02' % (shop_item_name, location.price)
|
||||
purchase_text = '\x08%s %d Rupees\x09\x01\x01\x1B\x05\x42Buy\x01Don\'t buy\x05\x40\x02' % (shop_item_name, location.price)
|
||||
|
||||
@@ -53,7 +53,7 @@ def isliteral(expr):
|
||||
class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
|
||||
def __init__(self, world, player):
|
||||
self.world = world
|
||||
self.multiworld = world
|
||||
self.player = player
|
||||
self.events = set()
|
||||
# map Region -> rule ast string -> item name
|
||||
@@ -86,9 +86,9 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
ctx=ast.Load()),
|
||||
args=[ast.Str(escaped_items[node.id]), ast.Constant(self.player)],
|
||||
keywords=[])
|
||||
elif node.id in self.world.__dict__:
|
||||
elif node.id in self.multiworld.__dict__:
|
||||
# Settings are constant
|
||||
return ast.parse('%r' % self.world.__dict__[node.id], mode='eval').body
|
||||
return ast.parse('%r' % self.multiworld.__dict__[node.id], mode='eval').body
|
||||
elif node.id in State.__dict__:
|
||||
return self.make_call(node, node.id, [], [])
|
||||
elif node.id in self.kwarg_defaults or node.id in allowed_globals:
|
||||
@@ -137,7 +137,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
|
||||
if isinstance(count, ast.Name):
|
||||
# Must be a settings constant
|
||||
count = ast.parse('%r' % self.world.__dict__[count.id], mode='eval').body
|
||||
count = ast.parse('%r' % self.multiworld.__dict__[count.id], mode='eval').body
|
||||
|
||||
if iname in escaped_items:
|
||||
iname = escaped_items[iname]
|
||||
@@ -182,7 +182,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
new_args = []
|
||||
for child in node.args:
|
||||
if isinstance(child, ast.Name):
|
||||
if child.id in self.world.__dict__:
|
||||
if child.id in self.multiworld.__dict__:
|
||||
# child = ast.Attribute(
|
||||
# value=ast.Attribute(
|
||||
# value=ast.Name(id='state', ctx=ast.Load()),
|
||||
@@ -190,7 +190,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
# ctx=ast.Load()),
|
||||
# attr=child.id,
|
||||
# ctx=ast.Load())
|
||||
child = ast.Constant(getattr(self.world, child.id))
|
||||
child = ast.Constant(getattr(self.multiworld, child.id))
|
||||
elif child.id in rule_aliases:
|
||||
child = self.visit(child)
|
||||
elif child.id in escaped_items:
|
||||
@@ -217,7 +217,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
value=ast.Attribute(
|
||||
value=ast.Attribute(
|
||||
value=ast.Name(id='state', ctx=ast.Load()),
|
||||
attr='world',
|
||||
attr='multiworld',
|
||||
ctx=ast.Load()),
|
||||
attr='worlds',
|
||||
ctx=ast.Load()),
|
||||
@@ -242,7 +242,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
# Fast check for json can_use
|
||||
if (len(node.ops) == 1 and isinstance(node.ops[0], ast.Eq)
|
||||
and isinstance(node.left, ast.Name) and isinstance(node.comparators[0], ast.Name)
|
||||
and node.left.id not in self.world.__dict__ and node.comparators[0].id not in self.world.__dict__):
|
||||
and node.left.id not in self.multiworld.__dict__ and node.comparators[0].id not in self.multiworld.__dict__):
|
||||
return ast.NameConstant(node.left.id == node.comparators[0].id)
|
||||
|
||||
node.left = escape_or_string(node.left)
|
||||
@@ -378,7 +378,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
# Requires the target regions have been defined in the world.
|
||||
def create_delayed_rules(self):
|
||||
for region_name, node, subrule_name in self.delayed_rules:
|
||||
region = self.world.world.get_region(region_name, self.player)
|
||||
region = self.multiworld.multiworld.get_region(region_name, self.player)
|
||||
event = OOTLocation(self.player, subrule_name, type='Event', parent=region, internal=True)
|
||||
event.show_in_spoiler = False
|
||||
|
||||
@@ -395,7 +395,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
set_rule(event, access_rule)
|
||||
region.locations.append(event)
|
||||
|
||||
self.world.make_event_item(subrule_name, event)
|
||||
self.multiworld.make_event_item(subrule_name, event)
|
||||
# Safeguard in case this is called multiple times per world
|
||||
self.delayed_rules.clear()
|
||||
|
||||
@@ -448,7 +448,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
## Handlers for compile-time optimizations (former State functions)
|
||||
|
||||
def at_day(self, node):
|
||||
if self.world.ensure_tod_access:
|
||||
if self.multiworld.ensure_tod_access:
|
||||
# tod has DAY or (tod == NONE and (ss or find a path from a provider))
|
||||
# parsing is better than constructing this expression by hand
|
||||
r = self.current_spot if type(self.current_spot) == OOTRegion else self.current_spot.parent_region
|
||||
@@ -456,7 +456,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
return ast.NameConstant(True)
|
||||
|
||||
def at_dampe_time(self, node):
|
||||
if self.world.ensure_tod_access:
|
||||
if self.multiworld.ensure_tod_access:
|
||||
# tod has DAMPE or (tod == NONE and (find a path from a provider))
|
||||
# parsing is better than constructing this expression by hand
|
||||
r = self.current_spot if type(self.current_spot) == OOTRegion else self.current_spot.parent_region
|
||||
@@ -464,10 +464,10 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
return ast.NameConstant(True)
|
||||
|
||||
def at_night(self, node):
|
||||
if self.current_spot.type == 'GS Token' and self.world.logic_no_night_tokens_without_suns_song:
|
||||
if self.current_spot.type == 'GS Token' and self.multiworld.logic_no_night_tokens_without_suns_song:
|
||||
# Using visit here to resolve 'can_play' rule
|
||||
return self.visit(ast.parse('can_play(Suns_Song)', mode='eval').body)
|
||||
if self.world.ensure_tod_access:
|
||||
if self.multiworld.ensure_tod_access:
|
||||
# tod has DAMPE or (tod == NONE and (ss or find a path from a provider))
|
||||
# parsing is better than constructing this expression by hand
|
||||
r = self.current_spot if type(self.current_spot) == OOTRegion else self.current_spot.parent_region
|
||||
@@ -501,7 +501,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
return ast.parse(f"state._oot_reach_as_age('{r.name}', 'adult', {self.player})", mode='eval').body
|
||||
|
||||
def current_spot_starting_age_access(self, node):
|
||||
return self.current_spot_child_access(node) if self.world.starting_age == 'child' else self.current_spot_adult_access(node)
|
||||
return self.current_spot_child_access(node) if self.multiworld.starting_age == 'child' else self.current_spot_adult_access(node)
|
||||
|
||||
def has_bottle(self, node):
|
||||
return ast.parse(f"state._oot_has_bottle({self.player})", mode='eval').body
|
||||
|
||||
@@ -26,7 +26,7 @@ class OOTLogic(LogicMixin):
|
||||
|
||||
# Used for fall damage and other situations where damage is unavoidable
|
||||
def _oot_can_live_dmg(self, player, hearts):
|
||||
mult = self.world.worlds[player].damage_multiplier
|
||||
mult = self.multiworld.worlds[player].damage_multiplier
|
||||
if hearts*4 >= 3:
|
||||
return mult != 'ohko' and mult != 'quadruple'
|
||||
else:
|
||||
@@ -39,7 +39,7 @@ class OOTLogic(LogicMixin):
|
||||
def _oot_reach_as_age(self, regionname, age, player):
|
||||
if self.age[player] is None:
|
||||
self.age[player] = age
|
||||
can_reach = self.world.get_region(regionname, player).can_reach(self)
|
||||
can_reach = self.multiworld.get_region(regionname, player).can_reach(self)
|
||||
self.age[player] = None
|
||||
return can_reach
|
||||
return self.age[player] == age
|
||||
@@ -52,7 +52,7 @@ class OOTLogic(LogicMixin):
|
||||
}
|
||||
if regionname in name_map[tod]:
|
||||
return True
|
||||
region = self.world.get_region(regionname, player)
|
||||
region = self.multiworld.get_region(regionname, player)
|
||||
if region.provides_time == TimeOfDay.ALL or regionname == 'Root':
|
||||
self.day_reachable_regions[player].add(regionname)
|
||||
self.dampe_reachable_regions[player].add(regionname)
|
||||
@@ -82,7 +82,7 @@ class OOTLogic(LogicMixin):
|
||||
rrp = getattr(self, f'{age}_reachable_regions')[player]
|
||||
bc = getattr(self, f'{age}_blocked_connections')[player]
|
||||
queue = deque(getattr(self, f'{age}_blocked_connections')[player])
|
||||
start = self.world.get_region('Menu', player)
|
||||
start = self.multiworld.get_region('Menu', player)
|
||||
|
||||
# init on first call - this can't be done on construction since the regions don't exist yet
|
||||
if not start in rrp:
|
||||
@@ -110,7 +110,7 @@ class OOTLogic(LogicMixin):
|
||||
def set_rules(ootworld):
|
||||
logger = logging.getLogger('')
|
||||
|
||||
world = ootworld.world
|
||||
world = ootworld.multiworld
|
||||
player = ootworld.player
|
||||
|
||||
if ootworld.logic_rules != 'no_logic':
|
||||
@@ -213,10 +213,10 @@ def set_shop_rules(ootworld):
|
||||
# The goal is to automatically set item rules based on age requirements in case entrances were shuffled
|
||||
def set_entrances_based_rules(ootworld):
|
||||
|
||||
if ootworld.world.accessibility == 'beatable':
|
||||
if ootworld.multiworld.accessibility == 'beatable':
|
||||
return
|
||||
|
||||
all_state = ootworld.world.get_all_state(False)
|
||||
all_state = ootworld.multiworld.get_all_state(False)
|
||||
|
||||
for location in filter(lambda location: location.type == 'Shop', ootworld.get_locations()):
|
||||
# If a shop is not reachable as adult, it can't have Goron Tunic or Zora Tunic as child can't buy these
|
||||
|
||||
@@ -118,14 +118,14 @@ class OOTWorld(World):
|
||||
|
||||
def generate_early(self):
|
||||
# Player name MUST be at most 16 bytes ascii-encoded, otherwise won't write to ROM correctly
|
||||
if len(bytes(self.world.get_player_name(self.player), 'ascii')) > 16:
|
||||
if len(bytes(self.multiworld.get_player_name(self.player), 'ascii')) > 16:
|
||||
raise Exception(
|
||||
f"OoT: Player {self.player}'s name ({self.world.get_player_name(self.player)}) must be ASCII-compatible")
|
||||
f"OoT: Player {self.player}'s name ({self.multiworld.get_player_name(self.player)}) must be ASCII-compatible")
|
||||
|
||||
self.parser = Rule_AST_Transformer(self, self.player)
|
||||
|
||||
for (option_name, option) in oot_options.items():
|
||||
result = getattr(self.world, option_name)[self.player]
|
||||
result = getattr(self.multiworld, option_name)[self.player]
|
||||
if isinstance(result, Range):
|
||||
option_value = int(result)
|
||||
elif isinstance(result, Toggle):
|
||||
@@ -141,7 +141,7 @@ class OOTWorld(World):
|
||||
self.remove_from_start_inventory = [] # some items will be precollected but not in the inventory
|
||||
self.starting_items = Counter()
|
||||
self.starting_songs = False # whether starting_items contains a song
|
||||
self.file_hash = [self.world.random.randint(0, 31) for i in range(5)]
|
||||
self.file_hash = [self.multiworld.random.randint(0, 31) for i in range(5)]
|
||||
|
||||
self.item_name_groups = {
|
||||
"medallions": {"Light Medallion", "Forest Medallion", "Fire Medallion", "Water Medallion",
|
||||
@@ -185,13 +185,13 @@ class OOTWorld(World):
|
||||
# Determine skipped trials in GT
|
||||
# This needs to be done before the logic rules in GT are parsed
|
||||
trial_list = ['Forest', 'Fire', 'Water', 'Spirit', 'Shadow', 'Light']
|
||||
chosen_trials = self.world.random.sample(trial_list, self.trials) # chooses a list of trials to NOT skip
|
||||
chosen_trials = self.multiworld.random.sample(trial_list, self.trials) # chooses a list of trials to NOT skip
|
||||
self.skipped_trials = {trial: (trial not in chosen_trials) for trial in trial_list}
|
||||
|
||||
# Determine which dungeons are MQ
|
||||
# Possible future plan: allow user to pick which dungeons are MQ
|
||||
if self.logic_rules == 'glitchless':
|
||||
mq_dungeons = self.world.random.sample(dungeon_table, self.mq_dungeons)
|
||||
mq_dungeons = self.multiworld.random.sample(dungeon_table, self.mq_dungeons)
|
||||
else:
|
||||
self.mq_dungeons = 0
|
||||
mq_dungeons = []
|
||||
@@ -208,8 +208,8 @@ class OOTWorld(World):
|
||||
|
||||
# No Logic forces all tricks on, prog balancing off and beatable-only
|
||||
elif self.logic_rules == 'no_logic':
|
||||
self.world.progression_balancing[self.player].value = False
|
||||
self.world.accessibility[self.player] = self.world.accessibility[self.player].from_text("minimal")
|
||||
self.multiworld.progression_balancing[self.player].value = False
|
||||
self.multiworld.accessibility[self.player] = self.multiworld.accessibility[self.player].from_text("minimal")
|
||||
for trick in normalized_name_tricks.values():
|
||||
setattr(self, trick['name'], True)
|
||||
|
||||
@@ -310,7 +310,7 @@ class OOTWorld(World):
|
||||
|
||||
for region in region_json:
|
||||
new_region = OOTRegion(region['region_name'], RegionType.Generic, None, self.player)
|
||||
new_region.world = self.world
|
||||
new_region.multiworld = self.multiworld
|
||||
if 'pretty_name' in region:
|
||||
new_region.pretty_name = region['pretty_name']
|
||||
if 'font_color' in region:
|
||||
@@ -355,19 +355,19 @@ class OOTWorld(World):
|
||||
new_location.show_in_spoiler = False
|
||||
if 'exits' in region:
|
||||
for exit, rule in region['exits'].items():
|
||||
new_exit = OOTEntrance(self.player, self.world, '%s -> %s' % (new_region.name, exit), new_region)
|
||||
new_exit = OOTEntrance(self.player, self.multiworld, '%s -> %s' % (new_region.name, exit), new_region)
|
||||
new_exit.vanilla_connected_region = exit
|
||||
new_exit.rule_string = rule
|
||||
if self.world.logic_rules != 'none':
|
||||
if self.multiworld.logic_rules != 'none':
|
||||
self.parser.parse_spot_rule(new_exit)
|
||||
if new_exit.never:
|
||||
logger.debug('Dropping unreachable exit: %s', new_exit.name)
|
||||
else:
|
||||
new_region.exits.append(new_exit)
|
||||
|
||||
self.world.regions.append(new_region)
|
||||
self.multiworld.regions.append(new_region)
|
||||
self.regions.append(new_region)
|
||||
self.world._recache()
|
||||
self.multiworld._recache()
|
||||
|
||||
def set_scrub_prices(self):
|
||||
# Get Deku Scrub Locations
|
||||
@@ -387,7 +387,7 @@ class OOTWorld(World):
|
||||
elif self.shuffle_scrubs == 'random':
|
||||
# this is a random value between 0-99
|
||||
# average value is ~33 rupees
|
||||
price = int(self.world.random.betavariate(1, 2) * 99)
|
||||
price = int(self.multiworld.random.betavariate(1, 2) * 99)
|
||||
|
||||
# Set price in the dictionary as well as the location.
|
||||
self.scrub_prices[scrub_item] = price
|
||||
@@ -402,14 +402,14 @@ class OOTWorld(World):
|
||||
self.shop_prices = {}
|
||||
for region in self.regions:
|
||||
if self.shopsanity == 'random':
|
||||
shop_item_count = self.world.random.randint(0, 4)
|
||||
shop_item_count = self.multiworld.random.randint(0, 4)
|
||||
else:
|
||||
shop_item_count = int(self.shopsanity)
|
||||
|
||||
for location in region.locations:
|
||||
if location.type == 'Shop':
|
||||
if location.name[-1:] in shop_item_indexes[:shop_item_count]:
|
||||
self.shop_prices[location.name] = int(self.world.random.betavariate(1.5, 2) * 60) * 5
|
||||
self.shop_prices[location.name] = int(self.multiworld.random.betavariate(1.5, 2) * 60) * 5
|
||||
|
||||
def fill_bosses(self, bossCount=9):
|
||||
boss_location_names = (
|
||||
@@ -424,7 +424,7 @@ class OOTWorld(World):
|
||||
'Links Pocket'
|
||||
)
|
||||
boss_rewards = [item for item in self.itempool if item.type == 'DungeonReward']
|
||||
boss_locations = [self.world.get_location(loc, self.player) for loc in boss_location_names]
|
||||
boss_locations = [self.multiworld.get_location(loc, self.player) for loc in boss_location_names]
|
||||
|
||||
placed_prizes = [loc.item.name for loc in boss_locations if loc.item is not None]
|
||||
prizepool = [item for item in boss_rewards if item.name not in placed_prizes]
|
||||
@@ -432,12 +432,12 @@ class OOTWorld(World):
|
||||
|
||||
while bossCount:
|
||||
bossCount -= 1
|
||||
self.world.random.shuffle(prizepool)
|
||||
self.world.random.shuffle(prize_locs)
|
||||
self.multiworld.random.shuffle(prizepool)
|
||||
self.multiworld.random.shuffle(prize_locs)
|
||||
item = prizepool.pop()
|
||||
loc = prize_locs.pop()
|
||||
loc.place_locked_item(item)
|
||||
self.world.itempool.remove(item)
|
||||
self.multiworld.itempool.remove(item)
|
||||
|
||||
def create_item(self, name: str):
|
||||
if name in item_table:
|
||||
@@ -449,7 +449,7 @@ class OOTWorld(World):
|
||||
def make_event_item(self, name, location, item=None):
|
||||
if item is None:
|
||||
item = self.create_item(name)
|
||||
self.world.push_item(location, item, collect=False)
|
||||
self.multiworld.push_item(location, item, collect=False)
|
||||
location.locked = True
|
||||
location.event = True
|
||||
if name not in item_table:
|
||||
@@ -463,11 +463,11 @@ class OOTWorld(World):
|
||||
world_type = 'Glitched World'
|
||||
overworld_data_path = data_path(world_type, 'Overworld.json')
|
||||
menu = OOTRegion('Menu', None, None, self.player)
|
||||
start = OOTEntrance(self.player, self.world, 'New Game', menu)
|
||||
start = OOTEntrance(self.player, self.multiworld, 'New Game', menu)
|
||||
menu.exits.append(start)
|
||||
self.world.regions.append(menu)
|
||||
self.multiworld.regions.append(menu)
|
||||
self.load_regions_from_json(overworld_data_path)
|
||||
start.connect(self.world.get_region('Root', self.player))
|
||||
start.connect(self.multiworld.get_region('Root', self.player))
|
||||
create_dungeons(self)
|
||||
self.parser.create_delayed_rules()
|
||||
|
||||
@@ -478,7 +478,7 @@ class OOTWorld(World):
|
||||
# Bind entrances to vanilla
|
||||
for region in self.regions:
|
||||
for exit in region.exits:
|
||||
exit.connect(self.world.get_region(exit.vanilla_connected_region, self.player))
|
||||
exit.connect(self.multiworld.get_region(exit.vanilla_connected_region, self.player))
|
||||
|
||||
def create_items(self):
|
||||
# Generate itempool
|
||||
@@ -491,7 +491,7 @@ class OOTWorld(World):
|
||||
junk_pool = get_junk_pool(self)
|
||||
removed_items = []
|
||||
# Determine starting items
|
||||
for item in self.world.precollected_items[self.player]:
|
||||
for item in self.multiworld.precollected_items[self.player]:
|
||||
if item.name in self.remove_from_start_inventory:
|
||||
self.remove_from_start_inventory.remove(item.name)
|
||||
removed_items.append(item.name)
|
||||
@@ -512,7 +512,7 @@ class OOTWorld(World):
|
||||
if self.start_with_rupees:
|
||||
self.starting_items['Rupees'] = 999
|
||||
|
||||
self.world.itempool += self.itempool
|
||||
self.multiworld.itempool += self.itempool
|
||||
self.remove_from_start_inventory.extend(removed_items)
|
||||
|
||||
def set_rules(self):
|
||||
@@ -533,7 +533,7 @@ class OOTWorld(World):
|
||||
for entrance in self.get_shuffled_entrances():
|
||||
if entrance.connected_region is not None:
|
||||
entrance.disconnect()
|
||||
entrance.connect(self.world.get_region(entrance.vanilla_connected_region, self.player))
|
||||
entrance.connect(self.multiworld.get_region(entrance.vanilla_connected_region, self.player))
|
||||
if entrance.assumed:
|
||||
assumed_entrance = entrance.assumed
|
||||
if assumed_entrance.connected_region is not None:
|
||||
@@ -570,27 +570,27 @@ class OOTWorld(World):
|
||||
|
||||
# Kill unreachable events that can't be gotten even with all items
|
||||
# Make sure to only kill actual internal events, not in-game "events"
|
||||
all_state = self.world.get_all_state(False)
|
||||
all_state = self.multiworld.get_all_state(False)
|
||||
all_locations = self.get_locations()
|
||||
reachable = self.world.get_reachable_locations(all_state, self.player)
|
||||
reachable = self.multiworld.get_reachable_locations(all_state, self.player)
|
||||
unreachable = [loc for loc in all_locations if
|
||||
(loc.internal or loc.type == 'Drop') and loc.event and loc.locked and loc not in reachable]
|
||||
for loc in unreachable:
|
||||
loc.parent_region.locations.remove(loc)
|
||||
# Exception: Sell Big Poe is an event which is only reachable if Bottle with Big Poe is in the item pool.
|
||||
# We allow it to be removed only if Bottle with Big Poe is not in the itempool.
|
||||
bigpoe = self.world.get_location('Sell Big Poe from Market Guard House', self.player)
|
||||
bigpoe = self.multiworld.get_location('Sell Big Poe from Market Guard House', self.player)
|
||||
if not all_state.has('Bottle with Big Poe', self.player) and bigpoe not in reachable:
|
||||
bigpoe.parent_region.locations.remove(bigpoe)
|
||||
self.world.clear_location_cache()
|
||||
self.multiworld.clear_location_cache()
|
||||
|
||||
# If fast scarecrow then we need to kill the Pierre location as it will be unreachable
|
||||
if self.free_scarecrow:
|
||||
loc = self.world.get_location("Pierre", self.player)
|
||||
loc = self.multiworld.get_location("Pierre", self.player)
|
||||
loc.parent_region.locations.remove(loc)
|
||||
# If open zora's domain then we need to kill Deliver Rutos Letter
|
||||
if self.zora_fountain == 'open':
|
||||
loc = self.world.get_location("Deliver Rutos Letter", self.player)
|
||||
loc = self.multiworld.get_location("Deliver Rutos Letter", self.player)
|
||||
loc.parent_region.locations.remove(loc)
|
||||
|
||||
def pre_fill(self):
|
||||
@@ -641,11 +641,11 @@ class OOTWorld(World):
|
||||
if loc.item is None and (
|
||||
self.shuffle_song_items != 'dungeon' or loc.name not in dungeon_song_locations)]
|
||||
if itempools['dungeon']: # only do this if there's anything to shuffle
|
||||
dungeon_itempool = [item for item in self.world.itempool if item.player == self.player and item.name in itempools['dungeon']]
|
||||
dungeon_itempool = [item for item in self.multiworld.itempool if item.player == self.player and item.name in itempools['dungeon']]
|
||||
for item in dungeon_itempool:
|
||||
self.world.itempool.remove(item)
|
||||
self.world.random.shuffle(dungeon_locations)
|
||||
fill_restrictive(self.world, self.world.get_all_state(False), dungeon_locations,
|
||||
self.multiworld.itempool.remove(item)
|
||||
self.multiworld.random.shuffle(dungeon_locations)
|
||||
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), dungeon_locations,
|
||||
dungeon_itempool, True, True)
|
||||
any_dungeon_locations.extend(dungeon_locations) # adds only the unfilled locations
|
||||
|
||||
@@ -653,22 +653,22 @@ class OOTWorld(World):
|
||||
if self.shuffle_fortresskeys == 'any_dungeon':
|
||||
itempools['any_dungeon'].add('Small Key (Thieves Hideout)')
|
||||
if itempools['any_dungeon']:
|
||||
any_dungeon_itempool = [item for item in self.world.itempool if item.player == self.player and item.name in itempools['any_dungeon']]
|
||||
any_dungeon_itempool = [item for item in self.multiworld.itempool if item.player == self.player and item.name in itempools['any_dungeon']]
|
||||
for item in any_dungeon_itempool:
|
||||
self.world.itempool.remove(item)
|
||||
self.multiworld.itempool.remove(item)
|
||||
any_dungeon_itempool.sort(key=lambda item:
|
||||
{'GanonBossKey': 4, 'BossKey': 3, 'SmallKey': 2, 'HideoutSmallKey': 1}.get(item.type, 0))
|
||||
self.world.random.shuffle(any_dungeon_locations)
|
||||
fill_restrictive(self.world, self.world.get_all_state(False), any_dungeon_locations,
|
||||
self.multiworld.random.shuffle(any_dungeon_locations)
|
||||
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), any_dungeon_locations,
|
||||
any_dungeon_itempool, True, True)
|
||||
|
||||
# If anything is overworld-only, fill into local non-dungeon locations
|
||||
if self.shuffle_fortresskeys == 'overworld':
|
||||
itempools['overworld'].add('Small Key (Thieves Hideout)')
|
||||
if itempools['overworld']:
|
||||
overworld_itempool = [item for item in self.world.itempool if item.player == self.player and item.name in itempools['overworld']]
|
||||
overworld_itempool = [item for item in self.multiworld.itempool if item.player == self.player and item.name in itempools['overworld']]
|
||||
for item in overworld_itempool:
|
||||
self.world.itempool.remove(item)
|
||||
self.multiworld.itempool.remove(item)
|
||||
overworld_itempool.sort(key=lambda item:
|
||||
{'GanonBossKey': 4, 'BossKey': 3, 'SmallKey': 2, 'HideoutSmallKey': 1}.get(item.type, 0))
|
||||
non_dungeon_locations = [loc for loc in self.get_locations() if
|
||||
@@ -676,8 +676,8 @@ class OOTWorld(World):
|
||||
(loc.type != 'Shop' or loc.name in self.shop_prices) and
|
||||
(loc.type != 'Song' or self.shuffle_song_items != 'song') and
|
||||
(loc.name not in dungeon_song_locations or self.shuffle_song_items != 'dungeon')]
|
||||
self.world.random.shuffle(non_dungeon_locations)
|
||||
fill_restrictive(self.world, self.world.get_all_state(False), non_dungeon_locations,
|
||||
self.multiworld.random.shuffle(non_dungeon_locations)
|
||||
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), non_dungeon_locations,
|
||||
overworld_itempool, True, True)
|
||||
|
||||
# Place songs
|
||||
@@ -686,16 +686,16 @@ class OOTWorld(World):
|
||||
tries = 5
|
||||
if self.shuffle_song_items == 'song':
|
||||
song_locations = list(filter(lambda location: location.type == 'Song',
|
||||
self.world.get_unfilled_locations(player=self.player)))
|
||||
self.multiworld.get_unfilled_locations(player=self.player)))
|
||||
elif self.shuffle_song_items == 'dungeon':
|
||||
song_locations = list(filter(lambda location: location.name in dungeon_song_locations,
|
||||
self.world.get_unfilled_locations(player=self.player)))
|
||||
self.multiworld.get_unfilled_locations(player=self.player)))
|
||||
else:
|
||||
raise Exception(f"Unknown song shuffle type: {self.shuffle_song_items}")
|
||||
|
||||
songs = list(filter(lambda item: item.player == self.player and item.type == 'Song', self.world.itempool))
|
||||
songs = list(filter(lambda item: item.player == self.player and item.type == 'Song', self.multiworld.itempool))
|
||||
for song in songs:
|
||||
self.world.itempool.remove(song)
|
||||
self.multiworld.itempool.remove(song)
|
||||
|
||||
important_warps = (self.shuffle_special_interior_entrances or self.shuffle_overworld_entrances or
|
||||
self.warp_songs or self.spawn_positions)
|
||||
@@ -717,8 +717,8 @@ class OOTWorld(World):
|
||||
|
||||
while tries:
|
||||
try:
|
||||
self.world.random.shuffle(song_locations)
|
||||
fill_restrictive(self.world, self.world.get_all_state(False), song_locations[:], songs[:],
|
||||
self.multiworld.random.shuffle(song_locations)
|
||||
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), song_locations[:], songs[:],
|
||||
True, True)
|
||||
logger.debug(f"Successfully placed songs for player {self.player} after {6 - tries} attempt(s)")
|
||||
except FillError as e:
|
||||
@@ -741,33 +741,33 @@ class OOTWorld(World):
|
||||
# fast fill will fail because there is some logic on the shop items. we'll gather them up and place the shop items
|
||||
if self.shopsanity != 'off':
|
||||
shop_items = list(
|
||||
filter(lambda item: item.player == self.player and item.type == 'Shop', self.world.itempool))
|
||||
filter(lambda item: item.player == self.player and item.type == 'Shop', self.multiworld.itempool))
|
||||
shop_locations = list(
|
||||
filter(lambda location: location.type == 'Shop' and location.name not in self.shop_prices,
|
||||
self.world.get_unfilled_locations(player=self.player)))
|
||||
self.multiworld.get_unfilled_locations(player=self.player)))
|
||||
shop_items.sort(key=lambda item: {
|
||||
'Buy Deku Shield': 3 * int(self.open_forest == 'closed'),
|
||||
'Buy Goron Tunic': 2,
|
||||
'Buy Zora Tunic': 2
|
||||
}.get(item.name,
|
||||
int(item.advancement))) # place Deku Shields if needed, then tunics, then other advancement, then junk
|
||||
self.world.random.shuffle(shop_locations)
|
||||
self.multiworld.random.shuffle(shop_locations)
|
||||
for item in shop_items:
|
||||
self.world.itempool.remove(item)
|
||||
fill_restrictive(self.world, self.world.get_all_state(False), shop_locations, shop_items, True, True)
|
||||
self.multiworld.itempool.remove(item)
|
||||
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), shop_locations, shop_items, True, True)
|
||||
set_shop_rules(self) # sets wallet requirements on shop items, must be done after they are filled
|
||||
|
||||
# If skip child zelda is active and Song from Impa is unfilled, put a local giveable item into it.
|
||||
impa = self.world.get_location("Song from Impa", self.player)
|
||||
impa = self.multiworld.get_location("Song from Impa", self.player)
|
||||
if self.skip_child_zelda:
|
||||
if impa.item is None:
|
||||
item_to_place = self.world.random.choice(list(item for item in self.world.itempool if
|
||||
item.player == self.player and item.name in SaveContext.giveable_items))
|
||||
item_to_place = self.multiworld.random.choice(list(item for item in self.multiworld.itempool if
|
||||
item.player == self.player and item.name in SaveContext.giveable_items))
|
||||
impa.place_locked_item(item_to_place)
|
||||
self.world.itempool.remove(item_to_place)
|
||||
self.multiworld.itempool.remove(item_to_place)
|
||||
# Give items to startinventory
|
||||
self.world.push_precollected(impa.item)
|
||||
self.world.push_precollected(self.create_item("Zeldas Letter"))
|
||||
self.multiworld.push_precollected(impa.item)
|
||||
self.multiworld.push_precollected(self.create_item("Zeldas Letter"))
|
||||
|
||||
# Exclude locations in Ganon's Castle proportional to the number of items required to make the bridge
|
||||
# Check for dungeon ER later
|
||||
@@ -789,8 +789,8 @@ class OOTWorld(World):
|
||||
|
||||
gc = next(filter(lambda dungeon: dungeon.name == 'Ganons Castle', self.dungeons))
|
||||
locations = [loc.name for region in gc.regions for loc in region.locations if loc.item is None]
|
||||
junk_fill_locations = self.world.random.sample(locations, round(len(locations) * ganon_junk_fill))
|
||||
exclusion_rules(self.world, self.player, junk_fill_locations)
|
||||
junk_fill_locations = self.multiworld.random.sample(locations, round(len(locations) * ganon_junk_fill))
|
||||
exclusion_rules(self.multiworld, self.player, junk_fill_locations)
|
||||
|
||||
# Locations which are not sendable must be converted to events
|
||||
# This includes all locations for which show_in_spoiler is false, and shuffled shop items.
|
||||
@@ -813,12 +813,12 @@ class OOTWorld(World):
|
||||
trap_location_ids = [loc.address for loc in self.get_locations() if loc.item.trap]
|
||||
self.trap_appearances = {}
|
||||
for loc_id in trap_location_ids:
|
||||
self.trap_appearances[loc_id] = self.create_item(self.world.slot_seeds[self.player].choice(self.fake_items).name)
|
||||
self.trap_appearances[loc_id] = self.create_item(self.multiworld.slot_seeds[self.player].choice(self.fake_items).name)
|
||||
|
||||
# Seed hint RNG, used for ganon text lines also
|
||||
self.hint_rng = self.world.slot_seeds[self.player]
|
||||
self.hint_rng = self.multiworld.slot_seeds[self.player]
|
||||
|
||||
outfile_name = self.world.get_out_file_name_base(self.player)
|
||||
outfile_name = self.multiworld.get_out_file_name_base(self.player)
|
||||
rom = Rom(file=get_options()['oot_options']['rom_file'])
|
||||
if self.hints != 'none':
|
||||
buildWorldGossipHints(self)
|
||||
@@ -841,17 +841,17 @@ class OOTWorld(World):
|
||||
else:
|
||||
entrance = loadzone.reverse
|
||||
if entrance.reverse is not None:
|
||||
self.world.spoiler.set_entrance(entrance, entrance.replaces.reverse, 'both', self.player)
|
||||
self.multiworld.spoiler.set_entrance(entrance, entrance.replaces.reverse, 'both', self.player)
|
||||
else:
|
||||
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
|
||||
self.multiworld.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
|
||||
else:
|
||||
reverse = loadzone.replaces.reverse
|
||||
if reverse in all_entrances:
|
||||
all_entrances.remove(reverse)
|
||||
self.world.spoiler.set_entrance(loadzone, reverse, 'both', self.player)
|
||||
self.multiworld.spoiler.set_entrance(loadzone, reverse, 'both', self.player)
|
||||
else:
|
||||
for entrance in all_entrances:
|
||||
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
|
||||
self.multiworld.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
|
||||
|
||||
# Gathers hint data for OoT. Loops over all world locations for woth, barren, and major item locations.
|
||||
@classmethod
|
||||
@@ -979,9 +979,9 @@ class OOTWorld(World):
|
||||
|
||||
# Helper functions
|
||||
def get_shufflable_entrances(self, type=None, only_primary=False):
|
||||
return [entrance for entrance in self.world.get_entrances() if (entrance.player == self.player and
|
||||
(type == None or entrance.type == type) and
|
||||
(not only_primary or entrance.primary))]
|
||||
return [entrance for entrance in self.multiworld.get_entrances() if (entrance.player == self.player and
|
||||
(type == None or entrance.type == type) and
|
||||
(not only_primary or entrance.primary))]
|
||||
|
||||
def get_shuffled_entrances(self, type=None, only_primary=False):
|
||||
return [entrance for entrance in self.get_shufflable_entrances(type=type, only_primary=only_primary) if
|
||||
@@ -993,13 +993,13 @@ class OOTWorld(World):
|
||||
yield loc
|
||||
|
||||
def get_location(self, location):
|
||||
return self.world.get_location(location, self.player)
|
||||
return self.multiworld.get_location(location, self.player)
|
||||
|
||||
def get_region(self, region):
|
||||
return self.world.get_region(region, self.player)
|
||||
return self.multiworld.get_region(region, self.player)
|
||||
|
||||
def get_entrance(self, entrance):
|
||||
return self.world.get_entrance(entrance, self.player)
|
||||
return self.multiworld.get_entrance(entrance, self.player)
|
||||
|
||||
def is_major_item(self, item: OOTItem):
|
||||
if item.type == 'Token':
|
||||
@@ -1030,7 +1030,7 @@ class OOTWorld(World):
|
||||
# Specifically ensures that only real items are gotten, not any events.
|
||||
# In particular, ensures that Time Travel needs to be found.
|
||||
def get_state_with_complete_itempool(self):
|
||||
all_state = self.world.get_all_state(use_cache=False)
|
||||
all_state = self.multiworld.get_all_state(use_cache=False)
|
||||
# Remove event progression items
|
||||
for item, player in all_state.prog_items:
|
||||
if player == self.player and (item not in item_table or (item_table[item][2] is None and item_table[item][0] != 'DungeonReward')):
|
||||
|
||||
@@ -6,9 +6,9 @@ from BaseClasses import Location
|
||||
# TODO: implement Mapstone counting, Open, OpenWorld, connection rules
|
||||
|
||||
def set_rules(world):
|
||||
temp_base_rule(world.world, world.player)
|
||||
temp_base_rule(world.multiworld, world.player)
|
||||
for logicset in world.logic_sets:
|
||||
apply_or_ruleset(world.world, world.player, logicset)
|
||||
apply_or_ruleset(world.multiworld, world.player, logicset)
|
||||
|
||||
|
||||
def tautology(state):
|
||||
|
||||
@@ -24,17 +24,17 @@ class OriBlindForest(World):
|
||||
def generate_early(self):
|
||||
logic_sets = {"casual-core"}
|
||||
for logic_set in location_rules:
|
||||
if logic_set != "casual-core" and getattr(self.world, logic_set.replace("-", "_")):
|
||||
if logic_set != "casual-core" and getattr(self.multiworld, logic_set.replace("-", "_")):
|
||||
logic_sets.add(logic_set)
|
||||
self.logic_sets = logic_sets
|
||||
|
||||
set_rules = set_rules
|
||||
|
||||
def create_region(self, name: str):
|
||||
return Region(name, RegionType.Generic, name, self.player, self.world)
|
||||
return Region(name, RegionType.Generic, name, self.player, self.multiworld)
|
||||
|
||||
def create_regions(self):
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
menu = self.create_region("Menu")
|
||||
world.regions.append(menu)
|
||||
start = Entrance(self.player, "Start Game", menu)
|
||||
@@ -62,7 +62,7 @@ class OriBlindForest(World):
|
||||
|
||||
def generate_basic(self):
|
||||
for item_name, count in default_pool.items():
|
||||
self.world.itempool.extend([self.create_item(item_name) for _ in range(count)])
|
||||
self.multiworld.itempool.extend([self.create_item(item_name) for _ in range(count)])
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return Item(name,
|
||||
|
||||
@@ -159,7 +159,7 @@ class Overcooked2Level:
|
||||
sublevel: int
|
||||
|
||||
def __init__(self):
|
||||
self.world = Overcooked2GameWorld.ONE
|
||||
self.multiworld = Overcooked2GameWorld.ONE
|
||||
self.sublevel = 0
|
||||
|
||||
def __iter__(self):
|
||||
@@ -167,21 +167,21 @@ class Overcooked2Level:
|
||||
|
||||
def __next__(self):
|
||||
self.sublevel += 1
|
||||
if self.sublevel > self.world.sublevel_count:
|
||||
if self.world == Overcooked2GameWorld.KEVIN:
|
||||
if self.sublevel > self.multiworld.sublevel_count:
|
||||
if self.multiworld == Overcooked2GameWorld.KEVIN:
|
||||
raise StopIteration
|
||||
self.world = Overcooked2GameWorld(self.world.value + 1)
|
||||
self.multiworld = Overcooked2GameWorld(self.multiworld.value + 1)
|
||||
self.sublevel = 1
|
||||
|
||||
return self
|
||||
|
||||
@property
|
||||
def level_id(self) -> int:
|
||||
return self.world.base_id + (self.sublevel - 1)
|
||||
return self.multiworld.base_id + (self.sublevel - 1)
|
||||
|
||||
@property
|
||||
def level_name(self) -> str:
|
||||
return self.world.as_str + "-" + str(self.sublevel)
|
||||
return self.multiworld.as_str + "-" + str(self.sublevel)
|
||||
|
||||
@property
|
||||
def location_name_item(self) -> str:
|
||||
|
||||
@@ -79,7 +79,7 @@ class Overcooked2World(World):
|
||||
|
||||
def place_event(self, location_name: str, item_name: str,
|
||||
classification: ItemClassification = ItemClassification.progression_skip_balancing):
|
||||
location: Location = self.world.get_location(location_name, self.player)
|
||||
location: Location = self.multiworld.get_location(location_name, self.player)
|
||||
location.place_locked_item(self.create_event(item_name, classification))
|
||||
|
||||
def add_region(self, region_name: str):
|
||||
@@ -88,13 +88,13 @@ class Overcooked2World(World):
|
||||
RegionType.Generic,
|
||||
region_name,
|
||||
self.player,
|
||||
self.world,
|
||||
self.multiworld,
|
||||
)
|
||||
self.world.regions.append(region)
|
||||
self.multiworld.regions.append(region)
|
||||
|
||||
def connect_regions(self, source: str, target: str, rule: Optional[Callable[[CollectionState], bool]] = None):
|
||||
sourceRegion = self.world.get_region(source, self.player)
|
||||
targetRegion = self.world.get_region(target, self.player)
|
||||
sourceRegion = self.multiworld.get_region(source, self.player)
|
||||
targetRegion = self.multiworld.get_region(target, self.player)
|
||||
|
||||
connection = Entrance(self.player, '', sourceRegion)
|
||||
if rule:
|
||||
@@ -117,7 +117,7 @@ class Overcooked2World(World):
|
||||
else:
|
||||
location_id = level_id
|
||||
|
||||
region = self.world.get_region(region_name, self.player)
|
||||
region = self.multiworld.get_region(region_name, self.player)
|
||||
location = Overcooked2Location(
|
||||
self.player,
|
||||
location_name,
|
||||
@@ -145,8 +145,8 @@ class Overcooked2World(World):
|
||||
)
|
||||
|
||||
def get_options(self) -> Dict[str, Any]:
|
||||
return OC2Options({option.__name__: getattr(self.world, name)[self.player].result
|
||||
if issubclass(option, OC2OnToggle) else getattr(self.world, name)[self.player].value
|
||||
return OC2Options({option.__name__: getattr(self.multiworld, name)[self.player].result
|
||||
if issubclass(option, OC2OnToggle) else getattr(self.multiworld, name)[self.player].value
|
||||
for name, option in overcooked_options.items()})
|
||||
|
||||
# Helper Data
|
||||
@@ -170,7 +170,7 @@ class Overcooked2World(World):
|
||||
if self.options["ShuffleLevelOrder"]:
|
||||
self.level_mapping = \
|
||||
level_shuffle_factory(
|
||||
self.world.random,
|
||||
self.multiworld.random,
|
||||
self.options["PrepLevels"] != PrepLevelMode.excluded.value,
|
||||
self.options["IncludeHordeLevels"],
|
||||
)
|
||||
@@ -246,7 +246,7 @@ class Overcooked2World(World):
|
||||
|
||||
completion_condition: Callable[[CollectionState], bool] = lambda state: \
|
||||
state.has("Victory", self.player)
|
||||
self.world.completion_condition[self.player] = completion_condition
|
||||
self.multiworld.completion_condition[self.player] = completion_condition
|
||||
|
||||
def create_items(self):
|
||||
self.itempool = []
|
||||
@@ -298,7 +298,7 @@ class Overcooked2World(World):
|
||||
while len(self.itempool) < pool_count:
|
||||
self.itempool.append(self.create_item("Bonus Star", ItemClassification.useful))
|
||||
|
||||
self.world.itempool += self.itempool
|
||||
self.multiworld.itempool += self.itempool
|
||||
|
||||
def set_rules(self):
|
||||
pass
|
||||
@@ -324,7 +324,7 @@ class Overcooked2World(World):
|
||||
# Items get distributed to locations
|
||||
|
||||
def fill_json_data(self) -> Dict[str, Any]:
|
||||
mod_name = f"AP-{self.world.seed_name}-P{self.player}-{self.world.player_name[self.player]}"
|
||||
mod_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.player_name[self.player]}"
|
||||
|
||||
# Serialize Level Order
|
||||
story_level_order = dict()
|
||||
@@ -363,7 +363,7 @@ class Overcooked2World(World):
|
||||
# Set Kevin Unlock Requirements
|
||||
if self.options["KevinLevels"]:
|
||||
def kevin_level_to_keyholder_level_id(level_id: int) -> Optional[int]:
|
||||
location = self.world.find_item(f"Kevin-{level_id-36}", self.player)
|
||||
location = self.multiworld.find_item(f"Kevin-{level_id-36}", self.player)
|
||||
if location.player != self.player:
|
||||
return None # This kevin level will be unlocked by the server at runtime
|
||||
level_id = oc2_location_name_to_id[location.name]
|
||||
@@ -376,7 +376,7 @@ class Overcooked2World(World):
|
||||
|
||||
# Place Items at Level Completion Screens (local only)
|
||||
on_level_completed: Dict[str, list[Dict[str, str]]] = dict()
|
||||
regions = self.world.get_regions(self.player)
|
||||
regions = self.multiworld.get_regions(self.player)
|
||||
for region in regions:
|
||||
for location in region.locations:
|
||||
if location.item is None:
|
||||
|
||||
@@ -73,22 +73,25 @@ class PokemonRedBlueWorld(World):
|
||||
def encode_name(name, t):
|
||||
try:
|
||||
if len(encode_text(name)) > 7:
|
||||
raise IndexError(f"{t} name too long for player {self.world.player_name[self.player]}. Must be 7 characters or fewer.")
|
||||
raise IndexError(f"{t} name too long for player {self.multiworld.player_name[self.player]}. Must be 7 characters or fewer.")
|
||||
return encode_text(name, length=8, whitespace="@", safety=True)
|
||||
except KeyError as e:
|
||||
raise KeyError(f"Invalid character(s) in {t} name for player {self.world.player_name[self.player]}") from e
|
||||
self.trainer_name = encode_name(self.world.trainer_name[self.player].value, "Player")
|
||||
self.rival_name = encode_name(self.world.rival_name[self.player].value, "Rival")
|
||||
raise KeyError(f"Invalid character(s) in {t} name for player {self.multiworld.player_name[self.player]}") from e
|
||||
self.trainer_name = encode_name(self.multiworld.trainer_name[self.player].value, "Player")
|
||||
self.rival_name = encode_name(self.multiworld.rival_name[self.player].value, "Rival")
|
||||
|
||||
if self.world.badges_needed_for_hm_moves[self.player].value >= 2:
|
||||
if len(self.multiworld.player_name[self.player].encode()) > 16:
|
||||
raise Exception(f"Player name too long for {self.multiworld.get_player_name(self.player)}. Player name cannot exceed 16 bytes for Pokémon Red and Blue.")
|
||||
|
||||
if self.multiworld.badges_needed_for_hm_moves[self.player].value >= 2:
|
||||
badges_to_add = ["Marsh Badge", "Volcano Badge", "Earth Badge"]
|
||||
if self.world.badges_needed_for_hm_moves[self.player].value == 3:
|
||||
if self.multiworld.badges_needed_for_hm_moves[self.player].value == 3:
|
||||
badges = ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Marsh Badge",
|
||||
"Soul Badge", "Volcano Badge", "Earth Badge"]
|
||||
self.world.random.shuffle(badges)
|
||||
self.multiworld.random.shuffle(badges)
|
||||
badges_to_add += [badges.pop(), badges.pop()]
|
||||
hm_moves = ["Cut", "Fly", "Surf", "Strength", "Flash"]
|
||||
self.world.random.shuffle(hm_moves)
|
||||
self.multiworld.random.shuffle(hm_moves)
|
||||
self.extra_badges = {}
|
||||
for badge in badges_to_add:
|
||||
self.extra_badges[hm_moves.pop()] = badge
|
||||
@@ -96,42 +99,47 @@ class PokemonRedBlueWorld(World):
|
||||
process_pokemon_data(self)
|
||||
|
||||
def create_items(self) -> None:
|
||||
start_inventory = self.multiworld.start_inventory[self.player].value.copy()
|
||||
locations = [location for location in location_data if location.type == "Item"]
|
||||
item_pool = []
|
||||
for location in locations:
|
||||
if "Hidden" in location.name and not self.world.randomize_hidden_items[self.player].value:
|
||||
if "Hidden" in location.name and not self.multiworld.randomize_hidden_items[self.player].value:
|
||||
continue
|
||||
if "Rock Tunnel B1F" in location.region and not self.world.extra_key_items[self.player].value:
|
||||
if "Rock Tunnel B1F" in location.region and not self.multiworld.extra_key_items[self.player].value:
|
||||
continue
|
||||
if location.name == "Celadon City - Mansion Lady" and not self.world.tea[self.player].value:
|
||||
if location.name == "Celadon City - Mansion Lady" and not self.multiworld.tea[self.player].value:
|
||||
continue
|
||||
item = self.create_item(location.original_item)
|
||||
if location.original_item in self.multiworld.start_inventory[self.player].value and \
|
||||
location.original_item in item_groups["Unique"]:
|
||||
start_inventory[location.original_item] -= 1
|
||||
item = self.create_filler()
|
||||
else:
|
||||
item = self.create_item(location.original_item)
|
||||
if location.event:
|
||||
self.world.get_location(location.name, self.player).place_locked_item(item)
|
||||
elif ("Badge" not in item.name or self.world.badgesanity[self.player].value) and \
|
||||
(item.name != "Oak's Parcel" or self.world.old_man[self.player].value != 1):
|
||||
self.multiworld.get_location(location.name, self.player).place_locked_item(item)
|
||||
elif ("Badge" not in item.name or self.multiworld.badgesanity[self.player].value) and \
|
||||
(item.name != "Oak's Parcel" or self.multiworld.old_man[self.player].value != 1):
|
||||
item_pool.append(item)
|
||||
self.world.random.shuffle(item_pool)
|
||||
self.multiworld.random.shuffle(item_pool)
|
||||
|
||||
self.world.itempool += item_pool
|
||||
self.multiworld.itempool += item_pool
|
||||
|
||||
|
||||
def pre_fill(self):
|
||||
def generate_basic(self) -> None:
|
||||
|
||||
process_wild_pokemon(self)
|
||||
process_static_pokemon(self)
|
||||
|
||||
if self.world.old_man[self.player].value == 1:
|
||||
if self.multiworld.old_man[self.player].value == 1:
|
||||
item = self.create_item("Oak's Parcel")
|
||||
locations = []
|
||||
for location in self.world.get_locations():
|
||||
if location.player == self.player and location.item is None and location.can_reach(self.world.state) \
|
||||
for location in self.multiworld.get_locations():
|
||||
if location.player == self.player and location.item is None and location.can_reach(self.multiworld.state) \
|
||||
and location.item_rule(item):
|
||||
locations.append(location)
|
||||
self.world.random.choice(locations).place_locked_item(item)
|
||||
self.multiworld.random.choice(locations).place_locked_item(item)
|
||||
|
||||
if not self.world.badgesanity[self.player].value:
|
||||
self.world.non_local_items[self.player].value -= self.item_name_groups["Badges"]
|
||||
if not self.multiworld.badgesanity[self.player].value:
|
||||
self.multiworld.non_local_items[self.player].value -= self.item_name_groups["Badges"]
|
||||
for i in range(5):
|
||||
try:
|
||||
badges = []
|
||||
@@ -142,11 +150,11 @@ class PokemonRedBlueWorld(World):
|
||||
for loc in ["Pewter Gym - Brock 1", "Cerulean Gym - Misty 1", "Vermilion Gym - Lt. Surge 1",
|
||||
"Celadon Gym - Erika 1", "Fuchsia Gym - Koga 1", "Saffron Gym - Sabrina 1",
|
||||
"Cinnabar Gym - Blaine 1", "Viridian Gym - Giovanni 1"]:
|
||||
badgelocs.append(self.world.get_location(loc, self.player))
|
||||
state = self.world.get_all_state(False)
|
||||
self.world.random.shuffle(badges)
|
||||
self.world.random.shuffle(badgelocs)
|
||||
fill_restrictive(self.world, state, badgelocs.copy(), badges, True, True)
|
||||
badgelocs.append(self.multiworld.get_location(loc, self.player))
|
||||
state = self.multiworld.get_all_state(False)
|
||||
self.multiworld.random.shuffle(badges)
|
||||
self.multiworld.random.shuffle(badgelocs)
|
||||
fill_restrictive(self.multiworld, state, badgelocs.copy(), badges, True, True)
|
||||
except FillError:
|
||||
for location in badgelocs:
|
||||
location.item = None
|
||||
@@ -155,36 +163,36 @@ class PokemonRedBlueWorld(World):
|
||||
else:
|
||||
raise FillError(f"Failed to place badges for player {self.player}")
|
||||
|
||||
locs = [self.world.get_location("Fossil - Choice A", self.player),
|
||||
self.world.get_location("Fossil - Choice B", self.player)]
|
||||
locs = [self.multiworld.get_location("Fossil - Choice A", self.player),
|
||||
self.multiworld.get_location("Fossil - Choice B", self.player)]
|
||||
for loc in locs:
|
||||
add_item_rule(loc, lambda i: i.advancement or i.name in self.item_name_groups["Unique"]
|
||||
or i.name == "Master Ball")
|
||||
|
||||
loc = self.world.get_location("Pallet Town - Player's PC", self.player)
|
||||
loc = self.multiworld.get_location("Pallet Town - Player's PC", self.player)
|
||||
if loc.item is None:
|
||||
locs.append(loc)
|
||||
|
||||
for loc in locs:
|
||||
unplaced_items = []
|
||||
if loc.name in self.world.priority_locations[self.player].value:
|
||||
if loc.name in self.multiworld.priority_locations[self.player].value:
|
||||
add_item_rule(loc, lambda i: i.advancement)
|
||||
for item in self.world.itempool:
|
||||
for item in reversed(self.multiworld.itempool):
|
||||
if item.player == self.player and loc.item_rule(item):
|
||||
self.world.itempool.remove(item)
|
||||
state = sweep_from_pool(self.world.state, self.world.itempool + unplaced_items)
|
||||
self.multiworld.itempool.remove(item)
|
||||
state = sweep_from_pool(self.multiworld.state, self.multiworld.itempool + unplaced_items)
|
||||
if state.can_reach(loc, "Location", self.player):
|
||||
loc.place_locked_item(item)
|
||||
break
|
||||
else:
|
||||
unplaced_items.append(item)
|
||||
self.world.itempool += unplaced_items
|
||||
self.multiworld.itempool += unplaced_items
|
||||
|
||||
intervene = False
|
||||
test_state = self.world.get_all_state(False)
|
||||
test_state = self.multiworld.get_all_state(False)
|
||||
if not test_state.pokemon_rb_can_surf(self.player) or not test_state.pokemon_rb_can_strength(self.player):
|
||||
intervene = True
|
||||
elif self.world.accessibility[self.player].current_key != "minimal":
|
||||
elif self.multiworld.accessibility[self.player].current_key != "minimal":
|
||||
if not test_state.pokemon_rb_can_cut(self.player) or not test_state.pokemon_rb_can_flash(self.player):
|
||||
intervene = True
|
||||
if intervene:
|
||||
@@ -192,27 +200,30 @@ class PokemonRedBlueWorld(World):
|
||||
# let you choose the exact weights for HM compatibility
|
||||
logging.warning(
|
||||
f"HM-compatible Pokémon possibly missing, placing Mew on Route 1 for player {self.player}")
|
||||
loc = self.world.get_location("Route 1 - Wild Pokemon - 1", self.player)
|
||||
loc = self.multiworld.get_location("Route 1 - Wild Pokemon - 1", self.player)
|
||||
loc.item = self.create_item("Mew")
|
||||
|
||||
def create_regions(self):
|
||||
if self.world.free_fly_location[self.player].value:
|
||||
fly_map_code = self.world.random.randint(5, 9)
|
||||
if self.multiworld.free_fly_location[self.player].value:
|
||||
if self.multiworld.old_man[self.player].value == 0:
|
||||
fly_map_code = self.multiworld.random.randint(1, 9)
|
||||
else:
|
||||
fly_map_code = self.multiworld.random.randint(5, 9)
|
||||
if fly_map_code == 5:
|
||||
fly_map_code = 4
|
||||
if fly_map_code == 9:
|
||||
fly_map_code = 10
|
||||
if fly_map_code == 5:
|
||||
fly_map_code = 4
|
||||
else:
|
||||
fly_map_code = 0
|
||||
self.fly_map = ["Pallet Town", "Viridian City", "Pewter City", "Cerulean City", "Lavender Town",
|
||||
"Vermilion City", "Celadon City", "Fuchsia City", "Cinnabar Island", "Indigo Plateau",
|
||||
"Saffron City"][fly_map_code]
|
||||
self.fly_map_code = fly_map_code
|
||||
create_regions(self.world, self.player)
|
||||
self.world.completion_condition[self.player] = lambda state, player=self.player: state.has("Become Champion", player=player)
|
||||
create_regions(self.multiworld, self.player)
|
||||
self.multiworld.completion_condition[self.player] = lambda state, player=self.player: state.has("Become Champion", player=player)
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return PokemonRBItem(name, self.player)
|
||||
@@ -221,22 +232,44 @@ class PokemonRedBlueWorld(World):
|
||||
generate_output(self, output_directory)
|
||||
|
||||
def write_spoiler_header(self, spoiler_handle: TextIO):
|
||||
if self.world.free_fly_location[self.player].value:
|
||||
if self.multiworld.free_fly_location[self.player].value:
|
||||
spoiler_handle.write('Fly unlocks: %s\n' % self.fly_map)
|
||||
if self.extra_badges:
|
||||
for hm_move, badge in self.extra_badges.items():
|
||||
spoiler_handle.write(hm_move + " enabled by: " + (" " * 20)[:20 - len(hm_move)] + badge + "\n")
|
||||
|
||||
def write_spoiler(self, spoiler_handle):
|
||||
if self.world.randomize_type_matchup_types[self.player].value or \
|
||||
self.world.randomize_type_matchup_type_effectiveness[self.player].value:
|
||||
spoiler_handle.write(f"\n\nType matchups ({self.world.player_name[self.player]}):\n\n")
|
||||
if self.multiworld.randomize_type_matchup_types[self.player].value or \
|
||||
self.multiworld.randomize_type_matchup_type_effectiveness[self.player].value:
|
||||
spoiler_handle.write(f"\n\nType matchups ({self.multiworld.player_name[self.player]}):\n\n")
|
||||
for matchup in self.type_chart:
|
||||
spoiler_handle.write(f"{matchup[0]} deals {matchup[2] * 10}% damage to {matchup[1]}\n")
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice([item for item in item_table if item_table[item].classification in
|
||||
[ItemClassification.filler, ItemClassification.trap]])
|
||||
return self.multiworld.random.choice([item for item in item_table if item_table[item].classification in
|
||||
[ItemClassification.filler, ItemClassification.trap] and item not in
|
||||
item_groups["Vending Machine Drinks"]])
|
||||
|
||||
def fill_slot_data(self) -> dict:
|
||||
# for trackers
|
||||
return {
|
||||
"second_fossil_check_condition": self.multiworld.second_fossil_check_condition[self.player].value,
|
||||
"require_item_finder": self.multiworld.require_item_finder[self.player].value,
|
||||
"randomize_hidden_items": self.multiworld.randomize_hidden_items[self.player].value,
|
||||
"badges_needed_for_hm_moves": self.multiworld.badges_needed_for_hm_moves[self.player].value,
|
||||
"oaks_aide_rt_2": self.multiworld.oaks_aide_rt_2[self.player].value,
|
||||
"oaks_aide_rt_11": self.multiworld.oaks_aide_rt_11[self.player].value,
|
||||
"oaks_aide_rt_15": self.multiworld.oaks_aide_rt_15[self.player].value,
|
||||
"extra_key_items": self.multiworld.extra_key_items[self.player].value,
|
||||
"extra_strength_boulders": self.multiworld.extra_strength_boulders[self.player].value,
|
||||
"tea": self.multiworld.tea[self.player].value,
|
||||
"old_man": self.multiworld.old_man[self.player].value,
|
||||
"elite_four_condition": self.multiworld.elite_four_condition[self.player].value,
|
||||
"victory_road_condition": self.multiworld.victory_road_condition[self.player].value,
|
||||
"viridian_gym_condition": self.multiworld.viridian_gym_condition[self.player].value,
|
||||
"free_fly_map": self.fly_map_code,
|
||||
"extra_badges": self.extra_badges
|
||||
}
|
||||
|
||||
|
||||
class PokemonRBItem(Item):
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -14,6 +14,11 @@ As we are using Bizhawk, this guide is only applicable to Windows and Linux syst
|
||||
(select `Pokemon Client` during installation).
|
||||
- Pokémon Red and/or Blue ROM files. The Archipelago community cannot provide these.
|
||||
|
||||
## Optional Software
|
||||
|
||||
- [Pokémon Red and Blue Archipelago Map Tracker](https://github.com/j-imbo/pkmnrb_jim/releases/latest), for use with [PopTracker](https://github.com/black-sliver/PopTracker/releases)
|
||||
|
||||
|
||||
## Configuring Bizhawk
|
||||
|
||||
Once Bizhawk has been installed, open Bizhawk and change the following settings:
|
||||
@@ -82,3 +87,14 @@ To connect the client to the multiserver simply put `<address>:<port>` on the te
|
||||
server uses password, type in the bottom textfield `/connect <address>:<port> [password]`)
|
||||
|
||||
Now you are ready to start your adventure in Kanto.
|
||||
|
||||
## Auto-Tracking
|
||||
|
||||
Pokémon Red and Blue has a fully functional map tracker that supports auto-tracking.
|
||||
|
||||
1. Download [Pokémon Red and Blue Archipelago Map Tracker](https://github.com/j-imbo/pkmnrb_jim/releases/latest) and [PopTracker](https://github.com/black-sliver/PopTracker/releases).
|
||||
2. Open PopTracker, and load the Pokémon Red and Blue pack.
|
||||
3. Click on the "AP" symbol at the top.
|
||||
4. Enter the AP address, slot name and password.
|
||||
|
||||
The rest should take care of itself! Items and checks will be marked automatically, and it even knows your settings - It will hide checks & adjust logic accordingly.
|
||||
|
||||
@@ -66,9 +66,9 @@ item_table = {
|
||||
"Max Repel": ItemData(57, ItemClassification.filler, ["Consumables"]),
|
||||
"Dire Hit": ItemData(58, ItemClassification.filler, ["Consumables", "Battle Items"]),
|
||||
#"Coin": ItemData(59, ItemClassification.filler),
|
||||
"Fresh Water": ItemData(60, ItemClassification.filler, ["Consumables"]),
|
||||
"Soda Pop": ItemData(61, ItemClassification.filler, ["Consumables"]),
|
||||
"Lemonade": ItemData(62, ItemClassification.filler, ["Consumables"]),
|
||||
"Fresh Water": ItemData(60, ItemClassification.filler, ["Consumables", "Vending Machine Drinks"]),
|
||||
"Soda Pop": ItemData(61, ItemClassification.filler, ["Consumables", "Vending Machine Drinks"]),
|
||||
"Lemonade": ItemData(62, ItemClassification.filler, ["Consumables", "Vending Machine Drinks"]),
|
||||
"S.S. Ticket": ItemData(63, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"Gold Teeth": ItemData(64, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"X Attack": ItemData(65, ItemClassification.filler, ["Consumables", "Battle Items"]),
|
||||
|
||||
@@ -321,21 +321,21 @@ location_data = [
|
||||
LocationData("Viridian Forest", "Hidden Item Entrance Tree", "Antidote", rom_addresses['Hidden_Item_Viridian_Forest_2'], Hidden(1)),
|
||||
LocationData("Mt Moon B2F", "Hidden Item Dead End Before Fossils", "Moon Stone", rom_addresses['Hidden_Item_MtMoonB2F_1'], Hidden(2)),
|
||||
LocationData("Route 25", "Hidden Item Fence Outside Bill's House", "Ether", rom_addresses['Hidden_Item_Route_25_1'], Hidden(3)),
|
||||
LocationData("Route 9", "Hidden Item Rock By Grass", "Ether", rom_addresses['Hidden_Item_Route_9'], Hidden(4)),
|
||||
LocationData("Route 9", "Hidden Item Bush By Grass", "Ether", rom_addresses['Hidden_Item_Route_9'], Hidden(4)),
|
||||
LocationData("S.S. Anne 1F", "Hidden Item Kitchen Trash", "Great Ball", rom_addresses['Hidden_Item_SS_Anne_Kitchen'], Hidden(5)),
|
||||
LocationData("S.S. Anne B1F", "Hidden Item Under Pillow", "Hyper Potion", rom_addresses['Hidden_Item_SS_Anne_B1F'], Hidden(6)),
|
||||
LocationData("Route 10 North", "Hidden Item Behind Rock Tunnel Entrance Tree", "Super Potion", rom_addresses['Hidden_Item_Route_10_1'], Hidden(7)),
|
||||
LocationData("Route 10 South", "Hidden Item Rock", "Max Ether", rom_addresses['Hidden_Item_Route_10_2'], Hidden(8)),
|
||||
LocationData("Route 10 North", "Hidden Item Behind Rock Tunnel Entrance Cuttable Tree", "Super Potion", rom_addresses['Hidden_Item_Route_10_1'], Hidden(7)),
|
||||
LocationData("Route 10 South", "Hidden Item Bush", "Max Ether", rom_addresses['Hidden_Item_Route_10_2'], Hidden(8)),
|
||||
LocationData("Rocket Hideout B1F", "Hidden Item Pot Plant", "PP Up", rom_addresses['Hidden_Item_Rocket_Hideout_B1F'], Hidden(9)),
|
||||
LocationData("Rocket Hideout B3F", "Hidden Item Near East Item", "Nugget", rom_addresses['Hidden_Item_Rocket_Hideout_B3F'], Hidden(10)),
|
||||
LocationData("Rocket Hideout B4F", "Hidden Item Behind Giovanni", "Super Potion", rom_addresses['Hidden_Item_Rocket_Hideout_B4F'], Hidden(11)),
|
||||
LocationData("Pokemon Tower 5F", "Hidden Item Near West Staircase", "Elixir", rom_addresses['Hidden_Item_Pokemon_Tower_5F'], Hidden(12)),
|
||||
LocationData("Route 13", "Hidden Item Dead End Boulder", "PP Up", rom_addresses['Hidden_Item_Route_13_1'], Hidden(13)),
|
||||
LocationData("Route 13", "Hidden Item Dead End Bush", "PP Up", rom_addresses['Hidden_Item_Route_13_1'], Hidden(13)),
|
||||
LocationData("Route 13", "Hidden Item Dead End By Water Corner", "Calcium", rom_addresses['Hidden_Item_Route_13_2'], Hidden(14)),
|
||||
LocationData("Pokemon Mansion B1F", "Hidden Item Secret Key Room Corner", "Rare Candy", rom_addresses['Hidden_Item_Pokemon_Mansion_B1F'], Hidden(15)),
|
||||
LocationData("Safari Zone West", "Hidden Item Secret House Statue", "Revive", rom_addresses['Hidden_Item_Safari_Zone_West'], Hidden(17)),
|
||||
LocationData("Silph Co 5F", "Hidden Item Pot Plant", "Elixir", rom_addresses['Hidden_Item_Silph_Co_5F'], Hidden(18)),
|
||||
LocationData("Silph Co 9F", "Hidden Item Nurse Bed", "Max Potion", rom_addresses['Hidden_Item_Silph_Co_9F'], Hidden(19)),
|
||||
LocationData("Silph Co 9F", "Hidden Item Nurse Bed (Card Key)", "Max Potion", rom_addresses['Hidden_Item_Silph_Co_9F'], Hidden(19)),
|
||||
LocationData("Copycat's House", "Hidden Item Desk", "Nugget", rom_addresses['Hidden_Item_Copycats_House'], Hidden(20)),
|
||||
LocationData("Cerulean Cave 1F", "Hidden Item Center Rocks", "Rare Candy", rom_addresses['Hidden_Item_Cerulean_Cave_1F'], Hidden(21)),
|
||||
LocationData("Cerulean Cave B1F", "Hidden Item Northeast Rocks", "Ultra Ball", rom_addresses['Hidden_Item_Cerulean_Cave_B1F'], Hidden(22)),
|
||||
@@ -345,15 +345,15 @@ location_data = [
|
||||
LocationData("Seafoam Islands B4F", "Hidden Item Corner Island", "Ultra Ball", rom_addresses['Hidden_Item_Seafoam_Islands_B4F'], Hidden(26)),
|
||||
LocationData("Pokemon Mansion 1F", "Hidden Item Block Near Entrance Carpet", "Moon Stone", rom_addresses['Hidden_Item_Pokemon_Mansion_1F'], Hidden(27)),
|
||||
LocationData("Pokemon Mansion 3F", "Hidden Item Behind Burglar", "Max Revive", rom_addresses['Hidden_Item_Pokemon_Mansion_3F'], Hidden(28)),
|
||||
LocationData("Route 23 North", "Hidden Item Rocks Before Final Guard", "Full Restore", rom_addresses['Hidden_Item_Route_23_1'], Hidden(29)),
|
||||
LocationData("Route 23 North", "Hidden Item East Tree After Water", "Ultra Ball", rom_addresses['Hidden_Item_Route_23_2'], Hidden(30)),
|
||||
LocationData("Route 23 South", "Hidden Item On Island", "Max Ether", rom_addresses['Hidden_Item_Route_23_3'], Hidden(31)),
|
||||
LocationData("Route 23", "Hidden Item Rocks Before Final Guard", "Full Restore", rom_addresses['Hidden_Item_Route_23_1'], Hidden(29)),
|
||||
LocationData("Route 23", "Hidden Item East Bush After Water", "Ultra Ball", rom_addresses['Hidden_Item_Route_23_2'], Hidden(30)),
|
||||
LocationData("Route 23", "Hidden Item On Island", "Max Ether", rom_addresses['Hidden_Item_Route_23_3'], Hidden(31)),
|
||||
LocationData("Victory Road 2F", "Hidden Item Rock Before Moltres", "Ultra Ball", rom_addresses['Hidden_Item_Victory_Road_2F_1'], Hidden(32)),
|
||||
LocationData("Victory Road 2F", "Hidden Item Rock In Final Room", "Full Restore", rom_addresses['Hidden_Item_Victory_Road_2F_2'], Hidden(33)),
|
||||
#LocationData("Vermilion City", "Hidden Item The Truck", "Max Elixir", rom_addresses['Hidden_Item_Unused_6F'], Hidden(34)),
|
||||
|
||||
LocationData("Viridian City", "Hidden Item Cuttable Tree", "Potion", rom_addresses['Hidden_Item_Viridian_City'], Hidden(35)),
|
||||
LocationData("Route 11", "Hidden Item Isolated Tree Near Gate", "Potion", rom_addresses['Hidden_Item_Route_11'], Hidden(36)),
|
||||
LocationData("Route 12 West", "Hidden Item Tree Near Gate", "Hyper Potion", rom_addresses['Hidden_Item_Route_12'], Hidden(37)),
|
||||
LocationData("Route 11", "Hidden Item Isolated Bush Near Gate", "Potion", rom_addresses['Hidden_Item_Route_11'], Hidden(36)),
|
||||
LocationData("Route 12 West", "Hidden Item Bush Near Gate", "Hyper Potion", rom_addresses['Hidden_Item_Route_12'], Hidden(37)),
|
||||
LocationData("Route 17", "Hidden Item In Grass", "Rare Candy", rom_addresses['Hidden_Item_Route_17_1'], Hidden(38)),
|
||||
LocationData("Route 17", "Hidden Item Near Northernmost Sign", "Full Restore", rom_addresses['Hidden_Item_Route_17_2'], Hidden(39)),
|
||||
LocationData("Route 17", "Hidden Item East Center", "PP Up", rom_addresses['Hidden_Item_Route_17_3'], Hidden(40)),
|
||||
@@ -421,13 +421,13 @@ location_data = [
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Route 19", "Super Rod Pokemon - 4", "Goldeen", rom_addresses["Wild_Super_Rod_H"] + 7,
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 South", "Super Rod Pokemon - 1", "Slowbro", rom_addresses["Wild_Super_Rod_I"] + 1,
|
||||
LocationData("Route 23", "Super Rod Pokemon - 1", "Slowbro", rom_addresses["Wild_Super_Rod_I"] + 1,
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 South", "Super Rod Pokemon - 2", "Seaking", rom_addresses["Wild_Super_Rod_I"] + 3,
|
||||
LocationData("Route 23", "Super Rod Pokemon - 2", "Seaking", rom_addresses["Wild_Super_Rod_I"] + 3,
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 South", "Super Rod Pokemon - 3", "Kingler", rom_addresses["Wild_Super_Rod_I"] + 5,
|
||||
LocationData("Route 23", "Super Rod Pokemon - 3", "Kingler", rom_addresses["Wild_Super_Rod_I"] + 5,
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 South", "Super Rod Pokemon - 4", "Seadra", rom_addresses["Wild_Super_Rod_I"] + 7,
|
||||
LocationData("Route 23", "Super Rod Pokemon - 4", "Seadra", rom_addresses["Wild_Super_Rod_I"] + 7,
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Fuchsia City", "Super Rod Pokemon - 1", "Seaking", rom_addresses["Wild_Super_Rod_J"] + 1,
|
||||
None, event=True, type="Wild Encounter"),
|
||||
@@ -1480,25 +1480,25 @@ location_data = [
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Power Plant", "Wild Pokemon - 10", ["Electabuzz", "Raichu"],
|
||||
rom_addresses["Wild_PowerPlant"] + 19, None, event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 1", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route23"] + 1,
|
||||
LocationData("Route 23", "Wild Pokemon - 1", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route23"] + 1,
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 2", "Ditto", rom_addresses["Wild_Route23"] + 3, None, event=True,
|
||||
LocationData("Route 23", "Wild Pokemon - 2", "Ditto", rom_addresses["Wild_Route23"] + 3, None, event=True,
|
||||
type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 3", "Spearow", rom_addresses["Wild_Route23"] + 5, None,
|
||||
LocationData("Route 23", "Wild Pokemon - 3", "Spearow", rom_addresses["Wild_Route23"] + 5, None,
|
||||
event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 4", "Fearow", rom_addresses["Wild_Route23"] + 7, None,
|
||||
LocationData("Route 23", "Wild Pokemon - 4", "Fearow", rom_addresses["Wild_Route23"] + 7, None,
|
||||
event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 5", "Ditto", rom_addresses["Wild_Route23"] + 9, None, event=True,
|
||||
LocationData("Route 23", "Wild Pokemon - 5", "Ditto", rom_addresses["Wild_Route23"] + 9, None, event=True,
|
||||
type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 6", "Fearow", rom_addresses["Wild_Route23"] + 11, None,
|
||||
LocationData("Route 23", "Wild Pokemon - 6", "Fearow", rom_addresses["Wild_Route23"] + 11, None,
|
||||
event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 7", ["Arbok", "Sandslash"], rom_addresses["Wild_Route23"] + 13,
|
||||
LocationData("Route 23", "Wild Pokemon - 7", ["Arbok", "Sandslash"], rom_addresses["Wild_Route23"] + 13,
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 8", "Ditto", rom_addresses["Wild_Route23"] + 15, None,
|
||||
LocationData("Route 23", "Wild Pokemon - 8", "Ditto", rom_addresses["Wild_Route23"] + 15, None,
|
||||
event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 9", "Fearow", rom_addresses["Wild_Route23"] + 17, None,
|
||||
LocationData("Route 23", "Wild Pokemon - 9", "Fearow", rom_addresses["Wild_Route23"] + 17, None,
|
||||
event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 10", "Fearow", rom_addresses["Wild_Route23"] + 19, None,
|
||||
LocationData("Route 23", "Wild Pokemon - 10", "Fearow", rom_addresses["Wild_Route23"] + 19, None,
|
||||
event=True, type="Wild Encounter"),
|
||||
LocationData("Victory Road 2F", "Wild Pokemon - 1", "Machop", rom_addresses["Wild_VictoryRoad2F"] + 1, None,
|
||||
event=True, type="Wild Encounter"),
|
||||
|
||||
@@ -6,39 +6,39 @@ class PokemonLogic(LogicMixin):
|
||||
def pokemon_rb_can_surf(self, player):
|
||||
return (((self.has("HM03 Surf", player) and self.can_learn_hm("10000", player))
|
||||
or self.has("Flippers", player)) and (self.has("Soul Badge", player) or
|
||||
self.has(self.world.worlds[player].extra_badges.get("Surf"), player)
|
||||
or self.world.badges_needed_for_hm_moves[player].value == 0))
|
||||
self.has(self.multiworld.worlds[player].extra_badges.get("Surf"), player)
|
||||
or self.multiworld.badges_needed_for_hm_moves[player].value == 0))
|
||||
|
||||
def pokemon_rb_can_cut(self, player):
|
||||
return ((self.has("HM01 Cut", player) and self.can_learn_hm("100", player) or self.has("Master Sword", player))
|
||||
and (self.has("Cascade Badge", player) or
|
||||
self.has(self.world.worlds[player].extra_badges.get("Cut"), player) or
|
||||
self.world.badges_needed_for_hm_moves[player].value == 0))
|
||||
self.has(self.multiworld.worlds[player].extra_badges.get("Cut"), player) or
|
||||
self.multiworld.badges_needed_for_hm_moves[player].value == 0))
|
||||
|
||||
def pokemon_rb_can_fly(self, player):
|
||||
return (((self.has("HM02 Fly", player) and self.can_learn_hm("1000", player)) or self.has("Flute", player)) and
|
||||
(self.has("Thunder Badge", player) or self.has(self.world.worlds[player].extra_badges.get("Fly"), player)
|
||||
or self.world.badges_needed_for_hm_moves[player].value == 0))
|
||||
(self.has("Thunder Badge", player) or self.has(self.multiworld.worlds[player].extra_badges.get("Fly"), player)
|
||||
or self.multiworld.badges_needed_for_hm_moves[player].value == 0))
|
||||
|
||||
def pokemon_rb_can_strength(self, player):
|
||||
return ((self.has("HM04 Strength", player) and self.can_learn_hm("100000", player)) or
|
||||
self.has("Titan's Mitt", player)) and (self.has("Rainbow Badge", player) or
|
||||
self.has(self.world.worlds[player].extra_badges.get("Strength"), player)
|
||||
or self.world.badges_needed_for_hm_moves[player].value == 0)
|
||||
self.has(self.multiworld.worlds[player].extra_badges.get("Strength"), player)
|
||||
or self.multiworld.badges_needed_for_hm_moves[player].value == 0)
|
||||
|
||||
def pokemon_rb_can_flash(self, player):
|
||||
return (((self.has("HM05 Flash", player) and self.can_learn_hm("1000000", player)) or self.has("Lamp", player))
|
||||
and (self.has("Boulder Badge", player) or self.has(self.world.worlds[player].extra_badges.get("Flash"),
|
||||
player) or self.world.badges_needed_for_hm_moves[player].value == 0))
|
||||
and (self.has("Boulder Badge", player) or self.has(self.multiworld.worlds[player].extra_badges.get("Flash"),
|
||||
player) or self.multiworld.badges_needed_for_hm_moves[player].value == 0))
|
||||
|
||||
def can_learn_hm(self, move, player):
|
||||
for pokemon, data in self.world.worlds[player].local_poke_data.items():
|
||||
for pokemon, data in self.multiworld.worlds[player].local_poke_data.items():
|
||||
if self.has(pokemon, player) and data["tms"][6] & int(move, 2):
|
||||
return True
|
||||
return False
|
||||
|
||||
def pokemon_rb_can_get_hidden_items(self, player):
|
||||
return self.has("Item Finder", player) or not self.world.require_item_finder[player].value
|
||||
return self.has("Item Finder", player) or not self.multiworld.require_item_finder[player].value
|
||||
|
||||
def pokemon_rb_cerulean_cave(self, count, player):
|
||||
return len([item for item in
|
||||
@@ -49,7 +49,7 @@ class PokemonLogic(LogicMixin):
|
||||
"HM04 Strength", "HM05 Flash"] if self.has(item, player)]) >= count
|
||||
|
||||
def pokemon_rb_can_pass_guards(self, player):
|
||||
if self.world.tea[player].value:
|
||||
if self.multiworld.tea[player].value:
|
||||
return self.has("Tea", player)
|
||||
else:
|
||||
# this could just be "True", but you never know what weird options I might introduce later ;)
|
||||
|
||||
@@ -83,9 +83,9 @@ class BadgeSanity(Toggle):
|
||||
|
||||
|
||||
class BadgesNeededForHMMoves(Choice):
|
||||
"""Off will remove the requirement for badges to use HM moves. Extra will give the Marsh, Volcano, and Earth
|
||||
Badges a random HM move to enable. Extra Plus will additionally pick two random badges to enable a second HM move.
|
||||
A man in Cerulean City will reveal the moves enabled by each Badge."""
|
||||
"""Off will remove the requirement for badges to use HM moves. Extra will give the Marsh, Volcano, and Earth Badges
|
||||
a random HM move to enable. Extra Plus will additionally pick two random badges to enable a second HM move.
|
||||
You will only need one of the required badges to use the HM move."""
|
||||
display_name = "Badges Needed For HM Moves"
|
||||
default = 1
|
||||
option_on = 1
|
||||
|
||||
@@ -41,8 +41,7 @@ def create_regions(world: MultiWorld, player: int):
|
||||
create_region(world, player, "Route 2 East", locations_per_region),
|
||||
create_region(world, player, "Diglett's Cave", locations_per_region),
|
||||
create_region(world, player, "Route 22", locations_per_region),
|
||||
create_region(world, player, "Route 23 South", locations_per_region),
|
||||
create_region(world, player, "Route 23 North", locations_per_region),
|
||||
create_region(world, player, "Route 23", locations_per_region),
|
||||
create_region(world, player, "Viridian Forest", locations_per_region),
|
||||
create_region(world, player, "Pewter City", locations_per_region),
|
||||
create_region(world, player, "Pewter Gym", locations_per_region),
|
||||
@@ -150,15 +149,15 @@ def create_regions(world: MultiWorld, player: int):
|
||||
connect(world, player, "Menu", "Anywhere", one_way=True)
|
||||
connect(world, player, "Menu", "Pallet Town", one_way=True)
|
||||
connect(world, player, "Menu", "Fossil", lambda state: state.pokemon_rb_fossil_checks(
|
||||
state.world.second_fossil_check_condition[player].value, player), one_way=True)
|
||||
state.multiworld.second_fossil_check_condition[player].value, player), one_way=True)
|
||||
connect(world, player, "Pallet Town", "Route 1")
|
||||
connect(world, player, "Route 1", "Viridian City")
|
||||
connect(world, player, "Viridian City", "Route 22")
|
||||
connect(world, player, "Route 22", "Route 23 South",
|
||||
lambda state: state.pokemon_rb_has_badges(state.world.victory_road_condition[player].value, player))
|
||||
connect(world, player, "Route 23 South", "Route 23 North", lambda state: state.pokemon_rb_can_surf(player))
|
||||
connect(world, player, "Route 22", "Route 23",
|
||||
lambda state: state.pokemon_rb_has_badges(state.multiworld.victory_road_condition[player].value, player) and
|
||||
state.pokemon_rb_can_surf(player))
|
||||
connect(world, player, "Viridian City North", "Viridian Gym", lambda state:
|
||||
state.pokemon_rb_has_badges(state.world.viridian_gym_condition[player].value, player), one_way=True)
|
||||
state.pokemon_rb_has_badges(state.multiworld.viridian_gym_condition[player].value, player), one_way=True)
|
||||
connect(world, player, "Route 2", "Route 2 East", lambda state: state.pokemon_rb_can_cut(player))
|
||||
connect(world, player, "Route 2 East", "Diglett's Cave", lambda state: state.pokemon_rb_can_cut(player))
|
||||
connect(world, player, "Route 2", "Viridian City North")
|
||||
@@ -178,7 +177,7 @@ def create_regions(world: MultiWorld, player: int):
|
||||
connect(world, player, "Route 9", "Route 10 North")
|
||||
connect(world, player, "Route 10 North", "Rock Tunnel 1F", lambda state: state.pokemon_rb_can_flash(player))
|
||||
connect(world, player, "Route 10 North", "Power Plant", lambda state: state.pokemon_rb_can_surf(player) and
|
||||
(state.has("Plant Key", player) or not state.world.extra_key_items[player].value), one_way=True)
|
||||
(state.has("Plant Key", player) or not state.multiworld.extra_key_items[player].value), one_way=True)
|
||||
connect(world, player, "Rock Tunnel 1F", "Route 10 South", lambda state: state.pokemon_rb_can_flash(player))
|
||||
connect(world, player, "Rock Tunnel 1F", "Rock Tunnel B1F")
|
||||
connect(world, player, "Lavender Town", "Pokemon Tower 1F", one_way=True)
|
||||
@@ -205,7 +204,7 @@ def create_regions(world: MultiWorld, player: int):
|
||||
connect(world, player, "S.S. Anne 1F", "S.S. Anne B1F", one_way=True)
|
||||
connect(world, player, "Vermilion City", "Route 11")
|
||||
connect(world, player, "Vermilion City", "Diglett's Cave")
|
||||
connect(world, player, "Route 12 West", "Route 11 East", lambda state: state.pokemon_rb_can_strength(player) or not state.world.extra_strength_boulders[player].value)
|
||||
connect(world, player, "Route 12 West", "Route 11 East", lambda state: state.pokemon_rb_can_strength(player) or not state.multiworld.extra_strength_boulders[player].value)
|
||||
connect(world, player, "Route 12 North", "Route 12 South", lambda state: state.has("Poke Flute", player) or state.pokemon_rb_can_surf( player))
|
||||
connect(world, player, "Route 12 West", "Route 12 North", lambda state: state.has("Poke Flute", player))
|
||||
connect(world, player, "Route 12 West", "Route 12 South", lambda state: state.has("Poke Flute", player))
|
||||
@@ -227,25 +226,25 @@ def create_regions(world: MultiWorld, player: int):
|
||||
connect(world, player, "Fuchsia City", "Fuchsia Gym", one_way=True)
|
||||
connect(world, player, "Fuchsia City", "Route 18")
|
||||
connect(world, player, "Fuchsia City", "Safari Zone Gate", one_way=True)
|
||||
connect(world, player, "Safari Zone Gate", "Safari Zone Center", lambda state: state.has("Safari Pass", player) or not state.world.extra_key_items[player].value, one_way=True)
|
||||
connect(world, player, "Safari Zone Gate", "Safari Zone Center", lambda state: state.has("Safari Pass", player) or not state.multiworld.extra_key_items[player].value, one_way=True)
|
||||
connect(world, player, "Safari Zone Center", "Safari Zone East", one_way=True)
|
||||
connect(world, player, "Safari Zone Center", "Safari Zone West", one_way=True)
|
||||
connect(world, player, "Safari Zone Center", "Safari Zone North", one_way=True)
|
||||
connect(world, player, "Fuchsia City", "Route 15")
|
||||
connect(world, player, "Route 15", "Route 14")
|
||||
connect(world, player, "Route 14", "Route 13")
|
||||
connect(world, player, "Route 13", "Route 12 South", lambda state: state.pokemon_rb_can_strength(player) or state.pokemon_rb_can_surf(player) or not state.world.extra_strength_boulders[player].value)
|
||||
connect(world, player, "Route 13", "Route 12 South", lambda state: state.pokemon_rb_can_strength(player) or state.pokemon_rb_can_surf(player) or not state.multiworld.extra_strength_boulders[player].value)
|
||||
connect(world, player, "Fuchsia City", "Route 19", lambda state: state.pokemon_rb_can_surf(player))
|
||||
connect(world, player, "Route 20 East", "Route 19")
|
||||
connect(world, player, "Route 20 West", "Cinnabar Island", lambda state: state.pokemon_rb_can_surf(player))
|
||||
connect(world, player, "Route 20 West", "Seafoam Islands 1F")
|
||||
connect(world, player, "Route 20 East", "Seafoam Islands 1F", one_way=True)
|
||||
connect(world, player, "Seafoam Islands 1F", "Route 20 East", lambda state: state.pokemon_rb_can_strength(player), one_way=True)
|
||||
connect(world, player, "Viridian City", "Viridian City North", lambda state: state.has("Oak's Parcel", player) or state.world.old_man[player].value == 2 or state.pokemon_rb_can_cut(player))
|
||||
connect(world, player, "Viridian City", "Viridian City North", lambda state: state.has("Oak's Parcel", player) or state.multiworld.old_man[player].value == 2 or state.pokemon_rb_can_cut(player))
|
||||
connect(world, player, "Route 3", "Mt Moon 1F", one_way=True)
|
||||
connect(world, player, "Route 11", "Route 11 East", lambda state: state.pokemon_rb_can_strength(player))
|
||||
connect(world, player, "Cinnabar Island", "Cinnabar Gym", lambda state: state.has("Secret Key", player), one_way=True)
|
||||
connect(world, player, "Cinnabar Island", "Pokemon Mansion 1F", lambda state: state.has("Mansion Key", player) or not state.world.extra_key_items[player].value, one_way=True)
|
||||
connect(world, player, "Cinnabar Island", "Pokemon Mansion 1F", lambda state: state.has("Mansion Key", player) or not state.multiworld.extra_key_items[player].value, one_way=True)
|
||||
connect(world, player, "Seafoam Islands 1F", "Seafoam Islands B1F", one_way=True)
|
||||
connect(world, player, "Seafoam Islands B1F", "Seafoam Islands B2F", one_way=True)
|
||||
connect(world, player, "Seafoam Islands B2F", "Seafoam Islands B3F", one_way=True)
|
||||
@@ -263,19 +262,19 @@ def create_regions(world: MultiWorld, player: int):
|
||||
connect(world, player, "Silph Co 8F", "Silph Co 9F", one_way=True)
|
||||
connect(world, player, "Silph Co 9F", "Silph Co 10F", one_way=True)
|
||||
connect(world, player, "Silph Co 10F", "Silph Co 11F", one_way=True)
|
||||
connect(world, player, "Celadon City", "Rocket Hideout B1F", lambda state: state.has("Hideout Key", player) or not state.world.extra_key_items[player].value, one_way=True)
|
||||
connect(world, player, "Celadon City", "Rocket Hideout B1F", lambda state: state.has("Hideout Key", player) or not state.multiworld.extra_key_items[player].value, one_way=True)
|
||||
connect(world, player, "Rocket Hideout B1F", "Rocket Hideout B2F", one_way=True)
|
||||
connect(world, player, "Rocket Hideout B2F", "Rocket Hideout B3F", one_way=True)
|
||||
connect(world, player, "Rocket Hideout B3F", "Rocket Hideout B4F", one_way=True)
|
||||
connect(world, player, "Pokemon Mansion 1F", "Pokemon Mansion 2F", one_way=True)
|
||||
connect(world, player, "Pokemon Mansion 2F", "Pokemon Mansion 3F", one_way=True)
|
||||
connect(world, player, "Pokemon Mansion 1F", "Pokemon Mansion B1F", one_way=True)
|
||||
connect(world, player, "Route 23 North", "Victory Road 1F", lambda state: state.pokemon_rb_can_strength(player), one_way=True)
|
||||
connect(world, player, "Route 23", "Victory Road 1F", lambda state: state.pokemon_rb_can_strength(player), one_way=True)
|
||||
connect(world, player, "Victory Road 1F", "Victory Road 2F", one_way=True)
|
||||
connect(world, player, "Victory Road 2F", "Victory Road 3F", one_way=True)
|
||||
connect(world, player, "Victory Road 2F", "Indigo Plateau", lambda state: state.pokemon_rb_has_badges(state.world.elite_four_condition[player], player), one_way=True)
|
||||
connect(world, player, "Victory Road 2F", "Indigo Plateau", lambda state: state.pokemon_rb_has_badges(state.multiworld.elite_four_condition[player], player), one_way=True)
|
||||
connect(world, player, "Cerulean City", "Cerulean Cave 1F", lambda state:
|
||||
state.pokemon_rb_cerulean_cave(state.world.cerulean_cave_condition[player].value + (state.world.extra_key_items[player].value * 4), player) and
|
||||
state.pokemon_rb_cerulean_cave(state.multiworld.cerulean_cave_condition[player].value + (state.multiworld.extra_key_items[player].value * 4), player) and
|
||||
state.pokemon_rb_can_surf(player), one_way=True)
|
||||
connect(world, player, "Cerulean Cave 1F", "Cerulean Cave 2F", one_way=True)
|
||||
connect(world, player, "Cerulean Cave 1F", "Cerulean Cave B1F", lambda state: state.pokemon_rb_can_surf(player), one_way=True)
|
||||
|
||||
@@ -43,7 +43,7 @@ def get_encounter_slots(self):
|
||||
|
||||
for location in encounter_slots:
|
||||
if isinstance(location.original_item, list):
|
||||
location.original_item = location.original_item[not self.world.game_version[self.player].value]
|
||||
location.original_item = location.original_item[not self.multiworld.game_version[self.player].value]
|
||||
return encounter_slots
|
||||
|
||||
|
||||
@@ -64,19 +64,19 @@ def randomize_pokemon(self, mon, mons_list, randomize_type):
|
||||
if randomize_type == 3:
|
||||
stat_base = get_base_stat_total(mon)
|
||||
type_mons.sort(key=lambda mon: abs(get_base_stat_total(mon) - stat_base))
|
||||
mon = type_mons[round(self.world.random.triangular(0, len(type_mons) - 1, 0))]
|
||||
mon = type_mons[round(self.multiworld.random.triangular(0, len(type_mons) - 1, 0))]
|
||||
if randomize_type == 2:
|
||||
stat_base = get_base_stat_total(mon)
|
||||
mons_list.sort(key=lambda mon: abs(get_base_stat_total(mon) - stat_base))
|
||||
mon = mons_list[round(self.world.random.triangular(0, 50, 0))]
|
||||
mon = mons_list[round(self.multiworld.random.triangular(0, 50, 0))]
|
||||
elif randomize_type == 4:
|
||||
mon = self.world.random.choice(mons_list)
|
||||
mon = self.multiworld.random.choice(mons_list)
|
||||
return mon
|
||||
|
||||
|
||||
def process_trainer_data(self, data):
|
||||
mons_list = [pokemon for pokemon in poke_data.pokemon_data.keys() if pokemon not in poke_data.legendary_pokemon
|
||||
or self.world.trainer_legendaries[self.player].value]
|
||||
or self.multiworld.trainer_legendaries[self.player].value]
|
||||
address = rom_addresses["Trainer_Data"]
|
||||
while address < rom_addresses["Trainer_Data_End"]:
|
||||
if data[address] == 255:
|
||||
@@ -93,14 +93,14 @@ def process_trainer_data(self, data):
|
||||
for i in range(1, 4):
|
||||
for l in ["A", "B", "C", "D", "E", "F", "G", "H"]:
|
||||
if rom_addresses[f"Rival_Starter{i}_{l}"] == address:
|
||||
mon = " ".join(self.world.get_location(f"Pallet Town - Starter {i}", self.player).item.name.split()[1:])
|
||||
mon = " ".join(self.multiworld.get_location(f"Pallet Town - Starter {i}", self.player).item.name.split()[1:])
|
||||
if l in ["D", "E", "F", "G", "H"] and mon in poke_data.evolves_to:
|
||||
mon = poke_data.evolves_to[mon]
|
||||
if l in ["F", "G", "H"] and mon in poke_data.evolves_to:
|
||||
mon = poke_data.evolves_to[mon]
|
||||
if mon is None and self.world.randomize_trainer_parties[self.player].value:
|
||||
if mon is None and self.multiworld.randomize_trainer_parties[self.player].value:
|
||||
mon = poke_data.id_to_mon[data[address]]
|
||||
mon = randomize_pokemon(self, mon, mons_list, self.world.randomize_trainer_parties[self.player].value)
|
||||
mon = randomize_pokemon(self, mon, mons_list, self.multiworld.randomize_trainer_parties[self.player].value)
|
||||
if mon is not None:
|
||||
data[address] = poke_data.pokemon_data[mon]["id"]
|
||||
|
||||
@@ -114,22 +114,22 @@ def process_static_pokemon(self):
|
||||
|
||||
tower_6F_mons = set()
|
||||
for i in range(1, 11):
|
||||
tower_6F_mons.add(self.world.get_location(f"Pokemon Tower 6F - Wild Pokemon - {i}", self.player).item.name)
|
||||
tower_6F_mons.add(self.multiworld.get_location(f"Pokemon Tower 6F - Wild Pokemon - {i}", self.player).item.name)
|
||||
|
||||
mons_list = [pokemon for pokemon in poke_data.first_stage_pokemon if pokemon not in poke_data.legendary_pokemon
|
||||
or self.world.randomize_legendary_pokemon[self.player].value == 3]
|
||||
if self.world.randomize_legendary_pokemon[self.player].value == 0:
|
||||
or self.multiworld.randomize_legendary_pokemon[self.player].value == 3]
|
||||
if self.multiworld.randomize_legendary_pokemon[self.player].value == 0:
|
||||
for slot in legendary_slots:
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
location.place_locked_item(self.create_item("Missable " + slot.original_item))
|
||||
elif self.world.randomize_legendary_pokemon[self.player].value == 1:
|
||||
self.world.random.shuffle(legendary_mons)
|
||||
elif self.multiworld.randomize_legendary_pokemon[self.player].value == 1:
|
||||
self.multiworld.random.shuffle(legendary_mons)
|
||||
for slot in legendary_slots:
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
location.place_locked_item(self.create_item("Missable " + legendary_mons.pop()))
|
||||
elif self.world.randomize_legendary_pokemon[self.player].value == 2:
|
||||
elif self.multiworld.randomize_legendary_pokemon[self.player].value == 2:
|
||||
static_slots = static_slots + legendary_slots
|
||||
self.world.random.shuffle(static_slots)
|
||||
self.multiworld.random.shuffle(static_slots)
|
||||
static_slots.sort(key=lambda s: 0 if s.name == "Pokemon Tower 6F - Restless Soul" else 1)
|
||||
while legendary_slots:
|
||||
swap_slot = legendary_slots.pop()
|
||||
@@ -137,15 +137,15 @@ def process_static_pokemon(self):
|
||||
slot_type = slot.type.split()[0]
|
||||
if slot_type == "Legendary":
|
||||
slot_type = "Missable"
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
location.place_locked_item(self.create_item(slot_type + " " + swap_slot.original_item))
|
||||
swap_slot.original_item = slot.original_item
|
||||
elif self.world.randomize_legendary_pokemon[self.player].value == 3:
|
||||
elif self.multiworld.randomize_legendary_pokemon[self.player].value == 3:
|
||||
static_slots = static_slots + legendary_slots
|
||||
|
||||
for slot in static_slots:
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
randomize_type = self.world.randomize_static_pokemon[self.player].value
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
randomize_type = self.multiworld.randomize_static_pokemon[self.player].value
|
||||
slot_type = slot.type.split()[0]
|
||||
if slot_type == "Legendary":
|
||||
slot_type = "Missable"
|
||||
@@ -160,8 +160,8 @@ def process_static_pokemon(self):
|
||||
location.place_locked_item(mon)
|
||||
|
||||
for slot in starter_slots:
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
randomize_type = self.world.randomize_starter_pokemon[self.player].value
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
randomize_type = self.multiworld.randomize_starter_pokemon[self.player].value
|
||||
slot_type = "Missable"
|
||||
if not randomize_type:
|
||||
location.place_locked_item(self.create_item(slot_type + " " + slot.original_item))
|
||||
@@ -175,21 +175,21 @@ def process_wild_pokemon(self):
|
||||
encounter_slots = get_encounter_slots(self)
|
||||
|
||||
placed_mons = {pokemon: 0 for pokemon in poke_data.pokemon_data.keys()}
|
||||
if self.world.randomize_wild_pokemon[self.player].value:
|
||||
if self.multiworld.randomize_wild_pokemon[self.player].value:
|
||||
mons_list = [pokemon for pokemon in poke_data.pokemon_data.keys() if pokemon not in poke_data.legendary_pokemon
|
||||
or self.world.randomize_legendary_pokemon[self.player].value == 3]
|
||||
self.world.random.shuffle(encounter_slots)
|
||||
or self.multiworld.randomize_legendary_pokemon[self.player].value == 3]
|
||||
self.multiworld.random.shuffle(encounter_slots)
|
||||
locations = []
|
||||
for slot in encounter_slots:
|
||||
mon = randomize_pokemon(self, slot.original_item, mons_list, self.world.randomize_wild_pokemon[self.player].value)
|
||||
mon = randomize_pokemon(self, slot.original_item, mons_list, self.multiworld.randomize_wild_pokemon[self.player].value)
|
||||
# if static Pokemon are not randomized, we make sure nothing on Pokemon Tower 6F is a Marowak
|
||||
# if static Pokemon are randomized we deal with that during static encounter randomization
|
||||
while (self.world.randomize_static_pokemon[self.player].value == 0 and mon == "Marowak"
|
||||
while (self.multiworld.randomize_static_pokemon[self.player].value == 0 and mon == "Marowak"
|
||||
and "Pokemon Tower 6F" in slot.name):
|
||||
# to account for the possibility that only one ground type Pokemon exists, match only stats for this fix
|
||||
mon = randomize_pokemon(self, slot.original_item, mons_list, 2)
|
||||
placed_mons[mon] += 1
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
location.item = self.create_item(mon)
|
||||
location.event = True
|
||||
location.locked = True
|
||||
@@ -198,33 +198,33 @@ def process_wild_pokemon(self):
|
||||
|
||||
mons_to_add = []
|
||||
remaining_pokemon = [pokemon for pokemon in poke_data.pokemon_data.keys() if placed_mons[pokemon] == 0 and
|
||||
(pokemon not in poke_data.legendary_pokemon or self.world.randomize_legendary_pokemon[self.player].value == 3)]
|
||||
if self.world.catch_em_all[self.player].value == 1:
|
||||
(pokemon not in poke_data.legendary_pokemon or self.multiworld.randomize_legendary_pokemon[self.player].value == 3)]
|
||||
if self.multiworld.catch_em_all[self.player].value == 1:
|
||||
mons_to_add = [pokemon for pokemon in poke_data.first_stage_pokemon if placed_mons[pokemon] == 0 and
|
||||
(pokemon not in poke_data.legendary_pokemon or self.world.randomize_legendary_pokemon[self.player].value == 3)]
|
||||
elif self.world.catch_em_all[self.player].value == 2:
|
||||
(pokemon not in poke_data.legendary_pokemon or self.multiworld.randomize_legendary_pokemon[self.player].value == 3)]
|
||||
elif self.multiworld.catch_em_all[self.player].value == 2:
|
||||
mons_to_add = remaining_pokemon.copy()
|
||||
logic_needed_mons = max(self.world.oaks_aide_rt_2[self.player].value,
|
||||
self.world.oaks_aide_rt_11[self.player].value,
|
||||
self.world.oaks_aide_rt_15[self.player].value)
|
||||
if self.world.accessibility[self.player] == "minimal":
|
||||
logic_needed_mons = max(self.multiworld.oaks_aide_rt_2[self.player].value,
|
||||
self.multiworld.oaks_aide_rt_11[self.player].value,
|
||||
self.multiworld.oaks_aide_rt_15[self.player].value)
|
||||
if self.multiworld.accessibility[self.player] == "minimal":
|
||||
logic_needed_mons = 0
|
||||
|
||||
self.world.random.shuffle(remaining_pokemon)
|
||||
self.multiworld.random.shuffle(remaining_pokemon)
|
||||
while (len([pokemon for pokemon in placed_mons if placed_mons[pokemon] > 0])
|
||||
+ len(mons_to_add) < logic_needed_mons):
|
||||
mons_to_add.append(remaining_pokemon.pop())
|
||||
for mon in mons_to_add:
|
||||
stat_base = get_base_stat_total(mon)
|
||||
candidate_locations = get_encounter_slots(self)
|
||||
if self.world.randomize_wild_pokemon[self.player].value in [1, 3]:
|
||||
if self.multiworld.randomize_wild_pokemon[self.player].value in [1, 3]:
|
||||
candidate_locations = [slot for slot in candidate_locations if any([poke_data.pokemon_data[slot.original_item][
|
||||
"type1"] in [self.local_poke_data[mon]["type1"], self.local_poke_data[mon]["type2"]],
|
||||
poke_data.pokemon_data[slot.original_item]["type2"] in [self.local_poke_data[mon]["type1"],
|
||||
self.local_poke_data[mon]["type2"]]])]
|
||||
if not candidate_locations:
|
||||
candidate_locations = location_data
|
||||
candidate_locations = [self.world.get_location(location.name, self.player) for location in candidate_locations]
|
||||
candidate_locations = [self.multiworld.get_location(location.name, self.player) for location in candidate_locations]
|
||||
candidate_locations.sort(key=lambda slot: abs(get_base_stat_total(slot.item.name) - stat_base))
|
||||
for location in candidate_locations:
|
||||
if placed_mons[location.item.name] > 1 or location.item.name not in poke_data.first_stage_pokemon:
|
||||
@@ -236,7 +236,7 @@ def process_wild_pokemon(self):
|
||||
|
||||
else:
|
||||
for slot in encounter_slots:
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
location.item = self.create_item(slot.original_item)
|
||||
location.event = True
|
||||
location.locked = True
|
||||
@@ -250,19 +250,19 @@ def process_pokemon_data(self):
|
||||
learnsets = deepcopy(poke_data.learnsets)
|
||||
|
||||
for mon, mon_data in local_poke_data.items():
|
||||
if self.world.randomize_pokemon_stats[self.player].value == 1:
|
||||
if self.multiworld.randomize_pokemon_stats[self.player].value == 1:
|
||||
stats = [mon_data["hp"], mon_data["atk"], mon_data["def"], mon_data["spd"], mon_data["spc"]]
|
||||
self.world.random.shuffle(stats)
|
||||
self.multiworld.random.shuffle(stats)
|
||||
mon_data["hp"] = stats[0]
|
||||
mon_data["atk"] = stats[1]
|
||||
mon_data["def"] = stats[2]
|
||||
mon_data["spd"] = stats[3]
|
||||
mon_data["spc"] = stats[4]
|
||||
elif self.world.randomize_pokemon_stats[self.player].value == 2:
|
||||
elif self.multiworld.randomize_pokemon_stats[self.player].value == 2:
|
||||
old_stats = mon_data["hp"] + mon_data["atk"] + mon_data["def"] + mon_data["spd"] + mon_data["spc"] - 5
|
||||
stats = [1, 1, 1, 1, 1]
|
||||
while old_stats > 0:
|
||||
stat = self.world.random.randint(0, 4)
|
||||
stat = self.multiworld.random.randint(0, 4)
|
||||
if stats[stat] < 255:
|
||||
old_stats -= 1
|
||||
stats[stat] += 1
|
||||
@@ -271,30 +271,30 @@ def process_pokemon_data(self):
|
||||
mon_data["def"] = stats[2]
|
||||
mon_data["spd"] = stats[3]
|
||||
mon_data["spc"] = stats[4]
|
||||
if self.world.randomize_pokemon_types[self.player].value:
|
||||
if self.world.randomize_pokemon_types[self.player].value == 1 and mon in poke_data.evolves_from:
|
||||
if self.multiworld.randomize_pokemon_types[self.player].value:
|
||||
if self.multiworld.randomize_pokemon_types[self.player].value == 1 and mon in poke_data.evolves_from:
|
||||
type1 = local_poke_data[poke_data.evolves_from[mon]]["type1"]
|
||||
type2 = local_poke_data[poke_data.evolves_from[mon]]["type2"]
|
||||
if type1 == type2:
|
||||
if self.world.secondary_type_chance[self.player].value == -1:
|
||||
if self.multiworld.secondary_type_chance[self.player].value == -1:
|
||||
if mon_data["type1"] != mon_data["type2"]:
|
||||
while type2 == type1:
|
||||
type2 = self.world.random.choice(list(poke_data.type_names.values()))
|
||||
elif self.world.random.randint(1, 100) <= self.world.secondary_type_chance[self.player].value:
|
||||
type2 = self.world.random.choice(list(poke_data.type_names.values()))
|
||||
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
|
||||
elif self.multiworld.random.randint(1, 100) <= self.multiworld.secondary_type_chance[self.player].value:
|
||||
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
|
||||
else:
|
||||
type1 = self.world.random.choice(list(poke_data.type_names.values()))
|
||||
type1 = self.multiworld.random.choice(list(poke_data.type_names.values()))
|
||||
type2 = type1
|
||||
if ((self.world.secondary_type_chance[self.player].value == -1 and mon_data["type1"]
|
||||
!= mon_data["type2"]) or self.world.random.randint(1, 100)
|
||||
<= self.world.secondary_type_chance[self.player].value):
|
||||
if ((self.multiworld.secondary_type_chance[self.player].value == -1 and mon_data["type1"]
|
||||
!= mon_data["type2"]) or self.multiworld.random.randint(1, 100)
|
||||
<= self.multiworld.secondary_type_chance[self.player].value):
|
||||
while type2 == type1:
|
||||
type2 = self.world.random.choice(list(poke_data.type_names.values()))
|
||||
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
|
||||
|
||||
mon_data["type1"] = type1
|
||||
mon_data["type2"] = type2
|
||||
if self.world.randomize_pokemon_movesets[self.player].value:
|
||||
if self.world.randomize_pokemon_movesets[self.player].value == 1:
|
||||
if self.multiworld.randomize_pokemon_movesets[self.player].value:
|
||||
if self.multiworld.randomize_pokemon_movesets[self.player].value == 1:
|
||||
if mon_data["type1"] == "Normal" and mon_data["type2"] == "Normal":
|
||||
chances = [[75, "Normal"]]
|
||||
elif mon_data["type1"] == "Normal" or mon_data["type2"] == "Normal":
|
||||
@@ -312,30 +312,30 @@ def process_pokemon_data(self):
|
||||
moves = list(poke_data.moves.keys())
|
||||
for move in ["No Move"] + poke_data.hm_moves:
|
||||
moves.remove(move)
|
||||
mon_data["start move 1"] = get_move(moves, chances, self.world.random, True)
|
||||
mon_data["start move 1"] = get_move(moves, chances, self.multiworld.random, True)
|
||||
for i in range(2, 5):
|
||||
if mon_data[f"start move {i}"] != "No Move" or self.world.start_with_four_moves[
|
||||
if mon_data[f"start move {i}"] != "No Move" or self.multiworld.start_with_four_moves[
|
||||
self.player].value == 1:
|
||||
mon_data[f"start move {i}"] = get_move(moves, chances, self.world.random)
|
||||
mon_data[f"start move {i}"] = get_move(moves, chances, self.multiworld.random)
|
||||
if mon in learnsets:
|
||||
for move_num in range(0, len(learnsets[mon])):
|
||||
learnsets[mon][move_num] = get_move(moves, chances, self.world.random)
|
||||
if self.world.randomize_pokemon_catch_rates[self.player].value:
|
||||
mon_data["catch rate"] = self.world.random.randint(self.world.minimum_catch_rate[self.player], 255)
|
||||
learnsets[mon][move_num] = get_move(moves, chances, self.multiworld.random)
|
||||
if self.multiworld.randomize_pokemon_catch_rates[self.player].value:
|
||||
mon_data["catch rate"] = self.multiworld.random.randint(self.multiworld.minimum_catch_rate[self.player], 255)
|
||||
else:
|
||||
mon_data["catch rate"] = max(self.world.minimum_catch_rate[self.player], mon_data["catch rate"])
|
||||
mon_data["catch rate"] = max(self.multiworld.minimum_catch_rate[self.player], mon_data["catch rate"])
|
||||
|
||||
if mon in poke_data.evolves_from.keys() and mon_data["type1"] == local_poke_data[poke_data.evolves_from[mon]]["type1"] and mon_data["type2"] == local_poke_data[poke_data.evolves_from[mon]]["type2"]:
|
||||
mon_data["tms"] = local_poke_data[poke_data.evolves_from[mon]]["tms"]
|
||||
elif mon != "Mew":
|
||||
tms_hms = poke_data.tm_moves + poke_data.hm_moves
|
||||
for flag, tm_move in enumerate(tms_hms):
|
||||
if (flag < 50 and self.world.tm_compatibility[self.player].value == 1) or (flag >= 50 and self.world.hm_compatibility[self.player].value == 1):
|
||||
if (flag < 50 and self.multiworld.tm_compatibility[self.player].value == 1) or (flag >= 50 and self.multiworld.hm_compatibility[self.player].value == 1):
|
||||
type_match = poke_data.moves[tm_move]["type"] in [mon_data["type1"], mon_data["type2"]]
|
||||
bit = int(self.world.random.randint(1, 100) < [[90, 50, 25], [100, 75, 25]][flag >= 50][0 if type_match else 1 if poke_data.moves[tm_move]["type"] == "Normal" else 2])
|
||||
elif (flag < 50 and self.world.tm_compatibility[self.player].value == 2) or (flag >= 50 and self.world.hm_compatibility[self.player].value == 2):
|
||||
bit = [0, 1][self.world.random.randint(0, 1)]
|
||||
elif (flag < 50 and self.world.tm_compatibility[self.player].value == 3) or (flag >= 50 and self.world.hm_compatibility[self.player].value == 3):
|
||||
bit = int(self.multiworld.random.randint(1, 100) < [[90, 50, 25], [100, 75, 25]][flag >= 50][0 if type_match else 1 if poke_data.moves[tm_move]["type"] == "Normal" else 2])
|
||||
elif (flag < 50 and self.multiworld.tm_compatibility[self.player].value == 2) or (flag >= 50 and self.multiworld.hm_compatibility[self.player].value == 2):
|
||||
bit = [0, 1][self.multiworld.random.randint(0, 1)]
|
||||
elif (flag < 50 and self.multiworld.tm_compatibility[self.player].value == 3) or (flag >= 50 and self.multiworld.hm_compatibility[self.player].value == 3):
|
||||
bit = 1
|
||||
else:
|
||||
continue
|
||||
@@ -350,14 +350,14 @@ def process_pokemon_data(self):
|
||||
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
random = self.world.slot_seeds[self.player]
|
||||
game_version = self.world.game_version[self.player].current_key
|
||||
random = self.multiworld.slot_seeds[self.player]
|
||||
game_version = self.multiworld.game_version[self.player].current_key
|
||||
data = bytearray(get_base_rom_bytes(game_version))
|
||||
|
||||
basemd5 = hashlib.md5()
|
||||
basemd5.update(data)
|
||||
|
||||
for location in self.world.get_locations():
|
||||
for location in self.multiworld.get_locations():
|
||||
if location.player != self.player or location.rom_address is None:
|
||||
continue
|
||||
if location.item and location.item.player == self.player:
|
||||
@@ -377,42 +377,44 @@ def generate_output(self, output_directory: str):
|
||||
data[location.rom_address] = 0x2C # AP Item
|
||||
data[rom_addresses['Fly_Location']] = self.fly_map_code
|
||||
|
||||
if self.world.tea[self.player].value:
|
||||
if self.multiworld.tea[self.player].value:
|
||||
data[rom_addresses["Option_Tea"]] = 1
|
||||
data[rom_addresses["Guard_Drink_List"]] = 0x54
|
||||
data[rom_addresses["Guard_Drink_List"] + 1] = 0
|
||||
data[rom_addresses["Guard_Drink_List"] + 2] = 0
|
||||
|
||||
if self.world.extra_key_items[self.player].value:
|
||||
if self.multiworld.extra_key_items[self.player].value:
|
||||
data[rom_addresses['Options']] |= 4
|
||||
data[rom_addresses["Option_Blind_Trainers"]] = round(self.world.blind_trainers[self.player].value * 2.55)
|
||||
data[rom_addresses['Option_Cerulean_Cave_Condition']] = self.world.cerulean_cave_condition[self.player].value
|
||||
data[rom_addresses['Option_Encounter_Minimum_Steps']] = self.world.minimum_steps_between_encounters[self.player].value
|
||||
data[rom_addresses['Option_Victory_Road_Badges']] = self.world.victory_road_condition[self.player].value
|
||||
data[rom_addresses['Option_Pokemon_League_Badges']] = self.world.elite_four_condition[self.player].value
|
||||
data[rom_addresses['Option_Viridian_Gym_Badges']] = self.world.viridian_gym_condition[self.player].value
|
||||
data[rom_addresses['Option_EXP_Modifier']] = self.world.exp_modifier[self.player].value
|
||||
if not self.world.require_item_finder[self.player].value:
|
||||
data[rom_addresses["Option_Blind_Trainers"]] = round(self.multiworld.blind_trainers[self.player].value * 2.55)
|
||||
data[rom_addresses['Option_Cerulean_Cave_Condition']] = self.multiworld.cerulean_cave_condition[self.player].value
|
||||
data[rom_addresses['Option_Encounter_Minimum_Steps']] = self.multiworld.minimum_steps_between_encounters[self.player].value
|
||||
data[rom_addresses['Option_Victory_Road_Badges']] = self.multiworld.victory_road_condition[self.player].value
|
||||
data[rom_addresses['Option_Pokemon_League_Badges']] = self.multiworld.elite_four_condition[self.player].value
|
||||
data[rom_addresses['Option_Viridian_Gym_Badges']] = self.multiworld.viridian_gym_condition[self.player].value
|
||||
data[rom_addresses['Option_EXP_Modifier']] = self.multiworld.exp_modifier[self.player].value
|
||||
if not self.multiworld.require_item_finder[self.player].value:
|
||||
data[rom_addresses['Option_Itemfinder']] = 0
|
||||
if self.world.extra_strength_boulders[self.player].value:
|
||||
if self.multiworld.extra_strength_boulders[self.player].value:
|
||||
for i in range(0, 3):
|
||||
data[rom_addresses['Option_Boulders'] + (i * 3)] = 0x15
|
||||
if self.world.extra_key_items[self.player].value:
|
||||
if self.multiworld.extra_key_items[self.player].value:
|
||||
for i in range(0, 4):
|
||||
data[rom_addresses['Option_Rock_Tunnel_Extra_Items'] + (i * 3)] = 0x15
|
||||
if self.world.old_man[self.player].value == 2:
|
||||
if self.multiworld.old_man[self.player].value == 2:
|
||||
data[rom_addresses['Option_Old_Man']] = 0x11
|
||||
data[rom_addresses['Option_Old_Man_Lying']] = 0x15
|
||||
money = str(self.world.starting_money[self.player].value)
|
||||
money = str(self.multiworld.starting_money[self.player].value)
|
||||
while len(money) < 6:
|
||||
money = "0" + money
|
||||
data[rom_addresses["Starting_Money_High"]] = int(money[:2], 16)
|
||||
data[rom_addresses["Starting_Money_Middle"]] = int(money[2:4], 16)
|
||||
data[rom_addresses["Starting_Money_Low"]] = int(money[4:], 16)
|
||||
data[rom_addresses["Text_Badges_Needed_Viridian_Gym"]] = encode_text(
|
||||
str(self.multiworld.viridian_gym_condition[self.player].value))[0]
|
||||
data[rom_addresses["Text_Badges_Needed"]] = encode_text(
|
||||
str(max(self.world.victory_road_condition[self.player].value,
|
||||
self.world.elite_four_condition[self.player].value)))[0]
|
||||
if self.world.badges_needed_for_hm_moves[self.player].value == 0:
|
||||
str(max(self.multiworld.victory_road_condition[self.player].value,
|
||||
self.multiworld.elite_four_condition[self.player].value)))[0]
|
||||
if self.multiworld.badges_needed_for_hm_moves[self.player].value == 0:
|
||||
for hm_move in poke_data.hm_moves:
|
||||
write_bytes(data, bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
|
||||
rom_addresses["HM_" + hm_move + "_Badge_a"])
|
||||
@@ -437,7 +439,7 @@ def generate_output(self, output_directory: str):
|
||||
write_bytes(data, encode_text("Nothing"), rom_addresses["Badge_Text_" + badge.replace(" ", "_")])
|
||||
|
||||
chart = deepcopy(poke_data.type_chart)
|
||||
if self.world.randomize_type_matchup_types[self.player].value == 1:
|
||||
if self.multiworld.randomize_type_matchup_types[self.player].value == 1:
|
||||
attacking_types = []
|
||||
defending_types = []
|
||||
for matchup in chart:
|
||||
@@ -458,7 +460,7 @@ def generate_output(self, output_directory: str):
|
||||
for matchup, chart_row in zip(matchups, chart):
|
||||
chart_row[0] = matchup[0]
|
||||
chart_row[1] = matchup[1]
|
||||
elif self.world.randomize_type_matchup_types[self.player].value == 2:
|
||||
elif self.multiworld.randomize_type_matchup_types[self.player].value == 2:
|
||||
used_matchups = []
|
||||
for matchup in chart:
|
||||
matchup[0] = random.choice(list(poke_data.type_names.values()))
|
||||
@@ -467,17 +469,17 @@ def generate_output(self, output_directory: str):
|
||||
matchup[0] = random.choice(list(poke_data.type_names.values()))
|
||||
matchup[1] = random.choice(list(poke_data.type_names.values()))
|
||||
used_matchups.append([matchup[0], matchup[1]])
|
||||
if self.world.randomize_type_matchup_type_effectiveness[self.player].value == 1:
|
||||
if self.multiworld.randomize_type_matchup_type_effectiveness[self.player].value == 1:
|
||||
effectiveness_list = []
|
||||
for matchup in chart:
|
||||
effectiveness_list.append(matchup[2])
|
||||
random.shuffle(effectiveness_list)
|
||||
for (matchup, effectiveness) in zip(chart, effectiveness_list):
|
||||
matchup[2] = effectiveness
|
||||
elif self.world.randomize_type_matchup_type_effectiveness[self.player].value == 2:
|
||||
elif self.multiworld.randomize_type_matchup_type_effectiveness[self.player].value == 2:
|
||||
for matchup in chart:
|
||||
matchup[2] = random.choice([0] + ([5, 20] * 5))
|
||||
elif self.world.randomize_type_matchup_type_effectiveness[self.player].value == 3:
|
||||
elif self.multiworld.randomize_type_matchup_type_effectiveness[self.player].value == 3:
|
||||
for matchup in chart:
|
||||
matchup[2] = random.choice([i for i in range(0, 21) if i != 10])
|
||||
type_loc = rom_addresses["Type_Chart"]
|
||||
@@ -492,7 +494,7 @@ def generate_output(self, output_directory: str):
|
||||
# to the way effectiveness messages are generated.
|
||||
self.type_chart = sorted(chart, key=lambda matchup: 0 - matchup[2])
|
||||
|
||||
if self.world.normalize_encounter_chances[self.player].value:
|
||||
if self.multiworld.normalize_encounter_chances[self.player].value:
|
||||
chances = [25, 51, 77, 103, 129, 155, 180, 205, 230, 255]
|
||||
for i, chance in enumerate(chances):
|
||||
data[rom_addresses['Encounter_Chances'] + (i * 2)] = chance
|
||||
@@ -520,14 +522,14 @@ def generate_output(self, output_directory: str):
|
||||
for i, move in enumerate(self.learnsets[mon]):
|
||||
data[(address + 1) + i * 2] = poke_data.moves[move]["id"]
|
||||
|
||||
data[rom_addresses["Option_Aide_Rt2"]] = self.world.oaks_aide_rt_2[self.player]
|
||||
data[rom_addresses["Option_Aide_Rt11"]] = self.world.oaks_aide_rt_11[self.player]
|
||||
data[rom_addresses["Option_Aide_Rt15"]] = self.world.oaks_aide_rt_15[self.player]
|
||||
data[rom_addresses["Option_Aide_Rt2"]] = self.multiworld.oaks_aide_rt_2[self.player]
|
||||
data[rom_addresses["Option_Aide_Rt11"]] = self.multiworld.oaks_aide_rt_11[self.player]
|
||||
data[rom_addresses["Option_Aide_Rt15"]] = self.multiworld.oaks_aide_rt_15[self.player]
|
||||
|
||||
if self.world.safari_zone_normal_battles[self.player].value == 1:
|
||||
if self.multiworld.safari_zone_normal_battles[self.player].value == 1:
|
||||
data[rom_addresses["Option_Safari_Zone_Battle_Type"]] = 255
|
||||
|
||||
if self.world.reusable_tms[self.player].value:
|
||||
if self.multiworld.reusable_tms[self.player].value:
|
||||
data[rom_addresses["Option_Reusable_TMs"]] = 0xC9
|
||||
|
||||
process_trainer_data(self, data)
|
||||
@@ -537,17 +539,17 @@ def generate_output(self, output_directory: str):
|
||||
data[rom_addresses['Title_Mon_First']] = mons.pop()
|
||||
for mon in range(0, 16):
|
||||
data[rom_addresses['Title_Mons'] + mon] = mons.pop()
|
||||
if self.world.game_version[self.player].value:
|
||||
mons.sort(key=lambda mon: 0 if mon == self.world.get_location("Pallet Town - Starter 1", self.player).item.name
|
||||
else 1 if mon == self.world.get_location("Pallet Town - Starter 2", self.player).item.name else
|
||||
2 if mon == self.world.get_location("Pallet Town - Starter 3", self.player).item.name else 3)
|
||||
if self.multiworld.game_version[self.player].value:
|
||||
mons.sort(key=lambda mon: 0 if mon == self.multiworld.get_location("Pallet Town - Starter 1", self.player).item.name
|
||||
else 1 if mon == self.multiworld.get_location("Pallet Town - Starter 2", self.player).item.name else
|
||||
2 if mon == self.multiworld.get_location("Pallet Town - Starter 3", self.player).item.name else 3)
|
||||
else:
|
||||
mons.sort(key=lambda mon: 0 if mon == self.world.get_location("Pallet Town - Starter 2", self.player).item.name
|
||||
else 1 if mon == self.world.get_location("Pallet Town - Starter 1", self.player).item.name else
|
||||
2 if mon == self.world.get_location("Pallet Town - Starter 3", self.player).item.name else 3)
|
||||
write_bytes(data, encode_text(self.world.seed_name, 20, True), rom_addresses['Title_Seed'])
|
||||
mons.sort(key=lambda mon: 0 if mon == self.multiworld.get_location("Pallet Town - Starter 2", self.player).item.name
|
||||
else 1 if mon == self.multiworld.get_location("Pallet Town - Starter 1", self.player).item.name else
|
||||
2 if mon == self.multiworld.get_location("Pallet Town - Starter 3", self.player).item.name else 3)
|
||||
write_bytes(data, encode_text(self.multiworld.seed_name[-20:], 20, True), rom_addresses['Title_Seed'])
|
||||
|
||||
slot_name = self.world.player_name[self.player]
|
||||
slot_name = self.multiworld.player_name[self.player]
|
||||
slot_name.replace("@", " ")
|
||||
slot_name.replace("<", " ")
|
||||
slot_name.replace(">", " ")
|
||||
@@ -556,24 +558,24 @@ def generate_output(self, output_directory: str):
|
||||
write_bytes(data, self.trainer_name, rom_addresses['Player_Name'])
|
||||
write_bytes(data, self.rival_name, rom_addresses['Rival_Name'])
|
||||
|
||||
write_bytes(data, basemd5.digest(), 0xFFCC)
|
||||
write_bytes(data, self.world.seed_name.encode(), 0xFFDC)
|
||||
write_bytes(data, self.world.player_name[self.player].encode(), 0xFFF0)
|
||||
write_bytes(data, basemd5.digest(), 0xFFCB)
|
||||
write_bytes(data, self.multiworld.seed_name.encode(), 0xFFDB)
|
||||
write_bytes(data, self.multiworld.player_name[self.player].encode(), 0xFFF0)
|
||||
|
||||
|
||||
|
||||
outfilepname = f'_P{self.player}'
|
||||
outfilepname += f"_{self.world.get_file_safe_player_name(self.player).replace(' ', '_')}" \
|
||||
if self.world.player_name[self.player] != 'Player%d' % self.player else ''
|
||||
rompath = os.path.join(output_directory, f'AP_{self.world.seed_name}{outfilepname}.gb')
|
||||
outfilepname += f"_{self.multiworld.get_file_safe_player_name(self.player).replace(' ', '_')}" \
|
||||
if self.multiworld.player_name[self.player] != 'Player%d' % self.player else ''
|
||||
rompath = os.path.join(output_directory, f'AP_{self.multiworld.seed_name}{outfilepname}.gb')
|
||||
with open(rompath, 'wb') as outfile:
|
||||
outfile.write(data)
|
||||
if self.world.game_version[self.player].current_key == "red":
|
||||
if self.multiworld.game_version[self.player].current_key == "red":
|
||||
patch = RedDeltaPatch(os.path.splitext(rompath)[0] + RedDeltaPatch.patch_file_ending, player=self.player,
|
||||
player_name=self.world.player_name[self.player], patched_path=rompath)
|
||||
player_name=self.multiworld.player_name[self.player], patched_path=rompath)
|
||||
else:
|
||||
patch = BlueDeltaPatch(os.path.splitext(rompath)[0] + BlueDeltaPatch.patch_file_ending, player=self.player,
|
||||
player_name=self.world.player_name[self.player], patched_path=rompath)
|
||||
player_name=self.multiworld.player_name[self.player], patched_path=rompath)
|
||||
|
||||
patch.write()
|
||||
os.unlink(rompath)
|
||||
|
||||
@@ -327,9 +327,9 @@ rom_addresses = {
|
||||
"Learnset_Bellsprout": 0x3b9dc,
|
||||
"Learnset_Weepinbell": 0x3b9f0,
|
||||
"Learnset_Victreebel": 0x3ba00,
|
||||
"Type_Chart": 0x3e4b6,
|
||||
"Type_Chart_Divider": 0x3e5ac,
|
||||
"Ghost_Battle3": 0x3efd9,
|
||||
"Type_Chart": 0x3e4b0,
|
||||
"Type_Chart_Divider": 0x3e5a6,
|
||||
"Ghost_Battle3": 0x3efd3,
|
||||
"Missable_Pokemon_Mansion_1F_Item_1": 0x443d6,
|
||||
"Missable_Pokemon_Mansion_1F_Item_2": 0x443dd,
|
||||
"Map_Rock_TunnelF": 0x44676,
|
||||
@@ -585,4 +585,5 @@ rom_addresses = {
|
||||
"Badge_Text_Marsh_Badge": 0x9918c,
|
||||
"Badge_Text_Volcano_Badge": 0x991d6,
|
||||
"Badge_Text_Earth_Badge": 0x991f3,
|
||||
"Text_Badges_Needed_Viridian_Gym": 0xa49f2,
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ def set_rules(world, player):
|
||||
"Pallet Town - Rival's Sister": lambda state: state.has("Oak's Parcel", player),
|
||||
"Pallet Town - Oak's Post-Route-22-Rival Gift": lambda state: state.has("Oak's Parcel", player),
|
||||
"Viridian City - Sleepy Guy": lambda state: state.pokemon_rb_can_cut(player) or state.pokemon_rb_can_surf(player),
|
||||
"Route 2 - Oak's Aide": lambda state: state.pokemon_rb_has_pokemon(state.world.oaks_aide_rt_2[player].value + 5, player),
|
||||
"Route 2 - Oak's Aide": lambda state: state.pokemon_rb_has_pokemon(state.multiworld.oaks_aide_rt_2[player].value + 5, player),
|
||||
"Pewter City - Museum": lambda state: state.pokemon_rb_can_cut(player),
|
||||
"Cerulean City - Bicycle Shop": lambda state: state.has("Bike Voucher", player),
|
||||
"Lavender Town - Mr. Fuji": lambda state: state.has("Fuji Saved", player),
|
||||
"Vermilion Gym - Lt. Surge 1": lambda state: state.pokemon_rb_can_cut(player or state.pokemon_rb_can_surf(player)),
|
||||
"Vermilion Gym - Lt. Surge 2": lambda state: state.pokemon_rb_can_cut(player or state.pokemon_rb_can_surf(player)),
|
||||
"Route 11 - Oak's Aide": lambda state: state.pokemon_rb_has_pokemon(state.world.oaks_aide_rt_11[player].value + 5, player),
|
||||
"Route 11 - Oak's Aide": lambda state: state.pokemon_rb_has_pokemon(state.multiworld.oaks_aide_rt_11[player].value + 5, player),
|
||||
"Celadon City - Stranded Man": lambda state: state.pokemon_rb_can_surf(player),
|
||||
"Silph Co 11F - Silph Co President": lambda state: state.has("Card Key", player),
|
||||
"Fuchsia City - Safari Zone Warden": lambda state: state.has("Gold Teeth", player),
|
||||
@@ -73,12 +73,12 @@ def set_rules(world, player):
|
||||
"Anywhere - Good Rod Pokemon - 1": lambda state: state.has("Good Rod", player),
|
||||
"Anywhere - Good Rod Pokemon - 2": lambda state: state.has("Good Rod", player),
|
||||
"Anywhere - Old Rod Pokemon": lambda state: state.has("Old Rod", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 1": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 2": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 3": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 4": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 5": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 6": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 1": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 2": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 3": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 4": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 5": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 6": lambda state: state.has("Coin Case", player),
|
||||
"Cinnabar Island - Old Amber Pokemon": lambda state: state.has("Old Amber", player),
|
||||
"Cinnabar Island - Helix Fossil Pokemon": lambda state: state.has("Helix Fossil", player),
|
||||
"Cinnabar Island - Dome Fossil Pokemon": lambda state: state.has("Dome Fossil", player),
|
||||
@@ -96,10 +96,10 @@ def set_rules(world, player):
|
||||
player),
|
||||
"Route 25 - Hidden Item Fence Outside Bill's House": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Route 9 - Hidden Item Rock By Grass": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 9 - Hidden Item Bush By Grass": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"S.S. Anne 1F - Hidden Item Kitchen Trash": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"S.S. Anne B1F - Hidden Item Under Pillow": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 10 - Hidden Item Behind Rock Tunnel Entrance Tree": lambda
|
||||
"Route 10 - Hidden Item Behind Rock Tunnel Entrance Cuttable Tree": lambda
|
||||
state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 10 - Hidden Item Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Rocket Hideout B1F - Hidden Item Pot Plant": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
@@ -107,14 +107,15 @@ def set_rules(world, player):
|
||||
"Rocket Hideout B4F - Hidden Item Behind Giovanni": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Pokemon Tower 5F - Hidden Item Near West Staircase": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Route 13 - Hidden Item Dead End Boulder": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 13 - Hidden Item Dead End Bush": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 13 - Hidden Item Dead End By Water Corner": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Pokemon Mansion B1F - Hidden Item Secret Key Room Corner": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Safari Zone West - Hidden Item Secret House Statue": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Silph Co 5F - Hidden Item Pot Plant": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Silph Co 9F - Hidden Item Nurse Bed": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Silph Co 9F - Hidden Item Nurse Bed (Card Key)": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player) and state.has("Card Key", player),
|
||||
"Copycat's House - Hidden Item Desk": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Cerulean Cave 1F - Hidden Item Center Rocks": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Cerulean Cave B1F - Hidden Item Northeast Rocks": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
@@ -127,15 +128,15 @@ def set_rules(world, player):
|
||||
"Pokemon Mansion 3F - Hidden Item Behind Burglar": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 23 - Hidden Item Rocks Before Final Guard": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Route 23 - Hidden Item East Tree After Water": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
"Route 23 - Hidden Item East Bush After Water": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Route 23 - Hidden Item On Island": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Victory Road 2F - Hidden Item Rock Before Moltres": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Victory Road 2F - Hidden Item Rock In Final Room": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Viridian City - Hidden Item Cuttable Tree": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 11 - Hidden Item Isolated Tree Near Gate": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 12 - Hidden Item Tree Near Gate": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 11 - Hidden Item Isolated Bush Near Gate": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 12 - Hidden Item Bush Near Gate": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 17 - Hidden Item In Grass": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 17 - Hidden Item Near Northernmost Sign": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 17 - Hidden Item East Center": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
@@ -153,7 +154,7 @@ def set_rules(world, player):
|
||||
"Mt Moon B2F - Hidden Item Lone Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Seafoam Islands B3F - Hidden Item Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Vermilion City - Hidden Item In Water Near Fan Club": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
player) and state.pokemon_rb_can_surf(player),
|
||||
"Cerulean City - Hidden Item Gym Badge Guy's Backyard": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Route 4 - Hidden Item Plateau East Of Mt Moon": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
|
||||
@@ -5,10 +5,10 @@ from ..AutoWorld import LogicMixin
|
||||
|
||||
class RaftLogic(LogicMixin):
|
||||
def raft_paddleboard_mode_enabled(self, player):
|
||||
return self.world.paddleboard_mode[player].value
|
||||
return self.multiworld.paddleboard_mode[player].value
|
||||
|
||||
def raft_big_islands_available(self, player):
|
||||
return self.world.big_island_early_crafting[player].value or self.raft_can_access_radio_tower(player)
|
||||
return self.multiworld.big_island_early_crafting[player].value or self.raft_can_access_radio_tower(player)
|
||||
|
||||
def raft_can_smelt_items(self, player):
|
||||
return self.has("Smelter", player)
|
||||
|
||||
@@ -43,8 +43,8 @@ class RaftWorld(World):
|
||||
required_client_version = (0, 3, 4)
|
||||
|
||||
def generate_basic(self):
|
||||
minRPSpecified = self.world.minimum_resource_pack_amount[self.player].value
|
||||
maxRPSpecified = self.world.maximum_resource_pack_amount[self.player].value
|
||||
minRPSpecified = self.multiworld.minimum_resource_pack_amount[self.player].value
|
||||
maxRPSpecified = self.multiworld.maximum_resource_pack_amount[self.player].value
|
||||
minimumResourcePackAmount = min(minRPSpecified, maxRPSpecified)
|
||||
maximumResourcePackAmount = max(minRPSpecified, maxRPSpecified)
|
||||
# Generate item pool
|
||||
@@ -56,21 +56,21 @@ class RaftWorld(World):
|
||||
extraItemNamePool = []
|
||||
extras = len(location_table) - len(item_table) - 1 # Victory takes up 1 unaccounted-for slot
|
||||
if extras > 0:
|
||||
if (self.world.filler_item_types[self.player].value != 1): # Use resource packs
|
||||
if (self.multiworld.filler_item_types[self.player].value != 1): # Use resource packs
|
||||
for packItem in resourcePackItems:
|
||||
for i in range(minimumResourcePackAmount, maximumResourcePackAmount + 1):
|
||||
extraItemNamePool.append(createResourcePackName(i, packItem))
|
||||
|
||||
if self.world.filler_item_types[self.player].value != 0: # Use duplicate items
|
||||
if self.multiworld.filler_item_types[self.player].value != 0: # Use duplicate items
|
||||
dupeItemPool = item_table.copy()
|
||||
# Remove frequencies if necessary
|
||||
if self.world.island_frequency_locations[self.player].value != 5: # Not completely random locations
|
||||
if self.multiworld.island_frequency_locations[self.player].value != 5: # Not completely random locations
|
||||
dupeItemPool = (itm for itm in dupeItemPool if "Frequency" not in itm["name"])
|
||||
|
||||
# Remove progression or non-progression items if necessary
|
||||
if (self.world.duplicate_items[self.player].value == 0): # Progression only
|
||||
if (self.multiworld.duplicate_items[self.player].value == 0): # Progression only
|
||||
dupeItemPool = (itm for itm in dupeItemPool if itm["progression"] == True)
|
||||
elif (self.world.duplicate_items[self.player].value == 1): # Non-progression only
|
||||
elif (self.multiworld.duplicate_items[self.player].value == 1): # Non-progression only
|
||||
dupeItemPool = (itm for itm in dupeItemPool if itm["progression"] == False)
|
||||
|
||||
dupeItemPool = list(dupeItemPool)
|
||||
@@ -84,23 +84,23 @@ class RaftWorld(World):
|
||||
raft_item = self.create_item_replaceAsNecessary(randomItem)
|
||||
pool.append(raft_item)
|
||||
|
||||
self.world.itempool += pool
|
||||
self.multiworld.itempool += pool
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.world, self.player)
|
||||
create_regions(self.multiworld, self.player)
|
||||
|
||||
def get_pre_fill_items(self):
|
||||
if self.world.island_frequency_locations[self.player] in [0, 1, 2, 3]:
|
||||
return [loc.item for loc in self.world.get_filled_locations()]
|
||||
if self.multiworld.island_frequency_locations[self.player] in [0, 1, 2, 3]:
|
||||
return [loc.item for loc in self.multiworld.get_filled_locations()]
|
||||
return []
|
||||
|
||||
def create_item_replaceAsNecessary(self, name: str) -> Item:
|
||||
isFrequency = "Frequency" in name
|
||||
shouldUseProgressive = ((isFrequency and self.world.island_frequency_locations[self.player].value == 4)
|
||||
or (not isFrequency and self.world.progressive_items[self.player].value))
|
||||
shouldUseProgressive = ((isFrequency and self.multiworld.island_frequency_locations[self.player].value == 4)
|
||||
or (not isFrequency and self.multiworld.progressive_items[self.player].value))
|
||||
if shouldUseProgressive and name in progressive_table:
|
||||
name = progressive_table[name]
|
||||
return self.create_item(name)
|
||||
@@ -128,7 +128,7 @@ class RaftWorld(World):
|
||||
return super(RaftWorld, self).collect_item(state, item, remove)
|
||||
|
||||
def pre_fill(self):
|
||||
if self.world.island_frequency_locations[self.player] == 0:
|
||||
if self.multiworld.island_frequency_locations[self.player] == 0:
|
||||
self.setLocationItem("Radio Tower Frequency to Vasagatan", "Vasagatan Frequency")
|
||||
self.setLocationItem("Vasagatan Frequency to Balboa", "Balboa Island Frequency")
|
||||
self.setLocationItem("Relay Station quest", "Caravan Island Frequency")
|
||||
@@ -136,7 +136,7 @@ class RaftWorld(World):
|
||||
self.setLocationItem("Tangaroa Frequency to Varuna Point", "Varuna Point Frequency")
|
||||
self.setLocationItem("Varuna Point Frequency to Temperance", "Temperance Frequency")
|
||||
self.setLocationItem("Temperance Frequency to Utopia", "Utopia Frequency")
|
||||
elif self.world.island_frequency_locations[self.player] == 1:
|
||||
elif self.multiworld.island_frequency_locations[self.player] == 1:
|
||||
self.setLocationItemFromRegion("RadioTower", "Vasagatan Frequency")
|
||||
self.setLocationItemFromRegion("Vasagatan", "Balboa Island Frequency")
|
||||
self.setLocationItemFromRegion("BalboaIsland", "Caravan Island Frequency")
|
||||
@@ -144,7 +144,7 @@ class RaftWorld(World):
|
||||
self.setLocationItemFromRegion("Tangaroa", "Varuna Point Frequency")
|
||||
self.setLocationItemFromRegion("Varuna Point", "Temperance Frequency")
|
||||
self.setLocationItemFromRegion("Temperance", "Utopia Frequency")
|
||||
elif self.world.island_frequency_locations[self.player] in [2, 3]:
|
||||
elif self.multiworld.island_frequency_locations[self.player] in [2, 3]:
|
||||
locationToFrequencyItemMap = {
|
||||
"Vasagatan": "Vasagatan Frequency",
|
||||
"BalboaIsland": "Balboa Island Frequency",
|
||||
@@ -172,37 +172,37 @@ class RaftWorld(World):
|
||||
else:
|
||||
currentLocation = availableLocationList[0] # Utopia (only one left in list)
|
||||
availableLocationList.remove(currentLocation)
|
||||
if self.world.island_frequency_locations[self.player] == 2:
|
||||
if self.multiworld.island_frequency_locations[self.player] == 2:
|
||||
self.setLocationItem(locationToVanillaFrequencyLocationMap[previousLocation], locationToFrequencyItemMap[currentLocation])
|
||||
elif self.world.island_frequency_locations[self.player] == 3:
|
||||
elif self.multiworld.island_frequency_locations[self.player] == 3:
|
||||
self.setLocationItemFromRegion(previousLocation, locationToFrequencyItemMap[currentLocation])
|
||||
previousLocation = currentLocation
|
||||
|
||||
# Victory item
|
||||
self.world.get_location("Utopia Complete", self.player).place_locked_item(
|
||||
self.multiworld.get_location("Utopia Complete", self.player).place_locked_item(
|
||||
RaftItem("Victory", ItemClassification.progression, None, player=self.player))
|
||||
|
||||
def setLocationItem(self, location: str, itemName: str):
|
||||
itemToUse = next(filter(lambda itm: itm.name == itemName, self.world.itempool))
|
||||
self.world.itempool.remove(itemToUse)
|
||||
self.world.get_location(location, self.player).place_locked_item(itemToUse)
|
||||
itemToUse = next(filter(lambda itm: itm.name == itemName, self.multiworld.itempool))
|
||||
self.multiworld.itempool.remove(itemToUse)
|
||||
self.multiworld.get_location(location, self.player).place_locked_item(itemToUse)
|
||||
|
||||
def setLocationItemFromRegion(self, region: str, itemName: str):
|
||||
itemToUse = next(filter(lambda itm: itm.name == itemName, self.world.itempool))
|
||||
self.world.itempool.remove(itemToUse)
|
||||
itemToUse = next(filter(lambda itm: itm.name == itemName, self.multiworld.itempool))
|
||||
self.multiworld.itempool.remove(itemToUse)
|
||||
location = random.choice(list(loc for loc in location_table if loc["region"] == region))
|
||||
self.world.get_location(location["name"], self.player).place_locked_item(itemToUse)
|
||||
self.multiworld.get_location(location["name"], self.player).place_locked_item(itemToUse)
|
||||
|
||||
def fill_slot_data(self):
|
||||
return {
|
||||
"IslandGenerationDistance": self.world.island_generation_distance[self.player].value,
|
||||
"ExpensiveResearch": bool(self.world.expensive_research[self.player].value),
|
||||
"DeathLink": bool(self.world.death_link[self.player].value)
|
||||
"IslandGenerationDistance": self.multiworld.island_generation_distance[self.player].value,
|
||||
"ExpensiveResearch": bool(self.multiworld.expensive_research[self.player].value),
|
||||
"DeathLink": bool(self.multiworld.death_link[self.player].value)
|
||||
}
|
||||
|
||||
def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
ret = Region(name, RegionType.Generic, name, player)
|
||||
ret.world = world
|
||||
ret.multiworld = world
|
||||
if locations:
|
||||
for location in locations:
|
||||
loc_id = locations_lookup_name_to_id.get(location, 0)
|
||||
|
||||
@@ -1,136 +1,115 @@
|
||||
import typing
|
||||
from typing import Dict, NamedTuple, Optional
|
||||
|
||||
from BaseClasses import Item
|
||||
from .Names import ItemName
|
||||
from BaseClasses import Item, ItemClassification
|
||||
|
||||
|
||||
class ItemData(typing.NamedTuple):
|
||||
code: typing.Optional[int]
|
||||
progression: bool
|
||||
quantity: int = 1
|
||||
event: bool = False
|
||||
|
||||
|
||||
class LegacyItem(Item):
|
||||
class RLItem(Item):
|
||||
game: str = "Rogue Legacy"
|
||||
|
||||
|
||||
# Separate tables for each type of item.
|
||||
vendors_table = {
|
||||
ItemName.blacksmith: ItemData(90000, True),
|
||||
ItemName.enchantress: ItemData(90001, True),
|
||||
ItemName.architect: ItemData(90002, False),
|
||||
class RLItemData(NamedTuple):
|
||||
category: str
|
||||
code: Optional[int] = None
|
||||
classification: ItemClassification = ItemClassification.filler
|
||||
max_quantity: int = 1
|
||||
weight: int = 1
|
||||
|
||||
@property
|
||||
def is_event_item(self):
|
||||
return self.code is None
|
||||
|
||||
|
||||
def get_items_by_category(category: str) -> Dict[str, RLItemData]:
|
||||
item_dict: Dict[str, RLItemData] = {}
|
||||
for name, data in item_table.items():
|
||||
if data.category == category:
|
||||
item_dict.setdefault(name, data)
|
||||
|
||||
return item_dict
|
||||
|
||||
|
||||
item_table: Dict[str, RLItemData] = {
|
||||
# Vendors
|
||||
"Blacksmith": RLItemData("Vendors", 90_000, ItemClassification.useful),
|
||||
"Enchantress": RLItemData("Vendors", 90_001, ItemClassification.progression),
|
||||
"Architect": RLItemData("Vendors", 90_002, ItemClassification.useful),
|
||||
|
||||
# Classes
|
||||
"Progressive Knights": RLItemData("Classes", 90_003, ItemClassification.useful, 2),
|
||||
"Progressive Mages": RLItemData("Classes", 90_004, ItemClassification.useful, 2),
|
||||
"Progressive Barbarians": RLItemData("Classes", 90_005, ItemClassification.useful, 2),
|
||||
"Progressive Knaves": RLItemData("Classes", 90_006, ItemClassification.useful, 2),
|
||||
"Progressive Shinobis": RLItemData("Classes", 90_007, ItemClassification.useful, 2),
|
||||
"Progressive Miners": RLItemData("Classes", 90_008, ItemClassification.useful, 2),
|
||||
"Progressive Liches": RLItemData("Classes", 90_009, ItemClassification.useful, 2),
|
||||
"Progressive Spellthieves": RLItemData("Classes", 90_010, ItemClassification.useful, 2),
|
||||
"Dragons": RLItemData("Classes", 90_096, ItemClassification.progression),
|
||||
"Traitors": RLItemData("Classes", 90_097, ItemClassification.useful),
|
||||
|
||||
# Skills
|
||||
"Health Up": RLItemData("Skills", 90_013, ItemClassification.progression_skip_balancing, 15),
|
||||
"Mana Up": RLItemData("Skills", 90_014, ItemClassification.progression_skip_balancing, 15),
|
||||
"Attack Up": RLItemData("Skills", 90_015, ItemClassification.progression_skip_balancing, 15),
|
||||
"Magic Damage Up": RLItemData("Skills", 90_016, ItemClassification.progression_skip_balancing, 15),
|
||||
"Armor Up": RLItemData("Skills", 90_017, ItemClassification.useful, 15),
|
||||
"Equip Up": RLItemData("Skills", 90_018, ItemClassification.useful, 5),
|
||||
"Crit Chance Up": RLItemData("Skills", 90_019, ItemClassification.useful, 5),
|
||||
"Crit Damage Up": RLItemData("Skills", 90_020, ItemClassification.useful, 5),
|
||||
"Down Strike Up": RLItemData("Skills", 90_021),
|
||||
"Gold Gain Up": RLItemData("Skills", 90_022),
|
||||
"Potion Efficiency Up": RLItemData("Skills", 90_023),
|
||||
"Invulnerability Time Up": RLItemData("Skills", 90_024),
|
||||
"Mana Cost Down": RLItemData("Skills", 90_025),
|
||||
"Death Defiance": RLItemData("Skills", 90_026, ItemClassification.useful),
|
||||
"Haggling": RLItemData("Skills", 90_027, ItemClassification.useful),
|
||||
"Randomize Children": RLItemData("Skills", 90_028, ItemClassification.useful),
|
||||
|
||||
# Blueprints
|
||||
"Progressive Blueprints": RLItemData("Blueprints", 90_055, ItemClassification.useful, 15),
|
||||
"Squire Blueprints": RLItemData("Blueprints", 90_040, ItemClassification.useful),
|
||||
"Silver Blueprints": RLItemData("Blueprints", 90_041, ItemClassification.useful),
|
||||
"Guardian Blueprints": RLItemData("Blueprints", 90_042, ItemClassification.useful),
|
||||
"Imperial Blueprints": RLItemData("Blueprints", 90_043, ItemClassification.useful),
|
||||
"Royal Blueprints": RLItemData("Blueprints", 90_044, ItemClassification.useful),
|
||||
"Knight Blueprints": RLItemData("Blueprints", 90_045, ItemClassification.useful),
|
||||
"Ranger Blueprints": RLItemData("Blueprints", 90_046, ItemClassification.useful),
|
||||
"Sky Blueprints": RLItemData("Blueprints", 90_047, ItemClassification.useful),
|
||||
"Dragon Blueprints": RLItemData("Blueprints", 90_048, ItemClassification.useful),
|
||||
"Slayer Blueprints": RLItemData("Blueprints", 90_049, ItemClassification.useful),
|
||||
"Blood Blueprints": RLItemData("Blueprints", 90_050, ItemClassification.useful),
|
||||
"Sage Blueprints": RLItemData("Blueprints", 90_051, ItemClassification.useful),
|
||||
"Retribution Blueprints": RLItemData("Blueprints", 90_052, ItemClassification.useful),
|
||||
"Holy Blueprints": RLItemData("Blueprints", 90_053, ItemClassification.useful),
|
||||
"Dark Blueprints": RLItemData("Blueprints", 90_054, ItemClassification.useful),
|
||||
|
||||
# Runes
|
||||
"Vault Runes": RLItemData("Runes", 90_060, ItemClassification.progression),
|
||||
"Sprint Runes": RLItemData("Runes", 90_061, ItemClassification.progression),
|
||||
"Vampire Runes": RLItemData("Runes", 90_062, ItemClassification.useful),
|
||||
"Sky Runes": RLItemData("Runes", 90_063, ItemClassification.progression),
|
||||
"Siphon Runes": RLItemData("Runes", 90_064, ItemClassification.useful),
|
||||
"Retaliation Runes": RLItemData("Runes", 90_065),
|
||||
"Bounty Runes": RLItemData("Runes", 90_066),
|
||||
"Haste Runes": RLItemData("Runes", 90_067),
|
||||
"Curse Runes": RLItemData("Runes", 90_068),
|
||||
"Grace Runes": RLItemData("Runes", 90_069),
|
||||
"Balance Runes": RLItemData("Runes", 90_070, ItemClassification.useful),
|
||||
|
||||
# Junk
|
||||
"Triple Stat Increase": RLItemData("Filler", 90_030, weight=6),
|
||||
"1000 Gold": RLItemData("Filler", 90_031, weight=3),
|
||||
"3000 Gold": RLItemData("Filler", 90_032, weight=2),
|
||||
"5000 Gold": RLItemData("Filler", 90_033, weight=1),
|
||||
}
|
||||
|
||||
static_classes_table = {
|
||||
ItemName.knight: ItemData(90080, False),
|
||||
ItemName.paladin: ItemData(90081, False),
|
||||
ItemName.mage: ItemData(90082, False),
|
||||
ItemName.archmage: ItemData(90083, False),
|
||||
ItemName.barbarian: ItemData(90084, False),
|
||||
ItemName.barbarian_king: ItemData(90085, False),
|
||||
ItemName.knave: ItemData(90086, False),
|
||||
ItemName.assassin: ItemData(90087, False),
|
||||
ItemName.shinobi: ItemData(90088, False),
|
||||
ItemName.hokage: ItemData(90089, False),
|
||||
ItemName.miner: ItemData(90090, False),
|
||||
ItemName.spelunker: ItemData(90091, False),
|
||||
ItemName.lich: ItemData(90092, False),
|
||||
ItemName.lich_king: ItemData(90093, False),
|
||||
ItemName.spellthief: ItemData(90094, False),
|
||||
ItemName.spellsword: ItemData(90095, False),
|
||||
ItemName.dragon: ItemData(90096, False),
|
||||
ItemName.traitor: ItemData(90097, False),
|
||||
event_item_table: Dict[str, RLItemData] = {
|
||||
"Defeat Khidr": RLItemData("Event", classification=ItemClassification.progression),
|
||||
"Defeat Alexander": RLItemData("Event", classification=ItemClassification.progression),
|
||||
"Defeat Ponce de Leon": RLItemData("Event", classification=ItemClassification.progression),
|
||||
"Defeat Herodotus": RLItemData("Event", classification=ItemClassification.progression),
|
||||
"Defeat Neo Khidr": RLItemData("Event", classification=ItemClassification.progression),
|
||||
"Defeat Alexander IV": RLItemData("Event", classification=ItemClassification.progression),
|
||||
"Defeat Ponce de Freon": RLItemData("Event", classification=ItemClassification.progression),
|
||||
"Defeat Astrodotus": RLItemData("Event", classification=ItemClassification.progression),
|
||||
"Defeat The Fountain": RLItemData("Event", classification=ItemClassification.progression),
|
||||
}
|
||||
|
||||
progressive_classes_table = {
|
||||
ItemName.progressive_knight: ItemData(90003, False, 2),
|
||||
ItemName.progressive_mage: ItemData(90004, False, 2),
|
||||
ItemName.progressive_barbarian: ItemData(90005, False, 2),
|
||||
ItemName.progressive_knave: ItemData(90006, False, 2),
|
||||
ItemName.progressive_shinobi: ItemData(90007, False, 2),
|
||||
ItemName.progressive_miner: ItemData(90008, False, 2),
|
||||
ItemName.progressive_lich: ItemData(90009, False, 2),
|
||||
ItemName.progressive_spellthief: ItemData(90010, False, 2),
|
||||
}
|
||||
|
||||
configurable_skill_unlocks_table = {
|
||||
ItemName.health: ItemData(90013, True, 15),
|
||||
ItemName.mana: ItemData(90014, True, 15),
|
||||
ItemName.attack: ItemData(90015, True, 15),
|
||||
ItemName.magic_damage: ItemData(90016, True, 15),
|
||||
ItemName.armor: ItemData(90017, True, 10),
|
||||
ItemName.equip: ItemData(90018, True, 10),
|
||||
ItemName.crit_chance: ItemData(90019, False, 5),
|
||||
ItemName.crit_damage: ItemData(90020, False, 5),
|
||||
}
|
||||
|
||||
skill_unlocks_table = {
|
||||
ItemName.down_strike: ItemData(90021, False),
|
||||
ItemName.gold_gain: ItemData(90022, False),
|
||||
ItemName.potion_efficiency: ItemData(90023, False),
|
||||
ItemName.invulnerability_time: ItemData(90024, False),
|
||||
ItemName.mana_cost_down: ItemData(90025, False),
|
||||
ItemName.death_defiance: ItemData(90026, False),
|
||||
ItemName.haggling: ItemData(90027, False),
|
||||
ItemName.random_children: ItemData(90028, False),
|
||||
}
|
||||
|
||||
blueprints_table = {
|
||||
ItemName.squire_blueprints: ItemData(90040, False),
|
||||
ItemName.silver_blueprints: ItemData(90041, False),
|
||||
ItemName.guardian_blueprints: ItemData(90042, False),
|
||||
ItemName.imperial_blueprints: ItemData(90043, False),
|
||||
ItemName.royal_blueprints: ItemData(90044, False),
|
||||
ItemName.knight_blueprints: ItemData(90045, False),
|
||||
ItemName.ranger_blueprints: ItemData(90046, False),
|
||||
ItemName.sky_blueprints: ItemData(90047, False),
|
||||
ItemName.dragon_blueprints: ItemData(90048, False),
|
||||
ItemName.slayer_blueprints: ItemData(90049, False),
|
||||
ItemName.blood_blueprints: ItemData(90050, False),
|
||||
ItemName.sage_blueprints: ItemData(90051, False),
|
||||
ItemName.retribution_blueprints: ItemData(90052, False),
|
||||
ItemName.holy_blueprints: ItemData(90053, False),
|
||||
ItemName.dark_blueprints: ItemData(90054, False),
|
||||
}
|
||||
|
||||
progressive_blueprint_table = {
|
||||
ItemName.progressive_blueprints: ItemData(90055, False),
|
||||
}
|
||||
|
||||
runes_table = {
|
||||
ItemName.vault_runes: ItemData(90060, False),
|
||||
ItemName.sprint_runes: ItemData(90061, False),
|
||||
ItemName.vampire_runes: ItemData(90062, False),
|
||||
ItemName.sky_runes: ItemData(90063, False),
|
||||
ItemName.siphon_runes: ItemData(90064, False),
|
||||
ItemName.retaliation_runes: ItemData(90065, False),
|
||||
ItemName.bounty_runes: ItemData(90066, False),
|
||||
ItemName.haste_runes: ItemData(90067, False),
|
||||
ItemName.curse_runes: ItemData(90068, False),
|
||||
ItemName.grace_runes: ItemData(90069, False),
|
||||
ItemName.balance_runes: ItemData(90070, False),
|
||||
}
|
||||
|
||||
misc_items_table = {
|
||||
ItemName.trip_stat_increase: ItemData(90030, False),
|
||||
ItemName.gold_1000: ItemData(90031, False),
|
||||
ItemName.gold_3000: ItemData(90032, False),
|
||||
ItemName.gold_5000: ItemData(90033, False),
|
||||
# ItemName.rage_trap: ItemData(90034, False),
|
||||
}
|
||||
|
||||
# Complete item table.
|
||||
item_table = {
|
||||
**vendors_table,
|
||||
**static_classes_table,
|
||||
**progressive_classes_table,
|
||||
**configurable_skill_unlocks_table,
|
||||
**skill_unlocks_table,
|
||||
**blueprints_table,
|
||||
**progressive_blueprint_table,
|
||||
**runes_table,
|
||||
**misc_items_table,
|
||||
}
|
||||
|
||||
lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code}
|
||||
|
||||
@@ -1,90 +1,98 @@
|
||||
import typing
|
||||
from typing import Dict, NamedTuple, Optional
|
||||
|
||||
from BaseClasses import Location
|
||||
from .Names import LocationName
|
||||
|
||||
|
||||
class LegacyLocation(Location):
|
||||
class RLLocation(Location):
|
||||
game: str = "Rogue Legacy"
|
||||
|
||||
|
||||
base_location_table = {
|
||||
# Manor Renovations
|
||||
LocationName.manor_ground_base: 91000,
|
||||
LocationName.manor_main_base: 91001,
|
||||
LocationName.manor_main_bottom_window: 91002,
|
||||
LocationName.manor_main_top_window: 91003,
|
||||
LocationName.manor_main_roof: 91004,
|
||||
LocationName.manor_left_wing_base: 91005,
|
||||
LocationName.manor_left_wing_window: 91006,
|
||||
LocationName.manor_left_wing_roof: 91007,
|
||||
LocationName.manor_left_big_base: 91008,
|
||||
LocationName.manor_left_big_upper1: 91009,
|
||||
LocationName.manor_left_big_upper2: 91010,
|
||||
LocationName.manor_left_big_windows: 91011,
|
||||
LocationName.manor_left_big_roof: 91012,
|
||||
LocationName.manor_left_far_base: 91013,
|
||||
LocationName.manor_left_far_roof: 91014,
|
||||
LocationName.manor_left_extension: 91015,
|
||||
LocationName.manor_left_tree1: 91016,
|
||||
LocationName.manor_left_tree2: 91017,
|
||||
LocationName.manor_right_wing_base: 91018,
|
||||
LocationName.manor_right_wing_window: 91019,
|
||||
LocationName.manor_right_wing_roof: 91020,
|
||||
LocationName.manor_right_big_base: 91021,
|
||||
LocationName.manor_right_big_upper: 91022,
|
||||
LocationName.manor_right_big_roof: 91023,
|
||||
LocationName.manor_right_high_base: 91024,
|
||||
LocationName.manor_right_high_upper: 91025,
|
||||
LocationName.manor_right_high_tower: 91026,
|
||||
LocationName.manor_right_extension: 91027,
|
||||
LocationName.manor_right_tree: 91028,
|
||||
LocationName.manor_observatory_base: 91029,
|
||||
LocationName.manor_observatory_scope: 91030,
|
||||
class RLLocationData(NamedTuple):
|
||||
category: str
|
||||
code: Optional[int] = None
|
||||
|
||||
@property
|
||||
def is_event_location(self):
|
||||
return self.code is None
|
||||
|
||||
|
||||
def get_locations_by_category(category: str) -> Dict[str, RLLocationData]:
|
||||
location_dict: Dict[str, RLLocationData] = {}
|
||||
for name, data in location_table.items():
|
||||
if data.category == category:
|
||||
location_dict.setdefault(name, data)
|
||||
|
||||
return location_dict
|
||||
|
||||
|
||||
location_table: Dict[str, RLLocationData] = {
|
||||
# Manor Renovation
|
||||
"Manor - Ground Road": RLLocationData("Manor", 91_000),
|
||||
"Manor - Main Base": RLLocationData("Manor", 91_001),
|
||||
"Manor - Main Bottom Window": RLLocationData("Manor", 91_002),
|
||||
"Manor - Main Top Window": RLLocationData("Manor", 91_003),
|
||||
"Manor - Main Rooftop": RLLocationData("Manor", 91_004),
|
||||
"Manor - Left Wing Base": RLLocationData("Manor", 91_005),
|
||||
"Manor - Left Wing Window": RLLocationData("Manor", 91_006),
|
||||
"Manor - Left Wing Rooftop": RLLocationData("Manor", 91_007),
|
||||
"Manor - Left Big Base": RLLocationData("Manor", 91_008),
|
||||
"Manor - Left Big Upper 1": RLLocationData("Manor", 91_009),
|
||||
"Manor - Left Big Upper 2": RLLocationData("Manor", 91_010),
|
||||
"Manor - Left Big Windows": RLLocationData("Manor", 91_011),
|
||||
"Manor - Left Big Rooftop": RLLocationData("Manor", 91_012),
|
||||
"Manor - Left Far Base": RLLocationData("Manor", 91_013),
|
||||
"Manor - Left Far Roof": RLLocationData("Manor", 91_014),
|
||||
"Manor - Left Extension": RLLocationData("Manor", 91_015),
|
||||
"Manor - Left Tree 1": RLLocationData("Manor", 91_016),
|
||||
"Manor - Left Tree 2": RLLocationData("Manor", 91_017),
|
||||
"Manor - Right Wing Base": RLLocationData("Manor", 91_018),
|
||||
"Manor - Right Wing Window": RLLocationData("Manor", 91_019),
|
||||
"Manor - Right Wing Rooftop": RLLocationData("Manor", 91_020),
|
||||
"Manor - Right Big Base": RLLocationData("Manor", 91_021),
|
||||
"Manor - Right Big Upper": RLLocationData("Manor", 91_022),
|
||||
"Manor - Right Big Rooftop": RLLocationData("Manor", 91_023),
|
||||
"Manor - Right High Base": RLLocationData("Manor", 91_024),
|
||||
"Manor - Right High Upper": RLLocationData("Manor", 91_025),
|
||||
"Manor - Right High Tower": RLLocationData("Manor", 91_026),
|
||||
"Manor - Right Extension": RLLocationData("Manor", 91_027),
|
||||
"Manor - Right Tree": RLLocationData("Manor", 91_028),
|
||||
"Manor - Observatory Base": RLLocationData("Manor", 91_029),
|
||||
"Manor - Observatory Telescope": RLLocationData("Manor", 91_030),
|
||||
|
||||
# Boss Rewards
|
||||
LocationName.boss_castle: 91100,
|
||||
LocationName.boss_forest: 91102,
|
||||
LocationName.boss_tower: 91104,
|
||||
LocationName.boss_dungeon: 91106,
|
||||
|
||||
# Special Rooms
|
||||
LocationName.special_jukebox: 91200,
|
||||
LocationName.special_painting: 91201,
|
||||
LocationName.special_cheapskate: 91202,
|
||||
LocationName.special_carnival: 91203,
|
||||
"Castle Hamson Boss Reward": RLLocationData("Boss", 91_100),
|
||||
"Forest Abkhazia Boss Reward": RLLocationData("Boss", 91_102),
|
||||
"The Maya Boss Reward": RLLocationData("Boss", 91_104),
|
||||
"Land of Darkness Boss Reward": RLLocationData("Boss", 91_106),
|
||||
|
||||
# Special Locations
|
||||
LocationName.castle: None,
|
||||
LocationName.garden: None,
|
||||
LocationName.tower: None,
|
||||
LocationName.dungeon: None,
|
||||
LocationName.fountain: None,
|
||||
"Jukebox": RLLocationData("Special", 91_200),
|
||||
"Painting": RLLocationData("Special", 91_201),
|
||||
"Cheapskate Elf's Game": RLLocationData("Special", 91_202),
|
||||
"Carnival": RLLocationData("Special", 91_203),
|
||||
|
||||
# Diaries
|
||||
**{f"Diary {i+1}": RLLocationData("Diary", 91_300 + i) for i in range(0, 25)},
|
||||
|
||||
# Chests
|
||||
**{f"Castle Hamson - Chest {i+1}": RLLocationData("Chests", 91_600 + i) for i in range(0, 50)},
|
||||
**{f"Forest Abkhazia - Chest {i+1}": RLLocationData("Chests", 91_700 + i) for i in range(0, 50)},
|
||||
**{f"The Maya - Chest {i+1}": RLLocationData("Chests", 91_800 + i) for i in range(0, 50)},
|
||||
**{f"Land of Darkness - Chest {i+1}": RLLocationData("Chests", 91_900 + i) for i in range(0, 50)},
|
||||
**{f"Chest {i+1}": RLLocationData("Chests", 92_000 + i) for i in range(0, 200)},
|
||||
|
||||
# Fairy Chests
|
||||
**{f"Castle Hamson - Fairy Chest {i+1}": RLLocationData("Fairies", 91_400 + i) for i in range(0, 15)},
|
||||
**{f"Forest Abkhazia - Fairy Chest {i+1}": RLLocationData("Fairies", 91_450 + i) for i in range(0, 15)},
|
||||
**{f"The Maya - Fairy Chest {i+1}": RLLocationData("Fairies", 91_500 + i) for i in range(0, 15)},
|
||||
**{f"Land of Darkness - Fairy Chest {i+1}": RLLocationData("Fairies", 91_550 + i) for i in range(0, 15)},
|
||||
**{f"Fairy Chest {i+1}": RLLocationData("Fairies", 92_200 + i) for i in range(0, 60)},
|
||||
}
|
||||
|
||||
diary_location_table = {f"{LocationName.diary} {i + 1}": i + 91300 for i in range(0, 25)}
|
||||
|
||||
fairy_chest_location_table = {
|
||||
**{f"{LocationName.castle} - Fairy Chest {i + 1}": i + 91400 for i in range(0, 50)},
|
||||
**{f"{LocationName.garden} - Fairy Chest {i + 1}": i + 91450 for i in range(0, 50)},
|
||||
**{f"{LocationName.tower} - Fairy Chest {i + 1}": i + 91500 for i in range(0, 50)},
|
||||
**{f"{LocationName.dungeon} - Fairy Chest {i + 1}": i + 91550 for i in range(0, 50)},
|
||||
**{f"Fairy Chest {i + 1}": i + 92200 for i in range(0, 60)},
|
||||
event_location_table: Dict[str, RLLocationData] = {
|
||||
"Castle Hamson Boss Room": RLLocationData("Event"),
|
||||
"Forest Abkhazia Boss Room": RLLocationData("Event"),
|
||||
"The Maya Boss Room": RLLocationData("Event"),
|
||||
"Land of Darkness Boss Room": RLLocationData("Event"),
|
||||
"Fountain Room": RLLocationData("Event"),
|
||||
}
|
||||
|
||||
chest_location_table = {
|
||||
**{f"{LocationName.castle} - Chest {i + 1}": i + 91600 for i in range(0, 100)},
|
||||
**{f"{LocationName.garden} - Chest {i + 1}": i + 91700 for i in range(0, 100)},
|
||||
**{f"{LocationName.tower} - Chest {i + 1}": i + 91800 for i in range(0, 100)},
|
||||
**{f"{LocationName.dungeon} - Chest {i + 1}": i + 91900 for i in range(0, 100)},
|
||||
**{f"Chest {i + 1}": i + 92000 for i in range(0, 120)},
|
||||
}
|
||||
|
||||
location_table = {
|
||||
**base_location_table,
|
||||
**diary_location_table,
|
||||
**fairy_chest_location_table,
|
||||
**chest_location_table,
|
||||
}
|
||||
|
||||
lookup_id_to_name: typing.Dict[int, str] = {id: name for name, _ in location_table.items()}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
# Vendor Definitions
|
||||
blacksmith = "Blacksmith"
|
||||
enchantress = "Enchantress"
|
||||
architect = "Architect"
|
||||
|
||||
# Progressive Class Definitions
|
||||
progressive_knight = "Progressive Knights"
|
||||
progressive_mage = "Progressive Mages"
|
||||
progressive_barbarian = "Progressive Barbarians"
|
||||
progressive_knave = "Progressive Knaves"
|
||||
progressive_shinobi = "Progressive Shinobis"
|
||||
progressive_miner = "Progressive Miners"
|
||||
progressive_lich = "Progressive Liches"
|
||||
progressive_spellthief = "Progressive Spellthieves"
|
||||
|
||||
# Static Class Definitions
|
||||
knight = "Knights"
|
||||
paladin = "Paladins"
|
||||
mage = "Mages"
|
||||
archmage = "Archmages"
|
||||
barbarian = "Barbarians"
|
||||
barbarian_king = "Barbarian Kings"
|
||||
knave = "Knaves"
|
||||
assassin = "Assassins"
|
||||
shinobi = "Shinobis"
|
||||
hokage = "Hokages"
|
||||
miner = "Miners"
|
||||
spelunker = "Spelunkers"
|
||||
lich = "Lichs"
|
||||
lich_king = "Lich Kings"
|
||||
spellthief = "Spellthieves"
|
||||
spellsword = "Spellswords"
|
||||
dragon = "Dragons"
|
||||
traitor = "Traitors"
|
||||
|
||||
# Skill Unlock Definitions
|
||||
health = "Health Up"
|
||||
mana = "Mana Up"
|
||||
attack = "Attack Up"
|
||||
magic_damage = "Magic Damage Up"
|
||||
armor = "Armor Up"
|
||||
equip = "Equip Up"
|
||||
crit_chance = "Crit Chance Up"
|
||||
crit_damage = "Crit Damage Up"
|
||||
down_strike = "Down Strike Up"
|
||||
gold_gain = "Gold Gain Up"
|
||||
potion_efficiency = "Potion Efficiency Up"
|
||||
invulnerability_time = "Invulnerability Time Up"
|
||||
mana_cost_down = "Mana Cost Down"
|
||||
death_defiance = "Death Defiance"
|
||||
haggling = "Haggling"
|
||||
random_children = "Randomize Children"
|
||||
|
||||
# Misc. Definitions
|
||||
trip_stat_increase = "Triple Stat Increase"
|
||||
gold_1000 = "1000 Gold"
|
||||
gold_3000 = "3000 Gold"
|
||||
gold_5000 = "5000 Gold"
|
||||
rage_trap = "Rage Trap"
|
||||
|
||||
# Blueprint Definitions
|
||||
progressive_blueprints = "Progressive Blueprints"
|
||||
squire_blueprints = "Squire Blueprints"
|
||||
silver_blueprints = "Silver Blueprints"
|
||||
guardian_blueprints = "Guardian Blueprints"
|
||||
imperial_blueprints = "Imperial Blueprints"
|
||||
royal_blueprints = "Royal Blueprints"
|
||||
knight_blueprints = "Knight Blueprints"
|
||||
ranger_blueprints = "Ranger Blueprints"
|
||||
sky_blueprints = "Sky Blueprints"
|
||||
dragon_blueprints = "Dragon Blueprints"
|
||||
slayer_blueprints = "Slayer Blueprints"
|
||||
blood_blueprints = "Blood Blueprints"
|
||||
sage_blueprints = "Sage Blueprints"
|
||||
retribution_blueprints = "Retribution Blueprints"
|
||||
holy_blueprints = "Holy Blueprints"
|
||||
dark_blueprints = "Dark Blueprints"
|
||||
|
||||
# Rune Definitions
|
||||
vault_runes = "Vault Runes"
|
||||
sprint_runes = "Sprint Runes"
|
||||
vampire_runes = "Vampire Runes"
|
||||
sky_runes = "Sky Runes"
|
||||
siphon_runes = "Siphon Runes"
|
||||
retaliation_runes = "Retaliation Runes"
|
||||
bounty_runes = "Bounty Runes"
|
||||
haste_runes = "Haste Runes"
|
||||
curse_runes = "Curse Runes"
|
||||
grace_runes = "Grace Runes"
|
||||
balance_runes = "Balance Runes"
|
||||
|
||||
# Event Definitions
|
||||
boss_castle = "Defeat Castle Hamson Boss"
|
||||
boss_forest = "Defeat Forest Abkhazia Boss"
|
||||
boss_tower = "Defeat The Maya Boss"
|
||||
boss_dungeon = "Defeat The Land of Darkness Boss"
|
||||
boss_fountain = "Defeat The Fountain"
|
||||
@@ -1,55 +0,0 @@
|
||||
# Manor Piece Definitions
|
||||
manor_ground_base = "Manor Renovation - Ground Road"
|
||||
manor_main_base = "Manor Renovation - Main Base"
|
||||
manor_main_bottom_window = "Manor Renovation - Main Bottom Window"
|
||||
manor_main_top_window = "Manor Renovation - Main Top Window"
|
||||
manor_main_roof = "Manor Renovation - Main Rooftop"
|
||||
manor_left_wing_base = "Manor Renovation - Left Wing Base"
|
||||
manor_left_wing_window = "Manor Renovation - Left Wing Window"
|
||||
manor_left_wing_roof = "Manor Renovation - Left Wing Rooftop"
|
||||
manor_left_big_base = "Manor Renovation - Left Big Base"
|
||||
manor_left_big_upper1 = "Manor Renovation - Left Big Upper 1"
|
||||
manor_left_big_upper2 = "Manor Renovation - Left Big Upper 2"
|
||||
manor_left_big_windows = "Manor Renovation - Left Big Windows"
|
||||
manor_left_big_roof = "Manor Renovation - Left Big Rooftop"
|
||||
manor_left_far_base = "Manor Renovation - Left Far Base"
|
||||
manor_left_far_roof = "Manor Renovation - Left Far Roof"
|
||||
manor_left_extension = "Manor Renovation - Left Extension"
|
||||
manor_left_tree1 = "Manor Renovation - Left Tree 1"
|
||||
manor_left_tree2 = "Manor Renovation - Left Tree 2"
|
||||
manor_right_wing_base = "Manor Renovation - Right Wing Base"
|
||||
manor_right_wing_window = "Manor Renovation - Right Wing Window"
|
||||
manor_right_wing_roof = "Manor Renovation - Right Wing Rooftop"
|
||||
manor_right_big_base = "Manor Renovation - Right Big Base"
|
||||
manor_right_big_upper = "Manor Renovation - Right Big Upper"
|
||||
manor_right_big_roof = "Manor Renovation - Right Big Rooftop"
|
||||
manor_right_high_base = "Manor Renovation - Right High Base"
|
||||
manor_right_high_upper = "Manor Renovation - Right High Upper"
|
||||
manor_right_high_tower = "Manor Renovation - Right High Tower"
|
||||
manor_right_extension = "Manor Renovation - Right Extension"
|
||||
manor_right_tree = "Manor Renovation - Right Tree"
|
||||
manor_observatory_base = "Manor Renovation - Observatory Base"
|
||||
manor_observatory_scope = "Manor Renovation - Observatory Telescope"
|
||||
|
||||
# Boss Chest Definitions
|
||||
boss_castle = "Castle Hamson Boss"
|
||||
boss_forest = "Forest Abkhazia Boss"
|
||||
boss_tower = "The Maya Boss"
|
||||
boss_dungeon = "The Land of Darkness Boss"
|
||||
|
||||
# Special Room Definitions
|
||||
special_jukebox = "Jukebox"
|
||||
special_painting = "Painting"
|
||||
special_cheapskate = "Cheapskate Elf's Game"
|
||||
special_carnival = "Carnival"
|
||||
|
||||
# Shorthand Definitions
|
||||
diary = "Diary"
|
||||
|
||||
# Region Definitions
|
||||
outside = "Outside Castle Hamson"
|
||||
castle = "Castle Hamson"
|
||||
garden = "Forest Abkhazia"
|
||||
tower = "The Maya"
|
||||
dungeon = "The Land of Darkness"
|
||||
fountain = "Fountain Room"
|
||||
@@ -1,6 +1,6 @@
|
||||
import typing
|
||||
from typing import Dict
|
||||
|
||||
from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList, OptionSet
|
||||
from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionSet
|
||||
|
||||
|
||||
class StartingGender(Choice):
|
||||
@@ -63,9 +63,9 @@ class FairyChestsPerZone(Range):
|
||||
bonuses can be found in Fairy Chests.
|
||||
"""
|
||||
display_name = "Fairy Chests Per Zone"
|
||||
range_start = 5
|
||||
range_start = 0
|
||||
range_end = 15
|
||||
default = 5
|
||||
default = 1
|
||||
|
||||
|
||||
class ChestsPerZone(Range):
|
||||
@@ -74,9 +74,9 @@ class ChestsPerZone(Range):
|
||||
gold or stat bonuses can be found in Chests.
|
||||
"""
|
||||
display_name = "Chests Per Zone"
|
||||
range_start = 15
|
||||
range_end = 30
|
||||
default = 15
|
||||
range_start = 20
|
||||
range_end = 50
|
||||
default = 20
|
||||
|
||||
|
||||
class UniversalFairyChests(Toggle):
|
||||
@@ -111,8 +111,10 @@ class Architect(Choice):
|
||||
"""
|
||||
display_name = "Architect"
|
||||
option_start_unlocked = 0
|
||||
option_normal = 2
|
||||
option_early = 1
|
||||
option_anywhere = 2
|
||||
option_disabled = 3
|
||||
alias_normal = 2
|
||||
default = 2
|
||||
|
||||
|
||||
@@ -173,7 +175,7 @@ class NumberOfChildren(Range):
|
||||
default = 3
|
||||
|
||||
|
||||
class AdditionalNames(OptionList):
|
||||
class AdditionalNames(OptionSet):
|
||||
"""
|
||||
Set of additional names your potential offspring can have. If Allow Default Names is disabled, this is the only list
|
||||
of names your children can have. The first value will also be your initial character's name depending on Starting
|
||||
@@ -337,7 +339,8 @@ class AvailableClasses(OptionSet):
|
||||
default = {"Knight", "Mage", "Barbarian", "Knave", "Shinobi", "Miner", "Spellthief", "Lich", "Dragon", "Traitor"}
|
||||
valid_keys = {"Knight", "Mage", "Barbarian", "Knave", "Shinobi", "Miner", "Spellthief", "Lich", "Dragon", "Traitor"}
|
||||
|
||||
legacy_options: typing.Dict[str, type(Option)] = {
|
||||
|
||||
rl_options: Dict[str, type(Option)] = {
|
||||
"starting_gender": StartingGender,
|
||||
"starting_class": StartingClass,
|
||||
"available_classes": AvailableClasses,
|
||||
|
||||
@@ -1,72 +1,125 @@
|
||||
import typing
|
||||
from typing import Dict, List, NamedTuple, Optional
|
||||
|
||||
from BaseClasses import MultiWorld, Region, RegionType, Entrance, ItemClassification
|
||||
from .Items import LegacyItem
|
||||
from .Locations import LegacyLocation, diary_location_table, location_table, base_location_table
|
||||
from .Names import LocationName, ItemName
|
||||
|
||||
prog = ItemClassification.progression
|
||||
from BaseClasses import MultiWorld, Region, RegionType, Entrance
|
||||
from .Items import RLItem
|
||||
from .Locations import RLLocation, location_table, get_locations_by_category
|
||||
|
||||
|
||||
def create_regions(world, player: int):
|
||||
class RLRegionData(NamedTuple):
|
||||
locations: Optional[List[str]]
|
||||
exits: Optional[List[str]]
|
||||
|
||||
locations: typing.List[str] = []
|
||||
|
||||
# Add required locations.
|
||||
locations += [location for location in base_location_table]
|
||||
locations += [location for location in diary_location_table]
|
||||
def create_regions(world: MultiWorld, player: int):
|
||||
regions: Dict[str, RLRegionData] = {
|
||||
"Menu": RLRegionData(None, ["Castle Hamson"]),
|
||||
"The Manor": RLRegionData([], []),
|
||||
"Castle Hamson": RLRegionData([], ["Forest Abkhazia",
|
||||
"The Maya",
|
||||
"Land of Darkness",
|
||||
"The Fountain Room",
|
||||
"The Manor"]),
|
||||
"Forest Abkhazia": RLRegionData([], []),
|
||||
"The Maya": RLRegionData([], []),
|
||||
"Land of Darkness": RLRegionData([], []),
|
||||
"The Fountain Room": RLRegionData([], None),
|
||||
}
|
||||
|
||||
# Add chests per settings.
|
||||
if world.universal_fairy_chests[player]:
|
||||
fairies = int(world.fairy_chests_per_zone[player]) * 4
|
||||
for i in range(0, fairies):
|
||||
locations += [f"Fairy Chest {i + 1}"]
|
||||
else:
|
||||
fairies = int(world.fairy_chests_per_zone[player])
|
||||
for i in range(0, fairies):
|
||||
locations += [f"{LocationName.castle} - Fairy Chest {i + 1}"]
|
||||
locations += [f"{LocationName.garden} - Fairy Chest {i + 1}"]
|
||||
locations += [f"{LocationName.tower} - Fairy Chest {i + 1}"]
|
||||
locations += [f"{LocationName.dungeon} - Fairy Chest {i + 1}"]
|
||||
# Diaries
|
||||
for diary in range(0, 25):
|
||||
region: str
|
||||
if 0 <= diary < 6:
|
||||
region = "Castle Hamson"
|
||||
elif 6 <= diary < 12:
|
||||
region = "Forest Abkhazia"
|
||||
elif 12 <= diary < 18:
|
||||
region = "The Maya"
|
||||
elif 18 <= diary < 24:
|
||||
region = "Land of Darkness"
|
||||
else:
|
||||
region = "The Fountain Room"
|
||||
|
||||
if world.universal_chests[player]:
|
||||
chests = int(world.chests_per_zone[player]) * 4
|
||||
for i in range(0, chests):
|
||||
locations += [f"Chest {i + 1}"]
|
||||
else:
|
||||
chests = int(world.chests_per_zone[player])
|
||||
for i in range(0, chests):
|
||||
locations += [f"{LocationName.castle} - Chest {i + 1}"]
|
||||
locations += [f"{LocationName.garden} - Chest {i + 1}"]
|
||||
locations += [f"{LocationName.tower} - Chest {i + 1}"]
|
||||
locations += [f"{LocationName.dungeon} - Chest {i + 1}"]
|
||||
regions[region].locations.append(f"Diary {diary + 1}")
|
||||
|
||||
# Manor & Special
|
||||
for manor in get_locations_by_category("Manor").keys():
|
||||
regions["The Manor"].locations.append(manor)
|
||||
for special in get_locations_by_category("Special").keys():
|
||||
regions["Castle Hamson"].locations.append(special)
|
||||
|
||||
# Boss Rewards
|
||||
regions["Castle Hamson"].locations.append("Castle Hamson Boss Reward")
|
||||
regions["Forest Abkhazia"].locations.append("Forest Abkhazia Boss Reward")
|
||||
regions["The Maya"].locations.append("The Maya Boss Reward")
|
||||
regions["Land of Darkness"].locations.append("Land of Darkness Boss Reward")
|
||||
|
||||
# Events
|
||||
regions["Castle Hamson"].locations.append("Castle Hamson Boss Room")
|
||||
regions["Forest Abkhazia"].locations.append("Forest Abkhazia Boss Room")
|
||||
regions["The Maya"].locations.append("The Maya Boss Room")
|
||||
regions["Land of Darkness"].locations.append("Land of Darkness Boss Room")
|
||||
regions["The Fountain Room"].locations.append("Fountain Room")
|
||||
|
||||
# Chests
|
||||
chests = int(world.chests_per_zone[player])
|
||||
for i in range(0, chests):
|
||||
if world.universal_chests[player]:
|
||||
regions["Castle Hamson"].locations.append(f"Chest {i + 1}")
|
||||
regions["Forest Abkhazia"].locations.append(f"Chest {i + 1 + chests}")
|
||||
regions["The Maya"].locations.append(f"Chest {i + 1 + (chests * 2)}")
|
||||
regions["Land of Darkness"].locations.append(f"Chest {i + 1 + (chests * 3)}")
|
||||
else:
|
||||
regions["Castle Hamson"].locations.append(f"Castle Hamson - Chest {i + 1}")
|
||||
regions["Forest Abkhazia"].locations.append(f"Forest Abkhazia - Chest {i + 1}")
|
||||
regions["The Maya"].locations.append(f"The Maya - Chest {i + 1}")
|
||||
regions["Land of Darkness"].locations.append(f"Land of Darkness - Chest {i + 1}")
|
||||
|
||||
# Fairy Chests
|
||||
chests = int(world.fairy_chests_per_zone[player])
|
||||
for i in range(0, chests):
|
||||
if world.universal_fairy_chests[player]:
|
||||
regions["Castle Hamson"].locations.append(f"Fairy Chest {i + 1}")
|
||||
regions["Forest Abkhazia"].locations.append(f"Fairy Chest {i + 1 + chests}")
|
||||
regions["The Maya"].locations.append(f"Fairy Chest {i + 1 + (chests * 2)}")
|
||||
regions["Land of Darkness"].locations.append(f"Fairy Chest {i + 1 + (chests * 3)}")
|
||||
else:
|
||||
regions["Castle Hamson"].locations.append(f"Castle Hamson - Fairy Chest {i + 1}")
|
||||
regions["Forest Abkhazia"].locations.append(f"Forest Abkhazia - Fairy Chest {i + 1}")
|
||||
regions["The Maya"].locations.append(f"The Maya - Fairy Chest {i + 1}")
|
||||
regions["Land of Darkness"].locations.append(f"Land of Darkness - Fairy Chest {i + 1}")
|
||||
|
||||
# Set up the regions correctly.
|
||||
world.regions += [
|
||||
create_region(world, player, "Menu", None, [LocationName.outside]),
|
||||
create_region(world, player, LocationName.castle, locations),
|
||||
]
|
||||
for name, data in regions.items():
|
||||
world.regions.append(create_region(world, player, name, data.locations, data.exits))
|
||||
|
||||
# Connect entrances and set up events.
|
||||
world.get_entrance(LocationName.outside, player).connect(world.get_region(LocationName.castle, player))
|
||||
world.get_location(LocationName.castle, player).place_locked_item(LegacyItem(ItemName.boss_castle, prog, None, player))
|
||||
world.get_location(LocationName.garden, player).place_locked_item(LegacyItem(ItemName.boss_forest, prog, None, player))
|
||||
world.get_location(LocationName.tower, player).place_locked_item(LegacyItem(ItemName.boss_tower, prog, None, player))
|
||||
world.get_location(LocationName.dungeon, player).place_locked_item(LegacyItem(ItemName.boss_dungeon, prog, None, player))
|
||||
world.get_location(LocationName.fountain, player).place_locked_item(LegacyItem(ItemName.boss_fountain, prog, None, player))
|
||||
world.get_entrance("Castle Hamson", player).connect(world.get_region("Castle Hamson", player))
|
||||
world.get_entrance("The Manor", player).connect(world.get_region("The Manor", player))
|
||||
world.get_entrance("Forest Abkhazia", player).connect(world.get_region("Forest Abkhazia", player))
|
||||
world.get_entrance("The Maya", player).connect(world.get_region("The Maya", player))
|
||||
world.get_entrance("Land of Darkness", player).connect(world.get_region("Land of Darkness", player))
|
||||
world.get_entrance("The Fountain Room", player).connect(world.get_region("The Fountain Room", player))
|
||||
|
||||
|
||||
def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
# Shamelessly stolen from the ROR2 definition, lol
|
||||
ret = Region(name, RegionType.Generic, name, player)
|
||||
ret.world = world
|
||||
ret.multiworld = world
|
||||
if locations:
|
||||
for location in locations:
|
||||
loc_id = location_table.get(location, 0)
|
||||
location = LegacyLocation(player, location, loc_id, ret)
|
||||
for loc_name in locations:
|
||||
loc_data = location_table.get(loc_name)
|
||||
location = RLLocation(player, loc_name, loc_data.code if loc_data else None, ret)
|
||||
|
||||
# Special rule handling for fairy chests.
|
||||
if "Fairy" in loc_name:
|
||||
location.access_rule = lambda state: state.has("Dragons", player) or (
|
||||
state.has("Enchantress", player) and (
|
||||
state.has("Vault Runes", player) or
|
||||
state.has("Sprint Runes", player) or
|
||||
state.has("Sky Runes", player)))
|
||||
ret.locations.append(location)
|
||||
if exits:
|
||||
for exit in exits:
|
||||
ret.exits.append(Entrance(player, exit, ret))
|
||||
|
||||
entrance = Entrance(player, exit, ret)
|
||||
ret.exits.append(entrance)
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
@@ -1,177 +1,86 @@
|
||||
from BaseClasses import MultiWorld
|
||||
from .Names import LocationName, ItemName
|
||||
from BaseClasses import MultiWorld, CollectionState
|
||||
|
||||
from ..AutoWorld import LogicMixin
|
||||
from ..generic.Rules import set_rule
|
||||
|
||||
|
||||
class LegacyLogic(LogicMixin):
|
||||
def _legacy_has_any_vendors(self, player: int) -> bool:
|
||||
return self.has_any({ItemName.blacksmith, ItemName.enchantress}, player)
|
||||
def has_any_vendors(self: CollectionState, player: int) -> bool:
|
||||
return self.has_any({"Blacksmith", "Enchantress"}, player)
|
||||
|
||||
def _legacy_has_all_vendors(self, player: int) -> bool:
|
||||
return self.has_all({ItemName.blacksmith, ItemName.enchantress}, player)
|
||||
def has_all_vendors(self: CollectionState, player: int) -> bool:
|
||||
return self.has_all({"Blacksmith", "Enchantress"}, player)
|
||||
|
||||
def _legacy_has_stat_upgrades(self, player: int, amount: int) -> bool:
|
||||
return self._legacy_stat_upgrade_count(player) >= amount
|
||||
def has_stat_upgrades(self, player: int, amount: int) -> bool:
|
||||
return self.stat_upgrade_count(player) >= amount
|
||||
|
||||
def _legacy_total_stat_upgrades_count(self, player: int) -> int:
|
||||
return int(self.world.health_pool[player]) + \
|
||||
int(self.world.mana_pool[player]) + \
|
||||
int(self.world.attack_pool[player]) + \
|
||||
int(self.world.magic_damage_pool[player]) + \
|
||||
int(self.world.armor_pool[player]) + \
|
||||
int(self.world.equip_pool[player])
|
||||
def total_stat_upgrades_count(self, player: int) -> int:
|
||||
return int(self.multiworld.health_pool[player]) + \
|
||||
int(self.multiworld.mana_pool[player]) + \
|
||||
int(self.multiworld.attack_pool[player]) + \
|
||||
int(self.multiworld.magic_damage_pool[player])
|
||||
|
||||
def _legacy_stat_upgrade_count(self, player: int) -> int:
|
||||
return self.item_count(ItemName.health, player) + self.item_count(ItemName.mana, player) + \
|
||||
self.item_count(ItemName.attack, player) + self.item_count(ItemName.magic_damage, player) + \
|
||||
self.item_count(ItemName.armor, player) + self.item_count(ItemName.equip, player)
|
||||
def stat_upgrade_count(self: CollectionState, player: int) -> int:
|
||||
return self.item_count("Health Up", player) + self.item_count("Mana Up", player) + \
|
||||
self.item_count("Attack Up", player) + self.item_count("Magic Damage Up", player)
|
||||
|
||||
|
||||
def set_rules(world: MultiWorld, player: int):
|
||||
# Check for duplicate names.
|
||||
if len(set(world.additional_lady_names[player].value)) != len(world.additional_lady_names[player].value):
|
||||
raise Exception(f"Duplicate values are not allowed in additional_lady_names.")
|
||||
if len(set(world.additional_sir_names[player].value)) != len(world.additional_sir_names[player].value):
|
||||
raise Exception(f"Duplicate values are not allowed in additional_sir_names.")
|
||||
|
||||
if not world.allow_default_names[player]:
|
||||
# Check for quantity.
|
||||
name_count = len(world.additional_lady_names[player].value)
|
||||
if name_count < int(world.number_of_children[player]):
|
||||
raise Exception(f"allow_default_names is off, but not enough names are defined in additional_lady_names. Expected {int(world.number_of_children[player])}, Got {name_count}")
|
||||
|
||||
name_count = len(world.additional_sir_names[player].value)
|
||||
if name_count < int(world.number_of_children[player]):
|
||||
raise Exception(f"allow_default_names is off, but not enough names are defined in additional_sir_names. Expected {int(world.number_of_children[player])}, Got {name_count}")
|
||||
|
||||
# Chests
|
||||
if world.universal_chests[player]:
|
||||
for i in range(0, world.chests_per_zone[player]):
|
||||
set_rule(world.get_location(f"Chest {i + 1 + (world.chests_per_zone[player] * 1)}", player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(f"Chest {i + 1 + (world.chests_per_zone[player] * 2)}", player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(f"Chest {i + 1 + (world.chests_per_zone[player] * 3)}", player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
else:
|
||||
for i in range(0, world.chests_per_zone[player]):
|
||||
set_rule(world.get_location(f"{LocationName.garden} - Chest {i + 1}", player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(f"{LocationName.tower} - Chest {i + 1}", player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(f"{LocationName.dungeon} - Chest {i + 1}", player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
|
||||
# Fairy Chests
|
||||
if world.universal_fairy_chests[player]:
|
||||
for i in range(0, world.fairy_chests_per_zone[player]):
|
||||
set_rule(world.get_location(f"Fairy Chest {i + 1 + (world.fairy_chests_per_zone[player] * 1)}", player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(f"Fairy Chest {i + 1 + (world.fairy_chests_per_zone[player] * 2)}", player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(f"Fairy Chest {i + 1 + (world.fairy_chests_per_zone[player] * 3)}", player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
else:
|
||||
for i in range(0, world.fairy_chests_per_zone[player]):
|
||||
set_rule(world.get_location(f"{LocationName.garden} - Fairy Chest {i + 1}", player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(f"{LocationName.tower} - Fairy Chest {i + 1}", player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(f"{LocationName.dungeon} - Fairy Chest {i + 1}", player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
|
||||
# Vendors
|
||||
if world.vendors[player] == "early":
|
||||
set_rule(world.get_location(LocationName.boss_castle, player),
|
||||
lambda state: state._legacy_has_all_vendors(player))
|
||||
elif world.vendors[player] == "normal":
|
||||
set_rule(world.get_location(LocationName.garden, player),
|
||||
lambda state: state._legacy_has_any_vendors(player))
|
||||
|
||||
# Diaries
|
||||
for i in range(0, 5):
|
||||
set_rule(world.get_location(f"Diary {i + 6}", player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(f"Diary {i + 11}", player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(f"Diary {i + 16}", player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
set_rule(world.get_location(f"Diary {i + 21}", player),
|
||||
lambda state: state.has(ItemName.boss_dungeon, player))
|
||||
if world.vendors[player] == "normal":
|
||||
set_rule(world.get_location("Forest Abkhazia Boss Reward", player),
|
||||
lambda state: state.has_all_vendors(player))
|
||||
|
||||
# Scale each manor location.
|
||||
set_rule(world.get_location(LocationName.manor_left_wing_window, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_wing_roof, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_wing_window, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_wing_roof, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_big_base, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_big_base, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_tree1, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_tree2, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_tree, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_big_upper1, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_big_upper2, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_big_windows, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_big_roof, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_far_base, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_far_roof, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_extension, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_big_upper, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_big_roof, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_extension, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_high_base, player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_high_upper, player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_high_tower, player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
set_rule(world.get_location(LocationName.manor_observatory_base, player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
set_rule(world.get_location(LocationName.manor_observatory_scope, player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
manor_rules = {
|
||||
"Defeat Khidr" if world.khidr[player] == "vanilla" else "Defeat Neo Khidr": [
|
||||
"Manor - Left Wing Window",
|
||||
"Manor - Left Wing Rooftop",
|
||||
"Manor - Right Wing Window",
|
||||
"Manor - Right Wing Rooftop",
|
||||
"Manor - Left Big Base",
|
||||
"Manor - Right Big Base",
|
||||
"Manor - Left Tree 1",
|
||||
"Manor - Left Tree 2",
|
||||
"Manor - Right Tree",
|
||||
],
|
||||
"Defeat Alexander" if world.alexander[player] == "vanilla" else "Defeat Alexander IV": [
|
||||
"Manor - Left Big Upper 1",
|
||||
"Manor - Left Big Upper 2",
|
||||
"Manor - Left Big Windows",
|
||||
"Manor - Left Big Rooftop",
|
||||
"Manor - Left Far Base",
|
||||
"Manor - Left Far Roof",
|
||||
"Manor - Left Extension",
|
||||
"Manor - Right Big Upper",
|
||||
"Manor - Right Big Rooftop",
|
||||
"Manor - Right Extension",
|
||||
],
|
||||
"Defeat Ponce de Leon" if world.leon[player] == "vanilla" else "Defeat Ponce de Freon": [
|
||||
"Manor - Right High Base",
|
||||
"Manor - Right High Upper",
|
||||
"Manor - Right High Tower",
|
||||
"Manor - Observatory Base",
|
||||
"Manor - Observatory Telescope",
|
||||
]
|
||||
}
|
||||
|
||||
for event, locations in manor_rules.items():
|
||||
for location in locations:
|
||||
set_rule(world.get_location(location, player), lambda state: state.has(event, player))
|
||||
|
||||
# Standard Zone Progression
|
||||
set_rule(world.get_location(LocationName.garden, player),
|
||||
lambda state: state._legacy_has_stat_upgrades(player, 0.125 * state._legacy_total_stat_upgrades_count(player)) and state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.tower, player),
|
||||
lambda state: state._legacy_has_stat_upgrades(player, 0.3125 * state._legacy_total_stat_upgrades_count(player)) and state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.dungeon, player),
|
||||
lambda state: state._legacy_has_stat_upgrades(player, 0.5 * state._legacy_total_stat_upgrades_count(player)) and state.has(ItemName.boss_tower, player))
|
||||
world.get_entrance("Forest Abkhazia", player).access_rule = \
|
||||
(lambda state: state.has_stat_upgrades(player, 0.125 * state.total_stat_upgrades_count(player)) and
|
||||
(state.has("Defeat Khidr", player) or state.has("Defeat Neo Khidr", player)))
|
||||
world.get_entrance("The Maya", player).access_rule = \
|
||||
(lambda state: state.has_stat_upgrades(player, 0.25 * state.total_stat_upgrades_count(player)) and
|
||||
(state.has("Defeat Alexander", player) or state.has("Defeat Alexander IV", player)))
|
||||
world.get_entrance("Land of Darkness", player).access_rule = \
|
||||
(lambda state: state.has_stat_upgrades(player, 0.375 * state.total_stat_upgrades_count(player)) and
|
||||
(state.has("Defeat Ponce de Leon", player) or state.has("Defeat Ponce de Freon", player)))
|
||||
world.get_entrance("The Fountain Room", player).access_rule = \
|
||||
(lambda state: state.has_stat_upgrades(player, 0.5 * state.total_stat_upgrades_count(player)) and
|
||||
(state.has("Defeat Herodotus", player) or state.has("Defeat Astrodotus", player)))
|
||||
|
||||
# Bosses
|
||||
set_rule(world.get_location(LocationName.boss_castle, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.boss_forest, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.boss_tower, player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
set_rule(world.get_location(LocationName.boss_dungeon, player),
|
||||
lambda state: state.has(ItemName.boss_dungeon, player))
|
||||
set_rule(world.get_location(LocationName.fountain, player),
|
||||
lambda state: state._legacy_has_stat_upgrades(player, 0.625 * state._legacy_total_stat_upgrades_count(player))
|
||||
and state.has(ItemName.boss_castle, player)
|
||||
and state.has(ItemName.boss_forest, player)
|
||||
and state.has(ItemName.boss_tower, player)
|
||||
and state.has(ItemName.boss_dungeon, player))
|
||||
|
||||
world.completion_condition[player] = lambda state: state.has(ItemName.boss_fountain, player)
|
||||
world.completion_condition[player] = lambda state: state.has("Defeat The Fountain", player)
|
||||
|
||||
@@ -36,4 +36,3 @@ traits = [
|
||||
"Glaucoma",
|
||||
"Adopted",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,176 +1,265 @@
|
||||
import typing
|
||||
from typing import List
|
||||
|
||||
from BaseClasses import Item, ItemClassification, Tutorial
|
||||
from .Items import LegacyItem, ItemData, item_table, vendors_table, static_classes_table, progressive_classes_table, \
|
||||
skill_unlocks_table, blueprints_table, runes_table, misc_items_table
|
||||
from .Locations import LegacyLocation, location_table, base_location_table
|
||||
from .Options import legacy_options
|
||||
from BaseClasses import Tutorial
|
||||
from .Items import RLItem, RLItemData, event_item_table, item_table, get_items_by_category
|
||||
from .Locations import RLLocation, location_table
|
||||
from .Options import rl_options
|
||||
from .Regions import create_regions
|
||||
from .Rules import set_rules
|
||||
from .Names import ItemName
|
||||
from ..AutoWorld import World, WebWorld
|
||||
|
||||
|
||||
class LegacyWeb(WebWorld):
|
||||
class RLWeb(WebWorld):
|
||||
theme = "stone"
|
||||
tutorials = [Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
"A guide to setting up the Rogue Legacy Randomizer software on your computer. This guide covers single-player, multiworld, and related software.",
|
||||
"A guide to setting up the Rogue Legacy Randomizer software on your computer. This guide covers single-player, "
|
||||
"multiworld, and related software.",
|
||||
"English",
|
||||
"rogue-legacy_en.md",
|
||||
"rogue-legacy/en",
|
||||
["Phar"]
|
||||
)]
|
||||
bug_report_page = "https://github.com/ThePhar/RogueLegacyRandomizer/issues/new?assignees=&labels=bug&template=" \
|
||||
"report-an-issue---.md&title=%5BIssue%5D"
|
||||
|
||||
|
||||
class LegacyWorld(World):
|
||||
class RLWorld(World):
|
||||
"""
|
||||
Rogue Legacy is a genealogical rogue-"LITE" where anyone can be a hero. Each time you die, your child will succeed
|
||||
you. Every child is unique. One child might be colorblind, another might have vertigo-- they could even be a dwarf.
|
||||
But that's OK, because no one is perfect, and you don't have to be to succeed.
|
||||
"""
|
||||
game: str = "Rogue Legacy"
|
||||
option_definitions = legacy_options
|
||||
topology_present = False
|
||||
data_version = 3
|
||||
required_client_version = (0, 2, 3)
|
||||
web = LegacyWeb()
|
||||
option_definitions = rl_options
|
||||
topology_present = True
|
||||
data_version = 4
|
||||
required_client_version = (0, 3, 5)
|
||||
web = RLWeb()
|
||||
|
||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||
location_name_to_id = location_table
|
||||
location_name_to_id = {name: data.code for name, data in location_table.items()}
|
||||
|
||||
def _get_slot_data(self):
|
||||
return {
|
||||
"starting_gender": self.world.starting_gender[self.player],
|
||||
"starting_class": self.world.starting_class[self.player],
|
||||
"new_game_plus": self.world.new_game_plus[self.player],
|
||||
"fairy_chests_per_zone": self.world.fairy_chests_per_zone[self.player],
|
||||
"chests_per_zone": self.world.chests_per_zone[self.player],
|
||||
"universal_fairy_chests": self.world.universal_fairy_chests[self.player],
|
||||
"universal_chests": self.world.universal_chests[self.player],
|
||||
"vendors": self.world.vendors[self.player],
|
||||
"architect_fee": self.world.architect_fee[self.player],
|
||||
"disable_charon": self.world.disable_charon[self.player],
|
||||
"require_purchasing": self.world.require_purchasing[self.player],
|
||||
"gold_gain_multiplier": self.world.gold_gain_multiplier[self.player],
|
||||
"number_of_children": self.world.number_of_children[self.player],
|
||||
"khidr": self.world.khidr[self.player],
|
||||
"alexander": self.world.alexander[self.player],
|
||||
"leon": self.world.leon[self.player],
|
||||
"herodotus": self.world.herodotus[self.player],
|
||||
"allow_default_names": self.world.allow_default_names[self.player],
|
||||
"additional_sir_names": self.world.additional_sir_names[self.player],
|
||||
"additional_lady_names": self.world.additional_lady_names[self.player],
|
||||
"death_link": self.world.death_link[self.player],
|
||||
}
|
||||
item_pool: List[RLItem] = []
|
||||
prefill_items: List[RLItem] = []
|
||||
|
||||
def _create_items(self, name: str):
|
||||
data = item_table[name]
|
||||
return [self.create_item(name) for _ in range(data.quantity)]
|
||||
def setting(self, name: str):
|
||||
return getattr(self.multiworld, name)[self.player]
|
||||
|
||||
def fill_slot_data(self) -> dict:
|
||||
slot_data = self._get_slot_data()
|
||||
for option_name in legacy_options:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
slot_data[option_name] = option.value
|
||||
return {option_name: self.setting(option_name).value for option_name in rl_options}
|
||||
|
||||
return slot_data
|
||||
def generate_early(self):
|
||||
self.prefill_items = []
|
||||
|
||||
# Check validation of names.
|
||||
additional_lady_names = len(self.setting("additional_lady_names").value)
|
||||
additional_sir_names = len(self.setting("additional_sir_names").value)
|
||||
if not self.setting("allow_default_names"):
|
||||
# Check for max_quantity.
|
||||
if additional_lady_names < int(self.setting("number_of_children")):
|
||||
raise Exception(
|
||||
f"allow_default_names is off, but not enough names are defined in additional_lady_names. "
|
||||
f"Expected {int(self.setting('number_of_children'))}, Got {additional_lady_names}")
|
||||
|
||||
if additional_sir_names < int(self.setting("number_of_children")):
|
||||
raise Exception(
|
||||
f"allow_default_names is off, but not enough names are defined in additional_sir_names. "
|
||||
f"Expected {int(self.setting('number_of_children'))}, Got {additional_sir_names}")
|
||||
|
||||
if self.setting("vendors") == "early":
|
||||
self.prefill_items += [self.create_item("Blacksmith"), self.create_item("Enchantress")]
|
||||
|
||||
if self.setting("architect") == "early":
|
||||
self.prefill_items += [self.create_item("Architect")]
|
||||
|
||||
def generate_basic(self):
|
||||
itempool: typing.List[LegacyItem] = []
|
||||
total_required_locations = 64 + (self.world.chests_per_zone[self.player] * 4) + (self.world.fairy_chests_per_zone[self.player] * 4)
|
||||
self.item_pool = []
|
||||
total_locations = 64 + (self.setting("chests_per_zone") * 4) + (self.setting("fairy_chests_per_zone") * 4)
|
||||
|
||||
# Fill item pool with all required items
|
||||
for item in {**skill_unlocks_table, **runes_table}:
|
||||
# if Haggling, do not add if Disable Charon.
|
||||
if item == ItemName.haggling and self.world.disable_charon[self.player] == 1:
|
||||
# Add items to item pool. Anything with a "continue" will not be added to the item pool.
|
||||
for name, data in item_table.items():
|
||||
quantity = data.max_quantity
|
||||
|
||||
# Architect
|
||||
if name == "Architect":
|
||||
if self.setting("architect") == "disabled" or self.setting("architect") == "early":
|
||||
continue
|
||||
if self.setting("architect") == "start_unlocked":
|
||||
self.multiworld.push_precollected(self.create_item(name))
|
||||
continue
|
||||
|
||||
# Blacksmith and Enchantress
|
||||
if name == "Blacksmith" or name == "Enchantress":
|
||||
if self.setting("vendors") == "start_unlocked":
|
||||
self.multiworld.push_precollected(self.create_item(name))
|
||||
continue
|
||||
if self.setting("vendors") == "early":
|
||||
continue
|
||||
|
||||
# Haggling
|
||||
if name == "Haggling" and self.setting("disable_charon"):
|
||||
continue
|
||||
itempool += self._create_items(item)
|
||||
|
||||
# Blueprints
|
||||
if self.world.progressive_blueprints[self.player]:
|
||||
itempool += [self.create_item(ItemName.progressive_blueprints) for _ in range(15)]
|
||||
else:
|
||||
for item in blueprints_table:
|
||||
itempool += self._create_items(item)
|
||||
# Blueprints
|
||||
if data.category == "Blueprints":
|
||||
# No progressive blueprints if progressive_blueprints are disabled.
|
||||
if name == "Progressive Blueprints" and not self.setting("progressive_blueprints"):
|
||||
continue
|
||||
# No distinct blueprints if progressive_blueprints are enabled.
|
||||
elif name != "Progressive Blueprints" and self.setting("progressive_blueprints"):
|
||||
continue
|
||||
|
||||
# Check Pool settings to add a certain amount of these items.
|
||||
itempool += [self.create_item(ItemName.health) for _ in range(self.world.health_pool[self.player])]
|
||||
itempool += [self.create_item(ItemName.mana) for _ in range(self.world.mana_pool[self.player])]
|
||||
itempool += [self.create_item(ItemName.attack) for _ in range(self.world.attack_pool[self.player])]
|
||||
itempool += [self.create_item(ItemName.magic_damage) for _ in range(self.world.magic_damage_pool[self.player])]
|
||||
itempool += [self.create_item(ItemName.armor) for _ in range(self.world.armor_pool[self.player])]
|
||||
itempool += [self.create_item(ItemName.equip) for _ in range(self.world.equip_pool[self.player])]
|
||||
itempool += [self.create_item(ItemName.crit_chance) for _ in range(self.world.crit_chance_pool[self.player])]
|
||||
itempool += [self.create_item(ItemName.crit_damage) for _ in range(self.world.crit_damage_pool[self.player])]
|
||||
# Classes
|
||||
if data.category == "Classes":
|
||||
if name == "Progressive Knights":
|
||||
if "Knight" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
classes = self.world.available_classes[self.player]
|
||||
if "Dragon" in classes:
|
||||
itempool.append(self.create_item(ItemName.dragon))
|
||||
if "Traitor" in classes:
|
||||
itempool.append(self.create_item(ItemName.traitor))
|
||||
if self.world.starting_class[self.player] == "knight":
|
||||
itempool.append(self.create_item(ItemName.progressive_knight))
|
||||
elif "Knight" in classes:
|
||||
itempool.extend(self._create_items(ItemName.progressive_knight))
|
||||
if self.world.starting_class[self.player] == "mage":
|
||||
itempool.append(self.create_item(ItemName.progressive_mage))
|
||||
elif "Mage" in classes:
|
||||
itempool.extend(self._create_items(ItemName.progressive_mage))
|
||||
if self.world.starting_class[self.player] == "barbarian":
|
||||
itempool.append(self.create_item(ItemName.progressive_barbarian))
|
||||
elif "Barbarian" in classes:
|
||||
itempool.extend(self._create_items(ItemName.progressive_barbarian))
|
||||
if self.world.starting_class[self.player] == "knave":
|
||||
itempool.append(self.create_item(ItemName.progressive_knave))
|
||||
elif "Knave" in classes:
|
||||
itempool.extend(self._create_items(ItemName.progressive_knave))
|
||||
if self.world.starting_class[self.player] == "shinobi":
|
||||
itempool.append(self.create_item(ItemName.progressive_shinobi))
|
||||
elif "Shinobi" in classes:
|
||||
itempool.extend(self._create_items(ItemName.progressive_shinobi))
|
||||
if self.world.starting_class[self.player] == "miner":
|
||||
itempool.append(self.create_item(ItemName.progressive_miner))
|
||||
elif "Miner" in classes:
|
||||
itempool.extend(self._create_items(ItemName.progressive_miner))
|
||||
if self.world.starting_class[self.player] == "lich":
|
||||
itempool.append(self.create_item(ItemName.progressive_lich))
|
||||
elif "Lich" in classes:
|
||||
itempool.extend(self._create_items(ItemName.progressive_lich))
|
||||
if self.world.starting_class[self.player] == "spellthief":
|
||||
itempool.append(self.create_item(ItemName.progressive_spellthief))
|
||||
elif "Spellthief" in classes:
|
||||
itempool.extend(self._create_items(ItemName.progressive_spellthief))
|
||||
if self.setting("starting_class") == "knight":
|
||||
quantity = 1
|
||||
if name == "Progressive Mages":
|
||||
if "Mage" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
# Check if we need to start with these vendors or put them in the pool.
|
||||
if self.world.vendors[self.player] == "start_unlocked":
|
||||
self.world.push_precollected(self.world.create_item(ItemName.blacksmith, self.player))
|
||||
self.world.push_precollected(self.world.create_item(ItemName.enchantress, self.player))
|
||||
else:
|
||||
itempool += [self.create_item(ItemName.blacksmith), self.create_item(ItemName.enchantress)]
|
||||
if self.setting("starting_class") == "mage":
|
||||
quantity = 1
|
||||
if name == "Progressive Barbarians":
|
||||
if "Barbarian" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
# Add Architect.
|
||||
if self.world.architect[self.player] == "start_unlocked":
|
||||
self.world.push_precollected(self.world.create_item(ItemName.architect, self.player))
|
||||
elif self.world.architect[self.player] != "disabled":
|
||||
itempool.append(self.create_item(ItemName.architect))
|
||||
if self.setting("starting_class") == "barbarian":
|
||||
quantity = 1
|
||||
if name == "Progressive Knaves":
|
||||
if "Knave" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
# Fill item pool with the remaining
|
||||
for _ in range(len(itempool), total_required_locations):
|
||||
item = self.world.random.choice(list(misc_items_table.keys()))
|
||||
itempool.append(self.create_item(item))
|
||||
if self.setting("starting_class") == "knave":
|
||||
quantity = 1
|
||||
if name == "Progressive Miners":
|
||||
if "Miner" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
self.world.itempool += itempool
|
||||
if self.setting("starting_class") == "miner":
|
||||
quantity = 1
|
||||
if name == "Progressive Shinobis":
|
||||
if "Shinobi" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
if self.setting("starting_class") == "shinobi":
|
||||
quantity = 1
|
||||
if name == "Progressive Liches":
|
||||
if "Lich" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
if self.setting("starting_class") == "lich":
|
||||
quantity = 1
|
||||
if name == "Progressive Spellthieves":
|
||||
if "Spellthief" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
if self.setting("starting_class") == "spellthief":
|
||||
quantity = 1
|
||||
if name == "Dragons":
|
||||
if "Dragon" not in self.setting("available_classes"):
|
||||
continue
|
||||
if name == "Traitors":
|
||||
if "Traitor" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
# Skills
|
||||
if name == "Health Up":
|
||||
quantity = self.setting("health_pool")
|
||||
elif name == "Mana Up":
|
||||
quantity = self.setting("mana_pool")
|
||||
elif name == "Attack Up":
|
||||
quantity = self.setting("attack_pool")
|
||||
elif name == "Magic Damage Up":
|
||||
quantity = self.setting("magic_damage_pool")
|
||||
elif name == "Armor Up":
|
||||
quantity = self.setting("armor_pool")
|
||||
elif name == "Equip Up":
|
||||
quantity = self.setting("equip_pool")
|
||||
elif name == "Crit Chance Up":
|
||||
quantity = self.setting("crit_chance_pool")
|
||||
elif name == "Crit Damage Up":
|
||||
quantity = self.setting("crit_damage_pool")
|
||||
|
||||
# Ignore filler, it will be added in a later stage.
|
||||
if data.category == "Filler":
|
||||
continue
|
||||
|
||||
self.item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
|
||||
# Fill any empty locations with filler items.
|
||||
while len(self.item_pool) + len(self.prefill_items) < total_locations:
|
||||
self.item_pool.append(self.create_item(self.get_filler_item_name()))
|
||||
|
||||
self.multiworld.itempool += self.item_pool
|
||||
|
||||
def pre_fill(self) -> None:
|
||||
reachable = [loc for loc in self.multiworld.get_reachable_locations(player=self.player) if not loc.item]
|
||||
self.multiworld.random.shuffle(reachable)
|
||||
items = self.prefill_items.copy()
|
||||
for item in items:
|
||||
reachable.pop().place_locked_item(item)
|
||||
|
||||
def get_pre_fill_items(self) -> List[RLItem]:
|
||||
return self.prefill_items
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice(list(misc_items_table.keys()))
|
||||
fillers = get_items_by_category("Filler")
|
||||
weights = [data.weight for data in fillers.values()]
|
||||
return self.multiworld.random.choices([filler for filler in fillers.keys()], weights, k=1)[0]
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.world, self.player)
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
def create_item(self, name: str) -> RLItem:
|
||||
data = item_table[name]
|
||||
return LegacyItem(name, ItemClassification.progression if data.progression else ItemClassification.filler, data.code, self.player)
|
||||
return RLItem(name, data.classification, data.code, self.player)
|
||||
|
||||
def create_event(self, name: str) -> RLItem:
|
||||
data = event_item_table[name]
|
||||
return RLItem(name, data.classification, data.code, self.player)
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.multiworld, self.player)
|
||||
self._place_events()
|
||||
|
||||
def _place_events(self):
|
||||
# Fountain
|
||||
self.multiworld.get_location("Fountain Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat The Fountain"))
|
||||
|
||||
# Khidr / Neo Khidr
|
||||
if self.setting("khidr") == "vanilla":
|
||||
self.multiworld.get_location("Castle Hamson Boss Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat Khidr"))
|
||||
else:
|
||||
self.multiworld.get_location("Castle Hamson Boss Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat Neo Khidr"))
|
||||
|
||||
# Alexander / Alexander IV
|
||||
if self.setting("alexander") == "vanilla":
|
||||
self.multiworld.get_location("Forest Abkhazia Boss Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat Alexander"))
|
||||
else:
|
||||
self.multiworld.get_location("Forest Abkhazia Boss Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat Alexander IV"))
|
||||
|
||||
# Ponce de Leon / Ponce de Freon
|
||||
if self.setting("leon") == "vanilla":
|
||||
self.multiworld.get_location("The Maya Boss Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat Ponce de Leon"))
|
||||
else:
|
||||
self.multiworld.get_location("The Maya Boss Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat Ponce de Freon"))
|
||||
|
||||
# Herodotus / Astrodotus
|
||||
if self.setting("herodotus") == "vanilla":
|
||||
self.multiworld.get_location("Land of Darkness Boss Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat Herodotus"))
|
||||
else:
|
||||
self.multiworld.get_location("Land of Darkness Boss Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat Astrodotus"))
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
|
||||
## Where is the settings page?
|
||||
|
||||
The [player settings page for this game](../player-settings) is located contains all the options you need to configure
|
||||
and export a config file.
|
||||
The [player settings page for this game](../player-settings) contains most of the options you need to
|
||||
configure and export a config file. Some settings can only be made via YAML, but an explaination can be found in the
|
||||
[template yaml here](../../../static/generated/configs/Rogue%20Legacy.yaml).
|
||||
|
||||
## What does randomization do to this game?
|
||||
|
||||
You are not able to buy skill upgrades in the manor upgrade screen, and instead, need to find them in order to level up
|
||||
your character to make fighting the 5 bosses easier.
|
||||
Rogue Legacy Randomizer takes all the classes, skills, runes, and blueprints and spreads them out into chests, the manor
|
||||
upgrade screen, bosses, and some special individual locations. The goal is to become powerful enough to defeat the four
|
||||
zone bosses and then defeat The Fountain.
|
||||
|
||||
## What items and locations get shuffled?
|
||||
|
||||
@@ -16,6 +18,8 @@ All the skill upgrades, class upgrades, runes packs, and equipment packs are shu
|
||||
checks, chests and fairy chests, and boss rewards. Skill upgrades are also grouped in packs of 5 to make the finding of
|
||||
stats less of a chore. Runes and Equipment are also grouped together.
|
||||
|
||||
Some additional locations that can contain items are the Jukebox, the Portraits, and the mini-game rewards.
|
||||
|
||||
## Which items can be in another player's world?
|
||||
|
||||
Any of the items which can be shuffled may also be placed into another player's world. It is possible to choose to limit
|
||||
@@ -25,3 +29,8 @@ certain items to your own world.
|
||||
|
||||
When the player receives an item, your character will hold the item above their head and display it to the world. It's
|
||||
good for business!
|
||||
|
||||
## What do I do if I encounter a bug with the game?
|
||||
|
||||
Please reach out to Phar#4444 on Discord or you can drop a bug report on the
|
||||
[GitHub page for Rogue Legacy Randomizer](https://github.com/ThePhar/RogueLegacyRandomizer/issues/new?assignees=&labels=bug&template=report-an-issue---.md&title=%5BIssue%5D).
|
||||
@@ -28,25 +28,8 @@ provides an alternative one to the default values.
|
||||
Once you have entered the required values, you go to Connect and then select Confirm on the "Ready to Start" screen. Now
|
||||
you're off to start your legacy!
|
||||
|
||||
## Manual Installation
|
||||
## Recommended Installation Instructions
|
||||
|
||||
In order to run Rogue Legacy Randomizer you will need to have Rogue Legacy installed on your local machine. Extract the
|
||||
Randomizer release into a desired folder **outside** of your Rogue Legacy install. Copy the following files from your
|
||||
Rogue Legacy install into the main directory of your Rogue Legacy Randomizer install:
|
||||
|
||||
- DS2DEngine.dll
|
||||
- InputSystem.dll
|
||||
- Nuclex.Input.dll
|
||||
- SpriteSystem.dll
|
||||
- Tweener.dll
|
||||
|
||||
And copy the directory from your Rogue Legacy install as well into the main directory of your Rogue Legacy Randomizer
|
||||
install:
|
||||
|
||||
- Content/
|
||||
|
||||
Then copy the contents of the CustomContent directory in your Rogue Legacy Randomizer into the newly copied Content
|
||||
directory and overwrite all files.
|
||||
|
||||
**BE SURE YOU ARE REPLACING THE COPIED FILES IN YOUR ROGUE LEGACY RANDOMIZER DIRECTORY AND NOT REPLACING YOUR ROGUE
|
||||
LEGACY FILES!**
|
||||
Please read the README file on the
|
||||
[Rogue Legacy Randomizer GitHub](https://github.com/ThePhar/RogueLegacyRandomizer/blob/master/README.md) page for up to
|
||||
date installation instructions.
|
||||
|
||||
@@ -42,40 +42,40 @@ class RiskOfRainWorld(World):
|
||||
|
||||
def generate_early(self) -> None:
|
||||
# figure out how many revivals should exist in the pool
|
||||
self.total_revivals = int(self.world.total_revivals[self.player].value / 100 *
|
||||
self.world.total_locations[self.player].value)
|
||||
self.total_revivals = int(self.multiworld.total_revivals[self.player].value / 100 *
|
||||
self.multiworld.total_locations[self.player].value)
|
||||
|
||||
def generate_basic(self) -> None:
|
||||
# shortcut for starting_inventory... The start_with_revive option lets you start with a Dio's Best Friend
|
||||
if self.world.start_with_revive[self.player].value:
|
||||
self.world.push_precollected(self.world.create_item("Dio's Best Friend", self.player))
|
||||
if self.multiworld.start_with_revive[self.player].value:
|
||||
self.multiworld.push_precollected(self.multiworld.create_item("Dio's Best Friend", self.player))
|
||||
|
||||
# if presets are enabled generate junk_pool from the selected preset
|
||||
pool_option = self.world.item_weights[self.player].value
|
||||
pool_option = self.multiworld.item_weights[self.player].value
|
||||
junk_pool: Dict[str, int] = {}
|
||||
if self.world.item_pool_presets[self.player]:
|
||||
if self.multiworld.item_pool_presets[self.player]:
|
||||
# generate chaos weights if the preset is chosen
|
||||
if pool_option == ItemWeights.option_chaos:
|
||||
for name, max_value in item_pool_weights[pool_option].items():
|
||||
junk_pool[name] = self.world.random.randint(0, max_value)
|
||||
junk_pool[name] = self.multiworld.random.randint(0, max_value)
|
||||
else:
|
||||
junk_pool = item_pool_weights[pool_option].copy()
|
||||
else: # generate junk pool from user created presets
|
||||
junk_pool = {
|
||||
"Item Scrap, Green": self.world.green_scrap[self.player].value,
|
||||
"Item Scrap, Red": self.world.red_scrap[self.player].value,
|
||||
"Item Scrap, Yellow": self.world.yellow_scrap[self.player].value,
|
||||
"Item Scrap, White": self.world.white_scrap[self.player].value,
|
||||
"Common Item": self.world.common_item[self.player].value,
|
||||
"Uncommon Item": self.world.uncommon_item[self.player].value,
|
||||
"Legendary Item": self.world.legendary_item[self.player].value,
|
||||
"Boss Item": self.world.boss_item[self.player].value,
|
||||
"Lunar Item": self.world.lunar_item[self.player].value,
|
||||
"Equipment": self.world.equipment[self.player].value
|
||||
"Item Scrap, Green": self.multiworld.green_scrap[self.player].value,
|
||||
"Item Scrap, Red": self.multiworld.red_scrap[self.player].value,
|
||||
"Item Scrap, Yellow": self.multiworld.yellow_scrap[self.player].value,
|
||||
"Item Scrap, White": self.multiworld.white_scrap[self.player].value,
|
||||
"Common Item": self.multiworld.common_item[self.player].value,
|
||||
"Uncommon Item": self.multiworld.uncommon_item[self.player].value,
|
||||
"Legendary Item": self.multiworld.legendary_item[self.player].value,
|
||||
"Boss Item": self.multiworld.boss_item[self.player].value,
|
||||
"Lunar Item": self.multiworld.lunar_item[self.player].value,
|
||||
"Equipment": self.multiworld.equipment[self.player].value
|
||||
}
|
||||
|
||||
# remove lunar items from the pool if they're disabled in the yaml unless lunartic is rolled
|
||||
if not (self.world.enable_lunar[self.player] or pool_option == ItemWeights.option_lunartic):
|
||||
if not (self.multiworld.enable_lunar[self.player] or pool_option == ItemWeights.option_lunartic):
|
||||
junk_pool.pop("Lunar Item")
|
||||
|
||||
# Generate item pool
|
||||
@@ -84,38 +84,38 @@ class RiskOfRainWorld(World):
|
||||
itempool += ["Dio's Best Friend"] * self.total_revivals
|
||||
|
||||
# Fill remaining items with randomly generated junk
|
||||
itempool += self.world.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()),
|
||||
k=self.world.total_locations[self.player].value - self.total_revivals)
|
||||
itempool += self.multiworld.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()),
|
||||
k=self.multiworld.total_locations[self.player].value - self.total_revivals)
|
||||
|
||||
# Convert itempool into real items
|
||||
itempool = list(map(lambda name: self.create_item(name), itempool))
|
||||
|
||||
self.world.itempool += itempool
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
def set_rules(self) -> None:
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
def create_regions(self) -> None:
|
||||
menu = create_region(self.world, self.player, "Menu")
|
||||
petrichor = create_region(self.world, self.player, "Petrichor V",
|
||||
[f"ItemPickup{i + 1}" for i in range(self.world.total_locations[self.player].value)])
|
||||
menu = create_region(self.multiworld, self.player, "Menu")
|
||||
petrichor = create_region(self.multiworld, self.player, "Petrichor V",
|
||||
[f"ItemPickup{i + 1}" for i in range(self.multiworld.total_locations[self.player].value)])
|
||||
|
||||
connection = Entrance(self.player, "Lobby", menu)
|
||||
menu.exits.append(connection)
|
||||
connection.connect(petrichor)
|
||||
|
||||
self.world.regions += [menu, petrichor]
|
||||
self.multiworld.regions += [menu, petrichor]
|
||||
|
||||
create_events(self.world, self.player)
|
||||
create_events(self.multiworld, self.player)
|
||||
|
||||
def fill_slot_data(self):
|
||||
return {
|
||||
"itemPickupStep": self.world.item_pickup_step[self.player].value,
|
||||
"seed": "".join(self.world.slot_seeds[self.player].choice(string.digits) for _ in range(16)),
|
||||
"totalLocations": self.world.total_locations[self.player].value,
|
||||
"totalRevivals": self.world.total_revivals[self.player].value,
|
||||
"startWithDio": self.world.start_with_revive[self.player].value,
|
||||
"FinalStageDeath": self.world.final_stage_death[self.player].value
|
||||
"itemPickupStep": self.multiworld.item_pickup_step[self.player].value,
|
||||
"seed": "".join(self.multiworld.slot_seeds[self.player].choice(string.digits) for _ in range(16)),
|
||||
"totalLocations": self.multiworld.total_locations[self.player].value,
|
||||
"totalRevivals": self.multiworld.total_revivals[self.player].value,
|
||||
"startWithDio": self.multiworld.start_with_revive[self.player].value,
|
||||
"FinalStageDeath": self.multiworld.final_stage_death[self.player].value
|
||||
}
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
|
||||
@@ -690,7 +690,7 @@ def connect_regions(world, player, gates: typing.List[LevelGate], cannon_core_em
|
||||
def create_region(world: MultiWorld, player: int, active_locations, name: str, locations=None, exits=None):
|
||||
# Shamelessly stolen from the ROR2 definition
|
||||
ret = Region(name, None, name, player)
|
||||
ret.world = world
|
||||
ret.multiworld = world
|
||||
if locations:
|
||||
for location in locations:
|
||||
loc_id = active_locations.get(location, 0)
|
||||
|
||||
@@ -69,15 +69,15 @@ class SA2BWorld(World):
|
||||
return {
|
||||
"ModVersion": 101,
|
||||
"MusicMap": self.music_map,
|
||||
"MusicShuffle": self.world.music_shuffle[self.player].value,
|
||||
"RequiredRank": self.world.required_rank[self.player].value,
|
||||
"ChaoRaceChecks": self.world.chao_race_checks[self.player].value,
|
||||
"ChaoGardenDifficulty": self.world.chao_garden_difficulty[self.player].value,
|
||||
"DeathLink": self.world.death_link[self.player].value,
|
||||
"IncludeMissions": self.world.include_missions[self.player].value,
|
||||
"EmblemPercentageForCannonsCore": self.world.emblem_percentage_for_cannons_core[self.player].value,
|
||||
"NumberOfLevelGates": self.world.number_of_level_gates[self.player].value,
|
||||
"LevelGateDistribution": self.world.level_gate_distribution[self.player].value,
|
||||
"MusicShuffle": self.multiworld.music_shuffle[self.player].value,
|
||||
"RequiredRank": self.multiworld.required_rank[self.player].value,
|
||||
"ChaoRaceChecks": self.multiworld.chao_race_checks[self.player].value,
|
||||
"ChaoGardenDifficulty": self.multiworld.chao_garden_difficulty[self.player].value,
|
||||
"DeathLink": self.multiworld.death_link[self.player].value,
|
||||
"IncludeMissions": self.multiworld.include_missions[self.player].value,
|
||||
"EmblemPercentageForCannonsCore": self.multiworld.emblem_percentage_for_cannons_core[self.player].value,
|
||||
"NumberOfLevelGates": self.multiworld.number_of_level_gates[self.player].value,
|
||||
"LevelGateDistribution": self.multiworld.level_gate_distribution[self.player].value,
|
||||
"EmblemsForCannonsCore": self.emblems_for_cannons_core,
|
||||
"RegionEmblemMap": self.region_emblem_map,
|
||||
"GateCosts": self.gate_costs,
|
||||
@@ -92,14 +92,14 @@ class SA2BWorld(World):
|
||||
slot_data = self._get_slot_data()
|
||||
slot_data["MusicMap"] = self.music_map
|
||||
for option_name in sa2b_options:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
slot_data[option_name] = option.value
|
||||
|
||||
return slot_data
|
||||
|
||||
def get_levels_per_gate(self) -> list:
|
||||
levels_per_gate = list()
|
||||
max_gate_index = self.world.number_of_level_gates[self.player]
|
||||
max_gate_index = self.multiworld.number_of_level_gates[self.player]
|
||||
average_level_count = 30 / (max_gate_index + 1)
|
||||
levels_added = 0
|
||||
|
||||
@@ -112,8 +112,8 @@ class SA2BWorld(World):
|
||||
levels_added += 1
|
||||
additional_count_iterator += 1 if additional_count_iterator < max_gate_index else -max_gate_index
|
||||
|
||||
if self.world.level_gate_distribution[self.player] == 0 or self.world.level_gate_distribution[self.player] == 2:
|
||||
early_distribution = self.world.level_gate_distribution[self.player] == 0
|
||||
if self.multiworld.level_gate_distribution[self.player] == 0 or self.multiworld.level_gate_distribution[self.player] == 2:
|
||||
early_distribution = self.multiworld.level_gate_distribution[self.player] == 0
|
||||
levels_to_distribute = 5
|
||||
gate_index_offset = 0
|
||||
while levels_to_distribute > 0:
|
||||
@@ -134,10 +134,10 @@ class SA2BWorld(World):
|
||||
return levels_per_gate
|
||||
|
||||
def generate_early(self):
|
||||
self.gate_bosses = get_gate_bosses(self.world, self.player)
|
||||
self.gate_bosses = get_gate_bosses(self.multiworld, self.player)
|
||||
|
||||
def generate_basic(self):
|
||||
self.world.get_location(LocationName.biolizard, self.player).place_locked_item(self.create_item(ItemName.maria))
|
||||
self.multiworld.get_location(LocationName.biolizard, self.player).place_locked_item(self.create_item(ItemName.maria))
|
||||
|
||||
itempool: typing.List[SA2BItem] = []
|
||||
|
||||
@@ -155,20 +155,20 @@ class SA2BWorld(World):
|
||||
extra_junk_count = raw_emblem_count - total_emblem_count
|
||||
|
||||
self.emblems_for_cannons_core = math.floor(
|
||||
total_emblem_count * (self.world.emblem_percentage_for_cannons_core[self.player].value / 100.0))
|
||||
total_emblem_count * (self.multiworld.emblem_percentage_for_cannons_core[self.player].value / 100.0))
|
||||
|
||||
gate_cost_mult = 1.0
|
||||
if self.world.level_gate_costs[self.player].value == 0:
|
||||
if self.multiworld.level_gate_costs[self.player].value == 0:
|
||||
gate_cost_mult = 0.6
|
||||
elif self.world.level_gate_costs[self.player].value == 1:
|
||||
elif self.multiworld.level_gate_costs[self.player].value == 1:
|
||||
gate_cost_mult = 0.8
|
||||
|
||||
shuffled_region_list = list(range(30))
|
||||
emblem_requirement_list = list()
|
||||
self.world.random.shuffle(shuffled_region_list)
|
||||
self.multiworld.random.shuffle(shuffled_region_list)
|
||||
levels_per_gate = self.get_levels_per_gate()
|
||||
|
||||
check_for_impossible_shuffle(shuffled_region_list, math.ceil(levels_per_gate[0]), self.world)
|
||||
check_for_impossible_shuffle(shuffled_region_list, math.ceil(levels_per_gate[0]), self.multiworld)
|
||||
levels_added_to_gate = 0
|
||||
total_levels_added = 0
|
||||
current_gate = 0
|
||||
@@ -184,8 +184,8 @@ class SA2BWorld(World):
|
||||
total_levels_added += 1
|
||||
if levels_added_to_gate >= levels_per_gate[current_gate]:
|
||||
current_gate += 1
|
||||
if current_gate > self.world.number_of_level_gates[self.player].value:
|
||||
current_gate = self.world.number_of_level_gates[self.player].value
|
||||
if current_gate > self.multiworld.number_of_level_gates[self.player].value:
|
||||
current_gate = self.multiworld.number_of_level_gates[self.player].value
|
||||
else:
|
||||
current_gate_emblems = max(
|
||||
math.floor(total_emblem_count * math.pow(total_levels_added / 30.0, 2.0) * gate_cost_mult), current_gate)
|
||||
@@ -195,60 +195,60 @@ class SA2BWorld(World):
|
||||
|
||||
self.region_emblem_map = dict(zip(shuffled_region_list, emblem_requirement_list))
|
||||
|
||||
connect_regions(self.world, self.player, gates, self.emblems_for_cannons_core, self.gate_bosses)
|
||||
connect_regions(self.multiworld, self.player, gates, self.emblems_for_cannons_core, self.gate_bosses)
|
||||
|
||||
max_required_emblems = max(max(emblem_requirement_list), self.emblems_for_cannons_core)
|
||||
itempool += [self.create_item(ItemName.emblem) for _ in range(max_required_emblems)]
|
||||
|
||||
non_required_emblems = (total_emblem_count - max_required_emblems)
|
||||
junk_count = math.floor(non_required_emblems * (self.world.junk_fill_percentage[self.player].value / 100.0))
|
||||
junk_count = math.floor(non_required_emblems * (self.multiworld.junk_fill_percentage[self.player].value / 100.0))
|
||||
itempool += [self.create_item(ItemName.emblem, True) for _ in range(non_required_emblems - junk_count)]
|
||||
|
||||
# Carve Traps out of junk_count
|
||||
trap_weights = []
|
||||
trap_weights += ([ItemName.omochao_trap] * self.world.omochao_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.timestop_trap] * self.world.timestop_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.confuse_trap] * self.world.confusion_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.tiny_trap] * self.world.tiny_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.omochao_trap] * self.multiworld.omochao_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.timestop_trap] * self.multiworld.timestop_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.confuse_trap] * self.multiworld.confusion_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.tiny_trap] * self.multiworld.tiny_trap_weight[self.player].value)
|
||||
|
||||
junk_count += extra_junk_count
|
||||
trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.world.trap_fill_percentage[self.player].value / 100.0))
|
||||
trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.multiworld.trap_fill_percentage[self.player].value / 100.0))
|
||||
junk_count -= trap_count
|
||||
|
||||
junk_pool = []
|
||||
junk_keys = list(junk_table.keys())
|
||||
for i in range(junk_count):
|
||||
junk_item = self.world.random.choice(junk_keys)
|
||||
junk_item = self.multiworld.random.choice(junk_keys)
|
||||
junk_pool.append(self.create_item(junk_item))
|
||||
|
||||
itempool += junk_pool
|
||||
|
||||
trap_pool = []
|
||||
for i in range(trap_count):
|
||||
trap_item = self.world.random.choice(trap_weights)
|
||||
trap_item = self.multiworld.random.choice(trap_weights)
|
||||
trap_pool.append(self.create_item(trap_item))
|
||||
|
||||
itempool += trap_pool
|
||||
|
||||
self.world.itempool += itempool
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
# Music Shuffle
|
||||
if self.world.music_shuffle[self.player] == "levels":
|
||||
if self.multiworld.music_shuffle[self.player] == "levels":
|
||||
musiclist_o = list(range(0, 47))
|
||||
musiclist_s = musiclist_o.copy()
|
||||
self.world.random.shuffle(musiclist_s)
|
||||
self.multiworld.random.shuffle(musiclist_s)
|
||||
self.music_map = dict(zip(musiclist_o, musiclist_s))
|
||||
elif self.world.music_shuffle[self.player] == "full":
|
||||
elif self.multiworld.music_shuffle[self.player] == "full":
|
||||
musiclist_o = list(range(0, 78))
|
||||
musiclist_s = musiclist_o.copy()
|
||||
self.world.random.shuffle(musiclist_s)
|
||||
self.multiworld.random.shuffle(musiclist_s)
|
||||
self.music_map = dict(zip(musiclist_o, musiclist_s))
|
||||
else:
|
||||
self.music_map = dict()
|
||||
|
||||
def create_regions(self):
|
||||
self.location_table = setup_locations(self.world, self.player)
|
||||
create_regions(self.world, self.player, self.location_table)
|
||||
self.location_table = setup_locations(self.multiworld, self.player)
|
||||
create_regions(self.multiworld, self.player, self.location_table)
|
||||
|
||||
def create_item(self, name: str, force_non_progression=False) -> Item:
|
||||
data = item_table[name]
|
||||
@@ -269,12 +269,12 @@ class SA2BWorld(World):
|
||||
return created_item
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player, self.gate_bosses)
|
||||
set_rules(self.multiworld, self.player, self.gate_bosses)
|
||||
|
||||
def write_spoiler(self, spoiler_handle: typing.TextIO):
|
||||
spoiler_handle.write("\n")
|
||||
header_text = "Sonic Adventure 2 Bosses for {}:\n"
|
||||
header_text = header_text.format(self.world.player_name[self.player])
|
||||
header_text = header_text.format(self.multiworld.player_name[self.player])
|
||||
spoiler_handle.write(header_text)
|
||||
for x in range(len(self.gate_bosses.values())):
|
||||
text = "Gate {0} Boss: {1}\n"
|
||||
|
||||
@@ -236,7 +236,7 @@ def create_location(player: int, location_data: LocationData, region: Region,
|
||||
def create_region(world: MultiWorld, player: int, locations_per_region: Dict[str, List[LocationData]],
|
||||
location_cache: List[Location], name: str) -> Region:
|
||||
region = Region(name, RegionType.Generic, name, player)
|
||||
region.world = world
|
||||
region.multiworld = world
|
||||
|
||||
if name in locations_per_region:
|
||||
for location_data in locations_per_region[name]:
|
||||
|
||||
@@ -59,31 +59,31 @@ class SC2WoLWorld(World):
|
||||
|
||||
def create_regions(self):
|
||||
self.mission_req_table, self.final_mission_id, self.victory_item = create_regions(
|
||||
self.world, self.player, get_locations(self.world, self.player), self.location_cache
|
||||
self.multiworld, self.player, get_locations(self.multiworld, self.player), self.location_cache
|
||||
)
|
||||
|
||||
def generate_basic(self):
|
||||
excluded_items = get_excluded_items(self, self.world, self.player)
|
||||
excluded_items = get_excluded_items(self, self.multiworld, self.player)
|
||||
|
||||
starter_items = assign_starter_items(self.world, self.player, excluded_items, self.locked_locations)
|
||||
starter_items = assign_starter_items(self.multiworld, self.player, excluded_items, self.locked_locations)
|
||||
|
||||
pool = get_item_pool(self.world, self.player, self.mission_req_table, starter_items, excluded_items, self.location_cache)
|
||||
pool = get_item_pool(self.multiworld, self.player, self.mission_req_table, starter_items, excluded_items, self.location_cache)
|
||||
|
||||
fill_item_pool_with_dummy_items(self, self.world, self.player, self.locked_locations, self.location_cache, pool)
|
||||
fill_item_pool_with_dummy_items(self, self.multiworld, self.player, self.locked_locations, self.location_cache, pool)
|
||||
|
||||
self.world.itempool += pool
|
||||
self.multiworld.itempool += pool
|
||||
|
||||
def set_rules(self):
|
||||
setup_events(self.world, self.player, self.locked_locations, self.location_cache)
|
||||
self.world.completion_condition[self.player] = lambda state: state.has(self.victory_item, self.player)
|
||||
setup_events(self.multiworld, self.player, self.locked_locations, self.location_cache)
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has(self.victory_item, self.player)
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice(filler_items)
|
||||
return self.multiworld.random.choice(filler_items)
|
||||
|
||||
def fill_slot_data(self):
|
||||
slot_data = {}
|
||||
for option_name in sc2wol_options:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
if type(option.value) in {str, int}:
|
||||
slot_data[option_name] = int(option.value)
|
||||
slot_req_table = {}
|
||||
|
||||
@@ -4,7 +4,7 @@ import time
|
||||
|
||||
from NetUtils import ClientStatus, color
|
||||
from worlds.AutoSNIClient import SNIClient
|
||||
from .Rom import ROM_PLAYER_LIMIT as SM_ROM_PLAYER_LIMIT
|
||||
from .Rom import SM_ROM_MAX_PLAYERID
|
||||
|
||||
snes_logger = logging.getLogger("SNES")
|
||||
|
||||
@@ -143,7 +143,7 @@ class SMSNIClient(SNIClient):
|
||||
else:
|
||||
location_id = 0x00 #backward compat
|
||||
|
||||
player_id = item.player if item.player <= SM_ROM_PLAYER_LIMIT else 0
|
||||
player_id = item.player if item.player <= SM_ROM_MAX_PLAYERID else 0
|
||||
snes_buffered_write(ctx, SM_RECV_QUEUE_START + item_out_ptr * 4, bytes(
|
||||
[player_id & 0xFF, (player_id >> 8) & 0xFF, item_id & 0xFF, location_id & 0xFF]))
|
||||
item_out_ptr += 1
|
||||
|
||||
@@ -7,8 +7,8 @@ from Utils import read_snes_rom
|
||||
from worlds.Files import APDeltaPatch
|
||||
|
||||
SMJUHASH = '21f3e98df4780ee1c667b84e57d88675'
|
||||
ROM_PLAYER_LIMIT = 65535 # max archipelago player ID. note, SM ROM itself will only store 201 names+ids max
|
||||
|
||||
SM_ROM_MAX_PLAYERID = 65535
|
||||
SM_ROM_PLAYERDATA_COUNT = 202
|
||||
|
||||
class SMDeltaPatch(APDeltaPatch):
|
||||
hash = SMJUHASH
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user