ItemLinks: move item links to events, mess up their logic in doing so and lock them behind plando option "item_links" until they're fixed.
This commit is contained in:
parent
311fb04647
commit
c525c80b49
|
@ -21,11 +21,13 @@ else:
|
|||
auto_world = object
|
||||
|
||||
|
||||
class Group(TypedDict):
|
||||
class Group(TypedDict, total=False):
|
||||
name: str
|
||||
game: str
|
||||
world: auto_world
|
||||
players: Set[int]
|
||||
item_pool: Set[str]
|
||||
replacement_items: Dict[int, Optional[str]]
|
||||
|
||||
|
||||
class MultiWorld():
|
||||
|
@ -43,6 +45,7 @@ class MultiWorld():
|
|||
groups: Dict[int, Group]
|
||||
is_race: bool = False
|
||||
precollected_items: Dict[int, List[Item]]
|
||||
state: CollectionState
|
||||
|
||||
class AttributeProxy():
|
||||
def __init__(self, rule):
|
||||
|
@ -65,7 +68,6 @@ class MultiWorld():
|
|||
self.seed = None
|
||||
self.seed_name: str = "Unavailable"
|
||||
self.precollected_items = {player: [] for player in self.player_ids}
|
||||
self.state = CollectionState(self)
|
||||
self._cached_entrances = None
|
||||
self._cached_locations = None
|
||||
self._entrance_cache = {}
|
||||
|
@ -145,6 +147,9 @@ class MultiWorld():
|
|||
self.worlds = {}
|
||||
self.slot_seeds = {}
|
||||
|
||||
def get_all_ids(self):
|
||||
return self.player_ids + tuple(self.groups)
|
||||
|
||||
def add_group(self, name: str, game: str, players: Set[int] = frozenset()) -> Tuple[int, Group]:
|
||||
"""Create a group with name and return the assigned player ID and group.
|
||||
If a group of this name already exists, the set of players is extended instead of creating a new one."""
|
||||
|
@ -166,33 +171,11 @@ class MultiWorld():
|
|||
getattr(self, option_key)[new_id] = option(option.default)
|
||||
|
||||
self.worlds[new_id] = world_type(self, new_id)
|
||||
|
||||
self.player_name[new_id] = name
|
||||
# TODO: remove when LttP are transitioned over
|
||||
self.difficulty_requirements[new_id] = self.difficulty_requirements[next(iter(players))]
|
||||
|
||||
new_group = self.groups[new_id] = Group(name=name, game=game, players=players,
|
||||
world=self.worlds[new_id])
|
||||
|
||||
# instead of collect/remove overwrites, should encode sending as Events so they show up in spoiler log
|
||||
def group_collect(state, item) -> bool:
|
||||
changed = False
|
||||
for player in new_group["players"]:
|
||||
max(self.worlds[player].collect(state, item), changed)
|
||||
return changed
|
||||
|
||||
def group_remove(state, item) -> bool:
|
||||
changed = False
|
||||
for player in new_group["players"]:
|
||||
max(self.worlds[player].remove(state, item), changed)
|
||||
return changed
|
||||
|
||||
new_world = new_group["world"]
|
||||
new_world.collect = group_collect
|
||||
new_world.remove = group_remove
|
||||
|
||||
self.worlds[new_id] = new_world
|
||||
|
||||
return new_id, new_group
|
||||
|
||||
def get_player_groups(self, player) -> Set[int]:
|
||||
|
@ -221,6 +204,35 @@ class MultiWorld():
|
|||
setattr(self, option_key, getattr(args, option_key, {}))
|
||||
self.worlds[player] = world_type(self, player)
|
||||
|
||||
item_links = {}
|
||||
|
||||
for player in self.player_ids:
|
||||
for item_link in self.item_links[player].value:
|
||||
if item_link["name"] in item_links:
|
||||
item_links[item_link["name"]]["players"][player] = item_link["replacement_item"]
|
||||
item_links[item_link["name"]]["item_pool"] &= set(item_link["item_pool"])
|
||||
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']}).")
|
||||
item_links[item_link["name"]] = {
|
||||
"players": {player: item_link["replacement_item"]},
|
||||
"item_pool": set(item_link["item_pool"]),
|
||||
"game": self.game[player]
|
||||
}
|
||||
|
||||
for name, item_link in item_links.items():
|
||||
current_item_name_groups = AutoWorld.AutoWorldRegister.world_types[item_link["game"]].item_name_groups
|
||||
pool = set()
|
||||
for item in item_link["item_pool"]:
|
||||
pool |= current_item_name_groups.get(item, {item})
|
||||
item_link["item_pool"] = pool
|
||||
|
||||
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"]
|
||||
|
||||
# intended for unittests
|
||||
def set_default_common_options(self):
|
||||
for option_key, option in Options.common_options.items():
|
||||
|
@ -544,12 +556,12 @@ class CollectionState(object):
|
|||
def __init__(self, parent: MultiWorld):
|
||||
self.prog_items = Counter()
|
||||
self.world = parent
|
||||
self.reachable_regions = {player: set() for player in range(1, parent.players + 1)}
|
||||
self.blocked_connections = {player: set() for player in range(1, parent.players + 1)}
|
||||
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()
|
||||
self.path = {}
|
||||
self.locations_checked = set()
|
||||
self.stale = {player: True for player in range(1, parent.players + 1)}
|
||||
self.stale = {player: True for player in parent.get_all_ids()}
|
||||
for items in parent.precollected_items.values():
|
||||
for item in items:
|
||||
self.collect(item, True)
|
||||
|
@ -591,9 +603,9 @@ class CollectionState(object):
|
|||
ret = CollectionState(self.world)
|
||||
ret.prog_items = self.prog_items.copy()
|
||||
ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in
|
||||
range(1, self.world.players + 1)}
|
||||
self.reachable_regions}
|
||||
ret.blocked_connections = {player: copy.copy(self.blocked_connections[player]) for player in
|
||||
range(1, self.world.players + 1)}
|
||||
self.blocked_connections}
|
||||
ret.events = copy.copy(self.events)
|
||||
ret.path = copy.copy(self.path)
|
||||
ret.locations_checked = copy.copy(self.locations_checked)
|
||||
|
|
2
Fill.py
2
Fill.py
|
@ -310,7 +310,7 @@ def balance_multiworld_progression(world: MultiWorld):
|
|||
checked_locations = set()
|
||||
unchecked_locations = set(world.get_locations())
|
||||
|
||||
reachable_locations_count = {player: 0 for player in world.player_ids}
|
||||
reachable_locations_count = {player: 0 for player in world.get_all_ids()}
|
||||
|
||||
def get_sphere_locations(sphere_state, locations):
|
||||
sphere_state.sweep_for_events(key_only=True, locations=locations)
|
||||
|
|
|
@ -502,6 +502,9 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
|||
roll_alttp_settings(ret, game_weights, plando_options)
|
||||
else:
|
||||
raise Exception(f"Unsupported game {ret.game}")
|
||||
# not meant to stay here, intended to be removed when itemlinks are stable
|
||||
if not "item_links" in plando_options:
|
||||
ret.item_links.value = []
|
||||
return ret
|
||||
|
||||
|
||||
|
|
72
Main.py
72
Main.py
|
@ -11,7 +11,7 @@ import tempfile
|
|||
import zipfile
|
||||
from typing import Dict, Tuple, Optional, Set
|
||||
|
||||
from BaseClasses import MultiWorld, CollectionState, Region, RegionType, LocationProgressType
|
||||
from BaseClasses import MultiWorld, CollectionState, Region, RegionType, LocationProgressType, Location
|
||||
from worlds.alttp.Items import item_name_groups
|
||||
from worlds.alttp.Regions import lookup_vanilla_location_to_entrance
|
||||
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned
|
||||
|
@ -71,12 +71,13 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
|||
world.plando_connections = args.plando_connections.copy()
|
||||
world.required_medallions = args.required_medallions.copy()
|
||||
world.game = args.game.copy()
|
||||
world.set_options(args)
|
||||
world.player_name = args.name.copy()
|
||||
world.enemizer = args.enemizercli
|
||||
world.sprite = args.sprite.copy()
|
||||
world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option.
|
||||
|
||||
world.set_options(args)
|
||||
world.state = CollectionState(world)
|
||||
logger.info('Archipelago Version %s - Seed: %s\n', __version__, world.seed)
|
||||
|
||||
logger.info("Found World Types:")
|
||||
|
@ -138,38 +139,18 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
|||
AutoWorld.call_all(world, "generate_basic")
|
||||
|
||||
# temporary home for item links, should be moved out of Main
|
||||
item_links = {}
|
||||
for player in world.player_ids:
|
||||
for item_link in world.item_links[player].value:
|
||||
if item_link["name"] in item_links:
|
||||
item_links[item_link["name"]]["players"][player] = item_link["replacement_item"]
|
||||
item_links[item_link["name"]]["item_pool"] &= set(item_link["item_pool"])
|
||||
else:
|
||||
if item_link["name"] in world.player_name.values():
|
||||
raise Exception(f"Cannot name a ItemLink group the same as a player ({item_link['name']}).")
|
||||
item_links[item_link["name"]] = {
|
||||
"players": {player: item_link["replacement_item"]},
|
||||
"item_pool": set(item_link["item_pool"]),
|
||||
"game": world.game[player]
|
||||
}
|
||||
for group_id, group in world.groups.items():
|
||||
# TODO: remove when LttP options are transitioned over
|
||||
world.difficulty_requirements[group_id] = world.difficulty_requirements[next(iter(group["players"]))]
|
||||
|
||||
for name, item_link in item_links.items():
|
||||
current_item_name_groups = AutoWorld.AutoWorldRegister.world_types[item_link["game"]].item_name_groups
|
||||
pool = set()
|
||||
for item in item_link["item_pool"]:
|
||||
pool |= current_item_name_groups.get(item, {item})
|
||||
item_link["item_pool"] = pool
|
||||
|
||||
for group_name, item_link in item_links.items():
|
||||
game = item_link["game"]
|
||||
group_id, group = world.add_group(group_name, game, set(item_link["players"]))
|
||||
|
||||
def find_common_pool(players: Set[int], shared_pool: Set[int]) -> \
|
||||
Dict[int, Dict[str, int]]:
|
||||
def find_common_pool(players: Set[int], shared_pool: Set[str]):
|
||||
advancement = set()
|
||||
counters = {player: {name: 0 for name in shared_pool} for player in players}
|
||||
for item in world.itempool:
|
||||
if item.player in counters and item.name in shared_pool:
|
||||
counters[item.player][item.name] += 1
|
||||
if item.advancement:
|
||||
advancement.add(item.name)
|
||||
|
||||
for item in shared_pool:
|
||||
count = min(counters[player][item] for player in players)
|
||||
|
@ -179,17 +160,32 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
|||
else:
|
||||
for player in players:
|
||||
del(counters[player][item])
|
||||
return counters
|
||||
|
||||
common_item_count = find_common_pool(group["players"], item_link["item_pool"])
|
||||
return counters, advancement
|
||||
|
||||
common_item_count, common_advancement_items = find_common_pool(group["players"], group["item_pool"])
|
||||
# TODO: fix logic
|
||||
if common_advancement_items:
|
||||
logger.warning(f"Logical requirements for {', '.join(common_advancement_items)} in group {group['name']} "
|
||||
f"will be incorrect.")
|
||||
new_itempool = []
|
||||
for item_name, item_count in next(iter(common_item_count.values())).items():
|
||||
advancement = item_name in common_advancement_items
|
||||
for _ in range(item_count):
|
||||
new_itempool.append(group["world"].create_item(item_name))
|
||||
new_item = group["world"].create_item(item_name)
|
||||
new_item.advancement = advancement
|
||||
new_itempool.append(new_item)
|
||||
|
||||
region = Region("Menu", RegionType.Generic, "ItemLink", group_id, world)
|
||||
world.regions.append(region)
|
||||
locations = region.locations = []
|
||||
for item in world.itempool:
|
||||
if common_item_count.get(item.player, {}).get(item.name, 0):
|
||||
count = common_item_count.get(item.player, {}).get(item.name, 0)
|
||||
if count:
|
||||
loc = Location(group_id, f"Item Link: {item.name} -> {world.player_name[item.player]} {count}",
|
||||
None, region)
|
||||
loc.access_rule = lambda state: state.has(item.name, group_id, count)
|
||||
locations.append(loc)
|
||||
loc.place_locked_item(item)
|
||||
common_item_count[item.player][item.name] -= 1
|
||||
else:
|
||||
new_itempool.append(item)
|
||||
|
@ -197,13 +193,17 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
|||
itemcount = len(world.itempool)
|
||||
world.itempool = new_itempool
|
||||
|
||||
# can produce more items than were removed
|
||||
while itemcount > len(world.itempool):
|
||||
for player in group["players"]:
|
||||
if item_link["players"][player]:
|
||||
if group["replacement_items"][player]:
|
||||
world.itempool.append(AutoWorld.call_single(world, "create_item", player,
|
||||
item_link["players"][player]))
|
||||
group["replacement_items"][player]))
|
||||
else:
|
||||
AutoWorld.call_single(world, "create_filler", player)
|
||||
if any(world.item_links.values()):
|
||||
world._recache()
|
||||
world._all_state = None
|
||||
|
||||
logger.info("Running Item Plando")
|
||||
|
||||
|
|
Loading…
Reference in New Issue