Zillion: use "new" settings api and cleaning (#3903)
* Zillion: use "new" settings api and cleaning * python 3.10 typing update * don't separate assignments of item link players
This commit is contained in:
@@ -3,11 +3,12 @@ from contextlib import redirect_stdout
|
||||
import functools
|
||||
import settings
|
||||
import threading
|
||||
import typing
|
||||
from typing import Any, Dict, List, Set, Tuple, Optional, Union
|
||||
from typing import Any, ClassVar
|
||||
import os
|
||||
import logging
|
||||
|
||||
from typing_extensions import override
|
||||
|
||||
from BaseClasses import ItemClassification, LocationProgressType, \
|
||||
MultiWorld, Item, CollectionState, Entrance, Tutorial
|
||||
|
||||
@@ -76,7 +77,7 @@ class ZillionWorld(World):
|
||||
options_dataclass = ZillionOptions
|
||||
options: ZillionOptions # type: ignore
|
||||
|
||||
settings: typing.ClassVar[ZillionSettings] # type: ignore
|
||||
settings: ClassVar[ZillionSettings] # type: ignore
|
||||
# these type: ignore are because of this issue: https://github.com/python/typing/discussions/1486
|
||||
|
||||
topology_present = True # indicate if world type has any meaningful layout/pathing
|
||||
@@ -89,14 +90,14 @@ class ZillionWorld(World):
|
||||
|
||||
class LogStreamInterface:
|
||||
logger: logging.Logger
|
||||
buffer: List[str]
|
||||
buffer: list[str]
|
||||
|
||||
def __init__(self, logger: logging.Logger) -> None:
|
||||
self.logger = logger
|
||||
self.buffer = []
|
||||
|
||||
def write(self, msg: str) -> None:
|
||||
if msg.endswith('\n'):
|
||||
if msg.endswith("\n"):
|
||||
self.buffer.append(msg[:-1])
|
||||
self.logger.debug("".join(self.buffer))
|
||||
self.buffer = []
|
||||
@@ -108,21 +109,21 @@ class ZillionWorld(World):
|
||||
|
||||
lsi: LogStreamInterface
|
||||
|
||||
id_to_zz_item: Optional[Dict[int, ZzItem]] = None
|
||||
id_to_zz_item: dict[int, ZzItem] | None = None
|
||||
zz_system: System
|
||||
_item_counts: "Counter[str]" = Counter()
|
||||
_item_counts: Counter[str] = Counter()
|
||||
"""
|
||||
These are the items counts that will be in the game,
|
||||
which might be different from the item counts the player asked for in options
|
||||
(if the player asked for something invalid).
|
||||
"""
|
||||
my_locations: List[ZillionLocation] = []
|
||||
my_locations: list[ZillionLocation] = []
|
||||
""" This is kind of a cache to avoid iterating through all the multiworld locations in logic. """
|
||||
slot_data_ready: threading.Event
|
||||
""" This event is set in `generate_output` when the data is ready for `fill_slot_data` """
|
||||
logic_cache: Union[ZillionLogicCache, None] = None
|
||||
logic_cache: ZillionLogicCache | None = None
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
def __init__(self, world: MultiWorld, player: int) -> None:
|
||||
super().__init__(world, player)
|
||||
self.logger = logging.getLogger("Zillion")
|
||||
self.lsi = ZillionWorld.LogStreamInterface(self.logger)
|
||||
@@ -133,6 +134,7 @@ class ZillionWorld(World):
|
||||
_id_to_name, _id_to_zz_id, id_to_zz_item = make_id_to_others(start_char)
|
||||
self.id_to_zz_item = id_to_zz_item
|
||||
|
||||
@override
|
||||
def generate_early(self) -> None:
|
||||
zz_op, item_counts = validate(self.options)
|
||||
|
||||
@@ -150,12 +152,13 @@ class ZillionWorld(World):
|
||||
# just in case the options changed anything (I don't think they do)
|
||||
assert self.zz_system.randomizer, "init failed"
|
||||
for zz_name in self.zz_system.randomizer.locations:
|
||||
if zz_name != 'main':
|
||||
if zz_name != "main":
|
||||
assert self.zz_system.randomizer.loc_name_2_pretty[zz_name] in self.location_name_to_id, \
|
||||
f"{self.zz_system.randomizer.loc_name_2_pretty[zz_name]} not in location map"
|
||||
|
||||
self._make_item_maps(zz_op.start_char)
|
||||
|
||||
@override
|
||||
def create_regions(self) -> None:
|
||||
assert self.zz_system.randomizer, "generate_early hasn't been called"
|
||||
assert self.id_to_zz_item, "generate_early hasn't been called"
|
||||
@@ -177,23 +180,23 @@ class ZillionWorld(World):
|
||||
zz_loc.req.gun = 1
|
||||
assert len(self.zz_system.randomizer.get_locations(Req(gun=1, jump=1))) != 0
|
||||
|
||||
start = self.zz_system.randomizer.regions['start']
|
||||
start = self.zz_system.randomizer.regions["start"]
|
||||
|
||||
all: Dict[str, ZillionRegion] = {}
|
||||
all_regions: dict[str, ZillionRegion] = {}
|
||||
for here_zz_name, zz_r in self.zz_system.randomizer.regions.items():
|
||||
here_name = "Menu" if here_zz_name == "start" else zz_reg_name_to_reg_name(here_zz_name)
|
||||
all[here_name] = ZillionRegion(zz_r, here_name, here_name, p, w)
|
||||
self.multiworld.regions.append(all[here_name])
|
||||
all_regions[here_name] = ZillionRegion(zz_r, here_name, here_name, p, w)
|
||||
self.multiworld.regions.append(all_regions[here_name])
|
||||
|
||||
limited_skill = Req(gun=3, jump=3, skill=self.zz_system.randomizer.options.skill, hp=940, red=1, floppy=126)
|
||||
queue = deque([start])
|
||||
done: Set[str] = set()
|
||||
done: set[str] = set()
|
||||
while len(queue):
|
||||
zz_here = queue.popleft()
|
||||
here_name = "Menu" if zz_here.name == "start" else zz_reg_name_to_reg_name(zz_here.name)
|
||||
if here_name in done:
|
||||
continue
|
||||
here = all[here_name]
|
||||
here = all_regions[here_name]
|
||||
|
||||
for zz_loc in zz_here.locations:
|
||||
# if local gun reqs didn't place "keyword" item
|
||||
@@ -217,15 +220,16 @@ class ZillionWorld(World):
|
||||
self.my_locations.append(loc)
|
||||
|
||||
for zz_dest in zz_here.connections.keys():
|
||||
dest_name = "Menu" if zz_dest.name == 'start' else zz_reg_name_to_reg_name(zz_dest.name)
|
||||
dest = all[dest_name]
|
||||
exit = Entrance(p, f"{here_name} to {dest_name}", here)
|
||||
here.exits.append(exit)
|
||||
exit.connect(dest)
|
||||
dest_name = "Menu" if zz_dest.name == "start" else zz_reg_name_to_reg_name(zz_dest.name)
|
||||
dest = all_regions[dest_name]
|
||||
exit_ = Entrance(p, f"{here_name} to {dest_name}", here)
|
||||
here.exits.append(exit_)
|
||||
exit_.connect(dest)
|
||||
|
||||
queue.append(zz_dest)
|
||||
done.add(here.name)
|
||||
|
||||
@override
|
||||
def create_items(self) -> None:
|
||||
if not self.id_to_zz_item:
|
||||
self._make_item_maps("JJ")
|
||||
@@ -249,14 +253,11 @@ class ZillionWorld(World):
|
||||
self.logger.debug(f"Zillion Items: {item_name} 1")
|
||||
self.multiworld.itempool.append(self.create_item(item_name))
|
||||
|
||||
def set_rules(self) -> None:
|
||||
# logic for this game is in create_regions
|
||||
pass
|
||||
|
||||
@override
|
||||
def generate_basic(self) -> None:
|
||||
assert self.zz_system.randomizer, "generate_early hasn't been called"
|
||||
# main location name is an alias
|
||||
main_loc_name = self.zz_system.randomizer.loc_name_2_pretty[self.zz_system.randomizer.locations['main'].name]
|
||||
main_loc_name = self.zz_system.randomizer.loc_name_2_pretty[self.zz_system.randomizer.locations["main"].name]
|
||||
|
||||
self.multiworld.get_location(main_loc_name, self.player)\
|
||||
.place_locked_item(self.create_item("Win"))
|
||||
@@ -264,22 +265,18 @@ class ZillionWorld(World):
|
||||
lambda state: state.has("Win", self.player)
|
||||
|
||||
@staticmethod
|
||||
def stage_generate_basic(multiworld: MultiWorld, *args: Any) -> None:
|
||||
def stage_generate_basic(multiworld: MultiWorld, *args: Any) -> None: # noqa: ANN401
|
||||
# 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
|
||||
if group["game"] == "Zillion" and "item_pool" in group:
|
||||
item_pool = group["item_pool"]
|
||||
to_stay: Chars = "JJ"
|
||||
if "JJ" in item_pool:
|
||||
assert "players" in group
|
||||
group_players = group["players"]
|
||||
players_start_chars: List[Tuple[int, Chars]] = []
|
||||
group["players"] = group_players = set(group["players"])
|
||||
players_start_chars: list[tuple[int, Chars]] = []
|
||||
for player in group_players:
|
||||
z_world = multiworld.worlds[player]
|
||||
assert isinstance(z_world, ZillionWorld)
|
||||
@@ -291,17 +288,17 @@ class ZillionWorld(World):
|
||||
elif start_char_counts["Champ"] > start_char_counts["Apple"]:
|
||||
to_stay = "Champ"
|
||||
else: # equal
|
||||
choices: Tuple[Chars, ...] = ("Apple", "Champ")
|
||||
choices: tuple[Chars, ...] = ("Apple", "Champ")
|
||||
to_stay = multiworld.random.choice(choices)
|
||||
|
||||
for p, sc in players_start_chars:
|
||||
if sc != to_stay:
|
||||
group_players.remove(p)
|
||||
assert "world" in group
|
||||
group_world = group["world"]
|
||||
assert isinstance(group_world, ZillionWorld)
|
||||
group_world._make_item_maps(to_stay)
|
||||
|
||||
@override
|
||||
def post_fill(self) -> None:
|
||||
"""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."""
|
||||
@@ -317,10 +314,10 @@ class ZillionWorld(World):
|
||||
|
||||
assert self.zz_system.randomizer, "generate_early hasn't been called"
|
||||
|
||||
# debug_zz_loc_ids: Dict[str, int] = {}
|
||||
# debug_zz_loc_ids: dict[str, int] = {}
|
||||
empty = zz_items[4]
|
||||
multi_item = empty # a different patcher method differentiates empty from ap multi item
|
||||
multi_items: Dict[str, Tuple[str, str]] = {} # zz_loc_name to (item_name, player_name)
|
||||
multi_items: dict[str, tuple[str, str]] = {} # zz_loc_name to (item_name, player_name)
|
||||
for z_loc in self.multiworld.get_locations(self.player):
|
||||
assert isinstance(z_loc, ZillionLocation)
|
||||
# debug_zz_loc_ids[z_loc.zz_loc.name] = id(z_loc.zz_loc)
|
||||
@@ -343,7 +340,7 @@ class ZillionWorld(World):
|
||||
# print(id_)
|
||||
# print("size:", len(debug_zz_loc_ids))
|
||||
|
||||
# debug_loc_to_id: Dict[str, int] = {}
|
||||
# debug_loc_to_id: dict[str, int] = {}
|
||||
# regions = self.zz_randomizer.regions
|
||||
# for region in regions.values():
|
||||
# for loc in region.locations:
|
||||
@@ -358,10 +355,11 @@ class ZillionWorld(World):
|
||||
f"in world {self.player} didn't get an item"
|
||||
)
|
||||
|
||||
game_id = self.multiworld.player_name[self.player].encode() + b'\x00' + self.multiworld.seed_name[-6:].encode()
|
||||
game_id = self.multiworld.player_name[self.player].encode() + b"\x00" + self.multiworld.seed_name[-6:].encode()
|
||||
|
||||
return GenData(multi_items, self.zz_system.get_game(), game_id)
|
||||
|
||||
@override
|
||||
def generate_output(self, output_directory: str) -> None:
|
||||
"""This method gets called from a threadpool, do not use multiworld.random here.
|
||||
If you need any last-second randomization, use self.random instead."""
|
||||
@@ -383,6 +381,7 @@ class ZillionWorld(World):
|
||||
|
||||
self.logger.debug(f"Zillion player {self.player} finished generate_output")
|
||||
|
||||
@override
|
||||
def fill_slot_data(self) -> ZillionSlotInfo: # json of WebHostLib.models.Slot
|
||||
"""Fill in the `slot_data` field in the `Connected` network package.
|
||||
This is a way the generator can give custom data to the client.
|
||||
@@ -400,6 +399,7 @@ class ZillionWorld(World):
|
||||
|
||||
# end of ordered Main.py calls
|
||||
|
||||
@override
|
||||
def create_item(self, name: str) -> Item:
|
||||
"""Create an item for this world type and player.
|
||||
Warning: this may be called with self.multiworld = None, for example by MultiServer"""
|
||||
@@ -420,6 +420,7 @@ class ZillionWorld(World):
|
||||
z_item = ZillionItem(name, classification, item_id, self.player, zz_item)
|
||||
return z_item
|
||||
|
||||
@override
|
||||
def get_filler_item_name(self) -> str:
|
||||
"""Called when the item pool needs to be filled with additional items to match location count."""
|
||||
return "Empty"
|
||||
|
||||
Reference in New Issue
Block a user