Core: clean up BaseClasses a bit (#1731)
This commit is contained in:
parent
f4d9c294a3
commit
c46d8afcfa
|
@ -7,9 +7,9 @@ import random
|
|||
import secrets
|
||||
import typing # this can go away when Python 3.8 support is dropped
|
||||
from argparse import Namespace
|
||||
from collections import ChainMap, Counter, OrderedDict, deque
|
||||
from collections import ChainMap, Counter, deque
|
||||
from enum import IntEnum, IntFlag
|
||||
from typing import Any, Callable, Dict, Iterable, List, NamedTuple, Optional, Set, Tuple, TypedDict, Union
|
||||
from typing import Any, Callable, Dict, Iterable, Iterator, List, NamedTuple, Optional, Set, Tuple, TypedDict, Union
|
||||
|
||||
import NetUtils
|
||||
import Options
|
||||
|
@ -28,15 +28,15 @@ class Group(TypedDict, total=False):
|
|||
link_replacement: bool
|
||||
|
||||
|
||||
class ThreadBarrierProxy():
|
||||
class ThreadBarrierProxy:
|
||||
"""Passes through getattr while passthrough is True"""
|
||||
def __init__(self, obj: Any):
|
||||
def __init__(self, obj: object) -> None:
|
||||
self.passthrough = True
|
||||
self.obj = obj
|
||||
|
||||
def __getattr__(self, item):
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
if self.passthrough:
|
||||
return getattr(self.obj, item)
|
||||
return getattr(self.obj, name)
|
||||
else:
|
||||
raise RuntimeError("You are in a threaded context and global random state was removed for your safety. "
|
||||
"Please use multiworld.per_slot_randoms[player] or randomize ahead of output.")
|
||||
|
@ -1028,15 +1028,19 @@ class Item:
|
|||
def flags(self) -> int:
|
||||
return self.classification.as_flag()
|
||||
|
||||
def __eq__(self, other):
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, Item):
|
||||
return NotImplemented
|
||||
return self.name == other.name and self.player == other.player
|
||||
|
||||
def __lt__(self, other: Item) -> bool:
|
||||
def __lt__(self, other: object) -> bool:
|
||||
if not isinstance(other, Item):
|
||||
return NotImplemented
|
||||
if other.player != self.player:
|
||||
return other.player < self.player
|
||||
return self.name < other.name
|
||||
|
||||
def __hash__(self):
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.name, self.player))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
@ -1048,33 +1052,44 @@ class Item:
|
|||
return f"{self.name} (Player {self.player})"
|
||||
|
||||
|
||||
class Spoiler():
|
||||
multiworld: MultiWorld
|
||||
unreachables: Set[Location]
|
||||
class EntranceInfo(TypedDict, total=False):
|
||||
player: int
|
||||
entrance: str
|
||||
exit: str
|
||||
direction: str
|
||||
|
||||
def __init__(self, world):
|
||||
self.multiworld = world
|
||||
|
||||
class Spoiler:
|
||||
multiworld: MultiWorld
|
||||
hashes: Dict[int, str]
|
||||
entrances: Dict[Tuple[str, str, int], EntranceInfo]
|
||||
playthrough: Dict[str, Union[List[str], Dict[str, str]]] # sphere "0" is list, others are dict
|
||||
unreachables: Set[Location]
|
||||
paths: Dict[str, List[Union[Tuple[str, str], Tuple[str, None]]]] # last step takes no further exits
|
||||
|
||||
def __init__(self, multiworld: MultiWorld) -> None:
|
||||
self.multiworld = multiworld
|
||||
self.hashes = {}
|
||||
self.entrances = OrderedDict()
|
||||
self.entrances = {}
|
||||
self.playthrough = {}
|
||||
self.unreachables = set()
|
||||
self.paths = {}
|
||||
|
||||
def set_entrance(self, entrance: str, exit_: str, direction: str, player: int):
|
||||
def set_entrance(self, entrance: str, exit_: str, direction: str, player: int) -> None:
|
||||
if self.multiworld.players == 1:
|
||||
self.entrances[(entrance, direction, player)] = OrderedDict(
|
||||
[('entrance', entrance), ('exit', exit_), ('direction', direction)])
|
||||
self.entrances[(entrance, direction, player)] = \
|
||||
{"entrance": entrance, "exit": exit_, "direction": direction}
|
||||
else:
|
||||
self.entrances[(entrance, direction, player)] = OrderedDict(
|
||||
[('player', player), ('entrance', entrance), ('exit', exit_), ('direction', direction)])
|
||||
self.entrances[(entrance, direction, player)] = \
|
||||
{"player": player, "entrance": entrance, "exit": exit_, "direction": direction}
|
||||
|
||||
def create_playthrough(self, create_paths: bool = True):
|
||||
def create_playthrough(self, create_paths: bool = True) -> None:
|
||||
"""Destructive to the world while it is run, damage gets repaired afterwards."""
|
||||
from itertools import chain
|
||||
# get locations containing progress items
|
||||
multiworld = self.multiworld
|
||||
prog_locations = {location for location in multiworld.get_filled_locations() if location.item.advancement}
|
||||
state_cache = [None]
|
||||
state_cache: List[Optional[CollectionState]] = [None]
|
||||
collection_spheres: List[Set[Location]] = []
|
||||
state = CollectionState(multiworld)
|
||||
sphere_candidates = set(prog_locations)
|
||||
|
@ -1183,17 +1198,17 @@ class Spoiler():
|
|||
for item in removed_precollected:
|
||||
multiworld.push_precollected(item)
|
||||
|
||||
def create_paths(self, state: CollectionState, collection_spheres: List[Set[Location]]):
|
||||
def create_paths(self, state: CollectionState, collection_spheres: List[Set[Location]]) -> None:
|
||||
from itertools import zip_longest
|
||||
multiworld = self.multiworld
|
||||
|
||||
def flist_to_iter(node):
|
||||
while node:
|
||||
value, node = node
|
||||
yield value
|
||||
def flist_to_iter(path_value: Optional[PathValue]) -> Iterator[str]:
|
||||
while path_value:
|
||||
region_or_entrance, path_value = path_value
|
||||
yield region_or_entrance
|
||||
|
||||
def get_path(state, region):
|
||||
reversed_path_as_flist = state.path.get(region, (region, None))
|
||||
def get_path(state: CollectionState, region: Region) -> List[Union[Tuple[str, str], Tuple[str, None]]]:
|
||||
reversed_path_as_flist: PathValue = state.path.get(region, (str(region), None))
|
||||
string_path_flat = reversed(list(map(str, flist_to_iter(reversed_path_as_flist))))
|
||||
# Now we combine the flat string list into (region, exit) pairs
|
||||
pathsiter = iter(string_path_flat)
|
||||
|
@ -1219,14 +1234,11 @@ class Spoiler():
|
|||
self.paths[str(multiworld.get_region('Inverted Big Bomb Shop', player))] = \
|
||||
get_path(state, multiworld.get_region('Inverted Big Bomb Shop', player))
|
||||
|
||||
def to_file(self, filename: str):
|
||||
def write_option(option_key: str, option_obj: type(Options.Option)):
|
||||
def to_file(self, filename: str) -> None:
|
||||
def write_option(option_key: str, option_obj: Options.AssembleOptions) -> None:
|
||||
res = getattr(self.multiworld, option_key)[player]
|
||||
display_name = getattr(option_obj, "display_name", option_key)
|
||||
try:
|
||||
outfile.write(f'{display_name + ":":33}{res.current_option_name}\n')
|
||||
except:
|
||||
raise Exception
|
||||
outfile.write(f"{display_name + ':':33}{res.current_option_name}\n")
|
||||
|
||||
with open(filename, 'w', encoding="utf-8-sig") as outfile:
|
||||
outfile.write(
|
||||
|
@ -1259,15 +1271,15 @@ class Spoiler():
|
|||
AutoWorld.call_all(self.multiworld, "write_spoiler", outfile)
|
||||
|
||||
locations = [(str(location), str(location.item) if location.item is not None else "Nothing")
|
||||
for location in self.multiworld.get_locations() if location.show_in_spoiler]
|
||||
for location in self.multiworld.get_locations() if location.show_in_spoiler]
|
||||
outfile.write('\n\nLocations:\n\n')
|
||||
outfile.write('\n'.join(
|
||||
['%s: %s' % (location, item) for location, item in locations]))
|
||||
|
||||
outfile.write('\n\nPlaythrough:\n\n')
|
||||
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join(
|
||||
[' %s: %s' % (location, item) for (location, item) in sphere.items()] if sphere_nr != '0' else [
|
||||
f' {item}' for item in sphere])) for (sphere_nr, sphere) in self.playthrough.items()]))
|
||||
[f" {location}: {item}" for (location, item) in sphere.items()] if isinstance(sphere, dict) else
|
||||
[f" {item}" for item in sphere])) for (sphere_nr, sphere) in self.playthrough.items()]))
|
||||
if self.unreachables:
|
||||
outfile.write('\n\nUnreachable Items:\n\n')
|
||||
outfile.write(
|
||||
|
@ -1328,23 +1340,21 @@ class PlandoOptions(IntFlag):
|
|||
@classmethod
|
||||
def _handle_part(cls, part: str, base: PlandoOptions) -> PlandoOptions:
|
||||
try:
|
||||
part = cls[part]
|
||||
return base | cls[part]
|
||||
except Exception as e:
|
||||
raise KeyError(f"{part} is not a recognized name for a plando module. "
|
||||
f"Known options: {', '.join(flag.name for flag in cls)}") from e
|
||||
else:
|
||||
return base | part
|
||||
f"Known options: {', '.join(str(flag.name) for flag in cls)}") from e
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.value:
|
||||
return ", ".join(flag.name for flag in PlandoOptions if self.value & flag.value)
|
||||
return ", ".join(str(flag.name) for flag in PlandoOptions if self.value & flag.value)
|
||||
return "None"
|
||||
|
||||
|
||||
seeddigits = 20
|
||||
|
||||
|
||||
def get_seed(seed=None) -> int:
|
||||
def get_seed(seed: Optional[int] = None) -> int:
|
||||
if seed is None:
|
||||
random.seed(None)
|
||||
return random.randint(0, pow(10, seeddigits) - 1)
|
||||
|
|
Loading…
Reference in New Issue