From af11fa51501fc8bd6162b6cff817fcb2b72c0d50 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 16 Sep 2022 00:32:30 +0200 Subject: [PATCH] Core: auto alias (#1022) * Test: check that default templates can be parsed into Option objects --- Options.py | 7 ++++++- docs/world api.md | 6 ++---- test/webhost/TestFileGeneration.py | 13 +++++++++++- worlds/alttp/Options.py | 8 -------- worlds/factorio/Options.py | 2 -- worlds/hk/Options.py | 3 --- worlds/oot/Options.py | 9 --------- worlds/sm/Options.py | 2 -- worlds/sm64ex/Options.py | 22 ++++++++++++++++---- worlds/sm64ex/__init__.py | 1 + worlds/smz3/Options.py | 1 - worlds/soe/Options.py | 2 -- worlds/timespinner/Options.py | 32 +++++++++++++++++++++++++++++- 13 files changed, 70 insertions(+), 38 deletions(-) diff --git a/Options.py b/Options.py index 7eb108c9..56b54fe3 100644 --- a/Options.py +++ b/Options.py @@ -26,13 +26,18 @@ class AssembleOptions(abc.ABCMeta): attrs["name_lookup"].update({option_id: name for name, option_id in new_options.items()}) options.update(new_options) - # apply aliases, without name_lookup aliases = {name[6:].lower(): option_id for name, option_id in attrs.items() if name.startswith("alias_")} assert "random" not in aliases, "Choice option 'random' cannot be manually assigned." + # auto-alias Off and On being parsed as True and False + if "off" in options: + options["false"] = options["off"] + if "on" in options: + options["true"] = options["on"] + options.update(aliases) # auto-validate schema on __init__ diff --git a/docs/world api.md b/docs/world api.md index ffc0749e..fd0a3711 100644 --- a/docs/world api.md +++ b/docs/world api.md @@ -274,14 +274,12 @@ Define a property `option_ = ` per selectable value and `default = ` to set the default selection. Aliases can be set by defining a property `alias_ = `. -One special case where aliases are required is when option name is `yes`, `no`, -`on` or `off` because they parse to `True` or `False`: ```python option_off = 0 option_on = 1 option_some = 2 -alias_false = 0 -alias_true = 1 +alias_disabled = 0 +alias_enabled = 1 default = 0 ``` diff --git a/test/webhost/TestFileGeneration.py b/test/webhost/TestFileGeneration.py index 7f56864e..656206cd 100644 --- a/test/webhost/TestFileGeneration.py +++ b/test/webhost/TestFileGeneration.py @@ -14,9 +14,20 @@ class TestFileGeneration(unittest.TestCase): def testOptions(self): WebHost.create_options_files() - self.assertTrue(os.path.exists(os.path.join(self.correct_path, "static", "generated", "configs"))) + target = os.path.join(self.correct_path, "static", "generated", "configs") + self.assertTrue(os.path.exists(target)) self.assertFalse(os.path.exists(os.path.join(self.incorrect_path, "static", "generated", "configs"))) + # folder seems fine, so now we try to generate Options based on the default file + from WebHostLib.check import roll_options + file: os.DirEntry + for file in os.scandir(target): + if file.is_file() and file.name.endswith(".yaml"): + with self.subTest(file=file.name): + with open(file) as f: + for value in roll_options({file.name: f.read()})[0].values(): + self.assertTrue(value is True, f"Default Options for template {file.name} cannot be run.") + def testTutorial(self): WebHost.create_ordered_tutorials_file() self.assertTrue(os.path.exists(os.path.join(self.correct_path, "static", "generated", "tutorials.json"))) diff --git a/worlds/alttp/Options.py b/worlds/alttp/Options.py index b40becbd..184b8f3a 100644 --- a/worlds/alttp/Options.py +++ b/worlds/alttp/Options.py @@ -39,8 +39,6 @@ class OpenPyramid(Choice): option_auto = 3 default = option_goal - alias_true = option_open - alias_false = option_closed alias_yes = option_open alias_no = option_closed @@ -159,8 +157,6 @@ class Progressive(Choice): option_off = 0 option_grouped_random = 1 option_on = 2 - alias_false = 0 - alias_true = 2 default = 2 def want_progressives(self, random): @@ -202,8 +198,6 @@ class Hints(Choice): option_on = 2 option_full = 3 default = 2 - alias_false = 0 - alias_true = 2 class Scams(Choice): @@ -213,7 +207,6 @@ class Scams(Choice): option_king_zora = 1 option_bottle_merchant = 2 option_all = 3 - alias_false = 0 @property def gives_king_zora_hint(self): @@ -293,7 +286,6 @@ class HeartBeep(Choice): option_half = 2 option_quarter = 3 option_off = 4 - alias_false = 4 class HeartColor(Choice): diff --git a/worlds/factorio/Options.py b/worlds/factorio/Options.py index b7d13c64..64e03c43 100644 --- a/worlds/factorio/Options.py +++ b/worlds/factorio/Options.py @@ -137,8 +137,6 @@ class Progressive(Choice): option_off = 0 option_grouped_random = 1 option_on = 2 - alias_false = 0 - alias_true = 2 default = 2 def want_progressives(self, random): diff --git a/worlds/hk/Options.py b/worlds/hk/Options.py index fd8d0361..04923e70 100644 --- a/worlds/hk/Options.py +++ b/worlds/hk/Options.py @@ -409,7 +409,6 @@ class DeathLink(Choice): shade: DeathLink functions like a normal death if you do not already have a shade, shadeless otherwise. """ option_off = 0 - alias_false = 0 alias_no = 0 alias_true = 1 alias_on = 1 @@ -435,10 +434,8 @@ class CostSanity(Choice): These costs can be in Geo (except Grubfather, Seer and Eggshop), Grubs, Charms, Essence and/or Rancid Eggs """ option_off = 0 - alias_false = 0 alias_no = 0 option_on = 1 - alias_true = 1 alias_yes = 1 option_shopsonly = 2 option_notshops = 3 diff --git a/worlds/oot/Options.py b/worlds/oot/Options.py index ea9a8160..deee5587 100644 --- a/worlds/oot/Options.py +++ b/worlds/oot/Options.py @@ -101,7 +101,6 @@ class InteriorEntrances(Choice): option_off = 0 option_simple = 1 option_all = 2 - alias_false = 0 alias_true = 2 @@ -141,7 +140,6 @@ class MixEntrancePools(Choice): option_off = 0 option_indoor = 1 option_all = 2 - alias_false = 0 class DecoupleEntrances(Toggle): @@ -308,7 +306,6 @@ class ShopShuffle(Choice): option_off = 0 option_fixed_number = 1 option_random_number = 2 - alias_false = 0 class ShopSlots(Range): @@ -326,7 +323,6 @@ class TokenShuffle(Choice): option_dungeons = 1 option_overworld = 2 option_all = 3 - alias_false = 0 class ScrubShuffle(Choice): @@ -336,7 +332,6 @@ class ScrubShuffle(Choice): option_low = 1 option_regular = 2 option_random_prices = 3 - alias_false = 0 alias_affordable = 1 alias_expensive = 2 @@ -569,7 +564,6 @@ class Hints(Choice): option_agony = 2 option_always = 3 default = 3 - alias_false = 0 class MiscHints(DefaultOnToggle): @@ -673,8 +667,6 @@ class IceTraps(Choice): option_mayhem = 3 option_onslaught = 4 default = 1 - alias_false = 0 - alias_true = 2 alias_extra = 2 @@ -742,7 +734,6 @@ class Music(Choice): option_normal = 0 option_off = 1 option_randomized = 2 - alias_false = 1 class BackgroundMusic(Music): diff --git a/worlds/sm/Options.py b/worlds/sm/Options.py index 814b19f4..2f0c70a2 100644 --- a/worlds/sm/Options.py +++ b/worlds/sm/Options.py @@ -122,8 +122,6 @@ class AreaRandomization(Choice): option_off = 0 option_light = 1 option_on = 2 - alias_false = 0 - alias_true = 2 default = 0 class AreaLayout(Toggle): diff --git a/worlds/sm64ex/Options.py b/worlds/sm64ex/Options.py index 7d9a75dd..f29a65c5 100644 --- a/worlds/sm64ex/Options.py +++ b/worlds/sm64ex/Options.py @@ -1,48 +1,57 @@ import typing from Options import Option, DefaultOnToggle, Range, Toggle, DeathLink, Choice + class EnableCoinStars(DefaultOnToggle): """Disable to Ignore 100 Coin Stars. You can still collect them, but they don't do anything""" display_name = "Enable 100 Coin Stars" + class StrictCapRequirements(DefaultOnToggle): """If disabled, Stars that expect special caps may have to be acquired without the caps""" display_name = "Strict Cap Requirements" + class StrictCannonRequirements(DefaultOnToggle): """If disabled, Stars that expect cannons may have to be acquired without them. Only makes a difference if Buddy Checks are enabled""" display_name = "Strict Cannon Requirements" + class FirstBowserStarDoorCost(Range): """How many stars are required at the Star Door to Bowser in the Dark World""" range_start = 0 range_end = 50 default = 8 + class BasementStarDoorCost(Range): """How many stars are required at the Star Door in the Basement""" range_start = 0 range_end = 70 default = 30 + class SecondFloorStarDoorCost(Range): """How many stars are required to access the third floor""" range_start = 0 range_end = 90 default = 50 + class MIPS1Cost(Range): """How many stars are required to spawn MIPS the first time""" range_start = 0 range_end = 40 default = 15 + class MIPS2Cost(Range): """How many stars are required to spawn MIPS the secound time. Must be bigger or equal MIPS1Cost""" range_start = 0 range_end = 80 default = 50 + class StarsToFinish(Range): """How many stars are required at the infinite stairs""" display_name = "Endless Stairs Stars" @@ -50,35 +59,40 @@ class StarsToFinish(Range): range_end = 100 default = 70 + class AmountOfStars(Range): """How many stars exist. Disabling 100 Coin Stars removes 15 from the Pool. At least max of any Cost set""" range_start = 35 range_end = 120 default = 120 + class AreaRandomizer(Choice): """Randomize Entrances""" display_name = "Entrance Randomizer" - alias_false = 0 option_Off = 0 option_Courses_Only = 1 option_Courses_and_Secrets = 2 + class BuddyChecks(Toggle): """Bob-omb Buddies are checks, Cannon Unlocks are items""" display_name = "Bob-omb Buddy Checks" + class ExclamationBoxes(Choice): """Include 1Up Exclamation Boxes during randomization""" display_name = "Randomize 1Up !-Blocks" option_Off = 0 option_1Ups_Only = 1 + class ProgressiveKeys(DefaultOnToggle): """Keys will first grant you access to the Basement, then to the Secound Floor""" display_name = "Progressive Keys" -sm64_options: typing.Dict[str,type(Option)] = { + +sm64_options: typing.Dict[str, type(Option)] = { "AreaRandomizer": AreaRandomizer, "ProgressiveKeys": ProgressiveKeys, "EnableCoinStars": EnableCoinStars, @@ -93,5 +107,5 @@ sm64_options: typing.Dict[str,type(Option)] = { "StarsToFinish": StarsToFinish, "death_link": DeathLink, "BuddyChecks": BuddyChecks, - "ExclamationBoxes": ExclamationBoxes -} \ No newline at end of file + "ExclamationBoxes": ExclamationBoxes, +} diff --git a/worlds/sm64ex/__init__.py b/worlds/sm64ex/__init__.py index cf8a8e87..88b2261a 100644 --- a/worlds/sm64ex/__init__.py +++ b/worlds/sm64ex/__init__.py @@ -9,6 +9,7 @@ from .Regions import create_regions, sm64courses, sm64entrances_s, sm64_internal from BaseClasses import Item, Tutorial, ItemClassification from ..AutoWorld import World, WebWorld + class SM64Web(WebWorld): tutorials = [Tutorial( "Multiworld Setup Guide", diff --git a/worlds/smz3/Options.py b/worlds/smz3/Options.py index 2bbddf7a..e3d8b8dd 100644 --- a/worlds/smz3/Options.py +++ b/worlds/smz3/Options.py @@ -107,7 +107,6 @@ class HeartBeepSpeed(Choice): option_Half = 2 option_Normal = 3 option_Double = 4 - alias_false = 0 default = 3 class HeartColor(Choice): diff --git a/worlds/soe/Options.py b/worlds/soe/Options.py index 4ec0ce2b..c718cb4a 100644 --- a/worlds/soe/Options.py +++ b/worlds/soe/Options.py @@ -21,8 +21,6 @@ class OffOnFullChoice(Choice): option_on = 1 option_full = 2 alias_chaos = 2 - alias_false = 0 - alias_true = 1 class Difficulty(EvermizerFlags, Choice): diff --git a/worlds/timespinner/Options.py b/worlds/timespinner/Options.py index 588416dd..574259c9 100644 --- a/worlds/timespinner/Options.py +++ b/worlds/timespinner/Options.py @@ -3,66 +3,82 @@ from BaseClasses import MultiWorld from Options import Toggle, DefaultOnToggle, DeathLink, Choice, Range, Option, OptionDict from schema import Schema, And, Optional + class StartWithJewelryBox(Toggle): "Start with Jewelry Box unlocked" display_name = "Start with Jewelry Box" + #class ProgressiveVerticalMovement(Toggle): # "Always find vertical movement in the following order Succubus Hairpin -> Light Wall -> Celestial Sash" # display_name = "Progressive vertical movement" + #class ProgressiveKeycards(Toggle): # "Always find Security Keycard's in the following order D -> C -> B -> A" # display_name = "Progressive keycards" + class DownloadableItems(DefaultOnToggle): "With the tablet you will be able to download items at terminals" display_name = "Downloadable items" + class EyeSpy(Toggle): "Requires Oculus Ring in inventory to be able to break hidden walls." display_name = "Eye Spy" + class StartWithMeyef(Toggle): "Start with Meyef, ideal for when you want to play multiplayer." display_name = "Start with Meyef" + class QuickSeed(Toggle): "Start with Talaria Attachment, Nyoom!" display_name = "Quick seed" + class SpecificKeycards(Toggle): "Keycards can only open corresponding doors" display_name = "Specific Keycards" + class Inverted(Toggle): "Start in the past" display_name = "Inverted" + #class StinkyMaw(Toggle): # "Require gasmask for Maw" # display_name = "Stinky Maw" + class GyreArchives(Toggle): "Gyre locations are in logic. New warps are gated by Merchant Crow and Kobo" display_name = "Gyre Archives" + class Cantoran(Toggle): "Cantoran's fight and check are available upon revisiting his room" display_name = "Cantoran" + class LoreChecks(Toggle): "Memories and journal entries contain items." display_name = "Lore Checks" + class BossRando(Toggle): "Shuffles the positions of all bosses." display_name = "Boss Randomization" + class BossScaling(DefaultOnToggle): "When Boss Rando is enabled, scales the bosses' HP, XP, and ATK to the stats of the location they replace (Reccomended)" display_name = "Scale Random Boss Stats" + class DamageRando(Choice): "Randomly nerfs and buffs some orbs and their associated spells as well as some associated rings." display_name = "Damage Rando" @@ -73,9 +89,9 @@ class DamageRando(Choice): option_mostlybuffs = 4 option_allbuffs = 5 option_manual = 6 - alias_false = 0 alias_true = 2 + class DamageRandoOverrides(OptionDict): "Manual +/-/normal odds for an orb. Put 0 if you don't want a certain nerf or buff to be a possibility. Orbs that you don't specify will roll with 1/1/1 as odds" schema = Schema({ @@ -180,6 +196,7 @@ class DamageRandoOverrides(OptionDict): "Radiant": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 }, } + class HpCap(Range): "Sets the number that Lunais's HP maxes out at." display_name = "HP Cap" @@ -187,10 +204,12 @@ class HpCap(Range): range_end = 999 default = 999 + class BossHealing(DefaultOnToggle): "Enables/disables healing after boss fights. NOTE: Currently only applicable when Boss Rando is enabled." display_name = "Heal After Bosses" + class ShopFill(Choice): """Sets the items for sale in Merchant Crow's shops. Default: No sunglasses or trendy jacket, but sand vials for sale. @@ -203,10 +222,12 @@ class ShopFill(Choice): option_vanilla = 2 option_empty = 3 + class ShopWarpShards(DefaultOnToggle): "Shops always sell warp shards (when keys possessed), ignoring inventory setting." display_name = "Always Sell Warp Shards" + class ShopMultiplier(Range): "Multiplier for the cost of items in the shop. Set to 0 for free shops." display_name = "Shop Price Multiplier" @@ -214,6 +235,7 @@ class ShopMultiplier(Range): range_end = 10 default = 1 + class LootPool(Choice): """Sets the items that drop from enemies (does not apply to boss reward checks) Vanilla: Drops are the same as the base game @@ -224,6 +246,7 @@ class LootPool(Choice): option_randomized = 1 option_empty = 2 + class DropRateCategory(Choice): """Sets the drop rate when 'Loot Pool' is set to 'Random' Tiered: Based on item rarity/value @@ -237,6 +260,7 @@ class DropRateCategory(Choice): option_randomized = 2 option_fixed = 3 + class FixedDropRate(Range): "Base drop rate percentage when 'Drop Rate Category' is set to 'Fixed'" display_name = "Fixed Drop Rate" @@ -244,6 +268,7 @@ class FixedDropRate(Range): range_end = 100 default = 5 + class LootTierDistro(Choice): """Sets how often items of each rarity tier are placed when 'Loot Pool' is set to 'Random' Default Weight: Rarer items will be assigned to enemy drop slots less frequently than common items @@ -255,14 +280,17 @@ class LootTierDistro(Choice): option_full_random = 1 option_inverted_weight = 2 + class ShowBestiary(Toggle): "All entries in the bestiary are visible, without needing to kill one of a given enemy first" display_name = "Show Bestiary Entries" + class ShowDrops(Toggle): "All item drops in the bestiary are visible, without needing an enemy to drop one of a given item first" display_name = "Show Bestiary Item Drops" + # Some options that are available in the timespinner randomizer arent currently implemented timespinner_options: Dict[str, Option] = { "StartWithJewelryBox": StartWithJewelryBox, @@ -296,9 +324,11 @@ timespinner_options: Dict[str, Option] = { "DeathLink": DeathLink, } + def is_option_enabled(world: MultiWorld, player: int, name: str) -> bool: return get_option_value(world, player, name) > 0 + def get_option_value(world: MultiWorld, player: int, name: str) -> Union[int, dict]: option = getattr(world, name, None) if option == None: