Skip to content

Commit

Permalink
Merge branch 'main' into sni_death_text
Browse files Browse the repository at this point in the history
  • Loading branch information
Silvris authored Jul 16, 2023
2 parents 2f9874e + 8df08b5 commit a7f431c
Show file tree
Hide file tree
Showing 68 changed files with 12,262 additions and 3,308 deletions.
1 change: 1 addition & 0 deletions .github/workflows/unittests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ jobs:
python -m pip install --upgrade pip
pip install pytest pytest-subtests
python ModuleUpdate.py --yes --force --append "WebHostLib/requirements.txt"
python Launcher.py --update_settings # make sure host.yaml exists for tests
- name: Unittests
run: |
pytest
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ README.html
EnemizerCLI/
/Players/
/SNI/
/host.yaml
/options.yaml
/config.yaml
/logs/
Expand Down
124 changes: 76 additions & 48 deletions BaseClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from argparse import Namespace
from collections import ChainMap, Counter, deque
from enum import IntEnum, IntFlag
from typing import Any, Callable, Dict, Iterable, Iterator, List, NamedTuple, Optional, Set, Tuple, TypedDict, Union
from typing import Any, Callable, Dict, Iterable, Iterator, List, NamedTuple, Optional, Set, Tuple, TypedDict, Union, \
Type, ClassVar

import NetUtils
import Options
Expand Down Expand Up @@ -788,6 +789,44 @@ def remove(self, item: Item):
self.stale[item.player] = True


class Entrance:
access_rule: Callable[[CollectionState], bool] = staticmethod(lambda state: True)
hide_path: bool = False
player: int
name: str
parent_region: Optional[Region]
connected_region: Optional[Region] = None
# LttP specific, TODO: should make a LttPEntrance
addresses = None
target = None

def __init__(self, player: int, name: str = '', parent: Region = None):
self.name = name
self.parent_region = parent
self.player = player

def can_reach(self, state: CollectionState) -> bool:
if self.parent_region.can_reach(state) and self.access_rule(state):
if not self.hide_path and not self in state.path:
state.path[self] = (self.name, state.path.get(self.parent_region, (self.parent_region.name, None)))
return True

return False

def connect(self, region: Region, addresses: Any = None, target: Any = None) -> None:
self.connected_region = region
self.target = target
self.addresses = addresses
region.entrances.append(self)

def __repr__(self):
return self.__str__()

def __str__(self):
world = self.parent_region.multiworld if self.parent_region else None
return world.get_name_string_for_object(self) if world else f'{self.name} (Player {self.player})'


class Region:
name: str
_hint_text: str
Expand All @@ -796,6 +835,7 @@ class Region:
entrances: List[Entrance]
exits: List[Entrance]
locations: List[Location]
entrance_type: ClassVar[Type[Entrance]] = Entrance

def __init__(self, name: str, player: int, multiworld: MultiWorld, hint: Optional[str] = None):
self.name = name
Expand Down Expand Up @@ -823,20 +863,48 @@ def can_reach_private(self, state: CollectionState) -> bool:
def hint_text(self) -> str:
return self._hint_text if self._hint_text else self.name

def get_connecting_entrance(self, is_main_entrance: typing.Callable[[Entrance], bool]) -> Entrance:
def get_connecting_entrance(self, is_main_entrance: Callable[[Entrance], bool]) -> Entrance:
for entrance in self.entrances:
if is_main_entrance(entrance):
return entrance
for entrance in self.entrances: # BFS might be better here, trying DFS for now.
return entrance.parent_region.get_connecting_entrance(is_main_entrance)

def add_locations(self, locations: Dict[str, Optional[int]], location_type: Optional[typing.Type[Location]] = None) -> None:
"""Adds locations to the Region object, where location_type is your Location class and locations is a dict of
location names to address."""
def add_locations(self, locations: Dict[str, Optional[int]],
location_type: Optional[Type[Location]] = None) -> None:
"""
Adds locations to the Region object, where location_type is your Location class and locations is a dict of
location names to address.
:param locations: dictionary of locations to be created and added to this Region `{name: ID}`
:param location_type: Location class to be used to create the locations with"""
if location_type is None:
location_type = Location
for location, address in locations.items():
self.locations.append(location_type(self.player, location, address, self))

def connect(self, connecting_region: Region, name: Optional[str] = None,
rule: Optional[Callable[[CollectionState], bool]] = None) -> None:
"""
Connects this Region to another Region, placing the provided rule on the connection.
:param connecting_region: Region object to connect to path is `self -> exiting_region`
:param name: name of the connection being created
:param rule: callable to determine access of this connection to go from self to the exiting_region"""
exit_ = self.create_exit(name if name else f"{self.name} -> {connecting_region.name}")
if rule:
exit_.access_rule = rule
exit_.connect(connecting_region)

def create_exit(self, name: str) -> Entrance:
"""
Creates and returns an Entrance object as an exit of this region.
:param name: name of the Entrance being created
"""
exit_ = self.entrance_type(self.player, name, self)
self.exits.append(exit_)
return exit_

def add_exits(self, exits: Union[Iterable[str], Dict[str, Optional[str]]],
rules: Dict[str, Callable[[CollectionState], bool]] = None) -> None:
Expand All @@ -850,11 +918,9 @@ def add_exits(self, exits: Union[Iterable[str], Dict[str, Optional[str]]],
if not isinstance(exits, Dict):
exits = dict.fromkeys(exits)
for connecting_region, name in exits.items():
entrance = Entrance(self.player, name if name else f"{self.name} -> {connecting_region}", self)
if rules and connecting_region in rules:
entrance.access_rule = rules[connecting_region]
self.exits.append(entrance)
entrance.connect(self.multiworld.get_region(connecting_region, self.player))
self.connect(self.multiworld.get_region(connecting_region, self.player),
name,
rules[connecting_region] if rules and connecting_region in rules else None)

def __repr__(self):
return self.__str__()
Expand All @@ -863,44 +929,6 @@ def __str__(self):
return self.multiworld.get_name_string_for_object(self) if self.multiworld else f'{self.name} (Player {self.player})'


class Entrance:
access_rule: Callable[[CollectionState], bool] = staticmethod(lambda state: True)
hide_path: bool = False
player: int
name: str
parent_region: Optional[Region]
connected_region: Optional[Region] = None
# LttP specific, TODO: should make a LttPEntrance
addresses = None
target = None

def __init__(self, player: int, name: str = '', parent: Region = None):
self.name = name
self.parent_region = parent
self.player = player

def can_reach(self, state: CollectionState) -> bool:
if self.parent_region.can_reach(state) and self.access_rule(state):
if not self.hide_path and not self in state.path:
state.path[self] = (self.name, state.path.get(self.parent_region, (self.parent_region.name, None)))
return True

return False

def connect(self, region: Region, addresses: Any = None, target: Any = None) -> None:
self.connected_region = region
self.target = target
self.addresses = addresses
region.entrances.append(self)

def __repr__(self):
return self.__str__()

def __str__(self):
world = self.parent_region.multiworld if self.parent_region else None
return world.get_name_string_for_object(self) if world else f'{self.name} (Player {self.player})'


class LocationProgressType(IntEnum):
DEFAULT = 1
PRIORITY = 2
Expand Down
40 changes: 20 additions & 20 deletions Generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,44 +14,42 @@

ModuleUpdate.update()

import copy
import Utils
import Options
from BaseClasses import seeddigits, get_seed, PlandoOptions
from Main import main as ERmain
from settings import get_settings
from Utils import parse_yamls, version_tuple, __version__, tuplize_version, user_path
from worlds.alttp import Options as LttPOptions
from worlds.generic import PlandoConnection
from Utils import parse_yamls, version_tuple, __version__, tuplize_version, get_options, user_path
from worlds.alttp.EntranceRandomizer import parse_arguments
from Main import main as ERmain
from BaseClasses import seeddigits, get_seed, PlandoOptions
import Options
from worlds.alttp.Text import TextTable
from worlds.AutoWorld import AutoWorldRegister
import copy
from worlds.generic import PlandoConnection


def mystery_argparse():
options = get_options()
defaults = options["generator"]

def resolve_path(path: str, resolver: Callable[[str], str]) -> str:
return path if os.path.isabs(path) else resolver(path)
options = get_settings()
defaults = options.generator

parser = argparse.ArgumentParser(description="CMD Generation Interface, defaults come from host.yaml.")
parser.add_argument('--weights_file_path', default=defaults["weights_file_path"],
parser.add_argument('--weights_file_path', default=defaults.weights_file_path,
help='Path to the weights file to use for rolling game settings, urls are also valid')
parser.add_argument('--samesettings', help='Rolls settings per weights file rather than per player',
action='store_true')
parser.add_argument('--player_files_path', default=resolve_path(defaults["player_files_path"], user_path),
parser.add_argument('--player_files_path', default=defaults.player_files_path,
help="Input directory for player files.")
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
parser.add_argument('--multi', default=defaults["players"], type=lambda value: max(int(value), 1))
parser.add_argument('--spoiler', type=int, default=defaults["spoiler"])
parser.add_argument('--outputpath', default=resolve_path(options["general_options"]["output_path"], user_path),
parser.add_argument('--multi', default=defaults.players, type=lambda value: max(int(value), 1))
parser.add_argument('--spoiler', type=int, default=defaults.spoiler)
parser.add_argument('--outputpath', default=options.general_options.output_path,
help="Path to output folder. Absolute or relative to cwd.") # absolute or relative to cwd
parser.add_argument('--race', action='store_true', default=defaults["race"])
parser.add_argument('--meta_file_path', default=defaults["meta_file_path"])
parser.add_argument('--race', action='store_true', default=defaults.race)
parser.add_argument('--meta_file_path', default=defaults.meta_file_path)
parser.add_argument('--log_level', default='info', help='Sets log level')
parser.add_argument('--yaml_output', default=0, type=lambda value: max(int(value), 0),
help='Output rolled mystery results to yaml up to specified number (made for async multiworld)')
parser.add_argument('--plando', default=defaults["plando_options"],
parser.add_argument('--plando', default=defaults.plando_options,
help='List of options that can be set manually. Can be combined, for example "bosses, items"')
parser.add_argument("--skip_prog_balancing", action="store_true",
help="Skip progression balancing step during generation.")
Expand All @@ -71,6 +69,8 @@ def get_seed_name(random_source) -> str:
def main(args=None, callback=ERmain):
if not args:
args, options = mystery_argparse()
else:
options = get_settings()

seed = get_seed(args.seed)
Utils.init_logging(f"Generate_{seed}", loglevel=args.log_level)
Expand Down Expand Up @@ -137,7 +137,7 @@ def main(args=None, callback=ERmain):
erargs = parse_arguments(['--multi', str(args.multi)])
erargs.seed = seed
erargs.plando_options = args.plando
erargs.glitch_triforce = options["generator"]["glitch_triforce_room"]
erargs.glitch_triforce = options.generator.glitch_triforce_room
erargs.spoiler = args.spoiler
erargs.race = args.race
erargs.outputname = seed_name
Expand Down
23 changes: 18 additions & 5 deletions Launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from typing import Sequence, Union, Optional

import Utils
import settings
from worlds.LauncherComponents import Component, components, Type, SuffixIdentifier, icon_paths

if __name__ == "__main__":
Expand All @@ -33,7 +34,8 @@


def open_host_yaml():
file = user_path('host.yaml')
file = settings.get_settings().filename
assert file, "host.yaml missing"
if is_linux:
exe = which('sensible-editor') or which('gedit') or \
which('xdg-open') or which('gnome-open') or which('kde-open')
Expand Down Expand Up @@ -84,6 +86,11 @@ def open_folder(folder_path):
webbrowser.open(folder_path)


def update_settings():
from settings import get_settings
get_settings().save()


components.extend([
# Functions
Component("Open host.yaml", func=open_host_yaml),
Expand Down Expand Up @@ -256,11 +263,13 @@ def main(args: Optional[Union[argparse.Namespace, dict]] = None):
if not component:
logging.warning(f"Could not identify Component responsible for {args['Patch|Game|Component']}")

if args["update_settings"]:
update_settings()
if 'file' in args:
run_component(args["component"], args["file"], *args["args"])
elif 'component' in args:
run_component(args["component"], *args["args"])
else:
elif not args["update_settings"]:
run_gui()


Expand All @@ -269,9 +278,13 @@ def main(args: Optional[Union[argparse.Namespace, dict]] = None):
Utils.freeze_support()
multiprocessing.set_start_method("spawn") # if launched process uses kivy, fork won't work
parser = argparse.ArgumentParser(description='Archipelago Launcher')
parser.add_argument('Patch|Game|Component', type=str, nargs='?',
help="Pass either a patch file, a generated game or the name of a component to run.")
parser.add_argument('args', nargs="*", help="Arguments to pass to component.")
run_group = parser.add_argument_group("Run")
run_group.add_argument("--update_settings", action="store_true",
help="Update host.yaml and exit.")
run_group.add_argument("Patch|Game|Component", type=str, nargs="?",
help="Pass either a patch file, a generated game or the name of a component to run.")
run_group.add_argument("args", nargs="*",
help="Arguments to pass to component.")
main(parser.parse_args())

from worlds.LauncherComponents import processes
Expand Down
Loading

0 comments on commit a7f431c

Please sign in to comment.