diff --git a/BaseClasses.py b/BaseClasses.py index 016c80ec83..41c5ab81b1 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -312,7 +312,7 @@ class MultiWorld(): def initialize_regions(self, regions=None): for region in regions if regions else self.regions: - region.world = self + region.multiworld = self self._region_cache[region.player][region.name] = region @functools.cached_property @@ -602,7 +602,7 @@ PathValue = Tuple[str, Optional["PathValue"]] class CollectionState(): prog_items: typing.Counter[Tuple[str, int]] - world: MultiWorld + multiworld: MultiWorld reachable_regions: Dict[int, Set[Region]] blocked_connections: Dict[int, Set[Entrance]] events: Set[Location] @@ -614,7 +614,7 @@ class CollectionState(): def __init__(self, parent: MultiWorld): self.prog_items = Counter() - self.world = parent + self.multiworld = parent self.reachable_regions = {player: set() for player in parent.get_all_ids()} self.blocked_connections = {player: set() for player in parent.get_all_ids()} self.events = set() @@ -632,7 +632,7 @@ class CollectionState(): rrp = self.reachable_regions[player] bc = self.blocked_connections[player] queue = deque(self.blocked_connections[player]) - start = self.world.get_region('Menu', player) + start = self.multiworld.get_region('Menu', player) # init on first call - this can't be done on construction since the regions don't exist yet if start not in rrp: @@ -655,12 +655,12 @@ class CollectionState(): self.path[new_region] = (new_region.name, self.path.get(connection, None)) # Retry connections if the new region can unblock them - for new_entrance in self.world.indirect_connections.get(new_region, set()): + for new_entrance in self.multiworld.indirect_connections.get(new_region, set()): if new_entrance in bc and new_entrance not in queue: queue.append(new_entrance) def copy(self) -> CollectionState: - ret = CollectionState(self.world) + ret = CollectionState(self.multiworld) ret.prog_items = self.prog_items.copy() ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in self.reachable_regions} @@ -681,17 +681,17 @@ class CollectionState(): assert isinstance(player, int), "can_reach: player is required if spot is str" # try to resolve a name if resolution_hint == 'Location': - spot = self.world.get_location(spot, player) + spot = self.multiworld.get_location(spot, player) elif resolution_hint == 'Entrance': - spot = self.world.get_entrance(spot, player) + spot = self.multiworld.get_entrance(spot, player) else: # default to Region - spot = self.world.get_region(spot, player) + spot = self.multiworld.get_region(spot, player) return spot.can_reach(self) def sweep_for_events(self, key_only: bool = False, locations: Optional[Iterable[Location]] = None) -> None: if locations is None: - locations = self.world.get_filled_locations() + locations = self.multiworld.get_filled_locations() reachable_events = True # since the loop has a good chance to run more than once, only filter the events once locations = {location for location in locations if location.event and location not in self.events and @@ -718,7 +718,7 @@ class CollectionState(): def has_group(self, item_name_group: str, player: int, count: int = 1) -> bool: found: int = 0 - for item_name in self.world.worlds[player].item_name_groups[item_name_group]: + for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]: found += self.prog_items[item_name, player] if found >= count: return True @@ -726,17 +726,17 @@ class CollectionState(): def count_group(self, item_name_group: str, player: int) -> int: found: int = 0 - for item_name in self.world.worlds[player].item_name_groups[item_name_group]: + for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]: found += self.prog_items[item_name, player] return found def can_buy_unlimited(self, item: str, player: int) -> bool: return any(shop.region.player == player and shop.has_unlimited(item) and shop.region.can_reach(self) for - shop in self.world.shops) + shop in self.multiworld.shops) def can_buy(self, item: str, player: int) -> bool: return any(shop.region.player == player and shop.has(item) and shop.region.can_reach(self) for - shop in self.world.shops) + shop in self.multiworld.shops) def item_count(self, item: str, player: int) -> int: return self.prog_items[item, player] @@ -756,7 +756,7 @@ class CollectionState(): return self.has('Power Glove', player) or self.has('Titans Mitts', player) def bottle_count(self, player: int) -> int: - return min(self.world.difficulty_requirements[player].progressive_bottle_limit, + return min(self.multiworld.difficulty_requirements[player].progressive_bottle_limit, self.count_group("Bottles", player)) def has_hearts(self, player: int, count: int) -> int: @@ -765,7 +765,7 @@ class CollectionState(): def heart_count(self, player: int) -> int: # Warning: This only considers items that are marked as advancement items - diff = self.world.difficulty_requirements[player] + diff = self.multiworld.difficulty_requirements[player] return min(self.item_count('Boss Heart Container', player), diff.boss_heart_container_limit) \ + self.item_count('Sanctuary Heart Container', player) \ + min(self.item_count('Piece of Heart', player), diff.heart_piece_limit) // 4 \ @@ -782,9 +782,9 @@ class CollectionState(): elif self.has('Magic Upgrade (1/2)', player): basemagic = 16 if self.can_buy_unlimited('Green Potion', player) or self.can_buy_unlimited('Blue Potion', player): - if self.world.item_functionality[player] == 'hard' and not fullrefill: + if self.multiworld.item_functionality[player] == 'hard' and not fullrefill: basemagic = basemagic + int(basemagic * 0.5 * self.bottle_count(player)) - elif self.world.item_functionality[player] == 'expert' and not fullrefill: + elif self.multiworld.item_functionality[player] == 'expert' and not fullrefill: basemagic = basemagic + int(basemagic * 0.25 * self.bottle_count(player)) else: basemagic = basemagic + basemagic * self.bottle_count(player) @@ -799,12 +799,12 @@ class CollectionState(): or (self.has('Bombs (10)', player) and enemies < 6)) def can_shoot_arrows(self, player: int) -> bool: - if self.world.retro_bow[player]: + if self.multiworld.retro_bow[player]: return (self.has('Bow', player) or self.has('Silver Bow', player)) and self.can_buy('Single Arrow', player) return self.has('Bow', player) or self.has('Silver Bow', player) def can_get_good_bee(self, player: int) -> bool: - cave = self.world.get_region('Good Bee Cave', player) + cave = self.multiworld.get_region('Good Bee Cave', player) return ( self.has_group("Bottles", player) and self.has('Bug Catching Net', player) and @@ -815,7 +815,7 @@ class CollectionState(): def can_retrieve_tablet(self, player: int) -> bool: return self.has('Book of Mudora', player) and (self.has_beam_sword(player) or - (self.world.swordless[player] and + (self.multiworld.swordless[player] and self.has("Hammer", player))) def has_sword(self, player: int) -> bool: @@ -837,7 +837,7 @@ class CollectionState(): def can_melt_things(self, player: int) -> bool: return self.has('Fire Rod', player) or \ (self.has('Bombos', player) and - (self.world.swordless[player] or + (self.multiworld.swordless[player] or self.has_sword(player))) def can_avoid_lasers(self, player: int) -> bool: @@ -847,7 +847,7 @@ class CollectionState(): if self.has('Moon Pearl', player): return True - return region.is_light_world if self.world.mode[player] != 'inverted' else region.is_dark_world + return region.is_light_world if self.multiworld.mode[player] != 'inverted' else region.is_dark_world def can_reach_light_world(self, player: int) -> bool: if True in [i.is_light_world for i in self.reachable_regions[player]]: @@ -860,24 +860,24 @@ class CollectionState(): return False def has_misery_mire_medallion(self, player: int) -> bool: - return self.has(self.world.required_medallions[player][0], player) + return self.has(self.multiworld.required_medallions[player][0], player) def has_turtle_rock_medallion(self, player: int) -> bool: - return self.has(self.world.required_medallions[player][1], player) + return self.has(self.multiworld.required_medallions[player][1], player) def can_boots_clip_lw(self, player: int) -> bool: - if self.world.mode[player] == 'inverted': + if self.multiworld.mode[player] == 'inverted': return self.has('Pegasus Boots', player) and self.has('Moon Pearl', player) return self.has('Pegasus Boots', player) def can_boots_clip_dw(self, player: int) -> bool: - if self.world.mode[player] != 'inverted': + if self.multiworld.mode[player] != 'inverted': return self.has('Pegasus Boots', player) and self.has('Moon Pearl', player) return self.has('Pegasus Boots', player) def can_get_glitched_speed_lw(self, player: int) -> bool: rules = [self.has('Pegasus Boots', player), any([self.has('Hookshot', player), self.has_sword(player)])] - if self.world.mode[player] == 'inverted': + if self.multiworld.mode[player] == 'inverted': rules.append(self.has('Moon Pearl', player)) return all(rules) @@ -886,7 +886,7 @@ class CollectionState(): def can_get_glitched_speed_dw(self, player: int) -> bool: rules = [self.has('Pegasus Boots', player), any([self.has('Hookshot', player), self.has_sword(player)])] - if self.world.mode[player] != 'inverted': + if self.multiworld.mode[player] != 'inverted': rules.append(self.has('Moon Pearl', player)) return all(rules) @@ -897,7 +897,7 @@ class CollectionState(): if location: self.locations_checked.add(location) - changed = self.world.worlds[item.player].collect(self, item) + changed = self.multiworld.worlds[item.player].collect(self, item) if not changed and event: self.prog_items[item.name, item.player] += 1 @@ -911,7 +911,7 @@ class CollectionState(): return changed def remove(self, item: Item): - changed = self.world.worlds[item.player].remove(self, item) + changed = self.multiworld.worlds[item.player].remove(self, item) if changed: # invalidate caches, nothing can be trusted anymore now self.reachable_regions[item.player] = set() @@ -938,7 +938,7 @@ class Region: type: RegionType hint_text: str player: int - world: Optional[MultiWorld] + multiworld: Optional[MultiWorld] entrances: List[Entrance] exits: List[Entrance] locations: List[Location] @@ -956,7 +956,7 @@ class Region: self.entrances = [] self.exits = [] self.locations = [] - self.world = world + self.multiworld = world self.hint_text = hint self.player = player @@ -984,7 +984,7 @@ class Region: return self.__str__() def __str__(self): - return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})' + return self.multiworld.get_name_string_for_object(self) if self.multiworld else f'{self.name} (Player {self.player})' class Entrance: @@ -1021,7 +1021,7 @@ class Entrance: return self.__str__() def __str__(self): - world = self.parent_region.world if self.parent_region else None + world = self.parent_region.multiworld if self.parent_region else None return world.get_name_string_for_object(self) if world else f'{self.name} (Player {self.player})' @@ -1035,7 +1035,7 @@ class Dungeon(object): self.dungeon_items = dungeon_items self.bosses = dict() self.player = player - self.world = None + self.multiworld = None @property def boss(self) -> Optional[Boss]: @@ -1065,7 +1065,7 @@ class Dungeon(object): return self.__str__() def __str__(self): - return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})' + return self.multiworld.get_name_string_for_object(self) if self.multiworld else f'{self.name} (Player {self.player})' class Boss(): @@ -1130,7 +1130,7 @@ class Location: return self.__str__() def __str__(self): - world = self.parent_region.world if self.parent_region and self.parent_region.world else None + world = self.parent_region.multiworld if self.parent_region and self.parent_region.multiworld else None return world.get_name_string_for_object(self) if world else f'{self.name} (Player {self.player})' def __hash__(self): @@ -1227,17 +1227,17 @@ class Item: return self.__str__() def __str__(self) -> str: - if self.location and self.location.parent_region and self.location.parent_region.world: - return self.location.parent_region.world.get_name_string_for_object(self) + if self.location and self.location.parent_region and self.location.parent_region.multiworld: + return self.location.parent_region.multiworld.get_name_string_for_object(self) return f"{self.name} (Player {self.player})" class Spoiler(): - world: MultiWorld + multiworld: MultiWorld unreachables: Set[Location] def __init__(self, world): - self.world = world + self.multiworld = world self.hashes = {} self.entrances = OrderedDict() self.medallions = {} @@ -1249,7 +1249,7 @@ class Spoiler(): self.bosses = OrderedDict() def set_entrance(self, entrance: str, exit_: str, direction: str, player: int): - if self.world.players == 1: + if self.multiworld.players == 1: self.entrances[(entrance, direction, player)] = OrderedDict( [('entrance', entrance), ('exit', exit_), ('direction', direction)]) else: @@ -1258,45 +1258,45 @@ class Spoiler(): def parse_data(self): self.medallions = OrderedDict() - for player in self.world.get_game_players("A Link to the Past"): - self.medallions[f'Misery Mire ({self.world.get_player_name(player)})'] = \ - self.world.required_medallions[player][0] - self.medallions[f'Turtle Rock ({self.world.get_player_name(player)})'] = \ - self.world.required_medallions[player][1] + for player in self.multiworld.get_game_players("A Link to the Past"): + self.medallions[f'Misery Mire ({self.multiworld.get_player_name(player)})'] = \ + self.multiworld.required_medallions[player][0] + self.medallions[f'Turtle Rock ({self.multiworld.get_player_name(player)})'] = \ + self.multiworld.required_medallions[player][1] self.locations = OrderedDict() listed_locations = set() - lw_locations = [loc for loc in self.world.get_locations() if + lw_locations = [loc for loc in self.multiworld.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld and loc.show_in_spoiler] self.locations['Light World'] = OrderedDict( [(str(location), str(location.item) if location.item is not None else 'Nothing') for location in lw_locations]) listed_locations.update(lw_locations) - dw_locations = [loc for loc in self.world.get_locations() if + dw_locations = [loc for loc in self.multiworld.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld and loc.show_in_spoiler] self.locations['Dark World'] = OrderedDict( [(str(location), str(location.item) if location.item is not None else 'Nothing') for location in dw_locations]) listed_locations.update(dw_locations) - cave_locations = [loc for loc in self.world.get_locations() if + cave_locations = [loc for loc in self.multiworld.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.Cave and loc.show_in_spoiler] self.locations['Caves'] = OrderedDict( [(str(location), str(location.item) if location.item is not None else 'Nothing') for location in cave_locations]) listed_locations.update(cave_locations) - for dungeon in self.world.dungeons.values(): - dungeon_locations = [loc for loc in self.world.get_locations() if + for dungeon in self.multiworld.dungeons.values(): + dungeon_locations = [loc for loc in self.multiworld.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon and loc.show_in_spoiler] self.locations[str(dungeon)] = OrderedDict( [(str(location), str(location.item) if location.item is not None else 'Nothing') for location in dungeon_locations]) listed_locations.update(dungeon_locations) - other_locations = [loc for loc in self.world.get_locations() if + other_locations = [loc for loc in self.multiworld.get_locations() if loc not in listed_locations and loc.show_in_spoiler] if other_locations: self.locations['Other Locations'] = OrderedDict( @@ -1306,7 +1306,7 @@ class Spoiler(): self.shops = [] from worlds.alttp.Shops import ShopType, price_type_display_name, price_rate_display - for shop in self.world.shops: + for shop in self.multiworld.shops: if not shop.custom: continue shopdata = { @@ -1335,34 +1335,34 @@ class Spoiler(): index)] += f", {item['replacement']} - {item['replacement_price']} {price_type_display_name[item['replacement_price_type']]}" self.shops.append(shopdata) - for player in self.world.get_game_players("A Link to the Past"): + for player in self.multiworld.get_game_players("A Link to the Past"): self.bosses[str(player)] = OrderedDict() - self.bosses[str(player)]["Eastern Palace"] = self.world.get_dungeon("Eastern Palace", player).boss.name - self.bosses[str(player)]["Desert Palace"] = self.world.get_dungeon("Desert Palace", player).boss.name - self.bosses[str(player)]["Tower Of Hera"] = self.world.get_dungeon("Tower of Hera", player).boss.name + self.bosses[str(player)]["Eastern Palace"] = self.multiworld.get_dungeon("Eastern Palace", player).boss.name + self.bosses[str(player)]["Desert Palace"] = self.multiworld.get_dungeon("Desert Palace", player).boss.name + self.bosses[str(player)]["Tower Of Hera"] = self.multiworld.get_dungeon("Tower of Hera", player).boss.name self.bosses[str(player)]["Hyrule Castle"] = "Agahnim" - self.bosses[str(player)]["Palace Of Darkness"] = self.world.get_dungeon("Palace of Darkness", - player).boss.name - self.bosses[str(player)]["Swamp Palace"] = self.world.get_dungeon("Swamp Palace", player).boss.name - self.bosses[str(player)]["Skull Woods"] = self.world.get_dungeon("Skull Woods", player).boss.name - self.bosses[str(player)]["Thieves Town"] = self.world.get_dungeon("Thieves Town", player).boss.name - self.bosses[str(player)]["Ice Palace"] = self.world.get_dungeon("Ice Palace", player).boss.name - self.bosses[str(player)]["Misery Mire"] = self.world.get_dungeon("Misery Mire", player).boss.name - self.bosses[str(player)]["Turtle Rock"] = self.world.get_dungeon("Turtle Rock", player).boss.name - if self.world.mode[player] != 'inverted': + self.bosses[str(player)]["Palace Of Darkness"] = self.multiworld.get_dungeon("Palace of Darkness", + player).boss.name + self.bosses[str(player)]["Swamp Palace"] = self.multiworld.get_dungeon("Swamp Palace", player).boss.name + self.bosses[str(player)]["Skull Woods"] = self.multiworld.get_dungeon("Skull Woods", player).boss.name + self.bosses[str(player)]["Thieves Town"] = self.multiworld.get_dungeon("Thieves Town", player).boss.name + self.bosses[str(player)]["Ice Palace"] = self.multiworld.get_dungeon("Ice Palace", player).boss.name + self.bosses[str(player)]["Misery Mire"] = self.multiworld.get_dungeon("Misery Mire", player).boss.name + self.bosses[str(player)]["Turtle Rock"] = self.multiworld.get_dungeon("Turtle Rock", player).boss.name + if self.multiworld.mode[player] != 'inverted': self.bosses[str(player)]["Ganons Tower Basement"] = \ - self.world.get_dungeon('Ganons Tower', player).bosses['bottom'].name - self.bosses[str(player)]["Ganons Tower Middle"] = self.world.get_dungeon('Ganons Tower', player).bosses[ + self.multiworld.get_dungeon('Ganons Tower', player).bosses['bottom'].name + self.bosses[str(player)]["Ganons Tower Middle"] = self.multiworld.get_dungeon('Ganons Tower', player).bosses[ 'middle'].name - self.bosses[str(player)]["Ganons Tower Top"] = self.world.get_dungeon('Ganons Tower', player).bosses[ + self.bosses[str(player)]["Ganons Tower Top"] = self.multiworld.get_dungeon('Ganons Tower', player).bosses[ 'top'].name else: self.bosses[str(player)]["Ganons Tower Basement"] = \ - self.world.get_dungeon('Inverted Ganons Tower', player).bosses['bottom'].name + self.multiworld.get_dungeon('Inverted Ganons Tower', player).bosses['bottom'].name self.bosses[str(player)]["Ganons Tower Middle"] = \ - self.world.get_dungeon('Inverted Ganons Tower', player).bosses['middle'].name + self.multiworld.get_dungeon('Inverted Ganons Tower', player).bosses['middle'].name self.bosses[str(player)]["Ganons Tower Top"] = \ - self.world.get_dungeon('Inverted Ganons Tower', player).bosses['top'].name + self.multiworld.get_dungeon('Inverted Ganons Tower', player).bosses['top'].name self.bosses[str(player)]["Ganons Tower"] = "Agahnim 2" self.bosses[str(player)]["Ganon"] = "Ganon" @@ -1392,7 +1392,7 @@ class Spoiler(): return 'Yes' if variable else 'No' def write_option(option_key: str, option_obj: type(Options.Option)): - res = getattr(self.world, option_key)[player] + res = getattr(self.multiworld, option_key)[player] display_name = getattr(option_obj, "display_name", option_key) try: outfile.write(f'{display_name + ":":33}{res.get_current_option_name()}\n') @@ -1402,59 +1402,59 @@ class Spoiler(): with open(filename, 'w', encoding="utf-8-sig") as outfile: outfile.write( 'Archipelago Version %s - Seed: %s\n\n' % ( - Utils.__version__, self.world.seed)) - outfile.write('Filling Algorithm: %s\n' % self.world.algorithm) - outfile.write('Players: %d\n' % self.world.players) - AutoWorld.call_stage(self.world, "write_spoiler_header", outfile) + Utils.__version__, self.multiworld.seed)) + outfile.write('Filling Algorithm: %s\n' % self.multiworld.algorithm) + outfile.write('Players: %d\n' % self.multiworld.players) + AutoWorld.call_stage(self.multiworld, "write_spoiler_header", outfile) - for player in range(1, self.world.players + 1): - if self.world.players > 1: - outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_name(player))) - outfile.write('Game: %s\n' % self.world.game[player]) + for player in range(1, self.multiworld.players + 1): + if self.multiworld.players > 1: + outfile.write('\nPlayer %d: %s\n' % (player, self.multiworld.get_player_name(player))) + outfile.write('Game: %s\n' % self.multiworld.game[player]) for f_option, option in Options.per_game_common_options.items(): write_option(f_option, option) - options = self.world.worlds[player].option_definitions + options = self.multiworld.worlds[player].option_definitions if options: for f_option, option in options.items(): write_option(f_option, option) - AutoWorld.call_single(self.world, "write_spoiler_header", player, outfile) + AutoWorld.call_single(self.multiworld, "write_spoiler_header", player, outfile) - if player in self.world.get_game_players("A Link to the Past"): + if player in self.multiworld.get_game_players("A Link to the Past"): outfile.write('%s%s\n' % ('Hash: ', self.hashes[player])) - outfile.write('Logic: %s\n' % self.world.logic[player]) - outfile.write('Dark Room Logic: %s\n' % self.world.dark_room_logic[player]) - outfile.write('Mode: %s\n' % self.world.mode[player]) - outfile.write('Goal: %s\n' % self.world.goal[player]) - if "triforce" in self.world.goal[player]: # triforce hunt + outfile.write('Logic: %s\n' % self.multiworld.logic[player]) + outfile.write('Dark Room Logic: %s\n' % self.multiworld.dark_room_logic[player]) + outfile.write('Mode: %s\n' % self.multiworld.mode[player]) + outfile.write('Goal: %s\n' % self.multiworld.goal[player]) + if "triforce" in self.multiworld.goal[player]: # triforce hunt outfile.write("Pieces available for Triforce: %s\n" % - self.world.triforce_pieces_available[player]) + self.multiworld.triforce_pieces_available[player]) outfile.write("Pieces required for Triforce: %s\n" % - self.world.triforce_pieces_required[player]) - outfile.write('Difficulty: %s\n' % self.world.difficulty[player]) - outfile.write('Item Functionality: %s\n' % self.world.item_functionality[player]) - outfile.write('Entrance Shuffle: %s\n' % self.world.shuffle[player]) - if self.world.shuffle[player] != "vanilla": - outfile.write('Entrance Shuffle Seed %s\n' % self.world.worlds[player].er_seed) + self.multiworld.triforce_pieces_required[player]) + outfile.write('Difficulty: %s\n' % self.multiworld.difficulty[player]) + outfile.write('Item Functionality: %s\n' % self.multiworld.item_functionality[player]) + outfile.write('Entrance Shuffle: %s\n' % self.multiworld.shuffle[player]) + if self.multiworld.shuffle[player] != "vanilla": + outfile.write('Entrance Shuffle Seed %s\n' % self.multiworld.worlds[player].er_seed) outfile.write('Shop inventory shuffle: %s\n' % - bool_to_text("i" in self.world.shop_shuffle[player])) + bool_to_text("i" in self.multiworld.shop_shuffle[player])) outfile.write('Shop price shuffle: %s\n' % - bool_to_text("p" in self.world.shop_shuffle[player])) + bool_to_text("p" in self.multiworld.shop_shuffle[player])) outfile.write('Shop upgrade shuffle: %s\n' % - bool_to_text("u" in self.world.shop_shuffle[player])) + bool_to_text("u" in self.multiworld.shop_shuffle[player])) outfile.write('New Shop inventory: %s\n' % - bool_to_text("g" in self.world.shop_shuffle[player] or - "f" in self.world.shop_shuffle[player])) + bool_to_text("g" in self.multiworld.shop_shuffle[player] or + "f" in self.multiworld.shop_shuffle[player])) outfile.write('Custom Potion Shop: %s\n' % - bool_to_text("w" in self.world.shop_shuffle[player])) - outfile.write('Enemy health: %s\n' % self.world.enemy_health[player]) - outfile.write('Enemy damage: %s\n' % self.world.enemy_damage[player]) + bool_to_text("w" in self.multiworld.shop_shuffle[player])) + outfile.write('Enemy health: %s\n' % self.multiworld.enemy_health[player]) + outfile.write('Enemy damage: %s\n' % self.multiworld.enemy_damage[player]) outfile.write('Prize shuffle %s\n' % - self.world.shuffle_prizes[player]) + self.multiworld.shuffle_prizes[player]) if self.entrances: outfile.write('\n\nEntrances:\n\n') - outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_name(entry["player"])}: ' - if self.world.players > 1 else '', entry['entrance'], + outfile.write('\n'.join(['%s%s %s %s' % (f'{self.multiworld.get_player_name(entry["player"])}: ' + if self.multiworld.players > 1 else '', entry['entrance'], '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', entry['exit']) for entry in self.entrances.values()])) @@ -1464,7 +1464,7 @@ class Spoiler(): for dungeon, medallion in self.medallions.items(): outfile.write(f'\n{dungeon}: {medallion}') - AutoWorld.call_all(self.world, "write_spoiler", outfile) + AutoWorld.call_all(self.multiworld, "write_spoiler", outfile) outfile.write('\n\nLocations:\n\n') outfile.write('\n'.join( @@ -1477,11 +1477,11 @@ class Spoiler(): item for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops)) - for player in self.world.get_game_players("A Link to the Past"): - if self.world.boss_shuffle[player] != 'none': - bossmap = self.bosses[str(player)] if self.world.players > 1 else self.bosses + for player in self.multiworld.get_game_players("A Link to the Past"): + if self.multiworld.boss_shuffle[player] != 'none': + bossmap = self.bosses[str(player)] if self.multiworld.players > 1 else self.bosses outfile.write( - f'\n\nBosses{(f" ({self.world.get_player_name(player)})" if self.world.players > 1 else "")}:\n') + f'\n\nBosses{(f" ({self.multiworld.get_player_name(player)})" if self.multiworld.players > 1 else "")}:\n') outfile.write(' ' + '\n '.join([f'{x}: {y}' for x, y in bossmap.items()])) outfile.write('\n\nPlaythrough:\n\n') outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join( @@ -1505,7 +1505,7 @@ class Spoiler(): path_listings.append("{}\n {}".format(location, "\n => ".join(path_lines))) outfile.write('\n'.join(path_listings)) - AutoWorld.call_all(self.world, "write_spoiler_end", outfile) + AutoWorld.call_all(self.multiworld, "write_spoiler_end", outfile) class Tutorial(NamedTuple): diff --git a/CommonClient.py b/CommonClient.py index 4fc82b5e67..3703a581b2 100644 --- a/CommonClient.py +++ b/CommonClient.py @@ -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: diff --git a/Fill.py b/Fill.py index 105b359134..15af5a9ea4 100644 --- a/Fill.py +++ b/Fill.py @@ -294,7 +294,7 @@ def distribute_items_restrictive(world: MultiWorld) -> None: {len(unplaced_early_items)} items early.") itempool += unplaced_early_items - fill_locations += early_locations + early_priority_locations + fill_locations += early_locations world.random.shuffle(fill_locations) for item in itempool: diff --git a/Generate.py b/Generate.py index f19e23700c..ccf054842b 100644 --- a/Generate.py +++ b/Generate.py @@ -233,8 +233,8 @@ def main(args=None, callback=ERmain): else: raise RuntimeError(f'No weights specified for player {player}') - if len(set(erargs.name.values())) != len(erargs.name): - raise Exception(f"Names have to be unique. Names: {Counter(erargs.name.values())}") + if len(set(name.lower() for name in erargs.name.values())) != len(erargs.name): + raise Exception(f"Names have to be unique. Names: {Counter(name.lower() for name in erargs.name.values())}") if args.yaml_output: import yaml @@ -317,11 +317,11 @@ class SafeDict(dict): def handle_name(name: str, player: int, name_counter: Counter): - name_counter[name] += 1 + name_counter[name.lower()] += 1 + number = name_counter[name.lower()] new_name = "%".join([x.replace("%number%", "{number}").replace("%player%", "{player}") for x in name.split("%%")]) - new_name = string.Formatter().vformat(new_name, (), SafeDict(number=name_counter[name], - NUMBER=(name_counter[name] if name_counter[ - name] > 1 else ''), + new_name = string.Formatter().vformat(new_name, (), SafeDict(number=number, + NUMBER=(number if number > 1 else ''), player=player, PLAYER=(player if player > 1 else ''))) new_name = new_name.strip()[:16] diff --git a/MultiServer.py b/MultiServer.py index 1a672afaa6..ebd467137e 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -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 diff --git a/PokemonClient.py b/PokemonClient.py index 2328243de5..33e902a39d 100644 --- a/PokemonClient.py +++ b/PokemonClient.py @@ -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) diff --git a/data/client.kv b/data/client.kv index 4f924896d9..0b33cda0bd 100644 --- a/data/client.kv +++ b/data/client.kv @@ -15,6 +15,8 @@ : viewclass: 'SelectableLabel' scroll_y: 0 + scroll_type: ["content", "bars"] + bar_width: dp(12) effect_cls: "ScrollEffect" SelectableRecycleBoxLayout: default_size: None, dp(20) diff --git a/data/lua/PKMN_RB/pkmn_rb.lua b/data/lua/PKMN_RB/pkmn_rb.lua index 7518a5f12b..30ccbbd36b 100644 --- a/data/lua/PKMN_RB/pkmn_rb.lua +++ b/data/lua/PKMN_RB/pkmn_rb.lua @@ -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 diff --git a/docs/world api.md b/docs/world api.md index cf26cfd967..a43d61e527 100644 --- a/docs/world api.md +++ b/docs/world api.md @@ -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) diff --git a/kvui.py b/kvui.py index 3820864538..94e0a0918f 100644 --- a/kvui.py +++ b/kvui.py @@ -338,9 +338,12 @@ class GameManager(App): # top part server_label = ServerLabel() self.connect_layout.add_widget(server_label) - self.server_connect_bar = ConnectBarTextInput(text=self.ctx.server_address or "archipelago.gg", size_hint_y=None, + self.server_connect_bar = ConnectBarTextInput(text=self.ctx.suggested_address or "archipelago.gg:", size_hint_y=None, height=30, multiline=False, write_tab=False) - self.server_connect_bar.bind(on_text_validate=self.connect_button_action) + def connect_bar_validate(sender): + if not self.ctx.server: + self.connect_button_action(sender) + self.server_connect_bar.bind(on_text_validate=connect_bar_validate) self.connect_layout.add_widget(self.server_connect_bar) self.server_connect_button = Button(text="Connect", size=(100, 30), size_hint_y=None, size_hint_x=None) self.server_connect_button.bind(on_press=self.connect_button_action) @@ -381,17 +384,19 @@ class GameManager(App): bottom_layout.add_widget(info_button) self.textinput = TextInput(size_hint_y=None, height=30, multiline=False, write_tab=False) self.textinput.bind(on_text_validate=self.on_message) - - def text_focus(event): - """Needs to be set via delay, as unfocusing happens after on_message""" - self.textinput.focus = True - - self.textinput.text_focus = text_focus + self.textinput.text_validate_unfocus = False bottom_layout.add_widget(self.textinput) self.grid.add_widget(bottom_layout) self.commandprocessor("/help") Clock.schedule_interval(self.update_texts, 1 / 30) self.container.add_widget(self.grid) + + self.server_connect_bar.focus = True + self.server_connect_bar.select_text( + self.server_connect_bar.text.find(":") + 1, + len(self.server_connect_bar.text) + ) + return self.container def update_texts(self, dt): @@ -402,10 +407,12 @@ class GameManager(App): f" | Connected to: {self.ctx.server_address} " \ f"{'.'.join(str(e) for e in self.ctx.server_version)}" self.server_connect_button.text = "Disconnect" + self.server_connect_bar.readonly = True self.progressbar.max = len(self.ctx.checked_locations) + len(self.ctx.missing_locations) self.progressbar.value = len(self.ctx.checked_locations) else: self.server_connect_button.text = "Connect" + self.server_connect_bar.readonly = False self.title = self.base_title + " " + Utils.__version__ self.progressbar.value = 0 @@ -443,8 +450,6 @@ class GameManager(App): elif input_text: self.commandprocessor(input_text) - Clock.schedule_once(textinput.text_focus) - except Exception as e: logging.getLogger("Client").exception(e) @@ -453,6 +458,10 @@ class GameManager(App): self.log_panels["Archipelago"].on_message_markup(text) self.log_panels["All"].on_message_markup(text) + def focus_textinput(self): + if hasattr(self, "textinput"): + self.textinput.focus = True + def update_address_bar(self, text: str): if hasattr(self, "server_connect_bar"): self.server_connect_bar.text = text diff --git a/test/TestBase.py b/test/TestBase.py index 351ffb40a0..754f38dbfe 100644 --- a/test/TestBase.py +++ b/test/TestBase.py @@ -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)) diff --git a/test/dungeons/TestDungeon.py b/test/dungeons/TestDungeon.py index 0568e799f2..ba1752bf69 100644 --- a/test/dungeons/TestDungeon.py +++ b/test/dungeons/TestDungeon.py @@ -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) \ No newline at end of file + self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access) \ No newline at end of file diff --git a/test/general/TestFill.py b/test/general/TestFill.py index 86d86a223a..646fc3c85e 100644 --- a/test/general/TestFill.py +++ b/test/general/TestFill.py @@ -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 diff --git a/test/general/__init__.py b/test/general/__init__.py index 479f4af520..247b135c97 100644 --- a/test/general/__init__.py +++ b/test/general/__init__.py @@ -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 diff --git a/test/inverted/TestInverted.py b/test/inverted/TestInverted.py index 0c96f0b26d..309a34d54b 100644 --- a/test/inverted/TestInverted.py +++ b/test/inverted/TestInverted.py @@ -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() diff --git a/test/inverted/TestInvertedBombRules.py b/test/inverted/TestInvertedBombRules.py index f6afa9d0dc..47e177dd58 100644 --- a/test/inverted/TestInvertedBombRules.py +++ b/test/inverted/TestInvertedBombRules.py @@ -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 diff --git a/test/inverted_minor_glitches/TestInvertedMinor.py b/test/inverted_minor_glitches/TestInvertedMinor.py index 42e7c942d6..7ea7980bbe 100644 --- a/test/inverted_minor_glitches/TestInvertedMinor.py +++ b/test/inverted_minor_glitches/TestInvertedMinor.py @@ -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() diff --git a/test/inverted_owg/TestInvertedOWG.py b/test/inverted_owg/TestInvertedOWG.py index 064dd9e083..7dae358929 100644 --- a/test/inverted_owg/TestInvertedOWG.py +++ b/test/inverted_owg/TestInvertedOWG.py @@ -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() diff --git a/test/minecraft/TestMinecraft.py b/test/minecraft/TestMinecraft.py index ea849dc7df..cd87296c5a 100644 --- a/test/minecraft/TestMinecraft.py +++ b/test/minecraft/TestMinecraft.py @@ -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)) diff --git a/test/minor_glitches/TestMinor.py b/test/minor_glitches/TestMinor.py index 41cb13161a..de19a71706 100644 --- a/test/minor_glitches/TestMinor.py +++ b/test/minor_glitches/TestMinor.py @@ -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() diff --git a/test/owg/TestVanillaOWG.py b/test/owg/TestVanillaOWG.py index a4367fb55e..a12679b8cf 100644 --- a/test/owg/TestVanillaOWG.py +++ b/test/owg/TestVanillaOWG.py @@ -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() \ No newline at end of file + 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() \ No newline at end of file diff --git a/test/programs/TestMultiServer.py b/test/programs/TestMultiServer.py new file mode 100644 index 0000000000..6ea66e33bb --- /dev/null +++ b/test/programs/TestMultiServer.py @@ -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" diff --git a/test/vanilla/TestVanilla.py b/test/vanilla/TestVanilla.py index c9fa3f763b..32c9c61800 100644 --- a/test/vanilla/TestVanilla.py +++ b/test/vanilla/TestVanilla.py @@ -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() \ No newline at end of file + 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() \ No newline at end of file diff --git a/test/worlds/rogue_legacy/TestUnique.py b/test/worlds/rogue_legacy/TestUnique.py new file mode 100644 index 0000000000..dbe35dd94f --- /dev/null +++ b/test/worlds/rogue_legacy/TestUnique.py @@ -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 diff --git a/test/worlds/rogue_legacy/__init__.py b/test/worlds/rogue_legacy/__init__.py new file mode 100644 index 0000000000..41ddcde152 --- /dev/null +++ b/test/worlds/rogue_legacy/__init__.py @@ -0,0 +1,5 @@ +from test.worlds.test_base import WorldTestBase + + +class RLTestBase(WorldTestBase): + game = "Rogue Legacy" diff --git a/test/worlds/test_base.py b/test/worlds/test_base.py index 0d7272be19..22e92c6f2b 100644 --- a/test/worlds/test_base.py +++ b/test/worlds/test_base.py @@ -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) diff --git a/test/worlds/zillion/TestOptions.py b/test/worlds/zillion/TestOptions.py index 20397a4ec8..b00c70f730 100644 --- a/test/worlds/zillion/TestOptions.py +++ b/test/worlds/zillion/TestOptions.py @@ -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 diff --git a/test/worlds/zillion/__init__.py b/test/worlds/zillion/__init__.py index fb81bda522..1b3c2b42fd 100644 --- a/test/worlds/zillion/__init__.py +++ b/test/worlds/zillion/__init__.py @@ -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"): diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index 37cf5300a2..00588a0130 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -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]: diff --git a/worlds/alttp/Bosses.py b/worlds/alttp/Bosses.py index 870b3c7c2f..5f915a3342 100644 --- a/worlds/alttp/Bosses.py +++ b/worlds/alttp/Bosses.py @@ -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 diff --git a/worlds/alttp/Dungeons.py b/worlds/alttp/Dungeons.py index 8850786be6..866eb3e0dc 100644 --- a/worlds/alttp/Dungeons.py +++ b/worlds/alttp/Dungeons.py @@ -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'], diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index aa8d7ef3f0..a00a423a73 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -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() diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index 36e16cf57c..d1413e215d 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -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): diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 8431af9a26..93e0b02b14 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -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 diff --git a/worlds/archipidle/__init__.py b/worlds/archipidle/__init__.py index 8b1061b5d1..50e2912d09 100644 --- a/worlds/archipidle/__init__.py +++ b/worlds/archipidle/__init__.py @@ -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) diff --git a/worlds/checksfinder/__init__.py b/worlds/checksfinder/__init__.py index ec9091c3d2..5dce1b8e11 100644 --- a/worlds/checksfinder/__init__.py +++ b/worlds/checksfinder/__init__.py @@ -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 diff --git a/worlds/dark_souls_3/__init__.py b/worlds/dark_souls_3/__init__.py index df56306cc5..0b062b3a29 100644 --- a/worlds/dark_souls_3/__init__.py +++ b/worlds/dark_souls_3/__init__.py @@ -79,7 +79,7 @@ class DarkSouls3World(World): def create_regions(self): menu_region = Region("Menu", RegionType.Generic, "Menu", self.player) - self.world.regions.append(menu_region) + self.multiworld.regions.append(menu_region) # Create all Vanilla regions of Dark Souls III firelink_shrine_region = self.create_region("Firelink Shrine", fire_link_shrine_table) @@ -106,71 +106,71 @@ class DarkSouls3World(World): # Create the entrance to connect those regions menu_region.exits.append(Entrance(self.player, "New Game", menu_region)) - self.world.get_entrance("New Game", self.player).connect(firelink_shrine_region) + self.multiworld.get_entrance("New Game", self.player).connect(firelink_shrine_region) firelink_shrine_region.exits.append(Entrance(self.player, "Goto High Wall of Lothric", firelink_shrine_region)) firelink_shrine_region.exits.append(Entrance(self.player, "Goto Kiln Of The First Flame", firelink_shrine_region)) firelink_shrine_region.exits.append(Entrance(self.player, "Goto Bell Tower", firelink_shrine_region)) - self.world.get_entrance("Goto High Wall of Lothric", self.player).connect(high_wall_of_lothric_region) - self.world.get_entrance("Goto Kiln Of The First Flame", self.player).connect(kiln_of_the_first_flame_region) - self.world.get_entrance("Goto Bell Tower", self.player).connect(firelink_shrine_bell_tower_region) + self.multiworld.get_entrance("Goto High Wall of Lothric", self.player).connect(high_wall_of_lothric_region) + self.multiworld.get_entrance("Goto Kiln Of The First Flame", self.player).connect(kiln_of_the_first_flame_region) + self.multiworld.get_entrance("Goto Bell Tower", self.player).connect(firelink_shrine_bell_tower_region) high_wall_of_lothric_region.exits.append(Entrance(self.player, "Goto Undead Settlement", high_wall_of_lothric_region)) high_wall_of_lothric_region.exits.append(Entrance(self.player, "Goto Lothric Castle", high_wall_of_lothric_region)) - self.world.get_entrance("Goto Undead Settlement", self.player).connect(undead_settlement_region) - self.world.get_entrance("Goto Lothric Castle", self.player).connect(lothric_castle_region) + self.multiworld.get_entrance("Goto Undead Settlement", self.player).connect(undead_settlement_region) + self.multiworld.get_entrance("Goto Lothric Castle", self.player).connect(lothric_castle_region) undead_settlement_region.exits.append(Entrance(self.player, "Goto Road Of Sacrifices", undead_settlement_region)) - self.world.get_entrance("Goto Road Of Sacrifices", self.player).connect(road_of_sacrifices_region) + self.multiworld.get_entrance("Goto Road Of Sacrifices", self.player).connect(road_of_sacrifices_region) road_of_sacrifices_region.exits.append(Entrance(self.player, "Goto Cathedral", road_of_sacrifices_region)) road_of_sacrifices_region.exits.append(Entrance(self.player, "Goto Farron keep", road_of_sacrifices_region)) - self.world.get_entrance("Goto Cathedral", self.player).connect(cathedral_of_the_deep_region) - self.world.get_entrance("Goto Farron keep", self.player).connect(farron_keep_region) + self.multiworld.get_entrance("Goto Cathedral", self.player).connect(cathedral_of_the_deep_region) + self.multiworld.get_entrance("Goto Farron keep", self.player).connect(farron_keep_region) farron_keep_region.exits.append(Entrance(self.player, "Goto Carthus catacombs", farron_keep_region)) - self.world.get_entrance("Goto Carthus catacombs", self.player).connect(catacombs_of_carthus_region) + self.multiworld.get_entrance("Goto Carthus catacombs", self.player).connect(catacombs_of_carthus_region) catacombs_of_carthus_region.exits.append(Entrance(self.player, "Goto Irithyll of the boreal", catacombs_of_carthus_region)) catacombs_of_carthus_region.exits.append(Entrance(self.player, "Goto Smouldering Lake", catacombs_of_carthus_region)) - self.world.get_entrance("Goto Irithyll of the boreal", self.player).\ + self.multiworld.get_entrance("Goto Irithyll of the boreal", self.player).\ connect(irithyll_of_the_boreal_valley_region) - self.world.get_entrance("Goto Smouldering Lake", self.player).connect(smouldering_lake_region) + self.multiworld.get_entrance("Goto Smouldering Lake", self.player).connect(smouldering_lake_region) irithyll_of_the_boreal_valley_region.exits.append(Entrance(self.player, "Goto Irithyll dungeon", irithyll_of_the_boreal_valley_region)) irithyll_of_the_boreal_valley_region.exits.append(Entrance(self.player, "Goto Anor Londo", irithyll_of_the_boreal_valley_region)) - self.world.get_entrance("Goto Irithyll dungeon", self.player).connect(irithyll_dungeon_region) - self.world.get_entrance("Goto Anor Londo", self.player).connect(anor_londo_region) + self.multiworld.get_entrance("Goto Irithyll dungeon", self.player).connect(irithyll_dungeon_region) + self.multiworld.get_entrance("Goto Anor Londo", self.player).connect(anor_londo_region) irithyll_dungeon_region.exits.append(Entrance(self.player, "Goto Archdragon peak", irithyll_dungeon_region)) irithyll_dungeon_region.exits.append(Entrance(self.player, "Goto Profaned capital", irithyll_dungeon_region)) - self.world.get_entrance("Goto Archdragon peak", self.player).connect(archdragon_peak_region) - self.world.get_entrance("Goto Profaned capital", self.player).connect(profaned_capital_region) + self.multiworld.get_entrance("Goto Archdragon peak", self.player).connect(archdragon_peak_region) + self.multiworld.get_entrance("Goto Profaned capital", self.player).connect(profaned_capital_region) lothric_castle_region.exits.append(Entrance(self.player, "Goto Consumed King Garden", lothric_castle_region)) lothric_castle_region.exits.append(Entrance(self.player, "Goto Grand Archives", lothric_castle_region)) - self.world.get_entrance("Goto Consumed King Garden", self.player).connect(consumed_king_garden_region) - self.world.get_entrance("Goto Grand Archives", self.player).connect(grand_archives_region) + self.multiworld.get_entrance("Goto Consumed King Garden", self.player).connect(consumed_king_garden_region) + self.multiworld.get_entrance("Goto Grand Archives", self.player).connect(grand_archives_region) consumed_king_garden_region.exits.append(Entrance(self.player, "Goto Untended Graves", consumed_king_garden_region)) - self.world.get_entrance("Goto Untended Graves", self.player).connect(untended_graves_region) + self.multiworld.get_entrance("Goto Untended Graves", self.player).connect(untended_graves_region) # For each region, add the associated locations retrieved from the corresponding location_table def create_region(self, region_name, location_table) -> Region: - new_region = Region(region_name, RegionType.Generic, region_name, self.player, self.world) + new_region = Region(region_name, RegionType.Generic, region_name, self.player, self.multiworld) if location_table: for name, address in location_table.items(): location = DarkSouls3Location(self.player, name, self.location_name_to_id[name], new_region) new_region.locations.append(location) - self.world.regions.append(new_region) + self.multiworld.regions.append(new_region) return new_region def create_items(self): for name, address in self.item_name_to_id.items(): # Specific items will be included in the item pool under certain conditions. See generate_basic if name != "Basin of Vows": - self.world.itempool += [self.create_item(name)] + self.multiworld.itempool += [self.create_item(name)] def generate_early(self): pass @@ -178,46 +178,46 @@ class DarkSouls3World(World): def set_rules(self) -> None: # Define the access rules to the entrances - set_rule(self.world.get_entrance("Goto Bell Tower", self.player), + set_rule(self.multiworld.get_entrance("Goto Bell Tower", self.player), lambda state: state.has("Tower Key", self.player)) - set_rule(self.world.get_entrance("Goto Undead Settlement", self.player), + set_rule(self.multiworld.get_entrance("Goto Undead Settlement", self.player), lambda state: state.has("Small Lothric Banner", self.player)) - set_rule(self.world.get_entrance("Goto Lothric Castle", self.player), + set_rule(self.multiworld.get_entrance("Goto Lothric Castle", self.player), lambda state: state.has("Basin of Vows", self.player)) - set_rule(self.world.get_entrance("Goto Irithyll of the boreal", self.player), + set_rule(self.multiworld.get_entrance("Goto Irithyll of the boreal", self.player), lambda state: state.has("Small Doll", self.player)) - set_rule(self.world.get_entrance("Goto Archdragon peak", self.player), + set_rule(self.multiworld.get_entrance("Goto Archdragon peak", self.player), lambda state: state.can_reach("CKG: Soul of Consumed Oceiros", "Location", self.player)) - set_rule(self.world.get_entrance("Goto Profaned capital", self.player), + set_rule(self.multiworld.get_entrance("Goto Profaned capital", self.player), lambda state: state.has("Storm Ruler", self.player)) - set_rule(self.world.get_entrance("Goto Grand Archives", self.player), + set_rule(self.multiworld.get_entrance("Goto Grand Archives", self.player), lambda state: state.has("Grand Archives Key", self.player)) - set_rule(self.world.get_entrance("Goto Kiln Of The First Flame", self.player), + set_rule(self.multiworld.get_entrance("Goto Kiln Of The First Flame", self.player), lambda state: state.has("Cinders of a Lord - Abyss Watcher", self.player) and state.has("Cinders of a Lord - Yhorm the Giant", self.player) and state.has("Cinders of a Lord - Aldrich", self.player) and state.has("Cinders of a Lord - Lothric Prince", self.player)) # Define the access rules to some specific locations - set_rule(self.world.get_location("HWL: Soul of the Dancer", self.player), + set_rule(self.multiworld.get_location("HWL: Soul of the Dancer", self.player), lambda state: state.has("Basin of Vows", self.player)) - set_rule(self.world.get_location("HWL: Greirat's Ashes", self.player), + set_rule(self.multiworld.get_location("HWL: Greirat's Ashes", self.player), lambda state: state.has("Cell Key", self.player)) - set_rule(self.world.get_location("ID: Bellowing Dragoncrest Ring", self.player), + set_rule(self.multiworld.get_location("ID: Bellowing Dragoncrest Ring", self.player), lambda state: state.has("Jailbreaker's Key", self.player)) - set_rule(self.world.get_location("ID: Prisoner Chief's Ashes", self.player), + set_rule(self.multiworld.get_location("ID: Prisoner Chief's Ashes", self.player), lambda state: state.has("Jailer's Key Ring", self.player)) - set_rule(self.world.get_location("ID: Covetous Gold Serpent Ring", self.player), + set_rule(self.multiworld.get_location("ID: Covetous Gold Serpent Ring", self.player), lambda state: state.has("Old Cell Key", self.player)) - set_rule(self.world.get_location("ID: Karla's Ashes", self.player), + set_rule(self.multiworld.get_location("ID: Karla's Ashes", self.player), lambda state: state.has("Jailer's Key Ring", self.player)) black_hand_gotthard_corpse_rule = lambda state: \ (state.can_reach("AL: Cinders of a Lord - Aldrich", "Location", self.player) and state.can_reach("PC: Cinders of a Lord - Yhorm the Giant", "Location", self.player)) - set_rule(self.world.get_location("LC: Grand Archives Key", self.player), black_hand_gotthard_corpse_rule) - set_rule(self.world.get_location("LC: Gotthard Twinswords", self.player), black_hand_gotthard_corpse_rule) + set_rule(self.multiworld.get_location("LC: Grand Archives Key", self.player), black_hand_gotthard_corpse_rule) + set_rule(self.multiworld.get_location("LC: Gotthard Twinswords", self.player), black_hand_gotthard_corpse_rule) - self.world.completion_condition[self.player] = lambda state: \ + self.multiworld.completion_condition[self.player] = lambda state: \ state.has("Cinders of a Lord - Abyss Watcher", self.player) and \ state.has("Cinders of a Lord - Yhorm the Giant", self.player) and \ state.has("Cinders of a Lord - Aldrich", self.player) and \ @@ -226,30 +226,30 @@ class DarkSouls3World(World): def generate_basic(self): # Depending on the specified option, add the Basin of Vows to a specific location or to the item pool item = self.create_item("Basin of Vows") - if self.world.late_basin_of_vows[self.player]: - self.world.get_location("IBV: Soul of Pontiff Sulyvahn", self.player).place_locked_item(item) + if self.multiworld.late_basin_of_vows[self.player]: + self.multiworld.get_location("IBV: Soul of Pontiff Sulyvahn", self.player).place_locked_item(item) else: - self.world.itempool += [item] + self.multiworld.itempool += [item] # Fill item pool with additional items item_pool_len = self.item_name_to_id.__len__() total_required_locations = self.location_name_to_id.__len__() for i in range(item_pool_len, total_required_locations): - self.world.itempool += [self.create_item("Soul of an Intrepid Hero")] + self.multiworld.itempool += [self.create_item("Soul of an Intrepid Hero")] def generate_output(self, output_directory: str): # Depending on the specified option, modify items hexadecimal value to add an upgrade level item_dictionary_copy = item_dictionary.copy() - if self.world.randomize_weapons_level[self.player]: + if self.multiworld.randomize_weapons_level[self.player]: # Randomize some weapons upgrades for name in weapons_upgrade_5_table.keys(): - if self.world.random.randint(0, 100) < 33: - value = self.world.random.randint(1, 5) + if self.multiworld.random.randint(0, 100) < 33: + value = self.multiworld.random.randint(1, 5) item_dictionary_copy[name] += value for name in weapons_upgrade_10_table.keys(): - if self.world.random.randint(0, 100) < 33: - value = self.world.random.randint(1, 10) + if self.multiworld.random.randint(0, 100) < 33: + value = self.multiworld.random.randint(1, 10) item_dictionary_copy[name] += value # Create the mandatory lists to generate the player's output file @@ -258,7 +258,7 @@ class DarkSouls3World(World): locations_id = [] locations_address = [] locations_target = [] - for location in self.world.get_filled_locations(): + for location in self.multiworld.get_filled_locations(): if location.item.player == self.player: items_id.append(location.item.code) items_address.append(item_dictionary[location.item.name]) @@ -273,12 +273,12 @@ class DarkSouls3World(World): data = { "options": { - "auto_equip": self.world.auto_equip[self.player].value, - "lock_equip": self.world.lock_equip[self.player].value, - "no_weapon_requirements": self.world.no_weapon_requirements[self.player].value, + "auto_equip": self.multiworld.auto_equip[self.player].value, + "lock_equip": self.multiworld.lock_equip[self.player].value, + "no_weapon_requirements": self.multiworld.no_weapon_requirements[self.player].value, }, - "seed": self.world.seed_name, # to verify the server's multiworld - "slot": self.world.player_name[self.player], # to connect to server + "seed": self.multiworld.seed_name, # to verify the server's multiworld + "slot": self.multiworld.player_name[self.player], # to connect to server "base_id": self.base_id, # to merge location and items lists "locationsId": locations_id, "locationsAddress": locations_address, @@ -288,6 +288,6 @@ class DarkSouls3World(World): } # generate the file - filename = f"AP-{self.world.seed_name}-P{self.player}-{self.world.player_name[self.player]}.json" + filename = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.player_name[self.player]}.json" with open(os.path.join(output_directory, filename), 'w') as outfile: json.dump(data, outfile) diff --git a/worlds/dkc3/Regions.py b/worlds/dkc3/Regions.py index e33ff38c15..f29c4eb26d 100644 --- a/worlds/dkc3/Regions.py +++ b/worlds/dkc3/Regions.py @@ -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) diff --git a/worlds/dkc3/__init__.py b/worlds/dkc3/__init__.py index 332f23e491..9e2f9e67d8 100644 --- a/worlds/dkc3/__init__.py +++ b/worlds/dkc3/__init__.py @@ -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) diff --git a/worlds/factorio/Mod.py b/worlds/factorio/Mod.py index 33e3b75a7b..495218ef56 100644 --- a/worlds/factorio/Mod.py +++ b/worlds/factorio/Mod.py @@ -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: diff --git a/worlds/factorio/Shapes.py b/worlds/factorio/Shapes.py index 4f093c3a86..7ec6f07a86 100644 --- a/worlds/factorio/Shapes.py +++ b/worlds/factorio/Shapes.py @@ -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 diff --git a/worlds/factorio/__init__.py b/worlds/factorio/__init__.py index f1bdce1755..c533d425cc 100644 --- a/worlds/factorio/__init__.py +++ b/worlds/factorio/__init__.py @@ -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]]: diff --git a/worlds/ff1/__init__.py b/worlds/ff1/__init__.py index 24b8e7081b..5eeb3b1be9 100644 --- a/worlds/ff1/__init__.py +++ b/worlds/ff1/__init__.py @@ -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): diff --git a/worlds/generic/Rules.py b/worlds/generic/Rules.py index e69a34c504..865dc39578 100644 --- a/worlds/generic/Rules.py +++ b/worlds/generic/Rules.py @@ -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 diff --git a/worlds/generic/__init__.py b/worlds/generic/__init__.py index 0d8a220d98..4c7c14c48f 100644 --- a/worlds/generic/__init__.py +++ b/worlds/generic/__init__.py @@ -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": diff --git a/worlds/hk/Rules.py b/worlds/hk/Rules.py index 50c0572e47..4fe4160b4c 100644 --- a/worlds/hk/Rules.py +++ b/worlds/hk/Rules.py @@ -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 diff --git a/worlds/hk/__init__.py b/worlds/hk/__init__.py index 9ed0c929bb..6a1000b6c2 100644 --- a/worlds/hk/__init__.py +++ b/worlds/hk/__init__.py @@ -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) diff --git a/worlds/hylics2/Rules.py b/worlds/hylics2/Rules.py index be38e102ea..4b804386d3 100644 --- a/worlds/hylics2/Rules.py +++ b/worlds/hylics2/Rules.py @@ -89,7 +89,7 @@ class Hylics2Logic(LogicMixin): def set_rules(hylics2world): - world = hylics2world.world + world = hylics2world.multiworld player = hylics2world.player # Afterlife diff --git a/worlds/hylics2/__init__.py b/worlds/hylics2/__init__.py index b429eb6a44..80c80a0d36 100644 --- a/worlds/hylics2/__init__.py +++ b/worlds/hylics2/__init__.py @@ -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"]])) diff --git a/worlds/meritous/__init__.py b/worlds/meritous/__init__.py index d0d076da40..1bf1bfc0f2 100644 --- a/worlds/meritous/__init__.py +++ b/worlds/meritous/__init__.py @@ -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: diff --git a/worlds/minecraft/Rules.py b/worlds/minecraft/Rules.py index 06576d7012..2ec9523762 100644 --- a/worlds/minecraft/Rules.py +++ b/worlds/minecraft/Rules.py @@ -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): diff --git a/worlds/minecraft/__init__.py b/worlds/minecraft/__init__.py index fd5752bd40..f94a1159ed 100644 --- a/worlds/minecraft/__init__.py +++ b/worlds/minecraft/__init__.py @@ -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 diff --git a/worlds/oot/Cosmetics.py b/worlds/oot/Cosmetics.py index b91b4606f8..520641db63 100644 --- a/worlds/oot/Cosmetics.py +++ b/worlds/oot/Cosmetics.py @@ -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 diff --git a/worlds/oot/Dungeon.py b/worlds/oot/Dungeon.py index 50285013aa..98da609f3d 100644 --- a/worlds/oot/Dungeon.py +++ b/worlds/oot/Dungeon.py @@ -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) diff --git a/worlds/oot/Entrance.py b/worlds/oot/Entrance.py index 4e11083ea1..e480c957a6 100644 --- a/worlds/oot/Entrance.py +++ b/worlds/oot/Entrance.py @@ -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) diff --git a/worlds/oot/EntranceShuffle.py b/worlds/oot/EntranceShuffle.py index bd06a3d81b..cfe1a5da69 100644 --- a/worlds/oot/EntranceShuffle.py +++ b/worlds/oot/EntranceShuffle.py @@ -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}') diff --git a/worlds/oot/Hints.py b/worlds/oot/Hints.py index aaafeb20b8..3c3f5cc3a7 100644 --- a/worlds/oot/Hints.py +++ b/worlds/oot/Hints.py @@ -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 diff --git a/worlds/oot/ItemPool.py b/worlds/oot/ItemPool.py index 12c9c26292..e1b9ae7b58 100644 --- a/worlds/oot/ItemPool.py +++ b/worlds/oot/ItemPool.py @@ -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: diff --git a/worlds/oot/Messages.py b/worlds/oot/Messages.py index e576b96b51..a1a50549dd 100644 --- a/worlds/oot/Messages.py +++ b/worlds/oot/Messages.py @@ -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 diff --git a/worlds/oot/Patches.py b/worlds/oot/Patches.py index 322d2d838a..31e573113f 100644 --- a/worlds/oot/Patches.py +++ b/worlds/oot/Patches.py @@ -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) diff --git a/worlds/oot/RuleParser.py b/worlds/oot/RuleParser.py index 6f972447db..e6de24609a 100644 --- a/worlds/oot/RuleParser.py +++ b/worlds/oot/RuleParser.py @@ -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 diff --git a/worlds/oot/Rules.py b/worlds/oot/Rules.py index 915aae77c1..46b8b69cc5 100644 --- a/worlds/oot/Rules.py +++ b/worlds/oot/Rules.py @@ -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 diff --git a/worlds/oot/__init__.py b/worlds/oot/__init__.py index c985ea13f0..2f351506f2 100644 --- a/worlds/oot/__init__.py +++ b/worlds/oot/__init__.py @@ -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')): diff --git a/worlds/oribf/Rules.py b/worlds/oribf/Rules.py index fdf8a54de1..dea4d05c57 100644 --- a/worlds/oribf/Rules.py +++ b/worlds/oribf/Rules.py @@ -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): diff --git a/worlds/oribf/__init__.py b/worlds/oribf/__init__.py index 45e666efeb..485efdf6d9 100644 --- a/worlds/oribf/__init__.py +++ b/worlds/oribf/__init__.py @@ -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, diff --git a/worlds/overcooked2/Overcooked2Levels.py b/worlds/overcooked2/Overcooked2Levels.py index aac9ea0cbe..720ab66b36 100644 --- a/worlds/overcooked2/Overcooked2Levels.py +++ b/worlds/overcooked2/Overcooked2Levels.py @@ -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: diff --git a/worlds/overcooked2/__init__.py b/worlds/overcooked2/__init__.py index c47b755fbf..6f1e1281f8 100644 --- a/worlds/overcooked2/__init__.py +++ b/worlds/overcooked2/__init__.py @@ -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: diff --git a/worlds/pokemon_rb/__init__.py b/worlds/pokemon_rb/__init__.py index 4c37db8450..c6980fc0a7 100644 --- a/worlds/pokemon_rb/__init__.py +++ b/worlds/pokemon_rb/__init__.py @@ -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): diff --git a/worlds/pokemon_rb/basepatch_blue.bsdiff4 b/worlds/pokemon_rb/basepatch_blue.bsdiff4 index c688ebede0..1dd7101686 100644 Binary files a/worlds/pokemon_rb/basepatch_blue.bsdiff4 and b/worlds/pokemon_rb/basepatch_blue.bsdiff4 differ diff --git a/worlds/pokemon_rb/basepatch_red.bsdiff4 b/worlds/pokemon_rb/basepatch_red.bsdiff4 index a9005d3744..488ae2727c 100644 Binary files a/worlds/pokemon_rb/basepatch_red.bsdiff4 and b/worlds/pokemon_rb/basepatch_red.bsdiff4 differ diff --git a/worlds/pokemon_rb/docs/setup_en.md b/worlds/pokemon_rb/docs/setup_en.md index 58ed3b0dc6..7ec79b6107 100644 --- a/worlds/pokemon_rb/docs/setup_en.md +++ b/worlds/pokemon_rb/docs/setup_en.md @@ -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 `
:` on the te server uses password, type in the bottom textfield `/connect
: [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. diff --git a/worlds/pokemon_rb/items.py b/worlds/pokemon_rb/items.py index 53722c02a4..82ac6345d9 100644 --- a/worlds/pokemon_rb/items.py +++ b/worlds/pokemon_rb/items.py @@ -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"]), diff --git a/worlds/pokemon_rb/locations.py b/worlds/pokemon_rb/locations.py index a619336f57..e6f54dc813 100644 --- a/worlds/pokemon_rb/locations.py +++ b/worlds/pokemon_rb/locations.py @@ -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"), diff --git a/worlds/pokemon_rb/logic.py b/worlds/pokemon_rb/logic.py index 3b1a3594bd..8b60bd8b58 100644 --- a/worlds/pokemon_rb/logic.py +++ b/worlds/pokemon_rb/logic.py @@ -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 ;) diff --git a/worlds/pokemon_rb/options.py b/worlds/pokemon_rb/options.py index e7e972f3b8..6ab2b25485 100644 --- a/worlds/pokemon_rb/options.py +++ b/worlds/pokemon_rb/options.py @@ -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 diff --git a/worlds/pokemon_rb/regions.py b/worlds/pokemon_rb/regions.py index 1650e640cb..5141f06cf1 100644 --- a/worlds/pokemon_rb/regions.py +++ b/worlds/pokemon_rb/regions.py @@ -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) diff --git a/worlds/pokemon_rb/rom.py b/worlds/pokemon_rb/rom.py index 0487387363..12e22a0f8d 100644 --- a/worlds/pokemon_rb/rom.py +++ b/worlds/pokemon_rb/rom.py @@ -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) diff --git a/worlds/pokemon_rb/rom_addresses.py b/worlds/pokemon_rb/rom_addresses.py index c04a75bcc4..206365a287 100644 --- a/worlds/pokemon_rb/rom_addresses.py +++ b/worlds/pokemon_rb/rom_addresses.py @@ -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, } diff --git a/worlds/pokemon_rb/rules.py b/worlds/pokemon_rb/rules.py index 0e97b705db..2f885fd6e5 100644 --- a/worlds/pokemon_rb/rules.py +++ b/worlds/pokemon_rb/rules.py @@ -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), diff --git a/worlds/raft/Rules.py b/worlds/raft/Rules.py index a55c0ef3e8..e84068a6f5 100644 --- a/worlds/raft/Rules.py +++ b/worlds/raft/Rules.py @@ -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) diff --git a/worlds/raft/__init__.py b/worlds/raft/__init__.py index 860ba9aab5..e326f734ba 100644 --- a/worlds/raft/__init__.py +++ b/worlds/raft/__init__.py @@ -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) diff --git a/worlds/rogue_legacy/Items.py b/worlds/rogue_legacy/Items.py index 5a8f66d523..b52d603a1d 100644 --- a/worlds/rogue_legacy/Items.py +++ b/worlds/rogue_legacy/Items.py @@ -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} diff --git a/worlds/rogue_legacy/Locations.py b/worlds/rogue_legacy/Locations.py index ddbbecf0ec..2d4197e6db 100644 --- a/worlds/rogue_legacy/Locations.py +++ b/worlds/rogue_legacy/Locations.py @@ -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()} diff --git a/worlds/rogue_legacy/Names/ItemName.py b/worlds/rogue_legacy/Names/ItemName.py deleted file mode 100644 index 7532ac1936..0000000000 --- a/worlds/rogue_legacy/Names/ItemName.py +++ /dev/null @@ -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" diff --git a/worlds/rogue_legacy/Names/LocationName.py b/worlds/rogue_legacy/Names/LocationName.py deleted file mode 100644 index e4732f9f21..0000000000 --- a/worlds/rogue_legacy/Names/LocationName.py +++ /dev/null @@ -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" diff --git a/worlds/rogue_legacy/Names/__init__.py b/worlds/rogue_legacy/Names/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/worlds/rogue_legacy/Options.py b/worlds/rogue_legacy/Options.py index 13c9828451..d8298c85c8 100644 --- a/worlds/rogue_legacy/Options.py +++ b/worlds/rogue_legacy/Options.py @@ -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, diff --git a/worlds/rogue_legacy/Regions.py b/worlds/rogue_legacy/Regions.py index 5931bf2819..76cab97df6 100644 --- a/worlds/rogue_legacy/Regions.py +++ b/worlds/rogue_legacy/Regions.py @@ -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 + + diff --git a/worlds/rogue_legacy/Rules.py b/worlds/rogue_legacy/Rules.py index 6d2cb4cd31..b20cdd009f 100644 --- a/worlds/rogue_legacy/Rules.py +++ b/worlds/rogue_legacy/Rules.py @@ -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) diff --git a/worlds/rogue_legacy/Traits.py b/worlds/rogue_legacy/Traits.py index 5959891d9d..19a679756a 100644 --- a/worlds/rogue_legacy/Traits.py +++ b/worlds/rogue_legacy/Traits.py @@ -36,4 +36,3 @@ traits = [ "Glaucoma", "Adopted", ] - diff --git a/worlds/rogue_legacy/__init__.py b/worlds/rogue_legacy/__init__.py index 81e6202787..91107c75ea 100644 --- a/worlds/rogue_legacy/__init__.py +++ b/worlds/rogue_legacy/__init__.py @@ -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")) diff --git a/worlds/rogue_legacy/docs/en_Rogue Legacy.md b/worlds/rogue_legacy/docs/en_Rogue Legacy.md index df85b8afe6..f8a166abf9 100644 --- a/worlds/rogue_legacy/docs/en_Rogue Legacy.md +++ b/worlds/rogue_legacy/docs/en_Rogue Legacy.md @@ -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). \ No newline at end of file diff --git a/worlds/rogue_legacy/docs/rogue-legacy_en.md b/worlds/rogue_legacy/docs/rogue-legacy_en.md index 1c049c7ca5..decb484748 100644 --- a/worlds/rogue_legacy/docs/rogue-legacy_en.md +++ b/worlds/rogue_legacy/docs/rogue-legacy_en.md @@ -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. diff --git a/worlds/ror2/__init__.py b/worlds/ror2/__init__.py index af65a15ea4..0c84f5b1b3 100644 --- a/worlds/ror2/__init__.py +++ b/worlds/ror2/__init__.py @@ -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: diff --git a/worlds/sa2b/Regions.py b/worlds/sa2b/Regions.py index b39b13c100..dc32bf9e8c 100644 --- a/worlds/sa2b/Regions.py +++ b/worlds/sa2b/Regions.py @@ -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) diff --git a/worlds/sa2b/__init__.py b/worlds/sa2b/__init__.py index 7269a66c68..f341df9e85 100644 --- a/worlds/sa2b/__init__.py +++ b/worlds/sa2b/__init__.py @@ -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" diff --git a/worlds/sc2wol/Regions.py b/worlds/sc2wol/Regions.py index b0a3a51e44..d42a417b1a 100644 --- a/worlds/sc2wol/Regions.py +++ b/worlds/sc2wol/Regions.py @@ -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]: diff --git a/worlds/sc2wol/__init__.py b/worlds/sc2wol/__init__.py index 70226e7afd..dc77792d40 100644 --- a/worlds/sc2wol/__init__.py +++ b/worlds/sc2wol/__init__.py @@ -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 = {} diff --git a/worlds/sm/Client.py b/worlds/sm/Client.py index 190ce29ecc..16aea935c0 100644 --- a/worlds/sm/Client.py +++ b/worlds/sm/Client.py @@ -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 diff --git a/worlds/sm/Rom.py b/worlds/sm/Rom.py index e5f5bc7a37..67c0780dbf 100644 --- a/worlds/sm/Rom.py +++ b/worlds/sm/Rom.py @@ -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 diff --git a/worlds/sm/__init__.py b/worlds/sm/__init__.py index fc19b4e133..1cb5932592 100644 --- a/worlds/sm/__init__.py +++ b/worlds/sm/__init__.py @@ -5,7 +5,7 @@ import copy import os import threading import base64 -from typing import Set, TextIO +from typing import Any, Dict, Iterable, List, Set, TextIO, TypedDict from worlds.sm.variaRandomizer.graph.graph_utils import GraphUtils @@ -15,7 +15,7 @@ from .Regions import create_regions from .Rules import set_rules, add_entrance_rule from .Options import sm_options from .Client import SMSNIClient -from .Rom import get_base_rom_path, ROM_PLAYER_LIMIT, SMDeltaPatch, get_sm_symbols +from .Rom import get_base_rom_path, SM_ROM_MAX_PLAYERID, SM_ROM_PLAYERDATA_COUNT, SMDeltaPatch, get_sm_symbols import Utils from BaseClasses import Region, Entrance, Location, MultiWorld, Item, ItemClassification, RegionType, CollectionState, Tutorial @@ -67,6 +67,13 @@ class SMWeb(WebWorld): ["Farrak Kilhn"] )] + +class ByteEdit(TypedDict): + sym: Dict[str, Any] + offset: int + values: Iterable[int] + + locations_start_id = 82000 items_start_id = 83000 @@ -111,27 +118,27 @@ class SMWorld(World): def generate_early(self): Logic.factory('vanilla') - self.variaRando = VariaRandomizer(self.world, get_base_rom_path(), self.player) - self.world.state.smbm[self.player] = SMBoolManager(self.player, self.variaRando.maxDifficulty) + self.variaRando = VariaRandomizer(self.multiworld, get_base_rom_path(), self.player) + self.multiworld.state.smbm[self.player] = SMBoolManager(self.player, self.variaRando.maxDifficulty) # keeps Nothing items local so no player will ever pickup Nothing # doing so reduces contribution of this world to the Multiworld the more Nothing there is though - self.world.local_items[self.player].value.add('Nothing') - self.world.local_items[self.player].value.add('No Energy') + self.multiworld.local_items[self.player].value.add('Nothing') + self.multiworld.local_items[self.player].value.add('No Energy') if (self.variaRando.args.morphPlacement == "early"): - self.world.local_items[self.player].value.add('Morph') + self.multiworld.local_items[self.player].value.add('Morph') - self.remote_items = self.world.remote_items[self.player] + self.remote_items = self.multiworld.remote_items[self.player] if (len(self.variaRando.randoExec.setup.restrictedLocs) > 0): - self.world.accessibility[self.player] = self.world.accessibility[self.player].from_text("minimal") - logger.warning(f"accessibility forced to 'minimal' for player {self.world.get_player_name(self.player)} because of 'fun' settings") + self.multiworld.accessibility[self.player] = self.multiworld.accessibility[self.player].from_text("minimal") + logger.warning(f"accessibility forced to 'minimal' for player {self.multiworld.get_player_name(self.player)} because of 'fun' settings") def generate_basic(self): itemPool = self.variaRando.container.itemPool - self.startItems = [variaItem for item in self.world.precollected_items[self.player] for variaItem in ItemManager.Items.values() if variaItem.Name == item.name] - if self.world.start_inventory_removes_from_pool[self.player]: + self.startItems = [variaItem for item in self.multiworld.precollected_items[self.player] for variaItem in ItemManager.Items.values() if variaItem.Name == item.name] + if self.multiworld.start_inventory_removes_from_pool[self.player]: for item in self.startItems: if (item in itemPool): itemPool.remove(item) @@ -175,33 +182,34 @@ class SMWorld(World): else: pool.append(smitem) - self.world.itempool += pool + self.multiworld.itempool += pool for (location, item) in self.locked_items.items(): - self.world.get_location(location, self.player).place_locked_item(item) - self.world.get_location(location, self.player).address = None + self.multiworld.get_location(location, self.player).place_locked_item(item) + self.multiworld.get_location(location, self.player).address = None - startAP = self.world.get_entrance('StartAP', self.player) - startAP.connect(self.world.get_region(self.variaRando.args.startLocation, self.player)) + startAP = self.multiworld.get_entrance('StartAP', self.player) + startAP.connect(self.multiworld.get_region(self.variaRando.args.startLocation, self.player)) for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions: - src_region = self.world.get_region(src.Name, self.player) - dest_region = self.world.get_region(dest.Name, self.player) - if ((src.Name + "->" + dest.Name, self.player) not in self.world._entrance_cache): + src_region = self.multiworld.get_region(src.Name, self.player) + dest_region = self.multiworld.get_region(dest.Name, self.player) + if ((src.Name + "->" + dest.Name, self.player) not in self.multiworld._entrance_cache): src_region.exits.append(Entrance(self.player, src.Name + "->" + dest.Name, src_region)) - srcDestEntrance = self.world.get_entrance(src.Name + "->" + dest.Name, self.player) + srcDestEntrance = self.multiworld.get_entrance(src.Name + "->" + dest.Name, self.player) srcDestEntrance.connect(dest_region) - add_entrance_rule(self.world.get_entrance(src.Name + "->" + dest.Name, self.player), self.player, getAccessPoint(src.Name).traverse) + add_entrance_rule(self.multiworld.get_entrance(src.Name + "->" + dest.Name, self.player), self.player, getAccessPoint(src.Name).traverse) def set_rules(self): - set_rules(self.world, self.player) + set_rules(self.multiworld, self.player) def create_regions(self): create_locations(self, self.player) - create_regions(self, self.world, self.player) + create_regions(self, self.multiworld, self.player) - def getWordArray(self, w): # little-endian convert a 16-bit number to an array of numbers <= 255 each + def getWordArray(self, w: int) -> List[int]: + """ little-endian convert a 16-bit number to an array of numbers <= 255 each """ return [w & 0x00FF, (w & 0xFF00) >> 8] # used for remote location Credits Spoiler of local items @@ -281,48 +289,87 @@ class SMWorld(World): "data", "SMBasepatch_prebuilt", "variapatches.ips")) def APPostPatchRom(self, romPatcher): - symbols = get_sm_symbols(os.path.join(os.path.dirname(__file__), + symbols = get_sm_symbols(os.path.join(os.path.dirname(__file__), "data", "SMBasepatch_prebuilt", "sm-basepatch-symbols.json")) - multiWorldLocations = [] - multiWorldItems = [] + + # gather all player ids and names relevant to this rom, then write player name and player id data tables + playerIdSet: Set[int] = {0} # 0 is for "Archipelago" server + for itemLoc in self.multiworld.get_locations(): + assert itemLoc.item, f"World of player '{self.multiworld.player_name[itemLoc.player]}' has a loc.item " + \ + f"that is {itemLoc.item} during generate_output" + # add each playerid who has a location containing an item to send to us *or* to an item_link we're part of + if itemLoc.item.player == self.player or \ + (itemLoc.item.player in self.multiworld.groups and + self.player in self.multiworld.groups[itemLoc.item.player]['players']): + playerIdSet |= {itemLoc.player} + # add each playerid, including item link ids, that we'll be sending items to + if itemLoc.player == self.player: + playerIdSet |= {itemLoc.item.player} + if len(playerIdSet) > SM_ROM_PLAYERDATA_COUNT: + # max 202 entries, but it's possible for item links to add enough replacement items for us, that are placed + # in worlds that otherwise have no relation to us, that the 2*location count limit is exceeded + logger.warning("SM is interacting with too many players to fit in ROM. " + f"Removing the highest {len(playerIdSet) - SM_ROM_PLAYERDATA_COUNT} ids to fit") + playerIdSet = set(sorted(playerIdSet)[:SM_ROM_PLAYERDATA_COUNT]) + otherPlayerIndex: Dict[int, int] = {} # ap player id -> rom-local player index + playerNameData: List[ByteEdit] = [] + playerIdData: List[ByteEdit] = [] + # sort all player data by player id so that the game can look up a player's data reasonably quickly when + # the client sends an ap playerid to the game + for i, playerid in enumerate(sorted(playerIdSet)): + playername = self.multiworld.player_name[playerid] if playerid != 0 else "Archipelago" + playerIdForRom = playerid + if playerid > SM_ROM_MAX_PLAYERID: + # note, playerIdForRom = 0 is not unique so the game cannot look it up. + # instead it will display the player received-from as "Archipelago" + playerIdForRom = 0 + if playerid == self.player: + raise Exception(f"SM rom cannot fit enough bits to represent self player id {playerid}") + else: + logger.warning(f"SM rom cannot fit enough bits to represent player id {playerid}, setting to 0 in rom") + otherPlayerIndex[playerid] = i + playerNameData.append({"sym": symbols["rando_player_name_table"], + "offset": i * 16, + "values": playername[:16].upper().center(16).encode()}) + playerIdData.append({"sym": symbols["rando_player_id_table"], + "offset": i * 2, + "values": self.getWordArray(playerIdForRom)}) + + multiWorldLocations: List[ByteEdit] = [] + multiWorldItems: List[ByteEdit] = [] idx = 0 - self.playerIDMap = {} - playerIDCount = 0 # 0 is for "Archipelago" server; highest possible = 200 (201 entries) vanillaItemTypesCount = 21 - for itemLoc in self.world.get_locations(): + for itemLoc in self.multiworld.get_locations(): if itemLoc.player == self.player and locationsDict[itemLoc.name].Id != None: - # this SM world can find this item: write full item data to tables and assign player data for writing - romPlayerID = itemLoc.item.player if itemLoc.item.player <= ROM_PLAYER_LIMIT else 0 + # item to place in this SM world: write full item data to tables if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items: itemId = ItemManager.Items[itemLoc.item.type].Id else: - itemId = ItemManager.Items['ArchipelagoItem'].Id + idx + itemId = ItemManager.Items["ArchipelagoItem"].Id + idx multiWorldItems.append({"sym": symbols["message_item_names"], "offset": (vanillaItemTypesCount + idx)*64, "values": self.convertToROMItemName(itemLoc.item.name)}) idx += 1 - if (romPlayerID > 0 and romPlayerID not in self.playerIDMap.keys()): - playerIDCount += 1 - self.playerIDMap[romPlayerID] = playerIDCount + if itemLoc.item.player == self.player: + itemDestinationType = 0 # dest type 0 means 'regular old SM item' per itemtable.asm + elif itemLoc.item.player in self.multiworld.groups and \ + self.player in self.multiworld.groups[itemLoc.item.player]['players']: + # dest type 2 means 'SM item link item that sends to the current player and others' + # per itemtable.asm (groups are synonymous with item_links, currently) + itemDestinationType = 2 + else: + itemDestinationType = 1 # dest type 1 means 'item for entirely someone else' per itemtable.asm - [w0, w1] = self.getWordArray(0 if itemLoc.item.player == self.player else 1) + [w0, w1] = self.getWordArray(itemDestinationType) [w2, w3] = self.getWordArray(itemId) - [w4, w5] = self.getWordArray(romPlayerID) + [w4, w5] = self.getWordArray(otherPlayerIndex[itemLoc.item.player] if itemLoc.item.player in + otherPlayerIndex else 0) [w6, w7] = self.getWordArray(0 if itemLoc.item.advancement else 1) multiWorldLocations.append({"sym": symbols["rando_item_table"], "offset": locationsDict[itemLoc.name].Id*8, "values": [w0, w1, w2, w3, w4, w5, w6, w7]}) - elif itemLoc.item.player == self.player: - # this SM world owns the item: so in case the sending player might not have anything placed in this - # world to receive from it, assign them space in playerIDMap so that the ROM can display their name - # (SM item name not needed, as SM item type id will be in the message they send to this world live) - romPlayerID = itemLoc.player if itemLoc.player <= ROM_PLAYER_LIMIT else 0 - if (romPlayerID > 0 and romPlayerID not in self.playerIDMap.keys()): - playerIDCount += 1 - self.playerIDMap[romPlayerID] = playerIDCount - itemSprites = [{"fileName": "off_world_prog_item.bin", "paletteSymbolName": "prog_item_eight_palette_indices", "dataSymbolName": "offworld_graphics_data_progression_item"}, @@ -331,7 +378,7 @@ class SMWorld(World): "paletteSymbolName": "nonprog_item_eight_palette_indices", "dataSymbolName": "offworld_graphics_data_item"}] idx = 0 - offworldSprites = [] + offworldSprites: List[ByteEdit] = [] for itemSprite in itemSprites: with open(os.path.join(os.path.dirname(__file__), "data", "custom_sprite", itemSprite["fileName"]), 'rb') as stream: buffer = bytearray(stream.read()) @@ -343,31 +390,21 @@ class SMWorld(World): "values": buffer[8:264]}) idx += 1 - deathLink = [{"sym": symbols["config_deathlink"], - "offset": 0, - "values": [self.world.death_link[self.player].value]}] - remoteItem = [{"sym": symbols["config_remote_items"], - "offset": 0, - "values": self.getWordArray(0b001 + (0b010 if self.remote_items else 0b000))}] - ownPlayerId = [{"sym": symbols["config_player_id"], - "offset": 0, - "values": self.getWordArray(self.player)}] - - playerNames = [] - playerNameIDMap = [] - playerNames.append({"sym": symbols["rando_player_table"], - "offset": 0, - "values": "Archipelago".upper().center(16).encode()}) - playerNameIDMap.append({"sym": symbols["rando_player_id_table"], - "offset": 0, - "values": self.getWordArray(0)}) - for key,value in self.playerIDMap.items(): - playerNames.append({"sym": symbols["rando_player_table"], - "offset": value * 16, - "values": self.world.player_name[key][:16].upper().center(16).encode()}) - playerNameIDMap.append({"sym": symbols["rando_player_id_table"], - "offset": value * 2, - "values": self.getWordArray(key)}) + deathLink: List[ByteEdit] = [{ + "sym": symbols["config_deathlink"], + "offset": 0, + "values": [self.multiworld.death_link[self.player].value] + }] + remoteItem: List[ByteEdit] = [{ + "sym": symbols["config_remote_items"], + "offset": 0, + "values": self.getWordArray(0b001 + (0b010 if self.remote_items else 0b000)) + }] + ownPlayerId: List[ByteEdit] = [{ + "sym": symbols["config_player_id"], + "offset": 0, + "values": self.getWordArray(self.player) + }] patchDict = { 'MultiWorldLocations': multiWorldLocations, 'MultiWorldItems': multiWorldItems, @@ -375,15 +412,15 @@ class SMWorld(World): 'deathLink': deathLink, 'remoteItem': remoteItem, 'ownPlayerId': ownPlayerId, - 'PlayerName': playerNames, - 'PlayerNameIDMap': playerNameIDMap} + 'playerNameData': playerNameData, + 'playerIdData': playerIdData} # convert an array of symbolic byte_edit dicts like {"sym": symobj, "offset": 0, "values": [1, 0]} # to a single rom patch dict like {0x438c: [1, 0], 0xa4a5: [0, 0, 0]} which varia will understand and apply - def resolve_symbols_to_file_offset_based_dict(byte_edits_arr) -> dict: - this_patch_as_dict = {} + def resolve_symbols_to_file_offset_based_dict(byte_edits_arr: List[ByteEdit]) -> Dict[int, Iterable[int]]: + this_patch_as_dict: Dict[int, Iterable[int]] = {} for byte_edit in byte_edits_arr: - offset_within_rom_file = byte_edit["sym"]["offset_within_rom_file"] + byte_edit["offset"] + offset_within_rom_file: int = byte_edit["sym"]["offset_within_rom_file"] + byte_edit["offset"] this_patch_as_dict[offset_within_rom_file] = byte_edit["values"] return this_patch_as_dict @@ -399,7 +436,7 @@ class SMWorld(World): # set rom name # 21 bytes from Main import __version__ - self.romName = bytearray(f'SM{__version__.replace(".", "")[0:3]}_{self.player}_{self.world.seed:11}', 'utf8')[:21] + self.romName = bytearray(f'SM{__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed:11}', 'utf8')[:21] self.romName.extend([0] * (21 - len(self.romName))) # clients should read from 0x7FC0, the location of the rom title in the SNES header. # duplicative ROM name at 0x1C4F00 is still written here for now, since people with archipelago pre-0.3.0 client installed will still be depending on this location for connecting to SM @@ -493,20 +530,20 @@ class SMWorld(World): if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items else 'ArchipelagoItem'], locationsDict[itemLoc.name], True) - for itemLoc in self.world.get_locations() if itemLoc.player == self.player + for itemLoc in self.multiworld.get_locations() if itemLoc.player == self.player ] romPatcher.writeItemsLocs(itemLocs) - itemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type], locationsDict[itemLoc.name] if itemLoc.name in locationsDict and itemLoc.player == self.player else self.DummyLocation(self.world.get_player_name(itemLoc.player) + " " + itemLoc.name), True) for itemLoc in self.world.get_locations() if itemLoc.item.player == self.player] - progItemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type], locationsDict[itemLoc.name] if itemLoc.name in locationsDict and itemLoc.player == self.player else self.DummyLocation(self.world.get_player_name(itemLoc.player) + " " + itemLoc.name), True) for itemLoc in self.world.get_locations() if itemLoc.item.player == self.player and itemLoc.item.advancement == True] - # progItemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type if itemLoc.item.type in ItemManager.Items else 'ArchipelagoItem'], locationsDict[itemLoc.name], True) for itemLoc in self.world.get_locations() if itemLoc.player == self.player and itemLoc.item.player == self.player and itemLoc.item.advancement == True] + itemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type], locationsDict[itemLoc.name] if itemLoc.name in locationsDict and itemLoc.player == self.player else self.DummyLocation(self.multiworld.get_player_name(itemLoc.player) + " " + itemLoc.name), True) for itemLoc in self.multiworld.get_locations() if itemLoc.item.player == self.player] + progItemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type], locationsDict[itemLoc.name] if itemLoc.name in locationsDict and itemLoc.player == self.player else self.DummyLocation(self.multiworld.get_player_name(itemLoc.player) + " " + itemLoc.name), True) for itemLoc in self.multiworld.get_locations() if itemLoc.item.player == self.player and itemLoc.item.advancement == True] + # progItemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type if itemLoc.item.type in ItemManager.Items else 'ArchipelagoItem'], locationsDict[itemLoc.name], True) for itemLoc in self.multiworld.get_locations() if itemLoc.player == self.player and itemLoc.item.player == self.player and itemLoc.item.advancement == True] # romPatcher.writeSplitLocs(self.variaRando.args.majorsSplit, itemLocs, progItemLocs) romPatcher.writeSpoiler(itemLocs, progItemLocs) romPatcher.writeRandoSettings(self.variaRando.randoExec.randoSettings, itemLocs) def generate_output(self, output_directory: str): - outfilebase = self.world.get_out_file_name_base(self.player) + outfilebase = self.multiworld.get_out_file_name_base(self.player) outputFilename = os.path.join(output_directory, f"{outfilebase}.sfc") try: @@ -516,8 +553,8 @@ class SMWorld(World): except: raise else: - patch = SMDeltaPatch(os.path.splitext(outputFilename)[0]+SMDeltaPatch.patch_file_ending, player=self.player, - player_name=self.world.player_name[self.player], patched_path=outputFilename) + patch = SMDeltaPatch(os.path.splitext(outputFilename)[0] + SMDeltaPatch.patch_file_ending, player=self.player, + player_name=self.multiworld.player_name[self.player], patched_path=outputFilename) patch.write() finally: if os.path.exists(outputFilename): @@ -560,13 +597,13 @@ class SMWorld(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 fill_slot_data(self): slot_data = {} - if not self.world.is_race: + if not self.multiworld.is_race: for option_name in self.option_definitions: - option = getattr(self.world, option_name)[self.player] + option = getattr(self.multiworld, option_name)[self.player] slot_data[option_name] = option.value slot_data["Preset"] = { "Knows": {}, @@ -606,11 +643,11 @@ class SMWorld(World): player=self.player) def get_filler_item_name(self) -> str: - if self.world.random.randint(0, 100) < self.world.minor_qty[self.player].value: - power_bombs = self.world.power_bomb_qty[self.player].value - missiles = self.world.missile_qty[self.player].value - super_missiles = self.world.super_qty[self.player].value - roll = self.world.random.randint(1, power_bombs + missiles + super_missiles) + if self.multiworld.random.randint(0, 100) < self.multiworld.minor_qty[self.player].value: + power_bombs = self.multiworld.power_bomb_qty[self.player].value + missiles = self.multiworld.missile_qty[self.player].value + super_missiles = self.multiworld.super_qty[self.player].value + roll = self.multiworld.random.randint(1, power_bombs + missiles + super_missiles) if roll <= power_bombs: return "Power Bomb" elif roll <= power_bombs + missiles: @@ -621,20 +658,20 @@ class SMWorld(World): return "Nothing" def pre_fill(self): - if (self.variaRando.args.morphPlacement == "early") and next((item for item in self.world.itempool if item.player == self.player and item.name == "Morph Ball"), False): + if (self.variaRando.args.morphPlacement == "early") and next((item for item in self.multiworld.itempool if item.player == self.player and item.name == "Morph Ball"), False): viable = [] - for location in self.world.get_locations(): + for location in self.multiworld.get_locations(): if location.player == self.player \ and location.item is None \ - and location.can_reach(self.world.state): + and location.can_reach(self.multiworld.state): viable.append(location) - self.world.random.shuffle(viable) - key = self.world.create_item("Morph Ball", self.player) + self.multiworld.random.shuffle(viable) + key = self.multiworld.create_item("Morph Ball", self.player) loc = viable.pop() loc.place_locked_item(key) - self.world.itempool[:] = [item for item in self.world.itempool if - item.player != self.player or - item.name != "Morph Ball"] + self.multiworld.itempool[:] = [item for item in self.multiworld.itempool if + item.player != self.player or + item.name != "Morph Ball"] if len(self.NothingPool) > 0: nonChozoLoc = [] @@ -647,8 +684,8 @@ class SMWorld(World): else: nonChozoLoc.append(loc) - self.world.random.shuffle(nonChozoLoc) - self.world.random.shuffle(chozoLoc) + self.multiworld.random.shuffle(nonChozoLoc) + self.multiworld.random.shuffle(chozoLoc) missingCount = len(self.NothingPool) - len(nonChozoLoc) locations = nonChozoLoc if (missingCount > 0): @@ -677,17 +714,17 @@ class SMWorld(World): break def write_spoiler(self, spoiler_handle: TextIO): - if self.world.area_randomization[self.player].value != 0: + if self.multiworld.area_randomization[self.player].value != 0: spoiler_handle.write('\n\nArea Transitions:\n\n') - spoiler_handle.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_name(self.player)}: ' - if self.world.players > 1 else '', src.Name, + spoiler_handle.write('\n'.join(['%s%s %s %s' % (f'{self.multiworld.get_player_name(self.player)}: ' + if self.multiworld.players > 1 else '', src.Name, '<=>', dest.Name) for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions if not src.Boss])) - if self.world.boss_randomization[self.player].value != 0: + if self.multiworld.boss_randomization[self.player].value != 0: spoiler_handle.write('\n\nBoss Transitions:\n\n') - spoiler_handle.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_name(self.player)}: ' - if self.world.players > 1 else '', src.Name, + spoiler_handle.write('\n'.join(['%s%s %s %s' % (f'{self.multiworld.get_player_name(self.player)}: ' + if self.multiworld.players > 1 else '', src.Name, '<=>', dest.Name) for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions if src.Boss])) @@ -698,7 +735,7 @@ def create_locations(self, player: int): def create_region(self, world: MultiWorld, player: int, name: str, locations=None, exits=None): ret = Region(name, RegionType.LightWorld, name, player) - ret.world = world + ret.multiworld = world if locations: for loc in locations: location = self.locations[loc] @@ -720,7 +757,7 @@ class SMLocation(Location): return self.always_allow(state, item) or (self.item_rule(item) and (not check_access or (self.can_reach(state) and self.can_comeback(state, item)))) def can_comeback(self, state: CollectionState, item: Item): - randoExec = state.world.worlds[self.player].variaRando.randoExec + randoExec = state.multiworld.worlds[self.player].variaRando.randoExec for key in locationsDict[self.name].AccessFrom.keys(): if (randoExec.areaGraph.canAccessList( state.smbm[self.player], key, diff --git a/worlds/sm/data/SMBasepatch_prebuilt/multiworld-basepatch.ips b/worlds/sm/data/SMBasepatch_prebuilt/multiworld-basepatch.ips index f8fba9b0cf..b44fa72656 100644 Binary files a/worlds/sm/data/SMBasepatch_prebuilt/multiworld-basepatch.ips and b/worlds/sm/data/SMBasepatch_prebuilt/multiworld-basepatch.ips differ diff --git a/worlds/sm/data/SMBasepatch_prebuilt/multiworld.sym b/worlds/sm/data/SMBasepatch_prebuilt/multiworld.sym index e507501c86..c49a990f37 100644 --- a/worlds/sm/data/SMBasepatch_prebuilt/multiworld.sym +++ b/worlds/sm/data/SMBasepatch_prebuilt/multiworld.sym @@ -3,13 +3,15 @@ [labels] B8:83C1 :neg_1_1 -85:B9B4 :neg_1_2 -85:B9E6 :neg_1_3 -B8:C81F :neg_1_4 -B8:C831 :neg_1_5 -B8:C843 :neg_1_6 +B8:85C6 :neg_1_2 +B8:85E5 :neg_1_3 +85:BAB4 :neg_1_4 +85:BACF :neg_1_5 +B8:C81F :neg_1_6 +B8:C831 :neg_1_7 +B8:C843 :neg_1_8 B8:830C :pos_1_0 -B8:85D7 :pos_1_1 +B8:8693 :pos_1_1 84:FA6B :pos_1_2 84:FA75 :pos_1_3 B8:C862 :pos_1_4 @@ -20,7 +22,7 @@ B8:C87C :pos_1_6 85:990F CLIPLEN_end 85:990C CLIPLEN_no_multi 85:FF1D CLIPSET -B8:84E8 COLLECTTANK +B8:8503 COLLECTTANK 85:FF45 MISCFX 84:8BF2 NORMAL 85:FF4E SETFX @@ -28,6 +30,11 @@ B8:84E8 COLLECTTANK 84:F9E0 SOUNDFX_84 85:FF3C SPECIALFX 84:F896 ammo_loop_table +B8:85BA ap_playerid_to_rom_other_player_index +B8:85DD ap_playerid_to_rom_other_player_index_checklastrow +B8:85F8 ap_playerid_to_rom_other_player_index_correctindex +B8:85C0 ap_playerid_to_rom_other_player_index_do_search_stage_1 +B8:85F5 ap_playerid_to_rom_other_player_index_notfound 84:F874 archipelago_chozo_item_plm 84:F878 archipelago_hidden_item_plm 84:F870 archipelago_visible_item_plm @@ -51,11 +58,13 @@ B8:885C i_item_setup_shared B8:8878 i_item_setup_shared_all_items B8:8883 i_item_setup_shared_alwaysloaded 84:FA79 i_live_pickup -B8:8578 i_live_pickup_multiworld -B8:85BD i_live_pickup_multiworld_end -B8:8594 i_live_pickup_multiworld_local_item_or_offworld -B8:85A9 i_live_pickup_multiworld_own_item -B8:85B5 i_live_pickup_multiworld_own_item1 +B8:85FD i_live_pickup_multiworld +B8:8679 i_live_pickup_multiworld_end +B8:8659 i_live_pickup_multiworld_item_link_item +B8:8649 i_live_pickup_multiworld_otherplayers_item +B8:8635 i_live_pickup_multiworld_own_item +B8:8641 i_live_pickup_multiworld_own_item1 +B8:8620 i_live_pickup_multiworld_send_network 84:FA1E i_load_custom_graphics 84:FA39 i_load_custom_graphics_all_items 84:FA49 i_load_custom_graphics_alwaysloaded @@ -68,36 +77,41 @@ B8:85B5 i_live_pickup_multiworld_own_item1 84:F9E5 i_start_draw_loop_visible_or_chozo 84:F8A6 i_visible_item 84:FA53 i_visible_item_setup -85:BA8A message_PlaceholderBig -85:BA0A message_char_table -85:BABC message_hook_tilemap_calc -85:BADC message_hook_tilemap_calc_msgbox_mwrecv -85:BACE message_hook_tilemap_calc_msgbox_mwsend +85:BB73 message_PlaceholderBig +85:BAF3 message_char_table +85:BBAA message_hook_tilemap_calc +85:BBDD message_hook_tilemap_calc_msgbox_mw_item_link +85:BBCF message_hook_tilemap_calc_msgbox_mwrecv +85:BBC1 message_hook_tilemap_calc_msgbox_mwsend 85:824C message_hook_tilemap_calc_normal -85:BAC9 message_hook_tilemap_calc_vanilla +85:BBBC message_hook_tilemap_calc_vanilla +85:B9A3 message_item_link_distributed +85:BAA3 message_item_link_distributed_end 85:9963 message_item_names 85:B8A3 message_item_received 85:B9A3 message_item_received_end 85:B7A3 message_item_sent 85:B8A3 message_item_sent_end -85:BA95 message_multiworld_init_new_messagebox_if_needed -85:BAB1 message_multiworld_init_new_messagebox_if_needed_msgbox_mwrecv -85:BAB1 message_multiworld_init_new_messagebox_if_needed_msgbox_mwsend -85:BAA9 message_multiworld_init_new_messagebox_if_needed_vanilla -85:B9A3 message_write_placeholders -85:B9A5 message_write_placeholders_adjust -85:BA04 message_write_placeholders_end -85:B9CA message_write_placeholders_loop -85:B9DC message_write_placeholders_notfound -85:B9DF message_write_placeholders_value_ok +85:BB7E message_multiworld_init_new_messagebox_if_needed +85:BB9F message_multiworld_init_new_messagebox_if_needed_msgbox_mw_item_link +85:BB9F message_multiworld_init_new_messagebox_if_needed_msgbox_mwrecv +85:BB9F message_multiworld_init_new_messagebox_if_needed_msgbox_mwsend +85:BB97 message_multiworld_init_new_messagebox_if_needed_vanilla +85:BAA3 message_write_placeholders +85:BAA5 message_write_placeholders_adjust +85:BAED message_write_placeholders_end +B8:84BB mw_cleanup_item_link_messagebox B8:848B mw_display_item_sent -B8:84F8 mw_handle_queue -B8:8571 mw_handle_queue_end -B8:84FA mw_handle_queue_loop -B8:854A mw_handle_queue_new_remote_item -B8:8566 mw_handle_queue_next -B8:855C mw_handle_queue_perform_receive -B8:85C1 mw_hook_main_game +B8:8513 mw_handle_queue +B8:8562 mw_handle_queue_collect_item_if_present +B8:85B3 mw_handle_queue_end +B8:859B mw_handle_queue_found +B8:8522 mw_handle_queue_lookup_player +B8:8515 mw_handle_queue_loop +B8:857C mw_handle_queue_new_remote_item +B8:85A7 mw_handle_queue_next +B8:858E mw_handle_queue_perform_receive +B8:867D mw_hook_main_game B8:8311 mw_init B8:8366 mw_init_continuereset B8:83EA mw_init_end @@ -107,8 +121,9 @@ B8:8351 mw_init_smstringdata B8:8474 mw_load_sram B8:8482 mw_load_sram_done B8:8485 mw_load_sram_setnewgame -B8:84A9 mw_receive_item -B8:84E1 mw_receive_item_end +B8:84A9 mw_prep_item_link_messagebox +B8:84C4 mw_receive_item +B8:84FC mw_receive_item_end B8:8469 mw_save_sram B8:8442 mw_write_message 84:F888 nonprog_item_eight_palette_indices @@ -135,16 +150,16 @@ B8:8442 mw_write_message 84:F96E p_visible_item_end 84:F95B p_visible_item_loop 84:F967 p_visible_item_trigger -B8:85D8 patch_load_multiworld +B8:8694 patch_load_multiworld 84:FA7E perform_item_pickup 84:F886 plm_graphics_entry_offworld_item 84:F87C plm_graphics_entry_offworld_progression_item 84:FA90 plm_sequence_generic_item_0_bitmask 84:F87E prog_item_eight_palette_indices B8:E000 rando_item_table -B8:DC90 rando_player_id_table -B8:DE22 rando_player_id_table_end -B8:D000 rando_player_table +B8:DCA0 rando_player_id_table +B8:DE34 rando_player_id_table_end +B8:D000 rando_player_name_table B8:CF00 rando_seed_data B8:8800 sm_item_graphics B8:882E sm_item_plm_pickup_sequence_pointers @@ -158,15 +173,15 @@ B8:83EF write_repeated_memory B8:83F4 write_repeated_memory_loop [source files] -0000 f30c3fdc main.asm +0000 8d746646 main.asm 0001 06780555 ../common/nofanfare.asm -0002 4f9a780e ../common/multiworld.asm -0003 f7e9db95 ../common/itemextras.asm -0004 d6616c0c ../common/items.asm -0005 dbfcb38d ../common/startitem.asm +0002 ae952bc9 ../common/multiworld.asm +0003 613d24e1 ../common/itemextras.asm +0004 cc2c77e4 ../common/items.asm +0005 440b54fe ../common/startitem.asm [rom checksum] -b526d5c7 +f0ac0521 [addr-to-line mapping] ff:ffff 0000:00000001 @@ -343,296 +358,391 @@ b8:8435 0002:0000010f b8:8439 0002:00000110 b8:843d 0002:00000111 b8:8441 0002:00000112 -b8:8442 0002:00000118 -b8:8443 0002:00000118 -b8:8444 0002:00000119 -b8:8445 0002:00000119 -b8:8446 0002:0000011a -b8:844a 0002:0000011b -b8:844d 0002:0000011b -b8:844e 0002:0000011c -b8:844f 0002:0000011d -b8:8453 0002:0000011e -b8:8454 0002:0000011f -b8:8458 0002:00000120 -b8:8459 0002:00000121 -b8:845d 0002:00000123 -b8:8461 0002:00000124 -b8:8462 0002:00000125 -b8:8466 0002:00000126 -b8:8467 0002:00000126 -b8:8468 0002:00000127 -b8:8469 0002:0000012c -b8:846a 0002:0000012c +b8:8442 0002:0000011b +b8:8443 0002:0000011b +b8:8444 0002:0000011c +b8:8445 0002:0000011c +b8:8446 0002:0000011d +b8:844a 0002:0000011e +b8:844d 0002:0000011e +b8:844e 0002:0000011f +b8:844f 0002:00000120 +b8:8453 0002:00000121 +b8:8454 0002:00000122 +b8:8458 0002:00000123 +b8:8459 0002:00000124 +b8:845d 0002:00000126 +b8:8461 0002:00000127 +b8:8462 0002:00000128 +b8:8466 0002:00000129 +b8:8467 0002:00000129 +b8:8468 0002:0000012a +b8:8469 0002:0000012f +b8:846a 0002:0000012f b8:846b 0000:00000013 -b8:846d 0002:0000012f -b8:846e 0002:0000012f -b8:846f 0002:00000131 -b8:8470 0002:00000132 -b8:8473 0002:00000133 -b8:8474 0002:00000138 -b8:8475 0002:00000138 +b8:846d 0002:00000132 +b8:846e 0002:00000132 +b8:846f 0002:00000134 +b8:8470 0002:00000135 +b8:8473 0002:00000136 +b8:8474 0002:0000013b +b8:8475 0002:0000013b b8:8476 0000:00000013 -b8:8478 0002:0000013a -b8:847c 0002:0000013b -b8:8480 0002:0000013c -b8:8482 0002:0000013e -b8:8483 0002:0000013e -b8:8484 0002:0000013f -b8:8485 0002:00000147 -b8:8489 0002:00000148 -b8:848b 0002:0000014e -b8:848d 0002:0000014f -b8:848f 0002:00000152 -b8:8492 0002:00000153 -b8:8494 0002:00000154 -b8:8497 0002:00000155 -b8:8499 0002:00000156 -b8:849c 0002:00000157 -b8:84a0 0002:00000158 -b8:84a2 0002:00000159 -b8:84a4 0002:0000015a -b8:84a6 0002:0000015b -b8:84a8 0002:0000015c -b8:84a9 0002:00000160 -b8:84aa 0002:00000160 -b8:84ab 0002:00000161 -b8:84ae 0002:00000162 -b8:84b0 0002:00000163 -b8:84b3 0002:00000164 -b8:84b5 0002:00000165 -b8:84b6 0002:00000166 -b8:84b7 0002:00000168 -b8:84ba 0002:00000169 -b8:84bc 0002:0000016a -b8:84bf 0002:0000016b -b8:84c0 0002:0000016c -b8:84c3 0002:0000016d -b8:84c4 0002:0000016d -b8:84c5 0002:0000016e -b8:84c9 0002:0000016f -b8:84ca 0002:00000170 -b8:84cd 0002:00000171 -b8:84d1 0002:00000172 -b8:84d3 0002:00000174 -b8:84d6 0002:00000175 -b8:84d8 0002:00000176 -b8:84db 0002:00000177 -b8:84dd 0002:00000179 -b8:84e1 0002:0000017b -b8:84e3 0002:0000017c -b8:84e5 0002:0000017d -b8:84e6 0002:0000017d -b8:84e7 0002:0000017e -b8:84e8 0002:00000189 -b8:84e9 0002:0000018a -b8:84ed 0002:0000018b -b8:84ee 0002:0000018c -b8:84f2 0002:0000018d -b8:84f3 0002:0000018f -b8:84f7 0002:00000190 -b8:84f8 0002:000001c2 -b8:84f9 0002:000001c2 -b8:84fa 0002:000001c5 -b8:84fe 0002:000001c6 -b8:8502 0002:000001c7 -b8:8504 0002:000001c9 -b8:8506 0002:000001c9 -b8:8507 0002:000001cc -b8:850b 0002:000001cd -b8:850d 0002:000001ce -b8:8511 0002:000001cf -b8:8513 0002:000001d0 -b8:8517 0002:000001d1 -b8:851a 0002:000001d2 -b8:851c 0002:000001d3 -b8:851e 0002:000001d4 -b8:8522 0002:000001d5 -b8:8524 0002:000001d6 -b8:8526 0002:000001d7 -b8:8529 0002:000001d8 -b8:852c 0002:000001d9 -b8:852e 0002:000001da -b8:8536 0002:000001de -b8:8537 0002:000001df -b8:8538 0002:000001e0 -b8:853c 0002:000001e3 -b8:8540 0002:000001e4 -b8:8544 0002:000001e5 -b8:8546 0002:000001e7 -b8:8547 0002:000001e8 -b8:8548 0002:000001e9 -b8:854a 0002:000001ee -b8:854b 0002:000001ef -b8:854f 0002:000001f2 -b8:8553 0002:000001f3 -b8:8557 0002:000001f4 -b8:855b 0002:000001f5 -b8:855c 0002:000001f9 -b8:855e 0002:000001fa -b8:8561 0002:000001fb -b8:8563 0002:000001fc -b8:8566 0002:000001ff -b8:856a 0002:00000200 -b8:856b 0002:00000201 -b8:856f 0002:00000203 -b8:8571 0002:00000206 -b8:8573 0002:00000207 -b8:8575 0002:00000208 -b8:8576 0002:00000208 -b8:8577 0002:00000209 -b8:8578 0002:0000020d -b8:8579 0002:0000020d -b8:857a 0002:0000020d -b8:857b 0002:0000020e -b8:857f 0002:0000020f -b8:8582 0002:0000020f -b8:8583 0002:00000211 -b8:8587 0002:00000212 -b8:8588 0002:00000213 -b8:858c 0002:00000214 -b8:858f 0002:00000215 -b8:8591 0002:00000217 -b8:8594 0002:0000021a -b8:8598 0002:0000021b -b8:859c 0002:0000021c -b8:859e 0002:0000021e -b8:85a2 0002:0000021f -b8:85a3 0002:00000221 -b8:85a7 0002:00000222 -b8:85a9 0002:00000225 -b8:85ad 0002:00000226 -b8:85b0 0002:00000227 -b8:85b2 0002:00000228 -b8:85b5 0002:0000022b -b8:85b6 0002:0000022c -b8:85b7 0002:0000022d -b8:85bb 0002:0000022e -b8:85bd 0002:00000231 -b8:85be 0002:00000231 -b8:85bf 0002:00000231 -b8:85c0 0002:00000232 -b8:85c1 0002:00000236 -b8:85c5 0002:00000237 -b8:85c9 0002:00000238 -b8:85cb 0002:00000239 -b8:85cf 0002:0000023a -b8:85d2 0002:0000023b -b8:85d4 0002:0000023c -b8:85d7 0002:0000023e -b8:85d8 0002:00000241 -b8:85dc 0002:00000243 -b8:85dd 0002:00000244 -b8:85de 0002:00000245 -b8:85df 0002:00000246 -b8:85e0 0002:00000247 -8b:914a 0002:0000024c -81:80f7 0002:0000024f -81:8027 0002:00000252 -82:8bb3 0002:00000255 -85:b9a3 0002:000002ef -85:b9a4 0002:000002ef -85:b9a5 0002:000002f2 -85:b9a7 0002:000002f3 -85:b9ad 0002:000002f3 -85:b9ae 0002:000002f4 -85:b9b1 0002:000002f5 -85:b9b2 0002:000002f6 -85:b9b3 0002:000002f6 -85:b9b4 0002:000002fa -85:b9b7 0002:000002fb -85:b9bb 0002:000002fc -85:b9bd 0002:000002fc -85:b9bf 0002:000002fd -85:b9c2 0002:000002fe -85:b9c4 0002:00000300 -85:b9c5 0002:00000301 -85:b9c7 0002:00000305 -85:b9ca 0002:00000307 -85:b9cd 0002:00000308 -85:b9cf 0002:00000309 -85:b9d1 0002:0000030a -85:b9d5 0002:0000030b -85:b9d7 0002:0000030c -85:b9d9 0002:0000030d -85:b9da 0002:0000030e -85:b9dc 0002:00000310 -85:b9df 0002:00000312 -85:b9e2 0002:00000312 -85:b9e3 0002:00000313 -85:b9e6 0002:00000315 -85:b9ea 0002:00000316 -85:b9ed 0002:00000317 -85:b9ee 0002:00000318 -85:b9ef 0002:00000318 -85:b9f0 0002:00000319 -85:b9f4 0002:0000031a -85:b9f5 0002:0000031b -85:b9f9 0002:0000031c -85:b9fb 0002:0000031d -85:b9fc 0002:0000031e -85:b9fd 0002:0000031f -85:ba00 0002:00000320 -85:ba02 0002:00000321 -85:ba04 0002:00000324 -85:ba05 0002:00000324 -85:ba06 0002:00000325 -85:ba09 0002:00000326 -85:ba8a 0002:00000334 -85:ba8c 0002:00000335 -85:ba8f 0002:00000336 -85:ba92 0002:00000337 -85:ba95 0002:0000033f -85:ba96 0002:00000340 -85:ba98 0002:00000341 -85:ba9b 0002:00000342 -85:ba9d 0002:00000343 -85:ba9f 0002:00000344 -85:baa2 0002:00000345 -85:baa4 0002:00000346 -85:baa7 0002:00000347 -85:baa9 0002:0000034a -85:baaa 0002:0000034b -85:baab 0002:0000034c -85:baac 0002:0000034d -85:baae 0002:0000034e -85:baaf 0002:0000034f -85:bab0 0002:00000350 -85:bab1 0002:00000355 -85:bab4 0002:00000356 -85:bab5 0002:00000357 -85:bab8 0002:00000358 -85:bab9 0002:00000359 -85:baba 0002:0000035a -85:babb 0002:0000035b -85:babc 0002:00000366 -85:babd 0002:00000367 -85:babf 0002:00000368 -85:bac2 0002:00000369 -85:bac4 0002:0000036a -85:bac7 0002:0000036b -85:bac9 0002:0000036e -85:baca 0002:0000036f -85:bacb 0002:00000370 -85:bacd 0002:00000371 -85:bace 0002:00000373 -85:bacf 0002:00000374 -85:bad1 0002:00000375 -85:bad4 0002:00000376 -85:bad6 0002:00000377 -85:bad9 0002:00000378 -85:badb 0002:00000379 -85:badc 0002:0000037b -85:badd 0002:0000037c -85:badf 0002:0000037d -85:bae2 0002:0000037e -85:bae4 0002:0000037f -85:bae7 0002:00000380 -85:bae9 0002:00000381 -85:8246 0002:00000386 -85:8249 0002:00000387 -85:824b 0002:00000388 -85:82f9 0002:0000038c +b8:8478 0002:0000013d +b8:847c 0002:0000013e +b8:8480 0002:0000013f +b8:8482 0002:00000141 +b8:8483 0002:00000141 +b8:8484 0002:00000142 +b8:8485 0002:0000014a +b8:8489 0002:0000014b +b8:848b 0002:00000151 +b8:848d 0002:00000152 +b8:848f 0002:00000155 +b8:8492 0002:00000156 +b8:8494 0002:00000157 +b8:8497 0002:00000158 +b8:8499 0002:00000159 +b8:849c 0002:0000015a +b8:84a0 0002:0000015b +b8:84a2 0002:0000015c +b8:84a4 0002:0000015d +b8:84a6 0002:0000015e +b8:84a8 0002:0000015f +b8:84a9 0002:00000164 +b8:84ab 0002:00000165 +b8:84ad 0002:00000166 +b8:84b0 0002:00000167 +b8:84b2 0002:00000168 +b8:84b5 0002:00000169 +b8:84b7 0002:0000016a +b8:84ba 0002:0000016b +b8:84bb 0002:0000016e +b8:84bd 0002:0000016f +b8:84bf 0002:00000170 +b8:84c1 0002:00000171 +b8:84c3 0002:00000172 +b8:84c4 0002:00000176 +b8:84c5 0002:00000176 +b8:84c6 0002:00000177 +b8:84c9 0002:00000178 +b8:84cb 0002:00000179 +b8:84ce 0002:0000017a +b8:84d0 0002:0000017b +b8:84d1 0002:0000017c +b8:84d2 0002:0000017e +b8:84d5 0002:0000017f +b8:84d7 0002:00000180 +b8:84da 0002:00000181 +b8:84db 0002:00000182 +b8:84de 0002:00000183 +b8:84df 0002:00000183 +b8:84e0 0002:00000184 +b8:84e4 0002:00000185 +b8:84e5 0002:00000186 +b8:84e8 0002:00000187 +b8:84ec 0002:00000188 +b8:84ee 0002:0000018a +b8:84f1 0002:0000018b +b8:84f3 0002:0000018c +b8:84f6 0002:0000018d +b8:84f8 0002:0000018f +b8:84fc 0002:00000191 +b8:84fe 0002:00000192 +b8:8500 0002:00000193 +b8:8501 0002:00000193 +b8:8502 0002:00000194 +b8:8503 0002:0000019f +b8:8504 0002:000001a0 +b8:8508 0002:000001a1 +b8:8509 0002:000001a2 +b8:850d 0002:000001a3 +b8:850e 0002:000001a5 +b8:8512 0002:000001a6 +b8:8513 0002:000001db +b8:8514 0002:000001db +b8:8515 0002:000001de +b8:8519 0002:000001df +b8:851d 0002:000001e0 +b8:851f 0002:000001e1 +b8:8522 0002:000001e4 +b8:8524 0002:000001e4 +b8:8525 0002:000001e7 +b8:8529 0002:000001e8 +b8:852b 0002:000001e9 +b8:852f 0002:000001ea +b8:8533 0002:000001eb +b8:8535 0002:000001ef +b8:8537 0002:000001f0 +b8:8538 0002:000001f1 +b8:853b 0002:000001f2 +b8:853e 0002:000001f3 +b8:8540 0002:000001fc +b8:8544 0002:000001fd +b8:8547 0002:000001fe +b8:8549 0002:00000200 +b8:854b 0002:00000201 +b8:854c 0002:00000202 +b8:854f 0002:00000203 +b8:8551 0002:00000204 +b8:8552 0002:00000205 +b8:8555 0002:00000206 +b8:8556 0002:00000207 +b8:855a 0002:00000208 +b8:855b 0002:00000209 +b8:855e 0002:0000020a +b8:8560 0002:0000020d +b8:8562 0002:00000210 +b8:8564 0002:00000211 +b8:8565 0002:00000212 +b8:8568 0002:00000213 +b8:8569 0002:00000214 +b8:856a 0002:00000215 +b8:856e 0002:00000218 +b8:8572 0002:00000219 +b8:8576 0002:0000021a +b8:8578 0002:0000021c +b8:8579 0002:0000021d +b8:857a 0002:0000021e +b8:857c 0002:00000223 +b8:857d 0002:00000224 +b8:8581 0002:00000227 +b8:8585 0002:00000228 +b8:8589 0002:00000229 +b8:858d 0002:0000022a +b8:858e 0002:0000022e +b8:8592 0002:0000022f +b8:8596 0002:00000230 +b8:8598 0002:00000231 +b8:859b 0002:00000233 +b8:859d 0002:00000234 +b8:859f 0002:00000235 +b8:85a2 0002:00000236 +b8:85a4 0002:00000237 +b8:85a7 0002:0000023a +b8:85ab 0002:0000023b +b8:85ac 0002:0000023c +b8:85b0 0002:0000023e +b8:85b3 0002:00000241 +b8:85b5 0002:00000242 +b8:85b7 0002:00000243 +b8:85b8 0002:00000243 +b8:85b9 0002:00000244 +b8:85ba 0002:0000024c +b8:85bd 0002:0000024d +b8:85bf 0002:0000024e +b8:85c0 0002:00000255 +b8:85c2 0002:00000256 +b8:85c3 0002:0000025a +b8:85c6 0002:0000025d +b8:85ca 0002:0000025e +b8:85cc 0002:0000025f +b8:85ce 0002:00000260 +b8:85d0 0002:00000261 +b8:85d2 0002:00000263 +b8:85d3 0002:00000264 +b8:85d4 0002:00000265 +b8:85d7 0002:00000266 +b8:85d8 0002:00000267 +b8:85db 0002:00000268 +b8:85dd 0002:0000026b +b8:85df 0002:0000026c +b8:85e0 0002:0000026d +b8:85e1 0002:0000026e +b8:85e4 0002:0000026f +b8:85e5 0002:00000271 +b8:85e9 0002:00000272 +b8:85eb 0002:00000273 +b8:85ed 0002:00000274 +b8:85ef 0002:00000275 +b8:85f0 0002:00000276 +b8:85f1 0002:00000277 +b8:85f3 0002:00000278 +b8:85f5 0002:0000027b +b8:85f6 0002:0000027c +b8:85f7 0002:0000027d +b8:85f8 0002:0000027f +b8:85f9 0002:00000280 +b8:85fa 0002:00000281 +b8:85fb 0002:00000282 +b8:85fc 0002:00000283 +b8:85fd 0002:00000287 +b8:85fe 0002:00000287 +b8:85ff 0002:00000287 +b8:8600 0002:00000288 +b8:8604 0002:00000289 +b8:8607 0002:00000289 +b8:8608 0002:0000028b +b8:860c 0002:0000028c +b8:860d 0002:0000028d +b8:8611 0002:0000028e +b8:8613 0002:00000290 +b8:8617 0002:00000291 +b8:8618 0002:00000292 +b8:8619 0002:00000293 +b8:861a 0002:00000294 +b8:861e 0002:00000295 +b8:861f 0002:00000296 +b8:8620 0002:00000299 +b8:8624 0002:0000029a +b8:8628 0002:0000029c +b8:862c 0002:0000029d +b8:862e 0002:0000029e +b8:8631 0002:0000029f +b8:8633 0002:000002a1 +b8:8635 0002:000002a4 +b8:8639 0002:000002a5 +b8:863c 0002:000002a6 +b8:863e 0002:000002a7 +b8:8641 0002:000002aa +b8:8642 0002:000002ab +b8:8643 0002:000002ac +b8:8647 0002:000002ad +b8:8649 0002:000002b1 +b8:864d 0002:000002b2 +b8:864e 0002:000002b3 +b8:8652 0002:000002b4 +b8:8653 0002:000002b6 +b8:8657 0002:000002b7 +b8:8659 0002:000002bc +b8:865d 0002:000002bd +b8:865e 0002:000002be +b8:8662 0002:000002bf +b8:8663 0002:000002c0 +b8:8666 0002:000002c1 +b8:8668 0002:000002c3 +b8:866c 0002:000002c5 +b8:866d 0002:000002c6 +b8:866e 0002:000002c7 +b8:866f 0002:000002c8 +b8:8673 0002:000002c9 +b8:8677 0002:000002ca +b8:8679 0002:000002cd +b8:867a 0002:000002cd +b8:867b 0002:000002cd +b8:867c 0002:000002ce +b8:867d 0002:000002d2 +b8:8681 0002:000002d3 +b8:8685 0002:000002d4 +b8:8687 0002:000002d5 +b8:868b 0002:000002d6 +b8:868e 0002:000002d7 +b8:8690 0002:000002d8 +b8:8693 0002:000002da +b8:8694 0002:000002dd +b8:8698 0002:000002df +b8:8699 0002:000002e0 +b8:869a 0002:000002e1 +b8:869b 0002:000002e2 +b8:869c 0002:000002e3 +8b:914a 0002:000002e8 +81:80f7 0002:000002eb +81:8027 0002:000002ee +82:8bb3 0002:000002f1 +85:baa3 0002:00000392 +85:baa4 0002:00000392 +85:baa5 0002:00000395 +85:baa7 0002:00000396 +85:baad 0002:00000396 +85:baae 0002:00000397 +85:bab1 0002:00000398 +85:bab2 0002:00000399 +85:bab3 0002:00000399 +85:bab4 0002:0000039d +85:bab7 0002:0000039e +85:babb 0002:0000039f +85:babd 0002:0000039f +85:babf 0002:000003a0 +85:bac2 0002:000003a1 +85:bac4 0002:000003a3 +85:bac5 0002:000003a4 +85:bac7 0002:000003a5 +85:bacb 0002:000003a5 +85:bacc 0002:000003a6 +85:bacf 0002:000003a8 +85:bad3 0002:000003a9 +85:bad6 0002:000003aa +85:bad7 0002:000003ab +85:bad8 0002:000003ab +85:bad9 0002:000003ac +85:badd 0002:000003ad +85:bade 0002:000003ae +85:bae2 0002:000003af +85:bae4 0002:000003b0 +85:bae5 0002:000003b1 +85:bae6 0002:000003b2 +85:bae9 0002:000003b3 +85:baeb 0002:000003b4 +85:baed 0002:000003b7 +85:baee 0002:000003b7 +85:baef 0002:000003b8 +85:baf2 0002:000003b9 +85:bb73 0002:000003c7 +85:bb75 0002:000003c8 +85:bb78 0002:000003c9 +85:bb7b 0002:000003ca +85:bb7e 0002:000003d2 +85:bb7f 0002:000003d3 +85:bb81 0002:000003d4 +85:bb84 0002:000003d5 +85:bb86 0002:000003d6 +85:bb88 0002:000003d7 +85:bb8b 0002:000003d8 +85:bb8d 0002:000003d9 +85:bb90 0002:000003da +85:bb92 0002:000003db +85:bb95 0002:000003dc +85:bb97 0002:000003df +85:bb98 0002:000003e0 +85:bb99 0002:000003e1 +85:bb9a 0002:000003e2 +85:bb9c 0002:000003e3 +85:bb9d 0002:000003e4 +85:bb9e 0002:000003e5 +85:bb9f 0002:000003ec +85:bba2 0002:000003ed +85:bba3 0002:000003ee +85:bba6 0002:000003ef +85:bba7 0002:000003f0 +85:bba8 0002:000003f1 +85:bba9 0002:000003f2 +85:bbaa 0002:000003fd +85:bbab 0002:000003fe +85:bbad 0002:000003ff +85:bbb0 0002:00000400 +85:bbb2 0002:00000401 +85:bbb5 0002:00000402 +85:bbb7 0002:00000403 +85:bbba 0002:00000404 +85:bbbc 0002:00000407 +85:bbbd 0002:00000408 +85:bbbe 0002:00000409 +85:bbc0 0002:0000040a +85:bbc1 0002:0000040c +85:bbc2 0002:0000040d +85:bbc4 0002:0000040e +85:bbc7 0002:0000040f +85:bbc9 0002:00000410 +85:bbcc 0002:00000411 +85:bbce 0002:00000412 +85:bbcf 0002:00000414 +85:bbd0 0002:00000415 +85:bbd2 0002:00000416 +85:bbd5 0002:00000417 +85:bbd7 0002:00000418 +85:bbda 0002:00000419 +85:bbdc 0002:0000041a +85:bbdd 0002:0000041c +85:bbde 0002:0000041d +85:bbe0 0002:0000041e +85:bbe3 0002:0000041f +85:bbe5 0002:00000420 +85:bbe8 0002:00000421 +85:bbea 0002:00000422 +85:8246 0002:00000427 +85:8249 0002:00000428 +85:824b 0002:00000429 +85:82f9 0002:0000042d b8:885c 0003:00000045 b8:885d 0003:00000045 b8:885e 0003:00000046 @@ -680,78 +790,78 @@ b8:888a 0003:0000005d 84:f9f9 0004:000000e8 84:f9fd 0004:000000e9 84:fa00 0004:000000ea -84:fa02 0004:000000ec -84:fa05 0004:000000ed -84:fa06 0004:000000ee -84:fa0a 0004:000000f1 -84:fa0d 0004:000000f2 -84:fa0f 0004:000000f4 -84:fa11 0004:000000f5 -84:fa12 0004:000000f6 -84:fa14 0004:000000f7 -84:fa15 0004:000000f8 -84:fa19 0004:000000f9 -84:fa1a 0004:000000fa -84:fa1b 0004:000000fb -84:fa1c 0004:000000fe -84:fa1d 0004:000000ff -84:fa1e 0004:00000103 -84:fa1f 0004:00000103 -84:fa20 0004:00000103 -84:fa21 0004:00000104 -84:fa24 0004:00000105 -84:fa27 0004:00000106 -84:fa28 0004:00000107 -84:fa2c 0004:00000108 -84:fa2f 0004:00000109 -84:fa31 0004:0000010b -84:fa34 0004:0000010c -84:fa35 0004:0000010c -84:fa39 0004:0000010e -84:fa3a 0004:00000110 -84:fa3b 0004:00000111 -84:fa3c 0004:00000112 -84:fa40 0004:00000113 -84:fa42 0004:00000114 -84:fa43 0004:00000115 -84:fa44 0004:00000116 -84:fa47 0004:00000117 -84:fa48 0004:00000118 -84:fa49 0004:0000011b -84:fa4a 0004:0000011c -84:fa4c 0004:0000011d -84:fa4d 0004:0000011e -84:fa51 0004:0000011f -84:fa52 0004:00000120 -84:fa53 0004:00000123 -84:fa57 0004:00000124 -84:fa5a 0004:00000127 -84:fa5e 0004:00000128 -84:fa61 0004:0000012c -84:fa64 0004:0000012c -84:fa66 0004:0000012d -84:fa69 0004:0000012e -84:fa6b 0004:0000012f -84:fa6e 0004:0000012f -84:fa70 0004:00000130 -84:fa73 0004:00000131 -84:fa75 0004:00000132 -84:fa78 0004:00000135 -84:fa79 0004:00000139 -84:fa7d 0004:0000013a -84:fa7e 0004:0000013f -84:fa7f 0004:00000140 -84:fa80 0004:00000141 -84:fa81 0004:00000141 -84:fa82 0004:00000145 -84:fa86 0004:00000146 -84:fa87 0004:00000147 -84:fa88 0004:00000148 -84:fa89 0004:00000148 -84:fa8a 0004:00000149 -84:fa8d 0004:0000014a -84:fa8e 0004:0000014b -84:fa8f 0004:0000014c +84:fa02 0004:000000ee +84:fa05 0004:000000ef +84:fa06 0004:000000f0 +84:fa0a 0004:000000f3 +84:fa0d 0004:000000f4 +84:fa0f 0004:000000f6 +84:fa11 0004:000000f7 +84:fa12 0004:000000f8 +84:fa14 0004:000000f9 +84:fa15 0004:000000fa +84:fa19 0004:000000fb +84:fa1a 0004:000000fc +84:fa1b 0004:000000fd +84:fa1c 0004:00000100 +84:fa1d 0004:00000101 +84:fa1e 0004:00000105 +84:fa1f 0004:00000105 +84:fa20 0004:00000105 +84:fa21 0004:00000106 +84:fa24 0004:00000107 +84:fa27 0004:00000108 +84:fa28 0004:00000109 +84:fa2c 0004:0000010a +84:fa2f 0004:0000010b +84:fa31 0004:0000010d +84:fa34 0004:0000010e +84:fa35 0004:0000010e +84:fa39 0004:00000110 +84:fa3a 0004:00000112 +84:fa3b 0004:00000113 +84:fa3c 0004:00000114 +84:fa40 0004:00000115 +84:fa42 0004:00000116 +84:fa43 0004:00000117 +84:fa44 0004:00000118 +84:fa47 0004:00000119 +84:fa48 0004:0000011a +84:fa49 0004:0000011d +84:fa4a 0004:0000011e +84:fa4c 0004:0000011f +84:fa4d 0004:00000120 +84:fa51 0004:00000121 +84:fa52 0004:00000122 +84:fa53 0004:00000125 +84:fa57 0004:00000126 +84:fa5a 0004:00000129 +84:fa5e 0004:0000012a +84:fa61 0004:0000012e +84:fa64 0004:0000012e +84:fa66 0004:0000012f +84:fa69 0004:00000130 +84:fa6b 0004:00000131 +84:fa6e 0004:00000131 +84:fa70 0004:00000132 +84:fa73 0004:00000133 +84:fa75 0004:00000134 +84:fa78 0004:00000137 +84:fa79 0004:0000013b +84:fa7d 0004:0000013c +84:fa7e 0004:00000141 +84:fa7f 0004:00000142 +84:fa80 0004:00000143 +84:fa81 0004:00000143 +84:fa82 0004:00000147 +84:fa86 0004:00000148 +84:fa87 0004:00000149 +84:fa88 0004:0000014a +84:fa89 0004:0000014a +84:fa8a 0004:0000014b +84:fa8d 0004:0000014c +84:fa8e 0004:0000014d +84:fa8f 0004:0000014e 81:b303 0005:00000003 81:b307 0005:00000004 81:b308 0005:00000005 diff --git a/worlds/sm/data/SMBasepatch_prebuilt/sm-basepatch-symbols.json b/worlds/sm/data/SMBasepatch_prebuilt/sm-basepatch-symbols.json index 949b60f5b0..ae9117072e 100644 --- a/worlds/sm/data/SMBasepatch_prebuilt/sm-basepatch-symbols.json +++ b/worlds/sm/data/SMBasepatch_prebuilt/sm-basepatch-symbols.json @@ -4,7 +4,7 @@ "CLIPLEN_end": "85:990F", "CLIPLEN_no_multi": "85:990C", "CLIPSET": "85:FF1D", - "COLLECTTANK": "B8:84E8", + "COLLECTTANK": "B8:8503", "MISCFX": "85:FF45", "NORMAL": "84:8BF2", "SETFX": "85:FF4E", @@ -12,6 +12,11 @@ "SOUNDFX_84": "84:F9E0", "SPECIALFX": "85:FF3C", "ammo_loop_table": "84:F896", + "ap_playerid_to_rom_other_player_index": "B8:85BA", + "ap_playerid_to_rom_other_player_index_checklastrow": "B8:85DD", + "ap_playerid_to_rom_other_player_index_correctindex": "B8:85F8", + "ap_playerid_to_rom_other_player_index_do_search_stage_1": "B8:85C0", + "ap_playerid_to_rom_other_player_index_notfound": "B8:85F5", "archipelago_chozo_item_plm": "84:F874", "archipelago_hidden_item_plm": "84:F878", "archipelago_visible_item_plm": "84:F870", @@ -35,11 +40,13 @@ "i_item_setup_shared_all_items": "B8:8878", "i_item_setup_shared_alwaysloaded": "B8:8883", "i_live_pickup": "84:FA79", - "i_live_pickup_multiworld": "B8:8578", - "i_live_pickup_multiworld_end": "B8:85BD", - "i_live_pickup_multiworld_local_item_or_offworld": "B8:8594", - "i_live_pickup_multiworld_own_item": "B8:85A9", - "i_live_pickup_multiworld_own_item1": "B8:85B5", + "i_live_pickup_multiworld": "B8:85FD", + "i_live_pickup_multiworld_end": "B8:8679", + "i_live_pickup_multiworld_item_link_item": "B8:8659", + "i_live_pickup_multiworld_otherplayers_item": "B8:8649", + "i_live_pickup_multiworld_own_item": "B8:8635", + "i_live_pickup_multiworld_own_item1": "B8:8641", + "i_live_pickup_multiworld_send_network": "B8:8620", "i_load_custom_graphics": "84:FA1E", "i_load_custom_graphics_all_items": "84:FA39", "i_load_custom_graphics_alwaysloaded": "84:FA49", @@ -52,36 +59,41 @@ "i_start_draw_loop_visible_or_chozo": "84:F9E5", "i_visible_item": "84:F8A6", "i_visible_item_setup": "84:FA53", - "message_PlaceholderBig": "85:BA8A", - "message_char_table": "85:BA0A", - "message_hook_tilemap_calc": "85:BABC", - "message_hook_tilemap_calc_msgbox_mwrecv": "85:BADC", - "message_hook_tilemap_calc_msgbox_mwsend": "85:BACE", + "message_PlaceholderBig": "85:BB73", + "message_char_table": "85:BAF3", + "message_hook_tilemap_calc": "85:BBAA", + "message_hook_tilemap_calc_msgbox_mw_item_link": "85:BBDD", + "message_hook_tilemap_calc_msgbox_mwrecv": "85:BBCF", + "message_hook_tilemap_calc_msgbox_mwsend": "85:BBC1", "message_hook_tilemap_calc_normal": "85:824C", - "message_hook_tilemap_calc_vanilla": "85:BAC9", + "message_hook_tilemap_calc_vanilla": "85:BBBC", + "message_item_link_distributed": "85:B9A3", + "message_item_link_distributed_end": "85:BAA3", "message_item_names": "85:9963", "message_item_received": "85:B8A3", "message_item_received_end": "85:B9A3", "message_item_sent": "85:B7A3", "message_item_sent_end": "85:B8A3", - "message_multiworld_init_new_messagebox_if_needed": "85:BA95", - "message_multiworld_init_new_messagebox_if_needed_msgbox_mwrecv": "85:BAB1", - "message_multiworld_init_new_messagebox_if_needed_msgbox_mwsend": "85:BAB1", - "message_multiworld_init_new_messagebox_if_needed_vanilla": "85:BAA9", - "message_write_placeholders": "85:B9A3", - "message_write_placeholders_adjust": "85:B9A5", - "message_write_placeholders_end": "85:BA04", - "message_write_placeholders_loop": "85:B9CA", - "message_write_placeholders_notfound": "85:B9DC", - "message_write_placeholders_value_ok": "85:B9DF", + "message_multiworld_init_new_messagebox_if_needed": "85:BB7E", + "message_multiworld_init_new_messagebox_if_needed_msgbox_mw_item_link": "85:BB9F", + "message_multiworld_init_new_messagebox_if_needed_msgbox_mwrecv": "85:BB9F", + "message_multiworld_init_new_messagebox_if_needed_msgbox_mwsend": "85:BB9F", + "message_multiworld_init_new_messagebox_if_needed_vanilla": "85:BB97", + "message_write_placeholders": "85:BAA3", + "message_write_placeholders_adjust": "85:BAA5", + "message_write_placeholders_end": "85:BAED", + "mw_cleanup_item_link_messagebox": "B8:84BB", "mw_display_item_sent": "B8:848B", - "mw_handle_queue": "B8:84F8", - "mw_handle_queue_end": "B8:8571", - "mw_handle_queue_loop": "B8:84FA", - "mw_handle_queue_new_remote_item": "B8:854A", - "mw_handle_queue_next": "B8:8566", - "mw_handle_queue_perform_receive": "B8:855C", - "mw_hook_main_game": "B8:85C1", + "mw_handle_queue": "B8:8513", + "mw_handle_queue_collect_item_if_present": "B8:8562", + "mw_handle_queue_end": "B8:85B3", + "mw_handle_queue_found": "B8:859B", + "mw_handle_queue_lookup_player": "B8:8522", + "mw_handle_queue_loop": "B8:8515", + "mw_handle_queue_new_remote_item": "B8:857C", + "mw_handle_queue_next": "B8:85A7", + "mw_handle_queue_perform_receive": "B8:858E", + "mw_hook_main_game": "B8:867D", "mw_init": "B8:8311", "mw_init_continuereset": "B8:8366", "mw_init_end": "B8:83EA", @@ -91,8 +103,9 @@ "mw_load_sram": "B8:8474", "mw_load_sram_done": "B8:8482", "mw_load_sram_setnewgame": "B8:8485", - "mw_receive_item": "B8:84A9", - "mw_receive_item_end": "B8:84E1", + "mw_prep_item_link_messagebox": "B8:84A9", + "mw_receive_item": "B8:84C4", + "mw_receive_item_end": "B8:84FC", "mw_save_sram": "B8:8469", "mw_write_message": "B8:8442", "nonprog_item_eight_palette_indices": "84:F888", @@ -119,16 +132,16 @@ "p_visible_item_end": "84:F96E", "p_visible_item_loop": "84:F95B", "p_visible_item_trigger": "84:F967", - "patch_load_multiworld": "B8:85D8", + "patch_load_multiworld": "B8:8694", "perform_item_pickup": "84:FA7E", "plm_graphics_entry_offworld_item": "84:F886", "plm_graphics_entry_offworld_progression_item": "84:F87C", "plm_sequence_generic_item_0_bitmask": "84:FA90", "prog_item_eight_palette_indices": "84:F87E", "rando_item_table": "B8:E000", - "rando_player_id_table": "B8:DC90", - "rando_player_id_table_end": "B8:DE22", - "rando_player_table": "B8:D000", + "rando_player_id_table": "B8:DCA0", + "rando_player_id_table_end": "B8:DE34", + "rando_player_name_table": "B8:D000", "rando_seed_data": "B8:CF00", "sm_item_graphics": "B8:8800", "sm_item_plm_pickup_sequence_pointers": "B8:882E", diff --git a/worlds/sm64ex/__init__.py b/worlds/sm64ex/__init__.py index b1eef4ff2c..f9cae84dc8 100644 --- a/worlds/sm64ex/__init__.py +++ b/worlds/sm64ex/__init__.py @@ -43,18 +43,18 @@ class SM64World(World): option_definitions = sm64_options def generate_early(self): - self.topology_present = self.world.AreaRandomizer[self.player].value + self.topology_present = self.multiworld.AreaRandomizer[self.player].value def create_regions(self): - create_regions(self.world,self.player) + create_regions(self.multiworld, self.player) def set_rules(self): self.area_connections = {} - set_rules(self.world, self.player, self.area_connections) + set_rules(self.multiworld, self.player, self.area_connections) if self.topology_present: # Write area_connections to spoiler log for entrance, destination in self.area_connections.items(): - self.world.spoiler.set_entrance( + self.multiworld.spoiler.set_entrance( sm64_internalloc_to_string[entrance] + " Entrance", sm64_internalloc_to_string[destination], 'entrance', self.player) @@ -72,74 +72,74 @@ class SM64World(World): return item def generate_basic(self): - starcount = self.world.AmountOfStars[self.player].value - if (not self.world.EnableCoinStars[self.player].value): - starcount = max(35,self.world.AmountOfStars[self.player].value-15) - starcount = max(starcount, self.world.FirstBowserStarDoorCost[self.player].value, - self.world.BasementStarDoorCost[self.player].value, self.world.SecondFloorStarDoorCost[self.player].value, - self.world.MIPS1Cost[self.player].value, self.world.MIPS2Cost[self.player].value, - self.world.StarsToFinish[self.player].value) - self.world.itempool += [self.create_item("Power Star") for i in range(0,starcount)] - self.world.itempool += [self.create_item("1Up Mushroom") for i in range(starcount,120 - (15 if not self.world.EnableCoinStars[self.player].value else 0))] + starcount = self.multiworld.AmountOfStars[self.player].value + if (not self.multiworld.EnableCoinStars[self.player].value): + starcount = max(35,self.multiworld.AmountOfStars[self.player].value-15) + starcount = max(starcount, self.multiworld.FirstBowserStarDoorCost[self.player].value, + self.multiworld.BasementStarDoorCost[self.player].value, self.multiworld.SecondFloorStarDoorCost[self.player].value, + self.multiworld.MIPS1Cost[self.player].value, self.multiworld.MIPS2Cost[self.player].value, + self.multiworld.StarsToFinish[self.player].value) + self.multiworld.itempool += [self.create_item("Power Star") for i in range(0,starcount)] + self.multiworld.itempool += [self.create_item("1Up Mushroom") for i in range(starcount,120 - (15 if not self.multiworld.EnableCoinStars[self.player].value else 0))] - if (not self.world.ProgressiveKeys[self.player].value): + if (not self.multiworld.ProgressiveKeys[self.player].value): key1 = self.create_item("Basement Key") key2 = self.create_item("Second Floor Key") - self.world.itempool += [key1,key2] + self.multiworld.itempool += [key1, key2] else: - self.world.itempool += [self.create_item("Progressive Key") for i in range(0,2)] + self.multiworld.itempool += [self.create_item("Progressive Key") for i in range(0,2)] wingcap = self.create_item("Wing Cap") metalcap = self.create_item("Metal Cap") vanishcap = self.create_item("Vanish Cap") - self.world.itempool += [wingcap,metalcap,vanishcap] + self.multiworld.itempool += [wingcap, metalcap, vanishcap] - if (self.world.BuddyChecks[self.player].value): - self.world.itempool += [self.create_item(name) for name, id in cannon_item_table.items()] + if (self.multiworld.BuddyChecks[self.player].value): + self.multiworld.itempool += [self.create_item(name) for name, id in cannon_item_table.items()] else: - self.world.get_location("BoB: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock BoB")) - self.world.get_location("WF: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock WF")) - self.world.get_location("JRB: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock JRB")) - self.world.get_location("CCM: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock CCM")) - self.world.get_location("SSL: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock SSL")) - self.world.get_location("SL: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock SL")) - self.world.get_location("WDW: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock WDW")) - self.world.get_location("TTM: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock TTM")) - self.world.get_location("THI: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock THI")) - self.world.get_location("RR: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock RR")) + self.multiworld.get_location("BoB: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock BoB")) + self.multiworld.get_location("WF: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock WF")) + self.multiworld.get_location("JRB: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock JRB")) + self.multiworld.get_location("CCM: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock CCM")) + self.multiworld.get_location("SSL: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock SSL")) + self.multiworld.get_location("SL: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock SL")) + self.multiworld.get_location("WDW: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock WDW")) + self.multiworld.get_location("TTM: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock TTM")) + self.multiworld.get_location("THI: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock THI")) + self.multiworld.get_location("RR: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock RR")) - if (self.world.ExclamationBoxes[self.player].value > 0): - self.world.itempool += [self.create_item("1Up Mushroom") for i in range(0,29)] + if (self.multiworld.ExclamationBoxes[self.player].value > 0): + self.multiworld.itempool += [self.create_item("1Up Mushroom") for i in range(0,29)] else: - self.world.get_location("CCM: 1Up Block Near Snowman", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("CCM: 1Up Block Ice Pillar", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("CCM: 1Up Block Secret Slide", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("BBH: 1Up Block Top of Mansion", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("HMC: 1Up Block above Pit", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("HMC: 1Up Block Past Rolling Rocks", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("SSL: 1Up Block Outside Pyramid", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("SSL: 1Up Block Pyramid Left Path", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("SSL: 1Up Block Pyramid Back", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("SL: 1Up Block Near Moneybags", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("SL: 1Up Block inside Igloo", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("WDW: 1Up Block in Downtown", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("TTM: 1Up Block on Red Mushroom", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("THI: 1Up Block THI Small near Start", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("THI: 1Up Block THI Large near Start", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("THI: 1Up Block Windy Area", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("TTC: 1Up Block Midway Up", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("TTC: 1Up Block at the Top", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("RR: 1Up Block Top of Red Coin Maze", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("RR: 1Up Block Under Fly Guy", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("RR: 1Up Block On House in the Sky", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("Bowser in the Dark World 1Up Block on Tower", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("Bowser in the Dark World 1Up Block near Goombas", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("Cavern of the Metal Cap 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("Vanish Cap Under the Moat 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("Bowser in the Fire Sea 1Up Block Swaying Stairs", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("Bowser in the Fire Sea 1Up Block Near Poles", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("Wing Mario Over the Rainbow 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom")) - self.world.get_location("Bowser in the Sky 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("CCM: 1Up Block Near Snowman", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("CCM: 1Up Block Ice Pillar", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("CCM: 1Up Block Secret Slide", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("BBH: 1Up Block Top of Mansion", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("HMC: 1Up Block above Pit", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("HMC: 1Up Block Past Rolling Rocks", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("SSL: 1Up Block Outside Pyramid", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("SSL: 1Up Block Pyramid Left Path", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("SSL: 1Up Block Pyramid Back", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("SL: 1Up Block Near Moneybags", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("SL: 1Up Block inside Igloo", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("WDW: 1Up Block in Downtown", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("TTM: 1Up Block on Red Mushroom", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("THI: 1Up Block THI Small near Start", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("THI: 1Up Block THI Large near Start", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("THI: 1Up Block Windy Area", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("TTC: 1Up Block Midway Up", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("TTC: 1Up Block at the Top", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("RR: 1Up Block Top of Red Coin Maze", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("RR: 1Up Block Under Fly Guy", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("RR: 1Up Block On House in the Sky", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("Bowser in the Dark World 1Up Block on Tower", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("Bowser in the Dark World 1Up Block near Goombas", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("Cavern of the Metal Cap 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("Vanish Cap Under the Moat 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("Bowser in the Fire Sea 1Up Block Swaying Stairs", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("Bowser in the Fire Sea 1Up Block Near Poles", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("Wing Mario Over the Rainbow 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom")) + self.multiworld.get_location("Bowser in the Sky 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom")) def get_filler_item_name(self) -> str: return "1Up Mushroom" @@ -147,21 +147,21 @@ class SM64World(World): def fill_slot_data(self): return { "AreaRando": self.area_connections, - "FirstBowserDoorCost": self.world.FirstBowserStarDoorCost[self.player].value, - "BasementDoorCost": self.world.BasementStarDoorCost[self.player].value, - "SecondFloorDoorCost": self.world.SecondFloorStarDoorCost[self.player].value, - "MIPS1Cost": self.world.MIPS1Cost[self.player].value, - "MIPS2Cost": self.world.MIPS2Cost[self.player].value, - "StarsToFinish": self.world.StarsToFinish[self.player].value, - "DeathLink": self.world.death_link[self.player].value, + "FirstBowserDoorCost": self.multiworld.FirstBowserStarDoorCost[self.player].value, + "BasementDoorCost": self.multiworld.BasementStarDoorCost[self.player].value, + "SecondFloorDoorCost": self.multiworld.SecondFloorStarDoorCost[self.player].value, + "MIPS1Cost": self.multiworld.MIPS1Cost[self.player].value, + "MIPS2Cost": self.multiworld.MIPS2Cost[self.player].value, + "StarsToFinish": self.multiworld.StarsToFinish[self.player].value, + "DeathLink": self.multiworld.death_link[self.player].value, } def generate_output(self, output_directory: str): - if self.world.players != 1: + if self.multiworld.players != 1: return data = { "slot_data": self.fill_slot_data(), - "location_to_item": {self.location_name_to_id[i.name] : item_table[i.item.name] for i in self.world.get_locations()}, + "location_to_item": {self.location_name_to_id[i.name] : item_table[i.item.name] for i in self.multiworld.get_locations()}, "data_package": { "data": { "games": { @@ -173,7 +173,7 @@ class SM64World(World): } } } - filename = f"{self.world.get_out_file_name_base(self.player)}.apsm64ex" + filename = f"{self.multiworld.get_out_file_name_base(self.player)}.apsm64ex" with open(os.path.join(output_directory, filename), 'w') as f: json.dump(data, f) @@ -182,7 +182,7 @@ class SM64World(World): er_hint_data = {} for entrance, destination in self.area_connections.items(): regionid = sm64_internalloc_to_regionid[destination] - region = self.world.get_region(sm64courses[regionid], self.player) + region = self.multiworld.get_region(sm64courses[regionid], self.player) for location in region.locations: er_hint_data[location.address] = sm64_internalloc_to_string[entrance] multidata['er_hint_data'][self.player] = er_hint_data diff --git a/worlds/smw/__init__.py b/worlds/smw/__init__.py index 2e9be535e9..89cc0a87ae 100644 --- a/worlds/smw/__init__.py +++ b/worlds/smw/__init__.py @@ -62,14 +62,14 @@ class SMWWorld(World): def _get_slot_data(self): return { - #"death_link": self.world.death_link[self.player].value, + #"death_link": self.multiworld.death_link[self.player].value, "active_levels": self.active_level_dict, } def fill_slot_data(self) -> dict: slot_data = self._get_slot_data() for option_name in smw_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 @@ -77,20 +77,20 @@ class SMWWorld(World): def generate_basic(self): itempool: typing.List[SMWItem] = [] - self.active_level_dict = dict(zip(generate_level_list(self.world, self.player), full_level_list)) - self.topology_present = self.world.level_shuffle[self.player] + self.active_level_dict = dict(zip(generate_level_list(self.multiworld, self.player), full_level_list)) + self.topology_present = self.multiworld.level_shuffle[self.player] - connect_regions(self.world, self.player, self.active_level_dict) + connect_regions(self.multiworld, self.player, self.active_level_dict) # Add Boss Token amount requirements for Worlds - add_rule(self.world.get_region(LocationName.donut_plains_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 1)) - add_rule(self.world.get_region(LocationName.vanilla_dome_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 2)) - add_rule(self.world.get_region(LocationName.forest_of_illusion_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 4)) - add_rule(self.world.get_region(LocationName.chocolate_island_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 5)) - add_rule(self.world.get_region(LocationName.valley_of_bowser_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 6)) + add_rule(self.multiworld.get_region(LocationName.donut_plains_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 1)) + add_rule(self.multiworld.get_region(LocationName.vanilla_dome_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 2)) + add_rule(self.multiworld.get_region(LocationName.forest_of_illusion_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 4)) + add_rule(self.multiworld.get_region(LocationName.chocolate_island_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 5)) + add_rule(self.multiworld.get_region(LocationName.valley_of_bowser_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 6)) total_required_locations = 96 - if self.world.dragon_coin_checks[self.player]: + if self.multiworld.dragon_coin_checks[self.player]: total_required_locations += 49 itempool += [self.create_item(ItemName.mario_run)] @@ -108,24 +108,24 @@ class SMWWorld(World): itempool += [self.create_item(ItemName.red_switch_palace)] itempool += [self.create_item(ItemName.blue_switch_palace)] - if self.world.goal[self.player] == "yoshi_egg_hunt": + if self.multiworld.goal[self.player] == "yoshi_egg_hunt": itempool += [self.create_item(ItemName.yoshi_egg) - for _ in range(self.world.number_of_yoshi_eggs[self.player])] - self.world.get_location(LocationName.yoshis_house, self.player).place_locked_item(self.create_item(ItemName.victory)) + for _ in range(self.multiworld.number_of_yoshi_eggs[self.player])] + self.multiworld.get_location(LocationName.yoshis_house, self.player).place_locked_item(self.create_item(ItemName.victory)) else: - self.world.get_location(LocationName.bowser, self.player).place_locked_item(self.create_item(ItemName.victory)) + self.multiworld.get_location(LocationName.bowser, self.player).place_locked_item(self.create_item(ItemName.victory)) junk_count = total_required_locations - len(itempool) trap_weights = [] - trap_weights += ([ItemName.ice_trap] * self.world.ice_trap_weight[self.player].value) - trap_weights += ([ItemName.stun_trap] * self.world.stun_trap_weight[self.player].value) - trap_weights += ([ItemName.literature_trap] * self.world.literature_trap_weight[self.player].value) - trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.world.trap_fill_percentage[self.player].value / 100.0)) + trap_weights += ([ItemName.ice_trap] * self.multiworld.ice_trap_weight[self.player].value) + trap_weights += ([ItemName.stun_trap] * self.multiworld.stun_trap_weight[self.player].value) + trap_weights += ([ItemName.literature_trap] * self.multiworld.literature_trap_weight[self.player].value) + 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 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 @@ -137,21 +137,21 @@ class SMWWorld(World): LocationName.valley_koopaling, LocationName.vanilla_reznor, LocationName.forest_reznor, LocationName.chocolate_reznor, LocationName.valley_reznor] for location_name in boss_location_names: - self.world.get_location(location_name, self.player).place_locked_item(self.create_item(ItemName.koopaling)) + self.multiworld.get_location(location_name, self.player).place_locked_item(self.create_item(ItemName.koopaling)) - self.world.itempool += itempool + self.multiworld.itempool += itempool def generate_output(self, output_directory: str): rompath = "" # if variable is not declared finally clause may fail 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_dict) + patch_rom(self.multiworld, rom, self.player, self.active_level_dict) - 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) self.rom_name = rom.name @@ -173,7 +173,7 @@ class SMWWorld(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 extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]): if self.topology_present: @@ -212,18 +212,18 @@ class SMWWorld(World): if level_index >= world_cutoffs[i]: continue - if self.world.dragon_coin_checks[self.player].value == 0 and "Dragon Coins" in loc_name: + if self.multiworld.dragon_coin_checks[self.player].value == 0 and "Dragon Coins" in loc_name: continue - location = self.world.get_location(loc_name, self.player) + location = self.multiworld.get_location(loc_name, self.player) er_hint_data[location.address] = world_names[i] break 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] @@ -244,4 +244,4 @@ class SMWWorld(World): return created_item def set_rules(self): - set_rules(self.world, self.player) + set_rules(self.multiworld, self.player) diff --git a/worlds/smz3/__init__.py b/worlds/smz3/__init__.py index 320d506fd2..944cf69080 100644 --- a/worlds/smz3/__init__.py +++ b/worlds/smz3/__init__.py @@ -190,24 +190,24 @@ class SMZ3World(World): self.config = Config() self.config.GameMode = GameMode.Multiworld self.config.Z3Logic = Z3Logic.Normal - self.config.SMLogic = SMLogic(self.world.sm_logic[self.player].value) - self.config.SwordLocation = SwordLocation(self.world.sword_location[self.player].value) - self.config.MorphLocation = MorphLocation(self.world.morph_location[self.player].value) - self.config.Goal = Goal(self.world.goal[self.player].value) - self.config.KeyShuffle = KeyShuffle(self.world.key_shuffle[self.player].value) - self.config.OpenTower = OpenTower(self.world.open_tower[self.player].value) - self.config.GanonVulnerable = GanonVulnerable(self.world.ganon_vulnerable[self.player].value) - self.config.OpenTourian = OpenTourian(self.world.open_tourian[self.player].value) + self.config.SMLogic = SMLogic(self.multiworld.sm_logic[self.player].value) + self.config.SwordLocation = SwordLocation(self.multiworld.sword_location[self.player].value) + self.config.MorphLocation = MorphLocation(self.multiworld.morph_location[self.player].value) + self.config.Goal = Goal(self.multiworld.goal[self.player].value) + self.config.KeyShuffle = KeyShuffle(self.multiworld.key_shuffle[self.player].value) + self.config.OpenTower = OpenTower(self.multiworld.open_tower[self.player].value) + self.config.GanonVulnerable = GanonVulnerable(self.multiworld.ganon_vulnerable[self.player].value) + self.config.OpenTourian = OpenTourian(self.multiworld.open_tourian[self.player].value) - self.local_random = random.Random(self.world.random.randint(0, 1000)) - self.smz3World = TotalSMZ3World(self.config, self.world.get_player_name(self.player), self.player, self.world.seed_name) + self.local_random = random.Random(self.multiworld.random.randint(0, 1000)) + self.smz3World = TotalSMZ3World(self.config, self.multiworld.get_player_name(self.player), self.player, self.multiworld.seed_name) self.smz3DungeonItems = [] SMZ3World.location_names = frozenset(self.smz3World.locationLookup.keys()) - self.world.state.smz3state[self.player] = TotalSMZ3Item.Progression([]) + self.multiworld.state.smz3state[self.player] = TotalSMZ3Item.Progression([]) def generate_basic(self): - self.smz3World.Setup(WorldState.Generate(self.config, self.world.random)) + self.smz3World.Setup(WorldState.Generate(self.config, self.multiworld.random)) self.dungeon = TotalSMZ3Item.Item.CreateDungeonPool(self.smz3World) self.dungeon.reverse() self.progression = TotalSMZ3Item.Item.CreateProgressionPool(self.smz3World) @@ -224,25 +224,25 @@ class SMZ3World(World): else: progressionItems = self.progression for item in self.keyCardsItems: - self.world.push_precollected(SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item)) + self.multiworld.push_precollected(SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item)) itemPool = [SMZ3Item(item.Type.name, ItemClassification.progression, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in progressionItems] + \ [SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in allJunkItems] self.smz3DungeonItems = [SMZ3Item(item.Type.name, ItemClassification.progression, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in self.dungeon] - self.world.itempool += itemPool + self.multiworld.itempool += itemPool def set_rules(self): # SM G4 is logically required to access Ganon's Tower in SMZ3 - self.world.completion_condition[self.player] = lambda state: \ + self.multiworld.completion_condition[self.player] = lambda state: \ self.smz3World.GetRegion("Ganon's Tower").CanEnter(state.smz3state[self.player]) and \ self.smz3World.GetRegion("Ganon's Tower").TowerAscend(state.smz3state[self.player]) for region in self.smz3World.Regions: - entrance = self.world.get_entrance('Menu' + "->" + region.Name, self.player) + entrance = self.multiworld.get_entrance('Menu' + "->" + region.Name, self.player) set_rule(entrance, lambda state, region=region: region.CanEnter(state.smz3state[self.player])) for loc in region.Locations: l = self.locations[loc.Name] - if self.world.accessibility[self.player] != 'locations': + if self.multiworld.accessibility[self.player] != 'locations': l.always_allow = lambda state, item, loc=loc: \ item.game == "SMZ3" and \ loc.alwaysAllow(TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[item.name], self.smz3World), state.smz3state[self.player]) @@ -255,13 +255,13 @@ class SMZ3World(World): def create_regions(self): self.create_locations(self.player) - startRegion = self.create_region(self.world, self.player, 'Menu') - self.world.regions.append(startRegion) + startRegion = self.create_region(self.multiworld, self.player, 'Menu') + self.multiworld.regions.append(startRegion) for region in self.smz3World.Regions: - currentRegion = self.create_region(self.world, self.player, region.Name, region.locationLookup.keys(), [region.Name + "->" + 'Menu']) - self.world.regions.append(currentRegion) - entrance = self.world.get_entrance(region.Name + "->" + 'Menu', self.player) + currentRegion = self.create_region(self.multiworld, self.player, region.Name, region.locationLookup.keys(), [region.Name + "->" + 'Menu']) + self.multiworld.regions.append(currentRegion) + entrance = self.multiworld.get_entrance(region.Name + "->" + 'Menu', self.player) entrance.connect(startRegion) exit = Entrance(self.player, 'Menu' + "->" + region.Name, startRegion) startRegion.exits.append(exit) @@ -347,7 +347,7 @@ class SMZ3World(World): sm_remote_idx = 0 lttp_remote_idx = 0 for location in self.smz3World.Locations: - if self.world.worlds[location.APLocation.item.player].game != self.game: + if self.multiworld.worlds[location.APLocation.item.player].game != self.game: if location.Type == LocationType.Visible or location.Type == LocationType.Chozo or location.Type == LocationType.Hidden: patch[0x390000 + sm_remote_idx*64] = self.convert_to_sm_item_name(location.APLocation.item.name) sm_remote_idx += 1 @@ -369,12 +369,12 @@ class SMZ3World(World): patch = {} # smSpinjumps - if (self.world.spin_jumps_animation[self.player].value == 1): + if (self.multiworld.spin_jumps_animation[self.player].value == 1): patch[self.SnesCustomization(0x9B93FE)] = bytearray([0x01]) # z3HeartBeep values = [ 0x00, 0x80, 0x40, 0x20, 0x10] - index = self.world.heart_beep_speed[self.player].value + index = self.multiworld.heart_beep_speed[self.player].value patch[0x400033] = bytearray([values[index if index < len(values) else 2]]) # z3HeartColor @@ -384,17 +384,17 @@ class SMZ3World(World): [0x2C, [0xC9, 0x69]], [0x28, [0xBC, 0x02]] ] - index = self.world.heart_color[self.player].value + index = self.multiworld.heart_color[self.player].value (hud, fileSelect) = values[index if index < len(values) else 0] for i in range(0, 20, 2): patch[self.SnesCustomization(0xDFA1E + i)] = bytearray([hud]) patch[self.SnesCustomization(0x1BD6AA)] = bytearray(fileSelect) # z3QuickSwap - patch[0x40004B] = bytearray([0x01 if self.world.quick_swap[self.player].value else 0x00]) + patch[0x40004B] = bytearray([0x01 if self.multiworld.quick_swap[self.player].value else 0x00]) # smEnergyBeepOff - if (self.world.energy_beep[self.player].value == 0): + if (self.multiworld.energy_beep[self.player].value == 0): for ([addr, value]) in [ [0x90EA9B, 0x80], [0x90F337, 0x80], @@ -411,12 +411,12 @@ class SMZ3World(World): base_combined_rom = basepatch.apply(base_combined_rom) patcher = TotalSMZ3Patch(self.smz3World, - [world.smz3World for key, world in self.world.worlds.items() if isinstance(world, SMZ3World) and hasattr(world, "smz3World")], - self.world.seed_name, - self.world.seed, - self.local_random, - {v: k for k, v in self.world.player_name.items()}, - next(iter(loc.player for loc in self.world.get_locations() if (loc.item.name == "SilverArrows" and loc.item.player == self.player)))) + [world.smz3World for key, world in self.multiworld.worlds.items() if isinstance(world, SMZ3World) and hasattr(world, "smz3World")], + self.multiworld.seed_name, + self.multiworld.seed, + self.local_random, + {v: k for k, v in self.multiworld.player_name.items()}, + next(iter(loc.player for loc in self.multiworld.get_locations() if (loc.item.name == "SilverArrows" and loc.item.player == self.player)))) patches = patcher.Create(self.smz3World.Config) patches.update(self.apply_sm_custom_sprite()) patches.update(self.apply_item_names()) @@ -427,13 +427,13 @@ class SMZ3World(World): base_combined_rom[addr + offset] = byte offset += 1 - outfilebase = self.world.get_out_file_name_base(self.player) + outfilebase = self.multiworld.get_out_file_name_base(self.player) filename = os.path.join(output_directory, f"{outfilebase}.sfc") with open(filename, "wb") as binary_file: binary_file.write(base_combined_rom) - patch = SMZ3DeltaPatch(os.path.splitext(filename)[0]+SMZ3DeltaPatch.patch_file_ending, player=self.player, - player_name=self.world.player_name[self.player], patched_path=filename) + patch = SMZ3DeltaPatch(os.path.splitext(filename)[0] + SMZ3DeltaPatch.patch_file_ending, player=self.player, + player_name=self.multiworld.player_name[self.player], patched_path=filename) patch.write() os.remove(filename) self.rom_name = bytearray(patcher.title, 'utf8') @@ -458,7 +458,7 @@ class SMZ3World(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() - payload = multidata["connect_names"][self.world.player_name[self.player]] + payload = multidata["connect_names"][self.multiworld.player_name[self.player]] multidata["connect_names"][new_name] = payload def fill_slot_data(self): @@ -495,18 +495,18 @@ class SMZ3World(World): if (not self.smz3World.Config.Keysanity): locations = [loc for loc in self.locations.values() if loc.item is None] - self.world.random.shuffle(locations) + self.multiworld.random.shuffle(locations) - all_state = self.world.get_all_state(False) + all_state = self.multiworld.get_all_state(False) for item in self.smz3DungeonItems: all_state.remove(item) all_dungeonItems = self.smz3DungeonItems[:] - fill_restrictive(self.world, all_state, locations, all_dungeonItems, True, True) + fill_restrictive(self.multiworld, all_state, locations, all_dungeonItems, True, True) # some small or big keys (those always_allow) can be unreachable in-game # while logic still collects some of them (probably to simulate the player collecting pot keys in the logic), some others don't # so we need to remove those exceptions as progression items - if self.world.accessibility[self.player] != 'locations': + if self.multiworld.accessibility[self.player] != 'locations': exception_item = [TotalSMZ3Item.ItemType.BigKeySW, TotalSMZ3Item.ItemType.BigKeySP, TotalSMZ3Item.ItemType.KeyTH] for item in self.smz3DungeonItems: if item.item.Type in exception_item and item.location.always_allow(all_state, item) and not all_state.can_reach(item.location): @@ -523,18 +523,18 @@ class SMZ3World(World): return [] def get_filler_item_name(self) -> str: - return self.world.random.choice(self.junkItemsNames) + return self.multiworld.random.choice(self.junkItemsNames) def write_spoiler(self, spoiler_handle: TextIO): - self.world.spoiler.unreachables.update(self.unreachable) + self.multiworld.spoiler.unreachables.update(self.unreachable) def JunkFillGT(self, factor): - poolLength = len(self.world.itempool) - playerGroups = self.world.get_player_groups(self.player) + poolLength = len(self.multiworld.itempool) + playerGroups = self.multiworld.get_player_groups(self.player) playerGroups.add(self.player) - junkPoolIdx = [i for i in range(0, poolLength) - if self.world.itempool[i].classification in (ItemClassification.filler, ItemClassification.trap) and - self.world.itempool[i].player in playerGroups] + junkPoolIdx = [i for i in range(0, poolLength) + if self.multiworld.itempool[i].classification in (ItemClassification.filler, ItemClassification.trap) and + self.multiworld.itempool[i].player in playerGroups] toRemove = [] for loc in self.locations.values(): # commenting this for now since doing a partial GT pre fill would allow for non SMZ3 progression in GT @@ -544,19 +544,19 @@ class SMZ3World(World): if loc.name in self.locationNamesGT and loc.item is None: poolLength = len(junkPoolIdx) # start looking at a random starting index and loop at start if no match found - start = self.world.random.randint(0, poolLength) + start = self.multiworld.random.randint(0, poolLength) for off in range(0, poolLength): i = (start + off) % poolLength - candidate = self.world.itempool[junkPoolIdx[i]] - if junkPoolIdx[i] not in toRemove and loc.can_fill(self.world.state, candidate, False): + candidate = self.multiworld.itempool[junkPoolIdx[i]] + if junkPoolIdx[i] not in toRemove and loc.can_fill(self.multiworld.state, candidate, False): itemFromPool = candidate toRemove.append(junkPoolIdx[i]) break - self.world.push_item(loc, itemFromPool, False) + self.multiworld.push_item(loc, itemFromPool, False) loc.event = False toRemove.sort(reverse = True) for i in toRemove: - self.world.itempool.pop(i) + self.multiworld.itempool.pop(i) def FillItemAtLocation(self, itemPool, itemType, location): itemToPlace = TotalSMZ3Item.Item.Get(itemPool, itemType, self.smz3World) @@ -564,28 +564,28 @@ class SMZ3World(World): raise Exception(f"Tried to place item {itemType} at {location.Name}, but there is no such item in the item pool") else: location.Item = itemToPlace - itemFromPool = next((i for i in self.world.itempool if i.player == self.player and i.name == itemToPlace.Type.name), None) + itemFromPool = next((i for i in self.multiworld.itempool if i.player == self.player and i.name == itemToPlace.Type.name), None) if itemFromPool is not None: - self.world.get_location(location.Name, self.player).place_locked_item(itemFromPool) - self.world.itempool.remove(itemFromPool) + self.multiworld.get_location(location.Name, self.player).place_locked_item(itemFromPool) + self.multiworld.itempool.remove(itemFromPool) else: itemFromPool = next((i for i in self.smz3DungeonItems if i.player == self.player and i.name == itemToPlace.Type.name), None) if itemFromPool is not None: - self.world.get_location(location.Name, self.player).place_locked_item(itemFromPool) + self.multiworld.get_location(location.Name, self.player).place_locked_item(itemFromPool) self.smz3DungeonItems.remove(itemFromPool) itemPool.remove(itemToPlace) def FrontFillItemInOwnWorld(self, itemPool, itemType): item = TotalSMZ3Item.Item.Get(itemPool, itemType, self.smz3World) - location = next(iter(self.world.random.sample(TotalSMZ3Location.AvailableGlobal(TotalSMZ3Location.Empty(self.smz3World.Locations), self.smz3World.Items()), 1)), None) + location = next(iter(self.multiworld.random.sample(TotalSMZ3Location.AvailableGlobal(TotalSMZ3Location.Empty(self.smz3World.Locations), self.smz3World.Items()), 1)), None) if (location == None): raise Exception(f"Tried to front fill {item.Name} in, but no location was available") location.Item = item - itemFromPool = next((i for i in self.world.itempool if i.player == self.player and i.name == item.Type.name and i.advancement == item.Progression), None) + itemFromPool = next((i for i in self.multiworld.itempool if i.player == self.player and i.name == item.Type.name and i.advancement == item.Progression), None) if itemFromPool is not None: - self.world.get_location(location.Name, self.player).place_locked_item(itemFromPool) - self.world.itempool.remove(itemFromPool) + self.multiworld.get_location(location.Name, self.player).place_locked_item(itemFromPool) + self.multiworld.itempool.remove(itemFromPool) itemPool.remove(item) def InitialFillInOwnWorld(self): @@ -621,7 +621,7 @@ class SMZ3World(World): def create_region(self, world: MultiWorld, player: int, name: str, locations=None, exits=None): ret = Region(name, RegionType.LightWorld, name, player) - ret.world = world + ret.multiworld = world if locations: for loc in locations: location = self.locations[loc] diff --git a/worlds/soe/__init__.py b/worlds/soe/__init__.py index 007bc6dc84..9f250391a8 100644 --- a/worlds/soe/__init__.py +++ b/worlds/soe/__init__.py @@ -178,11 +178,11 @@ class SoEWorld(World): def generate_early(self) -> None: # store option values that change logic - self.energy_core = self.world.energy_core[self.player].value - self.required_fragments = self.world.required_fragments[self.player].value - if self.required_fragments > self.world.available_fragments[self.player].value: - self.world.available_fragments[self.player].value = self.required_fragments - self.available_fragments = self.world.available_fragments[self.player].value + self.energy_core = self.multiworld.energy_core[self.player].value + self.required_fragments = self.multiworld.required_fragments[self.player].value + if self.required_fragments > self.multiworld.available_fragments[self.player].value: + self.multiworld.available_fragments[self.player].value = self.required_fragments + self.available_fragments = self.multiworld.available_fragments[self.player].value def create_event(self, event: str) -> Item: return SoEItem(event, ItemClassification.progression, None, self.player) @@ -209,12 +209,12 @@ class SoEWorld(World): def create_regions(self): # exclude 'hidden' on easy - max_difficulty = 1 if self.world.difficulty[self.player] == Difficulty.option_easy else 256 + max_difficulty = 1 if self.multiworld.difficulty[self.player] == Difficulty.option_easy else 256 # TODO: generate *some* regions from locations' requirements? - r = Region('Menu', RegionType.Generic, 'Menu', self.player, self.world) + r = Region('Menu', RegionType.Generic, 'Menu', self.player, self.multiworld) r.exits = [Entrance(self.player, 'New Game', r)] - self.world.regions += [r] + self.multiworld.regions += [r] # group locations into spheres (1, 2, 3+ at index 0, 1, 2) spheres: typing.Dict[int, typing.Dict[int, typing.List[SoELocation]]] = {} @@ -232,8 +232,8 @@ class SoEWorld(World): # mark some as excluded based on numbers above for trash_sphere, fills in trash_fills.items(): for typ, counts in fills.items(): - count = counts[self.world.difficulty[self.player].value] - for location in self.world.random.sample(spheres[trash_sphere][typ], count): + count = counts[self.multiworld.difficulty[self.player].value] + for location in self.multiworld.random.sample(spheres[trash_sphere][typ], count): location.progress_type = LocationProgressType.EXCLUDED # TODO: do we need to set an item rule? @@ -243,7 +243,7 @@ class SoEWorld(World): if item.name in {"Gauge", "Wheel"}: return False # and some more for non-easy, non-mystery - if self.world.difficulty[item.player] not in (Difficulty.option_easy, Difficulty.option_mystery): + if self.multiworld.difficulty[item.player] not in (Difficulty.option_easy, Difficulty.option_mystery): if item.name in {"Laser Lance", "Atom Smasher", "Diamond Eye"}: return False return True @@ -253,16 +253,16 @@ class SoEWorld(World): add_item_rule(location, sphere1_blocked_items_rule) # make some logically late(r) bosses priority locations to increase complexity - if self.world.difficulty[self.player] == Difficulty.option_mystery: - late_count = self.world.random.randint(0, 2) + if self.multiworld.difficulty[self.player] == Difficulty.option_mystery: + late_count = self.multiworld.random.randint(0, 2) else: - late_count = self.world.difficulty[self.player].value + late_count = self.multiworld.difficulty[self.player].value late_bosses = ("Tiny", "Aquagoth", "Megataur", "Rimsala", "Mungola", "Lightning Storm", "Magmar", "Volcano Viper") - late_locations = self.world.random.sample(late_bosses, late_count) + late_locations = self.multiworld.random.sample(late_bosses, late_count) # add locations to the world - r = Region('Ingame', RegionType.Generic, 'Ingame', self.player, self.world) + r = Region('Ingame', RegionType.Generic, 'Ingame', self.player, self.multiworld) for sphere in spheres.values(): for locations in sphere.values(): for location in locations: @@ -271,9 +271,9 @@ class SoEWorld(World): location.progress_type = LocationProgressType.PRIORITY r.locations.append(SoELocation(self.player, 'Done', None, r)) - self.world.regions += [r] + self.multiworld.regions += [r] - self.world.get_entrance('New Game', self.player).connect(self.world.get_region('Ingame', self.player)) + self.multiworld.get_entrance('New Game', self.player).connect(self.multiworld.get_region('Ingame', self.player)) def create_items(self): # add regular items to the pool @@ -298,17 +298,17 @@ class SoEWorld(World): for _ in range(self.available_fragments - 1): if len(ingredients) < 1: break # out of ingredients to replace - r = self.world.random.choice(ingredients) + r = self.multiworld.random.choice(ingredients) ingredients.remove(r) items[r] = self.create_item("Energy Core Fragment") # add traps to the pool - trap_count = self.world.trap_count[self.player].value + trap_count = self.multiworld.trap_count[self.player].value trap_chances = {} trap_names = {} if trap_count > 0: for trap_type in self.trap_types: - trap_option = getattr(self.world, f'trap_chance_{trap_type}')[self.player] + trap_option = getattr(self.multiworld, f'trap_chance_{trap_type}')[self.player] trap_chances[trap_type] = trap_option.value trap_names[trap_type] = trap_option.item_name trap_chances_total = sum(trap_chances.values()) @@ -318,7 +318,7 @@ class SoEWorld(World): trap_chances_total = len(trap_chances) def create_trap() -> Item: - v = self.world.random.randrange(trap_chances_total) + v = self.multiworld.random.randrange(trap_chances_total) for t, c in trap_chances.items(): if v < c: return self.create_item(trap_names[t]) @@ -328,26 +328,26 @@ class SoEWorld(World): for _ in range(trap_count): if len(ingredients) < 1: break # out of ingredients to replace - r = self.world.random.choice(ingredients) + r = self.multiworld.random.choice(ingredients) ingredients.remove(r) items[r] = create_trap() - self.world.itempool += items + self.multiworld.itempool += items def set_rules(self): - self.world.completion_condition[self.player] = lambda state: state.has('Victory', self.player) + self.multiworld.completion_condition[self.player] = lambda state: state.has('Victory', self.player) # set Done from goal option once we have multiple goals - set_rule(self.world.get_location('Done', self.player), - lambda state: state.soe_has(pyevermizer.P_FINAL_BOSS, self.world, self.player)) - set_rule(self.world.get_entrance('New Game', self.player), lambda state: True) + set_rule(self.multiworld.get_location('Done', self.player), + lambda state: state.soe_has(pyevermizer.P_FINAL_BOSS, self.multiworld, self.player)) + set_rule(self.multiworld.get_entrance('New Game', self.player), lambda state: True) for loc in _locations: - location = self.world.get_location(loc.name, self.player) + location = self.multiworld.get_location(loc.name, self.player) set_rule(location, self.make_rule(loc.requires)) def make_rule(self, requires: typing.List[typing.Tuple[int, int]]) -> typing.Callable[[typing.Any], bool]: def rule(state) -> bool: for count, progress in requires: - if not state.soe_has(progress, self.world, self.player, count): + if not state.soe_has(progress, self.multiworld, self.player, count): return False return True @@ -358,20 +358,20 @@ class SoEWorld(World): def generate_basic(self): # place Victory event - self.world.get_location('Done', self.player).place_locked_item(self.create_event('Victory')) + self.multiworld.get_location('Done', self.player).place_locked_item(self.create_event('Victory')) # place wings in halls NE to avoid softlock - wings_location = self.world.random.choice(self._halls_ne_chest_names) + wings_location = self.multiworld.random.choice(self._halls_ne_chest_names) wings_item = self.create_item('Wings') - self.world.get_location(wings_location, self.player).place_locked_item(wings_item) + self.multiworld.get_location(wings_location, self.player).place_locked_item(wings_item) # place energy core at vanilla location for vanilla mode if self.energy_core == EnergyCore.option_vanilla: energy_core = self.create_item('Energy Core') - self.world.get_location('Energy Core #285', self.player).place_locked_item(energy_core) + self.multiworld.get_location('Energy Core #285', self.player).place_locked_item(energy_core) # generate stuff for later - self.evermizer_seed = self.world.random.randint(0, 2 ** 16 - 1) # TODO: make this an option for "full" plando? + self.evermizer_seed = self.multiworld.random.randint(0, 2 ** 16 - 1) # TODO: make this an option for "full" plando? def generate_output(self, output_directory: str): - player_name = self.world.get_player_name(self.player) + player_name = self.multiworld.get_player_name(self.player) self.connect_name = player_name[:32] while len(self.connect_name.encode('utf-8')) > 32: self.connect_name = self.connect_name[:-1] @@ -379,27 +379,27 @@ class SoEWorld(World): placement_file = "" out_file = "" try: - money = self.world.money_modifier[self.player].value - exp = self.world.exp_modifier[self.player].value + money = self.multiworld.money_modifier[self.player].value + exp = self.multiworld.exp_modifier[self.player].value switches: typing.List[str] = [] - if self.world.death_link[self.player].value: + if self.multiworld.death_link[self.player].value: switches.append("--death-link") if self.energy_core == EnergyCore.option_fragments: switches.extend(('--available-fragments', str(self.available_fragments), '--required-fragments', str(self.required_fragments))) rom_file = get_base_rom_path() - out_base = output_path(output_directory, self.world.get_out_file_name_base(self.player)) + out_base = output_path(output_directory, self.multiworld.get_out_file_name_base(self.player)) out_file = out_base + '.sfc' placement_file = out_base + '.txt' patch_file = out_base + '.apsoe' flags = 'l' # spoiler log for option_name in self.option_definitions: - option = getattr(self.world, option_name)[self.player] + option = getattr(self.multiworld, option_name)[self.player] if hasattr(option, 'to_flag'): flags += option.to_flag() with open(placement_file, "wb") as f: # generate placement file - for location in filter(lambda l: l.player == self.player, self.world.get_locations()): + for location in filter(lambda l: l.player == self.player, self.multiworld.get_locations()): item = location.item assert item is not None, "Can't handle unfilled location" if item.code is None or location.address is None: @@ -414,7 +414,7 @@ class SoEWorld(World): if not os.path.exists(rom_file): raise FileNotFoundError(rom_file) - if (pyevermizer.main(rom_file, out_file, placement_file, self.world.seed_name, self.connect_name, + if (pyevermizer.main(rom_file, out_file, placement_file, self.multiworld.seed_name, self.connect_name, self.evermizer_seed, flags, money, exp, switches)): raise RuntimeError() patch = SoEDeltaPatch(patch_file, player=self.player, @@ -434,12 +434,12 @@ class SoEWorld(World): # wait for self.connect_name to be available. self.connect_name_available_event.wait() # we skip in case of error, so that the original error in the output thread is the one that gets raised - if self.connect_name and self.connect_name != self.world.player_name[self.player]: - payload = multidata["connect_names"][self.world.player_name[self.player]] + if self.connect_name and self.connect_name != self.multiworld.player_name[self.player]: + payload = multidata["connect_names"][self.multiworld.player_name[self.player]] multidata["connect_names"][self.connect_name] = payload def get_filler_item_name(self) -> str: - return self.world.random.choice(list(self.item_name_groups["Ingredients"])) + return self.multiworld.random.choice(list(self.item_name_groups["Ingredients"])) class SoEItem(Item): diff --git a/worlds/spire/__init__.py b/worlds/spire/__init__.py index 476afad8d9..d923ce75ff 100644 --- a/worlds/spire/__init__.py +++ b/worlds/spire/__init__.py @@ -40,10 +40,10 @@ class SpireWorld(World): def _get_slot_data(self): return { - 'seed': "".join(self.world.slot_seeds[self.player].choice(string.ascii_letters) for i in range(16)), - 'character': self.world.character[self.player], - 'ascension': self.world.ascension[self.player], - 'heart_run': self.world.heart_run[self.player] + 'seed': "".join(self.multiworld.slot_seeds[self.player].choice(string.ascii_letters) for i in range(16)), + 'character': self.multiworld.character[self.player], + 'ascension': self.multiworld.ascension[self.player], + 'heart_run': self.multiworld.heart_run[self.player] } def generate_basic(self): @@ -55,40 +55,40 @@ class SpireWorld(World): item = SpireItem(name, self.player) pool.append(item) - self.world.itempool += pool + self.multiworld.itempool += pool # Pair up our event locations with our event items for event, item in event_item_pairs.items(): event_item = SpireItem(item, self.player) - self.world.get_location(event, self.player).place_locked_item(event_item) + self.multiworld.get_location(event, self.player).place_locked_item(event_item) - if self.world.logic[self.player] != 'no logic': - self.world.completion_condition[self.player] = lambda state: state.has("Victory", self.player) + if self.multiworld.logic[self.player] != 'no logic': + self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.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 SpireItem(name, self.player) def create_regions(self): - create_regions(self.world, self.player) + create_regions(self.multiworld, self.player) def fill_slot_data(self) -> dict: slot_data = self._get_slot_data() for option_name in spire_options: - option = getattr(self.world, option_name)[self.player] + option = getattr(self.multiworld, option_name)[self.player] slot_data[option_name] = int(option.value) return slot_data def get_filler_item_name(self) -> str: - return self.world.random.choice(["Card Draw", "Card Draw", "Card Draw", "Relic", "Relic"]) + return self.multiworld.random.choice(["Card Draw", "Card Draw", "Card Draw", "Relic", "Relic"]) 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 = location_table.get(location, 0) diff --git a/worlds/subnautica/Rules.py b/worlds/subnautica/Rules.py index 8925f1e829..544b9a2b50 100644 --- a/worlds/subnautica/Rules.py +++ b/worlds/subnautica/Rules.py @@ -277,7 +277,7 @@ aggression_rules: Dict[int, Callable[["CollectionState", int], bool]] = { def set_rules(subnautica_world: "SubnauticaWorld"): player = subnautica_world.player - world = subnautica_world.world + world = subnautica_world.multiworld for loc in location_table.values(): set_location_rule(world, player, loc) diff --git a/worlds/subnautica/__init__.py b/worlds/subnautica/__init__.py index bd86dc5ce7..d421fe492c 100644 --- a/worlds/subnautica/__init__.py +++ b/worlds/subnautica/__init__.py @@ -47,22 +47,22 @@ class SubnauticaWorld(World): creatures_to_scan: List[str] def generate_early(self) -> None: - if "Seaglide Fragment" not in self.world.early_items[self.player]: - self.world.early_items[self.player].value["Seaglide Fragment"] = 2 + if "Seaglide Fragment" not in self.multiworld.early_items[self.player]: + self.multiworld.early_items[self.player].value["Seaglide Fragment"] = 2 - scan_option: Options.AggressiveScanLogic = self.world.creature_scan_logic[self.player] + scan_option: Options.AggressiveScanLogic = self.multiworld.creature_scan_logic[self.player] creature_pool = scan_option.get_pool() - self.world.creature_scans[self.player].value = min( + self.multiworld.creature_scans[self.player].value = min( len(creature_pool), - self.world.creature_scans[self.player].value + self.multiworld.creature_scans[self.player].value ) - self.creatures_to_scan = self.world.random.sample(creature_pool, - self.world.creature_scans[self.player].value) + self.creatures_to_scan = self.multiworld.random.sample(creature_pool, + self.multiworld.creature_scans[self.player].value) def create_regions(self): - self.world.regions += [ + self.multiworld.regions += [ self.create_region("Menu", None, ["Lifepod 5"]), self.create_region("Planet 4546B", Locations.events + @@ -75,13 +75,13 @@ class SubnauticaWorld(World): def generate_basic(self): # Link regions - self.world.get_entrance("Lifepod 5", self.player).connect(self.world.get_region("Planet 4546B", self.player)) + self.multiworld.get_entrance("Lifepod 5", self.player).connect(self.multiworld.get_region("Planet 4546B", self.player)) # Generate item pool pool = [] neptune_launch_platform = None - extras = self.world.creature_scans[self.player].value - valuable = self.world.item_pool[self.player] == Options.ItemPool.option_valuable + extras = self.multiworld.creature_scans[self.player].value + valuable = self.multiworld.item_pool[self.player] == Options.ItemPool.option_valuable for item in item_table.values(): for i in range(item["count"]): subnautica_item = self.create_item(item["name"]) @@ -92,26 +92,26 @@ class SubnauticaWorld(World): else: pool.append(subnautica_item) - for item_name in self.world.random.choices(sorted(Items.advancement_item_names - {"Neptune Launch Platform"}), - k=extras): + for item_name in self.multiworld.random.choices(sorted(Items.advancement_item_names - {"Neptune Launch Platform"}), + k=extras): item = self.create_item(item_name) item.classification = ItemClassification.filler # as it's an extra, just fast-fill it somewhere pool.append(item) - self.world.itempool += pool + self.multiworld.itempool += pool # Victory item - self.world.get_location("Aurora - Captain Data Terminal", self.player).place_locked_item( + self.multiworld.get_location("Aurora - Captain Data Terminal", self.player).place_locked_item( neptune_launch_platform) for event in Locations.events: - self.world.get_location(event, self.player).place_locked_item( + self.multiworld.get_location(event, self.player).place_locked_item( SubnauticaItem(event, ItemClassification.progression, None, player=self.player)) # make the goal event the victory "item" - self.world.get_location(self.world.goal[self.player].get_event_name(), self.player).item.name = "Victory" + self.multiworld.get_location(self.multiworld.goal[self.player].get_event_name(), self.player).item.name = "Victory" def fill_slot_data(self) -> Dict[str, Any]: - goal: Options.Goal = self.world.goal[self.player] - item_pool: Options.ItemPool = self.world.item_pool[self.player] + goal: Options.Goal = self.multiworld.goal[self.player] + item_pool: Options.ItemPool = self.multiworld.item_pool[self.player] vanilla_tech: List[str] = [] if item_pool == Options.ItemPool.option_valuable: for item in Items.item_table.values(): @@ -122,7 +122,7 @@ class SubnauticaWorld(World): "goal": goal.current_key, "vanilla_tech": vanilla_tech, "creatures_to_scan": self.creatures_to_scan, - "death_link": self.world.death_link[self.player].value, + "death_link": self.multiworld.death_link[self.player].value, } return slot_data @@ -136,7 +136,7 @@ class SubnauticaWorld(World): def create_region(self, name: str, locations=None, exits=None): ret = Region(name, RegionType.Generic, name, self.player) - ret.world = self.world + ret.multiworld = self.multiworld if locations: for location in locations: loc_id = self.location_name_to_id.get(location, None) diff --git a/worlds/timespinner/Regions.py b/worlds/timespinner/Regions.py index 098657b473..cd9c6b4c81 100644 --- a/worlds/timespinner/Regions.py +++ b/worlds/timespinner/Regions.py @@ -197,7 +197,7 @@ def create_location(player: int, location_data: LocationData, region: Region, lo 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]: diff --git a/worlds/timespinner/__init__.py b/worlds/timespinner/__init__.py index c8b94a2763..b93f10044f 100644 --- a/worlds/timespinner/__init__.py +++ b/worlds/timespinner/__init__.py @@ -64,47 +64,47 @@ class TimespinnerWorld(World): def generate_early(self): # in generate_early the start_inventory isnt copied over to precollected_items yet, so we can still modify the options directly - if self.world.start_inventory[self.player].value.pop('Meyef', 0) > 0: - self.world.StartWithMeyef[self.player].value = self.world.StartWithMeyef[self.player].option_true - if self.world.start_inventory[self.player].value.pop('Talaria Attachment', 0) > 0: - self.world.QuickSeed[self.player].value = self.world.QuickSeed[self.player].option_true - if self.world.start_inventory[self.player].value.pop('Jewelry Box', 0) > 0: - self.world.StartWithJewelryBox[self.player].value = self.world.StartWithJewelryBox[self.player].option_true + if self.multiworld.start_inventory[self.player].value.pop('Meyef', 0) > 0: + self.multiworld.StartWithMeyef[self.player].value = self.multiworld.StartWithMeyef[self.player].option_true + if self.multiworld.start_inventory[self.player].value.pop('Talaria Attachment', 0) > 0: + self.multiworld.QuickSeed[self.player].value = self.multiworld.QuickSeed[self.player].option_true + if self.multiworld.start_inventory[self.player].value.pop('Jewelry Box', 0) > 0: + self.multiworld.StartWithJewelryBox[self.player].value = self.multiworld.StartWithJewelryBox[self.player].option_true def create_regions(self): - create_regions(self.world, self.player, get_locations(self.world, self.player), - self.location_cache, self.pyramid_keys_unlock) + create_regions(self.multiworld, self.player, get_locations(self.multiworld, self.player), + self.location_cache, self.pyramid_keys_unlock) def create_item(self, name: str) -> Item: - return create_item_with_correct_settings(self.world, self.player, name) + return create_item_with_correct_settings(self.multiworld, self.player, name) def get_filler_item_name(self) -> str: - return self.world.random.choice(filler_items) + return self.multiworld.random.choice(filler_items) def set_rules(self): setup_events(self.player, self.locked_locations, self.location_cache) - self.world.completion_condition[self.player] = lambda state: state.has('Killed Nightmare', self.player) + self.multiworld.completion_condition[self.player] = lambda state: state.has('Killed Nightmare', self.player) def generate_basic(self): - excluded_items = get_excluded_items(self, self.world, self.player) + excluded_items = get_excluded_items(self, self.multiworld, self.player) - assign_starter_items(self.world, self.player, excluded_items, self.locked_locations) + assign_starter_items(self.multiworld, self.player, excluded_items, self.locked_locations) - if not is_option_enabled(self.world, self.player, "QuickSeed") and not is_option_enabled(self.world, self.player, "Inverted"): - place_first_progression_item(self.world, self.player, excluded_items, self.locked_locations) + if not is_option_enabled(self.multiworld, self.player, "QuickSeed") and not is_option_enabled(self.multiworld, self.player, "Inverted"): + place_first_progression_item(self.multiworld, self.player, excluded_items, self.locked_locations) - pool = get_item_pool(self.world, self.player, excluded_items) + pool = get_item_pool(self.multiworld, self.player, excluded_items) - 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 fill_slot_data(self) -> Dict[str, object]: slot_data: Dict[str, object] = {} for option_name in timespinner_options: - slot_data[option_name] = get_option_value(self.world, self.player, option_name) + slot_data[option_name] = get_option_value(self.multiworld, self.player, option_name) slot_data["StinkyMaw"] = True slot_data["ProgressiveVerticalMovement"] = False diff --git a/worlds/v6/__init__.py b/worlds/v6/__init__.py index 9c5fdbc8a1..5434ceec46 100644 --- a/worlds/v6/__init__.py +++ b/worlds/v6/__init__.py @@ -44,42 +44,42 @@ class V6World(World): option_definitions = v6_options def create_regions(self): - create_regions(self.world,self.player) + create_regions(self.multiworld, self.player) def set_rules(self): self.area_connections = {} self.area_cost_map = {} - set_rules(self.world, self.player, self.area_connections, self.area_cost_map) + set_rules(self.multiworld, self.player, self.area_connections, self.area_cost_map) def create_item(self, name: str) -> Item: return V6Item(name, ItemClassification.progression, item_table[name], self.player) def generate_basic(self): trinkets = [self.create_item("Trinket " + str(i+1).zfill(2)) for i in range(0,20)] - self.world.itempool += trinkets + self.multiworld.itempool += trinkets musiclist_o = [1,2,3,4,9,12] musiclist_s = musiclist_o.copy() - if self.world.MusicRandomizer[self.player].value: - self.world.random.shuffle(musiclist_s) + if self.multiworld.MusicRandomizer[self.player].value: + self.multiworld.random.shuffle(musiclist_s) self.music_map = dict(zip(musiclist_o, musiclist_s)) def fill_slot_data(self): return { "MusicRando": self.music_map, "AreaRando": self.area_connections, - "DoorCost": self.world.DoorCost[self.player].value, + "DoorCost": self.multiworld.DoorCost[self.player].value, "AreaCostRando": self.area_cost_map, - "DeathLink": self.world.death_link[self.player].value, - "DeathLink_Amnesty": self.world.DeathLinkAmnesty[self.player].value + "DeathLink": self.multiworld.death_link[self.player].value, + "DeathLink_Amnesty": self.multiworld.DeathLinkAmnesty[self.player].value } def generate_output(self, output_directory: str): - if self.world.players != 1: + if self.multiworld.players != 1: return data = { "slot_data": self.fill_slot_data(), - "location_to_item": {self.location_name_to_id[i.name] : item_table[i.item.name] for i in self.world.get_locations()}, + "location_to_item": {self.location_name_to_id[i.name] : item_table[i.item.name] for i in self.multiworld.get_locations()}, "data_package": { "data": { "games": { @@ -91,6 +91,6 @@ class V6World(World): } } } - filename = f"{self.world.get_out_file_name_base(self.player)}.apv6" + filename = f"{self.multiworld.get_out_file_name_base(self.player)}.apv6" with open(os.path.join(output_directory, filename), 'w') as f: json.dump(data, f) diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index d4e9a59771..758870a2c9 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -54,7 +54,7 @@ class WitnessWorld(World): def _get_slot_data(self): return { - 'seed': self.world.random.randint(0, 1000000), + 'seed': self.multiworld.random.randint(0, 1000000), 'victory_location': int(self.player_logic.VICTORY_LOCATION, 16), 'panelhex_to_id': self.locat.CHECK_PANELHEX_TO_ID, 'item_id_to_door_hexes': self.items.ITEM_ID_TO_DOOR_HEX, @@ -66,19 +66,19 @@ class WitnessWorld(World): } def generate_early(self): - if not (is_option_enabled(self.world, self.player, "shuffle_symbols") - or get_option_value(self.world, self.player, "shuffle_doors") - or is_option_enabled(self.world, self.player, "shuffle_lasers")): - if self.world.players == 1: + if not (is_option_enabled(self.multiworld, self.player, "shuffle_symbols") + or get_option_value(self.multiworld, self.player, "shuffle_doors") + or is_option_enabled(self.multiworld, self.player, "shuffle_lasers")): + if self.multiworld.players == 1: warning("This Witness world doesn't have any progression items. Please turn on Symbol Shuffle, Door" " Shuffle or Laser Shuffle if that doesn't seem right.") else: raise Exception("This Witness world doesn't have any progression items. Please turn on Symbol Shuffle," " Door Shuffle or Laser Shuffle.") - self.player_logic = WitnessPlayerLogic(self.world, self.player) - self.locat = WitnessPlayerLocations(self.world, self.player, self.player_logic) - self.items = WitnessPlayerItems(self.locat, self.world, self.player, self.player_logic) + self.player_logic = WitnessPlayerLogic(self.multiworld, self.player) + self.locat = WitnessPlayerLocations(self.multiworld, self.player, self.player_logic) + self.items = WitnessPlayerItems(self.locat, self.multiworld, self.player, self.player_logic) self.regio = WitnessRegions(self.locat) self.log_ids_to_hints = dict() @@ -99,12 +99,12 @@ class WitnessWorld(World): less_junk = 0 # Put good item on first check if symbol shuffle is on - symbols = is_option_enabled(self.world, self.player, "shuffle_symbols") + symbols = is_option_enabled(self.multiworld, self.player, "shuffle_symbols") - if symbols and get_option_value(self.world, self.player, "puzzle_randomization") != 1: - random_good_item = self.world.random.choice(self.items.GOOD_ITEMS) + if symbols and get_option_value(self.multiworld, self.player, "puzzle_randomization") != 1: + random_good_item = self.multiworld.random.choice(self.items.GOOD_ITEMS) - first_check = self.world.get_location( + first_check = self.multiworld.get_location( "Tutorial Gate Open", self.player ) first_check.place_locked_item(items_by_name[random_good_item]) @@ -113,7 +113,7 @@ class WitnessWorld(World): less_junk = 1 for item in self.player_logic.STARTING_INVENTORY: - self.world.push_precollected(items_by_name[item]) + self.multiworld.push_precollected(items_by_name[item]) pool.remove(items_by_name[item]) for item in self.items.EXTRA_AMOUNTS: @@ -133,28 +133,28 @@ class WitnessWorld(World): item_obj = self.create_item( self.player_logic.EVENT_ITEM_PAIRS[event_location] ) - location_obj = self.world.get_location(event_location, self.player) + location_obj = self.multiworld.get_location(event_location, self.player) location_obj.place_locked_item(item_obj) - self.world.itempool += pool + self.multiworld.itempool += pool def create_regions(self): - self.regio.create_regions(self.world, self.player, self.player_logic) + self.regio.create_regions(self.multiworld, self.player, self.player_logic) def set_rules(self): - set_rules(self.world, self.player, self.player_logic, self.locat) + set_rules(self.multiworld, self.player, self.player_logic, self.locat) def fill_slot_data(self) -> dict: - hint_amount = get_option_value(self.world, self.player, "hint_amount") + hint_amount = get_option_value(self.multiworld, self.player, "hint_amount") credits_hint = ("This Randomizer", "is brought to you by", "NewSoupVi, Jarno, jbzdarkid, sigma144", -1) audio_logs = get_audio_logs().copy() if hint_amount != 0: - generated_hints = make_hints(self.world, self.player, hint_amount) + generated_hints = make_hints(self.multiworld, self.player, hint_amount) - self.world.random.shuffle(audio_logs) + self.multiworld.random.shuffle(audio_logs) duplicates = len(audio_logs) // hint_amount @@ -169,7 +169,7 @@ class WitnessWorld(World): audio_log = audio_logs.pop() self.log_ids_to_hints[int(audio_log, 16)] = credits_hint - joke_hints = generate_joke_hints(self.world, len(audio_logs)) + joke_hints = generate_joke_hints(self.multiworld, len(audio_logs)) while audio_logs: audio_log = audio_logs.pop() @@ -181,7 +181,7 @@ class WitnessWorld(World): for option_name in the_witness_options: slot_data[option_name] = get_option_value( - self.world, self.player, option_name + self.multiworld, self.player, option_name ) return slot_data @@ -234,7 +234,7 @@ def create_region(world: MultiWorld, player: int, name: str, """ ret = Region(name, RegionType.Generic, name, player) - ret.world = world + ret.multiworld = world if region_locations: for location in region_locations: loc_id = locat.CHECK_LOCATION_TABLE[location] diff --git a/worlds/zillion/__init__.py b/worlds/zillion/__init__.py index 918b91ec9b..91e19c6ccc 100644 --- a/worlds/zillion/__init__.py +++ b/worlds/zillion/__init__.py @@ -121,10 +121,10 @@ class ZillionWorld(World): raise FileNotFoundError(rom_file) def generate_early(self) -> None: - if not hasattr(self.world, "zillion_logic_cache"): - setattr(self.world, "zillion_logic_cache", {}) + if not hasattr(self.multiworld, "zillion_logic_cache"): + setattr(self.multiworld, "zillion_logic_cache", {}) - zz_op, item_counts = validate(self.world, self.player) + zz_op, item_counts = validate(self.multiworld, self.player) self._item_counts = item_counts @@ -148,7 +148,7 @@ class ZillionWorld(World): assert self.zz_system.randomizer, "generate_early hasn't been called" assert self.id_to_zz_item, "generate_early hasn't been called" p = self.player - w = self.world + w = self.multiworld self.my_locations = [] self.zz_system.randomizer.place_canister_gun_reqs() @@ -159,7 +159,7 @@ class ZillionWorld(World): for here_zz_name, zz_r in self.zz_system.randomizer.regions.items(): here_name = "Menu" if here_zz_name == "start" else zz_reg_name_to_reg_name(here_zz_name) all[here_name] = ZillionRegion(zz_r, here_name, RegionType.Generic, here_name, p, w) - self.world.regions.append(all[here_name]) + self.multiworld.regions.append(all[here_name]) limited_skill = Req(gun=3, jump=3, skill=self.zz_system.randomizer.options.skill, hp=940, red=1, floppy=126) queue = deque([start]) @@ -191,7 +191,7 @@ class ZillionWorld(World): loc.access_rule = access_rule if not (limited_skill >= zz_loc.req): loc.progress_type = LocationProgressType.EXCLUDED - self.world.exclude_locations[p].value.add(loc.name) + self.multiworld.exclude_locations[p].value.add(loc.name) here.locations.append(loc) self.my_locations.append(loc) @@ -222,11 +222,11 @@ class ZillionWorld(World): if item_name in item_counts: count = item_counts[item_name] self.logger.debug(f"Zillion Items: {item_name} {count}") - self.world.itempool += [self.create_item(item_name) for _ in range(count)] + self.multiworld.itempool += [self.create_item(item_name) for _ in range(count)] elif item_id < (3 + base_id) and zz_item.code == RESCUE: # One of the 3 rescues will not be in the pool and its zz_item will be 'empty'. self.logger.debug(f"Zillion Items: {item_name} 1") - self.world.itempool.append(self.create_item(item_name)) + self.multiworld.itempool.append(self.create_item(item_name)) def set_rules(self) -> None: # logic for this game is in create_regions @@ -237,9 +237,9 @@ class ZillionWorld(World): # main location name is an alias main_loc_name = self.zz_system.randomizer.loc_name_2_pretty[self.zz_system.randomizer.locations['main'].name] - self.world.get_location(main_loc_name, self.player)\ + self.multiworld.get_location(main_loc_name, self.player)\ .place_locked_item(self.create_item("Win")) - self.world.completion_condition[self.player] = \ + self.multiworld.completion_condition[self.player] = \ lambda state: state.has("Win", self.player) @staticmethod @@ -295,7 +295,7 @@ class ZillionWorld(World): empty = zz_items[4] multi_item = empty # a different patcher method differentiates empty from ap multi item multi_items: Dict[str, Tuple[str, str]] = {} # zz_loc_name to (item_name, player_name) - for loc in self.world.get_locations(): + for loc in self.multiworld.get_locations(): if loc.player == self.player: z_loc = cast(ZillionLocation, loc) # debug_zz_loc_ids[z_loc.zz_loc.name] = id(z_loc.zz_loc) @@ -310,7 +310,7 @@ class ZillionWorld(World): z_loc.zz_loc.item = multi_item multi_items[z_loc.zz_loc.name] = ( z_loc.item.name, - self.world.get_player_name(z_loc.item.player) + self.multiworld.get_player_name(z_loc.item.player) ) # debug_zz_loc_ids.sort() # for name, id_ in debug_zz_loc_ids.items(): @@ -340,7 +340,7 @@ class ZillionWorld(World): zz_patcher.all_fixes_and_options(zz_options) zz_patcher.set_external_item_interface(zz_options.start_char, zz_options.max_level) zz_patcher.set_multiworld_items(multi_items) - game_id = self.world.player_name[self.player].encode() + b'\x00' + self.world.seed_name[-6:].encode() + game_id = self.multiworld.player_name[self.player].encode() + b'\x00' + self.multiworld.seed_name[-6:].encode() zz_patcher.set_rom_to_ram_data(game_id) def generate_output(self, output_directory: str) -> None: @@ -352,7 +352,7 @@ class ZillionWorld(World): # original_rom_bytes = self.zz_patcher.rom patched_rom_bytes = self.zz_system.patcher.get_patched_bytes() - out_file_base = self.world.get_out_file_name_base(self.player) + out_file_base = self.multiworld.get_out_file_name_base(self.player) filename = os.path.join( output_directory, @@ -363,7 +363,7 @@ class ZillionWorld(World): patch = ZillionDeltaPatch( os.path.splitext(filename)[0] + ZillionDeltaPatch.patch_file_ending, player=self.player, - player_name=self.world.player_name[self.player], + player_name=self.multiworld.player_name[self.player], patched_path=filename ) patch.write() @@ -401,7 +401,7 @@ class ZillionWorld(World): # def modify_multidata(self, multidata: Dict[str, Any]) -> None: # """For deeper modification of server multidata.""" # # not modifying multidata, just want to call this at the end of the generation process - # cache = getattr(self.world, "zillion_logic_cache") + # cache = getattr(self.multiworld, "zillion_logic_cache") # import sys # print(sys.getsizeof(cache)) @@ -409,7 +409,7 @@ class ZillionWorld(World): def create_item(self, name: str) -> Item: """Create an item for this world type and player. - Warning: this may be called with self.world = None, for example by MultiServer""" + Warning: this may be called with self.multiworld = None, for example by MultiServer""" item_id = _item_name_to_id[name] if not self.id_to_zz_item: diff --git a/worlds/zillion/logic.py b/worlds/zillion/logic.py index 01ed14346a..204f242500 100644 --- a/worlds/zillion/logic.py +++ b/worlds/zillion/logic.py @@ -18,7 +18,7 @@ def set_randomizer_locs(cs: CollectionState, p: int, zz_r: Randomizer) -> int: returns a hash of the player and of the set locations with their items """ - z_world = cs.world.worlds[p] + z_world = cs.multiworld.worlds[p] my_locations = cast(List[ZillionLocation], getattr(z_world, "my_locations")) _hash = p @@ -50,7 +50,7 @@ def cs_to_zz_locs(cs: CollectionState, p: int, zz_r: Randomizer, id_to_zz_item: returns frozenset of accessible zilliandomizer locations """ # caching this function because it would be slow - logic_cache: LogicCacheType = getattr(cs.world, "zillion_logic_cache", {}) + logic_cache: LogicCacheType = getattr(cs.multiworld, "zillion_logic_cache", {}) _hash = set_randomizer_locs(cs, p, zz_r) counts = item_counts(cs, p) _hash += hash(counts)