Add local and non_local items to item_links (#506)

* Add local and non_local items to item_links

* Whoops, don't pass list of list to verify_items.

* Give a did you mean result in the exception.
This commit is contained in:
CaitSith2 2022-05-15 07:41:11 -07:00 committed by GitHub
parent 2b6fc6dd3a
commit c0fb7d9f9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 76 additions and 14 deletions

View File

@ -23,6 +23,8 @@ class Group(TypedDict, total=False):
players: Set[int]
item_pool: Set[str]
replacement_items: Dict[int, Optional[str]]
local_items: Set[str]
non_local_items: Set[str]
class MultiWorld():
@ -219,6 +221,8 @@ class MultiWorld():
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", []))
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)}).")
@ -226,23 +230,37 @@ class MultiWorld():
"players": {player: item_link["replacement_item"]},
"item_pool": set(item_link["item_pool"]),
"exclude": set(item_link.get("exclude", [])),
"game": self.game[player]
"game": self.game[player],
"local_items": set(item_link.get("local_items", [])),
"non_local_items": set(item_link.get("non_local_items", []))
}
for name, item_link in item_links.items():
current_item_name_groups = AutoWorld.AutoWorldRegister.world_types[item_link["game"]].item_name_groups
pool = set()
local_items = set()
non_local_items = set()
for item in item_link["item_pool"]:
pool |= current_item_name_groups.get(item, {item})
for item in item_link["exclude"]:
pool -= current_item_name_groups.get(item, {item})
for item in item_link["local_items"]:
local_items |= current_item_name_groups.get(item, {item})
for item in item_link["non_local_items"]:
non_local_items |= current_item_name_groups.get(item, {item})
local_items &= pool
non_local_items &= pool
item_link["item_pool"] = pool
item_link["local_items"] = local_items
item_link["non_local_items"] = non_local_items
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"]
# intended for unittests
def set_default_common_options(self):

View File

@ -17,7 +17,7 @@ from worlds.alttp.Regions import lookup_vanilla_location_to_entrance
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned
from worlds.alttp.Shops import SHOP_ID_START, total_shop_slots, FillDisabledShopSlots
from Utils import output_path, get_options, __version__, version_tuple
from worlds.generic.Rules import locality_rules, exclusion_rules
from worlds.generic.Rules import locality_rules, exclusion_rules, group_locality_rules
from worlds import AutoWorld
ordered_areas = (
@ -127,6 +127,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
if world.players > 1:
for player in world.player_ids:
locality_rules(world, player)
group_locality_rules(world)
else:
world.non_local_items[1].value = set()
world.local_items[1].value = set()

View File

@ -651,10 +651,30 @@ class ItemLinks(OptionList):
"name": And(str, len),
"item_pool": [And(str, len)],
Optional("exclude"): [And(str, len)],
"replacement_item": Or(And(str, len), None)
"replacement_item": Or(And(str, len), None),
Optional("local_items"): [And(str, len)],
Optional("non_local_items"): [And(str, len)]
}
])
@staticmethod
def verify_items(items: typing.List[str], item_link: str, pool_name: str, world, allow_item_groups: bool = True) -> typing.Set:
pool = set()
for item_name in items:
if item_name not in world.item_names and (not allow_item_groups or item_name not in world.item_name_groups):
picks = get_fuzzy_results(item_name, world.item_names, limit=1)
picks_group = get_fuzzy_results(item_name, world.item_name_groups.keys(), limit=1)
picks_group = f" or '{picks_group[0][0]}' ({picks_group[0][1]}% sure)" if allow_item_groups else ""
raise Exception(f"Item {item_name} from item link {item_link} "
f"is not a valid item from {world.game} for {pool_name}. "
f"Did you mean '{picks[0][0]}' ({picks[0][1]}% sure){picks_group}")
if allow_item_groups:
pool |= world.item_name_groups.get(item_name, {item_name})
else:
pool |= {item_name}
return pool
def verify(self, world):
super(ItemLinks, self).verify(world)
existing_links = set()
@ -662,18 +682,27 @@ class ItemLinks(OptionList):
if link["name"] in existing_links:
raise Exception(f"You cannot have more than one link named {link['name']}.")
existing_links.add(link["name"])
for item_name in link["item_pool"]:
if item_name not in world.item_names and item_name not in world.item_name_groups:
raise Exception(f"Item {item_name} from item link {link} "
f"is not a valid item name from {world.game}")
pool = self.verify_items(link["item_pool"], link["name"], "item_pool", world)
local_items = set()
non_local_items = set()
if "exclude" in link:
for item_name in link["exclude"]:
if item_name not in world.item_names and item_name not in world.item_name_groups:
raise Exception(f"Item {item_name} from item link {link} "
f"is not a valid item name from {world.game}")
if link["replacement_item"] and link["replacement_item"] not in world.item_names:
raise Exception(f"Item {link['replacement_item']} from item link {link} "
f"is not a valid item name from {world.game}")
pool -= self.verify_items(link["exclude"], link["name"], "exclude", world)
if link["replacement_item"]:
self.verify_items([link["replacement_item"]], link["name"], "replacement_item", world, False)
if "local_items" in link:
local_items = self.verify_items(link["local_items"], link["name"], "local_items", world)
local_items &= pool
if "non_local_items" in link:
non_local_items = self.verify_items(link["non_local_items"], link["name"], "non_local_items", world)
non_local_items &= pool
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.")
per_game_common_options = {

View File

@ -12,6 +12,20 @@ else:
ItemRule = typing.Callable[[object], bool]
def group_locality_rules(world):
for group_id, group in world.groups.items():
if set(world.player_ids) == set(group["players"]):
continue
if group["local_items"]:
for location in world.get_locations():
if location.player not in group["players"]:
forbid_items_for_player(location, group["local_items"], group_id)
if group["non_local_items"]:
for location in world.get_locations():
if location.player in group["players"]:
forbid_items_for_player(location, group["non_local_items"], group_id)
def locality_rules(world, player: int):
if world.local_items[player].value:
for location in world.get_locations():