Core: auto alias (#1022)
* Test: check that default templates can be parsed into Option objects
This commit is contained in:
		| @@ -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__ | ||||
|   | ||||
| @@ -274,14 +274,12 @@ Define a property `option_<name> = <number>` per selectable value and | ||||
| `default = <number>` to set the default selection. Aliases can be set by | ||||
| defining a property `alias_<name> = <same number>`. | ||||
|  | ||||
| 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 | ||||
| ``` | ||||
|  | ||||
|   | ||||
| @@ -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"))) | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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 | ||||
| }  | ||||
|     "ExclamationBoxes": ExclamationBoxes, | ||||
| } | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -107,7 +107,6 @@ class HeartBeepSpeed(Choice): | ||||
|     option_Half = 2 | ||||
|     option_Normal = 3 | ||||
|     option_Double = 4 | ||||
|     alias_false = 0 | ||||
|     default = 3 | ||||
|  | ||||
| class HeartColor(Choice): | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Fabian Dill
					Fabian Dill