From 3205cbf932b26f50e29e5dcb336d9b726e00bb64 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 3 Jul 2022 14:11:52 +0200 Subject: [PATCH] Generate: convert plando settings to an IntFlag with error reporting for unknown plando names (#735) --- Generate.py | 66 ++++++++++++++++++++++++++++++------------ WebHostLib/check.py | 4 +-- WebHostLib/generate.py | 4 +-- 3 files changed, 51 insertions(+), 23 deletions(-) diff --git a/Generate.py b/Generate.py index 5b83b87d..b46c730c 100644 --- a/Generate.py +++ b/Generate.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import argparse import logging import random @@ -7,6 +9,7 @@ from typing import Set, Dict, Tuple, Callable, Any, Union import os from collections import Counter import string +import enum import ModuleUpdate @@ -25,7 +28,38 @@ from worlds.alttp.Text import TextTable from worlds.AutoWorld import AutoWorldRegister import copy -categories = set(AutoWorldRegister.world_types) + +class PlandoSettings(enum.IntFlag): + items = 0b0001 + connections = 0b0010 + texts = 0b0100 + bosses = 0b1000 + + @classmethod + def from_option_string(cls, option_string: str) -> PlandoSettings: + result = cls(0) + for part in option_string.split(","): + part = part.strip().lower() + if part: + result = cls._handle_part(part, result) + return result + + @classmethod + def from_set(cls, option_set: Set[str]) -> PlandoSettings: + result = cls(0) + for part in option_set: + result = cls._handle_part(part, result) + return result + + @classmethod + def _handle_part(cls, part: str, base: PlandoSettings) -> PlandoSettings: + try: + part = 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 def mystery_argparse(): @@ -64,7 +98,7 @@ def mystery_argparse(): args.weights_file_path = os.path.join(args.player_files_path, args.weights_file_path) if not os.path.isabs(args.meta_file_path): args.meta_file_path = os.path.join(args.player_files_path, args.meta_file_path) - args.plando: Set[str] = {arg.strip().lower() for arg in args.plando.split(",")} + args.plando: PlandoSettings = PlandoSettings.from_option_string(args.plando) return args, options @@ -127,7 +161,7 @@ def main(args=None, callback=ERmain): args.multi = max(player_id-1, args.multi) print(f"Generating for {args.multi} player{'s' if args.multi > 1 else ''}, {seed_name} Seed {seed} with plando: " - f"{', '.join(args.plando)}") + f"{args.plando}") if not weights_cache: raise Exception(f"No weights found. Provide a general weights file ({args.weights_file_path}) or individual player files. " @@ -403,7 +437,7 @@ def roll_triggers(weights: dict, triggers: list) -> dict: def get_plando_bosses(boss_shuffle: str, plando_options: Set[str]) -> str: if boss_shuffle in boss_shuffle_options: return boss_shuffle_options[boss_shuffle] - elif "bosses" in plando_options: + elif PlandoSettings.bosses in plando_options: options = boss_shuffle.lower().split(";") remainder_shuffle = "none" # vanilla bosses = [] @@ -452,7 +486,7 @@ def handle_option(ret: argparse.Namespace, game_weights: dict, option_key: str, setattr(ret, option_key, option(option.default)) -def roll_settings(weights: dict, plando_options: Set[str] = frozenset(("bosses",))): +def roll_settings(weights: dict, plando_options: PlandoSettings = PlandoSettings.bosses): if "linked_options" in weights: weights = roll_linked_options(weights) @@ -465,17 +499,11 @@ def roll_settings(weights: dict, plando_options: Set[str] = frozenset(("bosses", if tuplize_version(version) > version_tuple: raise Exception(f"Settings reports required version of generator is at least {version}, " f"however generator is of version {__version__}") - required_plando_options = requirements.get("plando", "") - if required_plando_options: - required_plando_options = set(option.strip() for option in required_plando_options.split(",")) - required_plando_options -= plando_options + required_plando_options = PlandoSettings.from_option_string(requirements.get("plando", "")) + if required_plando_options not in plando_options: if required_plando_options: - if len(required_plando_options) == 1: - raise Exception(f"Settings reports required plando module {', '.join(required_plando_options)}, " - f"which is not enabled.") - else: - raise Exception(f"Settings reports required plando modules {', '.join(required_plando_options)}, " - f"which are not enabled.") + raise Exception(f"Settings reports required plando module {str(required_plando_options)}, " + f"which is not enabled.") ret = argparse.Namespace() for option_key in Options.per_game_common_options: @@ -504,12 +532,12 @@ def roll_settings(weights: dict, plando_options: Set[str] = frozenset(("bosses", # skip setting this option if already set from common_options, defaulting to root option if not (option_key in Options.common_options and option_key not in game_weights): handle_option(ret, game_weights, option_key, option) - if "items" in plando_options: + if PlandoSettings.items in plando_options: ret.plando_items = game_weights.get("plando_items", []) if ret.game == "Minecraft" or ret.game == "Ocarina of Time": # bad hardcoded behavior to make this work for now ret.plando_connections = [] - if "connections" in plando_options: + if PlandoSettings.connections in plando_options: options = game_weights.get("plando_connections", []) for placement in options: if roll_percentage(get_choice("percentage", placement, 100)): @@ -629,7 +657,7 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options): raise Exception(f"unknown Medallion {medallion} for {'misery mire' if index == 0 else 'turtle rock'}") ret.plando_texts = {} - if "texts" in plando_options: + if PlandoSettings.texts in plando_options: tt = TextTable() tt.removeUnwantedText() options = weights.get("plando_texts", []) @@ -641,7 +669,7 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options): ret.plando_texts[at] = str(get_choice_legacy("text", placement)) ret.plando_connections = [] - if "connections" in plando_options: + if PlandoSettings.connections in plando_options: options = weights.get("plando_connections", []) for placement in options: if roll_percentage(get_choice_legacy("percentage", placement, 100)): diff --git a/WebHostLib/check.py b/WebHostLib/check.py index b560801c..a4e2f518 100644 --- a/WebHostLib/check.py +++ b/WebHostLib/check.py @@ -12,7 +12,7 @@ def allowed_file(filename): return filename.endswith(('.txt', ".yaml", ".zip")) -from Generate import roll_settings +from Generate import roll_settings, PlandoSettings from Utils import parse_yamls @@ -65,7 +65,7 @@ def get_yaml_data(file) -> Union[Dict[str, str], str]: def roll_options(options: Dict[str, Union[dict, str]], plando_options: Set[str] = frozenset({"bosses", "items", "connections", "texts"})) -> \ Tuple[Dict[str, Union[str, bool]], Dict[str, dict]]: - plando_options = set(plando_options) + plando_options = PlandoSettings.from_set(set(plando_options)) results = {} rolled_results = {} for filename, text in options.items(): diff --git a/WebHostLib/generate.py b/WebHostLib/generate.py index d8d46037..9c35a591 100644 --- a/WebHostLib/generate.py +++ b/WebHostLib/generate.py @@ -12,7 +12,7 @@ from flask import request, flash, redirect, url_for, session, render_template from worlds.alttp.EntranceRandomizer import parse_arguments from Main import main as ERmain from BaseClasses import seeddigits, get_seed -from Generate import handle_name +from Generate import handle_name, PlandoSettings import pickle from .models import * @@ -120,7 +120,7 @@ def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=No erargs.outputname = seedname erargs.outputpath = target.name erargs.teams = 1 - erargs.plando_options = ", ".join(plando_options) + erargs.plando_options = PlandoSettings.from_set(plando_options) name_counter = Counter() for player, (playerfile, settings) in enumerate(gen_options.items(), 1):