Merge branch 'main' into feature/ds3_use_slotdata

# Conflicts:
#	worlds/dark_souls_3/__init__.py
This commit is contained in:
Marechal-l
2022-11-01 12:00:32 +01:00
117 changed files with 3139 additions and 2940 deletions

View File

@@ -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):

View File

@@ -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:

View File

@@ -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:

View File

@@ -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]

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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
View File

@@ -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

View File

@@ -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))

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View File

@@ -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))

View File

@@ -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()

View File

@@ -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()

View 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"

View File

@@ -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()

View 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

View File

@@ -0,0 +1,5 @@
from test.worlds.test_base import WorldTestBase
class RLTestBase(WorldTestBase):
game = "Rogue Legacy"

View File

@@ -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)

View File

@@ -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

View File

@@ -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"):

View File

@@ -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]:

View File

@@ -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

View File

@@ -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'],

View File

@@ -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()

View File

@@ -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):

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -80,7 +80,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)
@@ -107,71 +107,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
@@ -179,46 +179,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 \
@@ -227,32 +227,32 @@ 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 fill_slot_data(self) -> Dict[str, object]:
slot_data: Dict[str, object] = {}
# 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
@@ -261,7 +261,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])
@@ -276,12 +276,12 @@ class DarkSouls3World(World):
slot_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,
@@ -291,6 +291,3 @@ class DarkSouls3World(World):
}
return slot_data
def generate_output(self, output_directory: str):
pass

View File

@@ -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)

View File

@@ -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)

View File

@@ -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:

View File

@@ -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

View File

@@ -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]]:

View File

@@ -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):

View File

@@ -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

View File

@@ -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":

View File

@@ -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

View File

@@ -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)

View File

@@ -89,7 +89,7 @@ class Hylics2Logic(LogicMixin):
def set_rules(hylics2world):
world = hylics2world.world
world = hylics2world.multiworld
player = hylics2world.player
# Afterlife

View File

@@ -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"]]))

View File

@@ -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:

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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}')

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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')):

View File

@@ -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):

View File

@@ -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,

View File

@@ -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:

View File

@@ -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:

View File

@@ -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):

View File

@@ -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.

View File

@@ -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"]),

View File

@@ -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"),

View File

@@ -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 ;)

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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,
}

View File

@@ -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),

View File

@@ -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)

View File

@@ -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)

View File

@@ -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}

View File

@@ -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()}

View File

@@ -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"

View File

@@ -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"

View File

@@ -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,

View File

@@ -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

View File

@@ -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)

View File

@@ -36,4 +36,3 @@ traits = [
"Glaucoma",
"Adopted",
]

View File

@@ -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"))

View File

@@ -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).

View File

@@ -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.

View File

@@ -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:

View File

@@ -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)

View File

@@ -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"

View File

@@ -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]:

View File

@@ -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 = {}

View File

@@ -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

View File

@@ -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