From ed76c1396119bcdde2bfa4574f1e1cd0cbe86a9d Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Thu, 20 Oct 2022 10:42:33 +0200 Subject: [PATCH] Core: assert that items have a single reference (#1075) * Core: assert that items have a single reference * Fix duplicate item reference in The Witness * Ori: fix duplicate item references * DKC3: fix duplicate item references * RL: fix duplicate item references * SA2B: fix duplicate item references * SMW: fix duplicate item references Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> --- worlds/AutoWorld.py | 8 ++++++++ worlds/dkc3/__init__.py | 16 ++++++---------- worlds/oribf/__init__.py | 2 +- worlds/rogue_legacy/__init__.py | 24 ++++++++++++------------ worlds/sa2b/__init__.py | 10 +++++----- worlds/smw/__init__.py | 13 +++++-------- worlds/witness/__init__.py | 2 +- 7 files changed, 38 insertions(+), 37 deletions(-) diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index 5dea0348..8d3fab64 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -79,8 +79,16 @@ def call_single(world: "MultiWorld", method_name: str, player: int, *args: Any) def call_all(world: "MultiWorld", method_name: str, *args: Any) -> None: world_types: Set[AutoWorldRegister] = set() for player in world.player_ids: + prev_item_count = len(world.itempool) world_types.add(world.worlds[player].__class__) call_single(world, method_name, player, *args) + if __debug__: + new_items = world.itempool[prev_item_count:] + for i, item in enumerate(new_items): + for other in new_items[i+1:]: + assert item is not other, ( + f"Duplicate item reference of \"{item.name}\" in \"{world.worlds[player].game}\" " + f"of player \"{world.player_name[player]}\". Please make a copy instead.") for world_type in world_types: stage_callable = getattr(world_type, f"stage_{method_name}", None) diff --git a/worlds/dkc3/__init__.py b/worlds/dkc3/__init__.py index 1389f83e..d45de8f8 100644 --- a/worlds/dkc3/__init__.py +++ b/worlds/dkc3/__init__.py @@ -65,10 +65,6 @@ class DKC3World(World): "active_levels": self.active_level_list, } - def _create_items(self, name: str): - data = item_table[name] - return [self.create_item(name)] * data.quantity - def fill_slot_data(self) -> dict: slot_data = self._get_slot_data() for option_name in dkc3_options: @@ -113,17 +109,17 @@ class DKC3World(World): 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) - itempool += [self.create_item(ItemName.bonus_coin)] * number_of_bonus_coins - itempool += [self.create_item(ItemName.dk_coin)] * 41 - itempool += [self.create_item(ItemName.banana_bird)] * number_of_banana_birds - itempool += [self.create_item(ItemName.krematoa_cog)] * number_of_cogs - itempool += [self.create_item(ItemName.progressive_boat)] * 3 + 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)] + itempool += [self.create_item(ItemName.banana_bird) for _ in range(number_of_banana_birds)] + itempool += [self.create_item(ItemName.krematoa_cog) for _ in range(number_of_cogs)] + itempool += [self.create_item(ItemName.progressive_boat) for _ in range(3)] 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): - junk_pool += [self.create_item(item_name)] + junk_pool.append(self.create_item(item_name)) itempool += junk_pool diff --git a/worlds/oribf/__init__.py b/worlds/oribf/__init__.py index 05d23765..45e666ef 100644 --- a/worlds/oribf/__init__.py +++ b/worlds/oribf/__init__.py @@ -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)] * count) + self.world.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/rogue_legacy/__init__.py b/worlds/rogue_legacy/__init__.py index ba58e133..81e62027 100644 --- a/worlds/rogue_legacy/__init__.py +++ b/worlds/rogue_legacy/__init__.py @@ -66,7 +66,7 @@ class LegacyWorld(World): def _create_items(self, name: str): data = item_table[name] - return [self.create_item(name)] * data.quantity + return [self.create_item(name) for _ in range(data.quantity)] def fill_slot_data(self) -> dict: slot_data = self._get_slot_data() @@ -89,20 +89,20 @@ class LegacyWorld(World): # Blueprints if self.world.progressive_blueprints[self.player]: - itempool += [self.create_item(ItemName.progressive_blueprints)] * 15 + itempool += [self.create_item(ItemName.progressive_blueprints) for _ in range(15)] else: for item in blueprints_table: itempool += self._create_items(item) # Check Pool settings to add a certain amount of these items. - itempool += [self.create_item(ItemName.health)] * int(self.world.health_pool[self.player]) - itempool += [self.create_item(ItemName.mana)] * int(self.world.mana_pool[self.player]) - itempool += [self.create_item(ItemName.attack)] * int(self.world.attack_pool[self.player]) - itempool += [self.create_item(ItemName.magic_damage)] * int(self.world.magic_damage_pool[self.player]) - itempool += [self.create_item(ItemName.armor)] * int(self.world.armor_pool[self.player]) - itempool += [self.create_item(ItemName.equip)] * int(self.world.equip_pool[self.player]) - itempool += [self.create_item(ItemName.crit_chance)] * int(self.world.crit_chance_pool[self.player]) - itempool += [self.create_item(ItemName.crit_damage)] * int(self.world.crit_damage_pool[self.player]) + 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 = self.world.available_classes[self.player] if "Dragon" in classes: @@ -153,12 +153,12 @@ class LegacyWorld(World): 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 += [self.create_item(ItemName.architect)] + itempool.append(self.create_item(ItemName.architect)) # 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 += [self.create_item(item)] + itempool.append(self.create_item(item)) self.world.itempool += itempool diff --git a/worlds/sa2b/__init__.py b/worlds/sa2b/__init__.py index ea248095..7269a66c 100644 --- a/worlds/sa2b/__init__.py +++ b/worlds/sa2b/__init__.py @@ -86,7 +86,7 @@ class SA2BWorld(World): def _create_items(self, name: str): data = item_table[name] - return [self.create_item(name)] * data.quantity + return [self.create_item(name) for _ in range(data.quantity)] def fill_slot_data(self) -> dict: slot_data = self._get_slot_data() @@ -198,11 +198,11 @@ class SA2BWorld(World): connect_regions(self.world, 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)] * max_required_emblems + 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)) - itempool += [self.create_item(ItemName.emblem, True)] * (non_required_emblems - junk_count) + itempool += [self.create_item(ItemName.emblem, True) for _ in range(non_required_emblems - junk_count)] # Carve Traps out of junk_count trap_weights = [] @@ -219,14 +219,14 @@ class SA2BWorld(World): junk_keys = list(junk_table.keys()) for i in range(junk_count): junk_item = self.world.random.choice(junk_keys) - junk_pool += [self.create_item(junk_item)] + 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_pool += [self.create_item(trap_item)] + trap_pool.append(self.create_item(trap_item)) itempool += trap_pool diff --git a/worlds/smw/__init__.py b/worlds/smw/__init__.py index 77931b7c..1dd64f53 100644 --- a/worlds/smw/__init__.py +++ b/worlds/smw/__init__.py @@ -65,10 +65,6 @@ class SMWWorld(World): "active_levels": self.active_level_dict, } - def _create_items(self, name: str): - data = item_table[name] - return [self.create_item(name)] * data.quantity - def fill_slot_data(self) -> dict: slot_data = self._get_slot_data() for option_name in smw_options: @@ -105,14 +101,15 @@ class SMWWorld(World): itempool += [self.create_item(ItemName.p_switch)] itempool += [self.create_item(ItemName.p_balloon)] itempool += [self.create_item(ItemName.super_star_active)] - itempool += [self.create_item(ItemName.progressive_powerup)] * 3 + itempool += [self.create_item(ItemName.progressive_powerup) for _ in range(3)] itempool += [self.create_item(ItemName.yellow_switch_palace)] itempool += [self.create_item(ItemName.green_switch_palace)] 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": - itempool += [self.create_item(ItemName.yoshi_egg)] * self.world.number_of_yoshi_eggs[self.player] + 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)) else: self.world.get_location(LocationName.bowser, self.player).place_locked_item(self.create_item(ItemName.victory)) @@ -128,11 +125,11 @@ class SMWWorld(World): trap_pool = [] for i in range(trap_count): trap_item = self.world.random.choice(trap_weights) - trap_pool += [self.create_item(trap_item)] + trap_pool.append(self.create_item(trap_item)) itempool += trap_pool - itempool += [self.create_item(ItemName.one_up_mushroom)] * junk_count + itempool += [self.create_item(ItemName.one_up_mushroom) for _ in range(junk_count)] boss_location_names = [LocationName.yoshis_island_koopaling, LocationName.donut_plains_koopaling, LocationName.vanilla_dome_koopaling, LocationName.twin_bridges_koopaling, LocationName.forest_koopaling, LocationName.chocolate_koopaling, diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index 7f5b9d2b..d4e9a597 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -117,9 +117,9 @@ class WitnessWorld(World): pool.remove(items_by_name[item]) for item in self.items.EXTRA_AMOUNTS: - witness_item = self.create_item(item) for i in range(0, self.items.EXTRA_AMOUNTS[item]): if len(pool) < len(self.locat.CHECK_LOCATION_TABLE) - len(self.locat.EVENT_LOCATION_TABLE) - less_junk: + witness_item = self.create_item(item) pool.append(witness_item) # Put in junk items to fill the rest