diff --git a/BaseClasses.py b/BaseClasses.py index f037c844..8327dbeb 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -26,6 +26,7 @@ class Group(TypedDict, total=False): replacement_items: Dict[int, Optional[str]] local_items: Set[str] non_local_items: Set[str] + link_replacement: bool class MultiWorld(): @@ -222,27 +223,32 @@ class MultiWorld(): def set_item_links(self): item_links = {} - + replacement_prio = [False, True, None] for player in self.player_ids: for item_link in self.item_links[player].value: if item_link["name"] in item_links: if item_links[item_link["name"]]["game"] != self.game[player]: raise Exception(f"Cannot ItemLink across games. Link: {item_link['name']}") - item_links[item_link["name"]]["players"][player] = item_link["replacement_item"] - item_links[item_link["name"]]["item_pool"] &= set(item_link["item_pool"]) - item_links[item_link["name"]]["exclude"] |= set(item_link.get("exclude", [])) - item_links[item_link["name"]]["local_items"] &= set(item_link.get("local_items", [])) - item_links[item_link["name"]]["non_local_items"] &= set(item_link.get("non_local_items", [])) + current_link = item_links[item_link["name"]] + current_link["players"][player] = item_link["replacement_item"] + current_link["item_pool"] &= set(item_link["item_pool"]) + current_link["exclude"] |= set(item_link.get("exclude", [])) + current_link["local_items"] &= set(item_link.get("local_items", [])) + current_link["non_local_items"] &= set(item_link.get("non_local_items", [])) + current_link["link_replacement"] = min(current_link["link_replacement"], + replacement_prio.index(item_link["link_replacement"])) else: if item_link["name"] in self.player_name.values(): - raise Exception(f"Cannot name a ItemLink group the same as a player ({item_link['name']}) ({self.get_player_name(player)}).") + raise Exception(f"Cannot name a ItemLink group the same as a player ({item_link['name']}) " + f"({self.get_player_name(player)}).") item_links[item_link["name"]] = { "players": {player: item_link["replacement_item"]}, "item_pool": set(item_link["item_pool"]), "exclude": set(item_link.get("exclude", [])), "game": self.game[player], "local_items": set(item_link.get("local_items", [])), - "non_local_items": set(item_link.get("non_local_items", [])) + "non_local_items": set(item_link.get("non_local_items", [])), + "link_replacement": replacement_prio.index(item_link["link_replacement"]), } for name, item_link in item_links.items(): @@ -267,10 +273,12 @@ class MultiWorld(): for group_name, item_link in item_links.items(): game = item_link["game"] group_id, group = self.add_group(group_name, game, set(item_link["players"])) + group["item_pool"] = item_link["item_pool"] group["replacement_items"] = item_link["players"] group["local_items"] = item_link["local_items"] group["non_local_items"] = item_link["non_local_items"] + group["link_replacement"] = replacement_prio[item_link["link_replacement"]] # intended for unittests def set_default_common_options(self): diff --git a/Main.py b/Main.py index 1e7de31c..31351fc5 100644 --- a/Main.py +++ b/Main.py @@ -210,11 +210,15 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No while itemcount > len(world.itempool): items_to_add = [] for player in group["players"]: - if group["replacement_items"][player]: - items_to_add.append( - AutoWorld.call_single(world, "create_item", player, group["replacement_items"][player])) + if group["link_replacement"]: + item_player = group_id else: - items_to_add.append(AutoWorld.call_single(world, "create_filler", player)) + item_player = player + if group["replacement_items"][player]: + items_to_add.append(AutoWorld.call_single(world, "create_item", item_player, + group["replacement_items"][player])) + else: + items_to_add.append(AutoWorld.call_single(world, "create_filler", item_player)) world.random.shuffle(items_to_add) world.itempool.extend(items_to_add[:itemcount - len(world.itempool)]) diff --git a/Options.py b/Options.py index 16deb90d..b386eb8e 100644 --- a/Options.py +++ b/Options.py @@ -927,7 +927,8 @@ class ItemLinks(OptionList): Optional("exclude"): [And(str, len)], "replacement_item": Or(And(str, len), None), Optional("local_items"): [And(str, len)], - Optional("non_local_items"): [And(str, len)] + Optional("non_local_items"): [And(str, len)], + Optional("link_replacement"): Or(None, bool), } ]) @@ -950,6 +951,7 @@ class ItemLinks(OptionList): return pool def verify(self, world, player_name: str, plando_options) -> None: + link: dict super(ItemLinks, self).verify(world, player_name, plando_options) existing_links = set() for link in self.value: @@ -974,7 +976,9 @@ class ItemLinks(OptionList): intersection = local_items.intersection(non_local_items) if intersection: - raise Exception(f"item_link {link['name']} has {intersection} items in both its local_items and non_local_items pool.") + raise Exception(f"item_link {link['name']} has {intersection} " + f"items in both its local_items and non_local_items pool.") + link.setdefault("link_replacement", None) per_game_common_options = { diff --git a/worlds/generic/docs/advanced_settings_en.md b/worlds/generic/docs/advanced_settings_en.md index a96598a8..b96b1d57 100644 --- a/worlds/generic/docs/advanced_settings_en.md +++ b/worlds/generic/docs/advanced_settings_en.md @@ -184,6 +184,7 @@ A Link to the Past: - Fire Rod - Ice Rod replacement_item: "Rupee (1)" + link_replacement: true triggers: - option_category: A Link to the Past option_name: smallkey_shuffle @@ -241,7 +242,7 @@ Timespinner: * `exclude_locations` forces a not important item to be placed on the `Cave 45` location. * `item_links` * For `A Link to the Past` all players in the `rods` item link group will share their fire and ice rods and the player - items will be replaced with single rupees. + items will be replaced with single rupees. The rupee will also be shared among those players. * For `Timespinner` all players in the `TSAll` item link group will share their entire item pool and the `Twin Pyramid Key` and `Timespinner Wheel` will be forced among the worlds of those in the group. The `null` replacement item will, instead of forcing a specific chosen item, allow the generator to randomly pick a filler item to replace the player items.