ItemLinks: allow linking replacement items as well (#1274)
This commit is contained in:
		
							parent
							
								
									449973687b
								
							
						
					
					
						commit
						7c3af68e59
					
				| 
						 | 
				
			
			@ -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):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										12
									
								
								Main.py
								
								
								
								
							
							
						
						
									
										12
									
								
								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)])
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 = {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue