Zillion: validate rescue item links (#1140)
This commit is contained in:
parent
53974d568b
commit
f298b8d6e7
16
Main.py
16
Main.py
|
@ -8,9 +8,9 @@ import concurrent.futures
|
||||||
import pickle
|
import pickle
|
||||||
import tempfile
|
import tempfile
|
||||||
import zipfile
|
import zipfile
|
||||||
from typing import Dict, Tuple, Optional, Set
|
from typing import Dict, List, Tuple, Optional, Set
|
||||||
|
|
||||||
from BaseClasses import MultiWorld, CollectionState, Region, RegionType, LocationProgressType, Location
|
from BaseClasses import Item, MultiWorld, CollectionState, Region, RegionType, LocationProgressType, Location
|
||||||
from worlds.alttp.Items import item_name_groups
|
from worlds.alttp.Items import item_name_groups
|
||||||
from worlds.alttp.Regions import is_main_entrance
|
from worlds.alttp.Regions import is_main_entrance
|
||||||
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned
|
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned
|
||||||
|
@ -154,8 +154,10 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
||||||
|
|
||||||
# temporary home for item links, should be moved out of Main
|
# temporary home for item links, should be moved out of Main
|
||||||
for group_id, group in world.groups.items():
|
for group_id, group in world.groups.items():
|
||||||
def find_common_pool(players: Set[int], shared_pool: Set[str]):
|
def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[
|
||||||
classifications = collections.defaultdict(int)
|
Optional[Dict[int, Dict[str, int]]], Optional[Dict[str, int]]
|
||||||
|
]:
|
||||||
|
classifications: Dict[str, int] = collections.defaultdict(int)
|
||||||
counters = {player: {name: 0 for name in shared_pool} for player in players}
|
counters = {player: {name: 0 for name in shared_pool} for player in players}
|
||||||
for item in world.itempool:
|
for item in world.itempool:
|
||||||
if item.player in counters and item.name in shared_pool:
|
if item.player in counters and item.name in shared_pool:
|
||||||
|
@ -165,7 +167,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
||||||
for player in players.copy():
|
for player in players.copy():
|
||||||
if all([counters[player][item] == 0 for item in shared_pool]):
|
if all([counters[player][item] == 0 for item in shared_pool]):
|
||||||
players.remove(player)
|
players.remove(player)
|
||||||
del(counters[player])
|
del (counters[player])
|
||||||
|
|
||||||
if not players:
|
if not players:
|
||||||
return None, None
|
return None, None
|
||||||
|
@ -177,14 +179,14 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
||||||
counters[player][item] = count
|
counters[player][item] = count
|
||||||
else:
|
else:
|
||||||
for player in players:
|
for player in players:
|
||||||
del(counters[player][item])
|
del (counters[player][item])
|
||||||
return counters, classifications
|
return counters, classifications
|
||||||
|
|
||||||
common_item_count, classifications = find_common_pool(group["players"], group["item_pool"])
|
common_item_count, classifications = find_common_pool(group["players"], group["item_pool"])
|
||||||
if not common_item_count:
|
if not common_item_count:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
new_itempool = []
|
new_itempool: List[Item] = []
|
||||||
for item_name, item_count in next(iter(common_item_count.values())).items():
|
for item_name, item_count in next(iter(common_item_count.values())).items():
|
||||||
for _ in range(item_count):
|
for _ in range(item_count):
|
||||||
new_item = group["world"].create_item(item_name)
|
new_item = group["world"].create_item(item_name)
|
||||||
|
|
|
@ -90,6 +90,8 @@ def call_all(world: "MultiWorld", method_name: str, *args: Any) -> None:
|
||||||
f"Duplicate item reference of \"{item.name}\" in \"{world.worlds[player].game}\" "
|
f"Duplicate item reference of \"{item.name}\" in \"{world.worlds[player].game}\" "
|
||||||
f"of player \"{world.player_name[player]}\". Please make a copy instead.")
|
f"of player \"{world.player_name[player]}\". Please make a copy instead.")
|
||||||
|
|
||||||
|
# TODO: investigate: Iterating through a set is not a deterministic order.
|
||||||
|
# If any random is used, this could make unreproducible seed.
|
||||||
for world_type in world_types:
|
for world_type in world_types:
|
||||||
stage_callable = getattr(world_type, f"stage_{method_name}", None)
|
stage_callable = getattr(world_type, f"stage_{method_name}", None)
|
||||||
if stage_callable:
|
if stage_callable:
|
||||||
|
|
|
@ -11,7 +11,7 @@ from BaseClasses import ItemClassification, LocationProgressType, \
|
||||||
from Options import AssembleOptions
|
from Options import AssembleOptions
|
||||||
from .logic import cs_to_zz_locs
|
from .logic import cs_to_zz_locs
|
||||||
from .region import ZillionLocation, ZillionRegion
|
from .region import ZillionLocation, ZillionRegion
|
||||||
from .options import zillion_options, validate
|
from .options import ZillionStartChar, zillion_options, validate
|
||||||
from .id_maps import item_name_to_id as _item_name_to_id, \
|
from .id_maps import item_name_to_id as _item_name_to_id, \
|
||||||
loc_name_to_id as _loc_name_to_id, make_id_to_others, \
|
loc_name_to_id as _loc_name_to_id, make_id_to_others, \
|
||||||
zz_reg_name_to_reg_name, base_id
|
zz_reg_name_to_reg_name, base_id
|
||||||
|
@ -242,6 +242,42 @@ class ZillionWorld(World):
|
||||||
self.world.completion_condition[self.player] = \
|
self.world.completion_condition[self.player] = \
|
||||||
lambda state: state.has("Win", self.player)
|
lambda state: state.has("Win", self.player)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def stage_generate_basic(multiworld: MultiWorld, *args: Any) -> None:
|
||||||
|
# item link pools are about to be created in main
|
||||||
|
# JJ can't be an item link unless all the players share the same start_char
|
||||||
|
# (The reason for this is that the JJ ZillionItem will have a different ZzItem depending
|
||||||
|
# on whether the start char is Apple or Champ, and the logic depends on that ZzItem.)
|
||||||
|
for group in multiworld.groups.values():
|
||||||
|
# TODO: remove asserts on group when we can specify which members of TypedDict are optional
|
||||||
|
assert "game" in group
|
||||||
|
if group["game"] == "Zillion":
|
||||||
|
assert "item_pool" in group
|
||||||
|
item_pool = group["item_pool"]
|
||||||
|
to_stay = "JJ"
|
||||||
|
if "JJ" in item_pool:
|
||||||
|
assert "players" in group
|
||||||
|
group_players = group["players"]
|
||||||
|
start_chars = cast(Dict[int, ZillionStartChar], getattr(multiworld, "start_char"))
|
||||||
|
players_start_chars = [
|
||||||
|
(player, start_chars[player].get_current_option_name())
|
||||||
|
for player in group_players
|
||||||
|
]
|
||||||
|
start_char_counts = Counter(sc for _, sc in players_start_chars)
|
||||||
|
# majority rules
|
||||||
|
if start_char_counts["Apple"] > start_char_counts["Champ"]:
|
||||||
|
to_stay = "Apple"
|
||||||
|
elif start_char_counts["Champ"] > start_char_counts["Apple"]:
|
||||||
|
to_stay = "Champ"
|
||||||
|
else: # equal
|
||||||
|
to_stay = multiworld.random.choice(("Apple", "Champ"))
|
||||||
|
|
||||||
|
for p, sc in players_start_chars:
|
||||||
|
if sc != to_stay:
|
||||||
|
group_players.remove(p)
|
||||||
|
assert "world" in group
|
||||||
|
cast(ZillionWorld, group["world"])._make_item_maps(to_stay)
|
||||||
|
|
||||||
def post_fill(self) -> None:
|
def post_fill(self) -> None:
|
||||||
"""Optional Method that is called after regular fill. Can be used to do adjustments before output generation.
|
"""Optional Method that is called after regular fill. Can be used to do adjustments before output generation.
|
||||||
This happens before progression balancing, so the items may not be in their final locations yet."""
|
This happens before progression balancing, so the items may not be in their final locations yet."""
|
||||||
|
|
Loading…
Reference in New Issue