Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
1c3790d
add conversion of existing goal option as alias for new system
nicopop Apr 23, 2024
0be50d5
Add check for compatible goal option before attempting to convert
nicopop Apr 29, 2024
ebb619d
Added warning and moved convert example to hook
nicopop May 18, 2024
49e202a
small fix
nicopop May 19, 2024
c8a88c8
WIP option.json loading and parsing
nicopop May 23, 2024
aac3e05
Merge branch 'main' into adding-Option.json
nicopop May 23, 2024
3192f04
made the options generation actually work
nicopop May 30, 2024
9c19947
Merge branch 'main' into adding-Option.json
nicopop Jul 13, 2024
3a744b9
updated descriptions and removed Undefined from enum
nicopop Jul 20, 2024
88580c7
Actually implement Range/NamedRange options
nicopop Jul 21, 2024
d5fb99f
Let manual load with option.json missing
nicopop Jul 21, 2024
c1fb867
allow goal description/default/display override
nicopop Jul 21, 2024
f6e4c12
use Locate to convert str type to Type
nicopop Jul 21, 2024
9fcdf97
update Schema catalog and option.json path to schema
nicopop Jul 21, 2024
c3b2ed7
Merge branch 'main' into adding-Option.json
nicopop Sep 20, 2024
bf55e1b
Add Option Groups
nicopop Sep 28, 2024
bc55ea4
Merge remote-tracking branch 'remotes/origin/main' into adding-Option…
nicopop Sep 28, 2024
bc7a2cf
cleanup of imports
nicopop Sep 28, 2024
79f26e2
Simplify import
nicopop Sep 28, 2024
f46c170
Added Visibility setting to options
nicopop Sep 29, 2024
544843a
Simplify Options schema
nicopop Nov 24, 2024
867499f
Remove locate and use list instead
nicopop Nov 24, 2024
e77a1f2
Restrict option type to what can be used right now
nicopop Nov 24, 2024
a0b3ff6
small fix
nicopop Nov 25, 2024
4040675
revert changes to init.py
nicopop Nov 25, 2024
38161b5
enable the example options that can be enabled
nicopop Nov 25, 2024
eb3c9ab
Combine Option Types together like Toggle and DefaultOnToggle
nicopop Nov 25, 2024
34d7240
small tweaks
nicopop Nov 25, 2024
476378f
fix yaml templates breaking when multiline description present
nicopop Nov 25, 2024
a1b19fa
Renamed params as proposed and split goal override from basic option …
nicopop Nov 25, 2024
975b41c
small options.json descriptions tweaks
nicopop Nov 25, 2024
b131807
removed the override dict in options.py
nicopop Nov 26, 2024
1758c47
cleanup of options.py
nicopop Nov 26, 2024
da72c1f
only modify option if it exists
nicopop Nov 26, 2024
63f55ea
make the schema even simpler to update
nicopop Nov 26, 2024
b5ac869
make override mode work with any existing options
nicopop Nov 26, 2024
b73c83b
Split Option.json data into core and user
nicopop Nov 27, 2024
1a92dfb
small tweaks for option group order
nicopop Nov 30, 2024
116ff4c
updated hooks example/information
nicopop Nov 30, 2024
bae6678
don't recreate choice options to add aliases
nicopop Dec 2, 2024
77fa93c
Schema descriptions tweaks 1th pass
nicopop Dec 2, 2024
c088b06
schema descriptions changed based on feedback
nicopop Dec 2, 2024
31ad275
Merge remote-tracking branch 'origin/main' into adding-Option.json
nicopop Dec 2, 2024
85ec7db
Small tweaks based on Fuzzy's feedback
nicopop Dec 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions schemas/Manual.options.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/ManualForArchipelago/Manual/main/schemas/Manual.options.schema.json",
"description": "Schema for Manual's options.json",
"type": "object",
"items": {
"$ref": "#/definitions/Option"
},
"properties": {
"$schema": {
"type":"string",
"description": "The schema to verify this document against."
},
"data": {
"description": "dict of options for this apworld",
"type": "object",
"patternProperties": {
"^_.*$": {"description": "A commented out Option", "$ref": "#/definitions/Option"},
"^.*$": {"description": "An Option for your World","$ref": "#/definitions/Option"}
}
},
"_comment": {"$ref": "#/definitions/comment"}
},
"definitions": {
"Option": {
"type": "object",
"properties": {
"display_name": {
"description": "The name shown on the webhost.",
"type": "string"
},
"type": {
"description": "The type of this options, check the official Archipelago docs for the options types here: \nhttps://github.com/ArchipelagoMW/Archipelago/blob/main/docs/options%20api.md#basic-option-classes",
"type": "string",
"enum": ["FreeText", "Toggle", "DefaultOnToggle", "Choice", "TextChoice", "Range", "NamedRange"]
},
"description": {
"description": "long description of what this option is for(is shown on hover on the webhost)",
"type": ["array", "string"],
"items": {"type": "string"}
},
"rich_text_doc": {
"description": "Enable support for HTML generated from the standard Python reStructuredText format for your description. \nfor more info: https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/options%20api.md#option-documentation",
"type": "boolean",
"default": false
},
"_comment": {"$ref": "#/definitions/comment"}
},
"required": ["type"],
"if": {"properties": { "type": {"const": "Choice"}}},
"then": {
"description": "Let the user pick from a list of values",
"properties": {
"values": {"$ref": "#/definitions/ChoiceValue"},
"aliases": {"$ref": "#/definitions/ChoiceAlias"},
"default": {"$ref": "#/definitions/DefaultInt"}
},
"required": ["values", "type"]
},
"else": {
"if": {"properties": { "type": {"const": "TextChoice"}}},
"then": {
"description": "Like Choice but the player can define their own values",
"properties": {
"values": {"$ref": "#/definitions/ChoiceValue"},
"aliases": {"$ref": "#/definitions/ChoiceAlias"},
"default": {"$ref": "#/definitions/DefaultInt"}
},
"required": ["values", "type"]
},
"else": {
"if": {"properties": { "type": {"const": "Range"}}},
"then": {
"description": "Allow the player to specify a value between 'start' and 'end'",
"properties": {
"range_start": {"$ref": "#/definitions/rangeStart"},
"range_end": {"$ref": "#/definitions/rangeEnd"},
"default": {"$ref": "#/definitions/DefaultInt"}
}
},
"else": {
"if": {"properties": { "type": {"const": "NamedRange"}}},
"then": {"description": "Like a Range, but with your own defined label for certain values",
"properties": {
"range_start": {"$ref": "#/definitions/rangeStart"},
"range_end": {"$ref": "#/definitions/rangeEnd"},
"default": {"$ref": "#/definitions/DefaultInt"},
"special_range_names":{"$ref": "#/definitions/special_range_names"}
}
},
"else": {
"if": {"properties": { "type": {"const": "Toggle"}}},
"then": {"description": "A simple boolean, the default value is False. Use DefaultOnToggle instead for True"},
"else":{
"if": {"properties": { "type": {"const": "DefaultOnToggle"}}},
"then": {"description": "A simple boolean, the default value is True. Use Toggle instead for False"},
"else":{
"if": {"properties": { "type": {"const": "FreeText"}}},
"then": {
"description": "This Option will return a Player's chosen String",
"properties": {
"default": {
"description": "Default Text value for this option",
"type": "string",
"default": ""
}
}
}
}
}
}
}
}
}
},
"rangeStart": {
"type": "integer",
"description": "The lowest Value for this range",
"default": 0
},
"rangeEnd": {
"type":"integer",
"description": "The Highest Value for this range",
"default": 1
},
"special_range_names": {
"description": "A Special Dict in the format 'name':int of named values for this range \nAll names need to be lowercase",
"type":"object",
"patternProperties": {
"^.*$": {
"description": "A named numerical value",
"type": "integer"
}
}
},
"ChoiceValue": {
"description": "A dict of possible values in the format 'name':int \nthe default option is, by default the one with 0 as value",
"type":"object",
"patternProperties": {
"^.*$": {
"description": "An int value this option can have",
"type": "integer"
}
},
"minProperties": 1
},
"ChoiceAlias": {
"description": "A dict of possible values in the format 'name':int/the name of a value from 'values'",
"type": "object",
"patternProperties": {
"^.*$": {
"description": "An alias for a value declared in 'values'",
"type": ["integer", "string"]
}
}
},
"DefaultInt": {
"description": "the default integer value of this option",
"type": "integer",
"default": 0
},
"comment": {
"description": "(Optional) Does nothing, Its mainly here for Dev notes for future devs to understand your logic",
"type": ["string", "array"],
"items": {
"description": "A line of comment",
"type":"string"
}
}
}
}
4 changes: 3 additions & 1 deletion src/Data.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
after_load_game_file, \
after_load_item_file, after_load_location_file, \
after_load_region_file, after_load_category_file, \
after_load_meta_file
after_load_option_file, after_load_meta_file

# blatantly copied from the minecraft ap world because why not
def load_data_file(*args) -> dict:
Expand All @@ -32,6 +32,7 @@ def convert_to_list(data, property_name: str) -> list:
location_table = convert_to_list(load_data_file('locations.json'), 'data') #list
region_table = load_data_file('regions.json') #dict
category_table = load_data_file('categories.json') or {} #dict
option_table = load_data_file('options.json') or {} #dict
meta_table = load_data_file('meta.json') or {} #dict

# Removal of schemas in root of tables
Expand All @@ -44,6 +45,7 @@ def convert_to_list(data, property_name: str) -> list:
location_table = after_load_location_file(location_table)
region_table = after_load_region_file(region_table)
category_table = after_load_category_file(category_table)
option_table = after_load_option_file(option_table)
meta_table = after_load_meta_file(meta_table)

# seed all of the tables for validation
Expand Down
5 changes: 5 additions & 0 deletions src/Helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,8 @@ def get_items_with_value(world: World, multiworld: MultiWorld, value: str, playe
and i.name in world.item_name_groups.get(f'has_{value}_value', [])}
world.item_values[player][value] = item_with_values
return world.item_values[player].get(value)

def convertToLongString(input: str | list) -> str: #Todo maybe find a better name for this
if not isinstance(input, str):
return str.join("\n", input)
return input
13 changes: 4 additions & 9 deletions src/Meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from BaseClasses import Tutorial
from worlds.AutoWorld import World, WebWorld
from .Data import meta_table
from .Helpers import convertToLongString

##############
# Meta Classes
Expand All @@ -20,18 +21,12 @@ class ManualWeb(WebWorld):
# Convert meta.json data to properties
######################################
def set_world_description(base_doc: str) -> str:
if meta_table.get("docs", {}).get("apworld_description", None) is None:
return base_doc
if meta_table.get("docs", {}).get("apworld_description"):
return convertToLongString(meta_table["docs"]["apworld_description"])

if isinstance(meta_table["docs"]["apworld_description"], str):
base_doc = meta_table["docs"]["apworld_description"]
else:
fullstring = ""
for line in meta_table["docs"]["apworld_description"]:
fullstring += "\n" + line
base_doc = fullstring
return base_doc


def set_world_webworld(web: WebWorld) -> WebWorld:
if meta_table.get("docs", {}).get("web", {}):
Web_Config = meta_table["docs"]["web"]
Expand Down
67 changes: 65 additions & 2 deletions src/Options.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,84 @@
from Options import FreeText, NumericOption, Toggle, DefaultOnToggle, Choice, TextChoice, Range, NamedRange, PerGameCommonOptions, DeathLink
from dataclasses import make_dataclass
from .hooks.Options import before_options_defined, after_options_defined
from .Data import category_table, game_table
from .Data import category_table, game_table, option_table
from .Helpers import convertToLongString

from .Locations import victory_names
from .Items import item_table
import logging


class FillerTrapPercent(Range):
"""How many fillers will be replaced with traps. 0 means no additional traps, 100 means all fillers are traps."""
range_end = 100


def createChoiceOptions(values: dict, aliases: dict) -> dict:
values = {'option_' + i: v for i, v in values.items()}
aliases = {'alias_' + i: v for i, v in aliases.items()}
return {**values, **aliases}

manual_options = before_options_defined({})
manual_goal_override = {}
for option_name, option in option_table.get('data', {}).items():
if option_name.startswith('_'): #To allow commenting out options
continue

if option_name in ['goal', 'filler_traps']:
if manual_options.get('goal'):
logging.warn("Existing Goal option found created via Hooks, it will be overwritten by Manual's generated Goal option.\nIf you want to support old yaml you will need to add alias in after_options_defined")
# todo do something for those situations, maybe convert, maybe warn idk for now
if option_name == 'goal':
if option['type'] != 'Choice':
raise Exception("a 'goal' option must be of type 'Choice'")
manual_goal_override['args'] = createChoiceOptions({}, option.get('aliases', {}))
manual_goal_override['args']['default'] = args['default'] = option.get('default', 0)
manual_goal_override['args']['display_name'] = option.get('display_name', option_name)
manual_goal_override['description'] = convertToLongString(option.get('description', ''))
continue

option_type = Toggle # ! I think there might be a better way to convert option['type'] -> type but I cant find it right now
args = {'display_name': option.get('display_name', option_name)}
if option['type'] == "DefaultOnToggle":
option_type = DefaultOnToggle

elif option['type'] == "Choice" or option['type'] == "TextChoice":
option_type = Choice
if option['type'] == "TextChoice":
option_type = TextChoice
args = {**args, **createChoiceOptions(option.get('values'), option.get('aliases', {}))}

elif option['type'] == "Range" or option['type'] == "NamedRange":
option_type = Range
args['range_start'] = option.get('range_start', 0)
args['range_end'] = option.get('range_end', 1)
if option['type'] == "NamedRange":
option_type = NamedRange
args['special_range_names'] = option.get('special_range_names', {})
args['special_range_names']['default'] = option.get('default', args['range_start'])

elif option['type'] == "FreeText":
option_type = FreeText

if option.get('default'):
args['default'] = option.get('default')

if option.get('rich_text_doc',None) is not None:
args["rich_text_doc"] = option["rich_text_doc"]

if option_name not in manual_options:
manual_options[option_name] = type(option_name, (option_type,), args )
manual_options[option_name].__doc__ = convertToLongString(option.get('description', "an Option"))

if len(victory_names) > 1:
goal = {'option_' + v: i for i, v in enumerate(victory_names)}
# Check for existing Goal option
if manual_goal_override:
goal = {**goal, **manual_goal_override['args']}
manual_options['goal'] = type('goal', (Choice,), goal)
manual_options['goal'].__doc__ = "Choose your victory condition."
manual_options['goal'].__doc__ = manual_goal_override.get('description', '') or "Choose your victory condition."


if any(item.get('trap') for item in item_table):
manual_options["filler_traps"] = FillerTrapPercent
Expand Down
62 changes: 62 additions & 0 deletions src/data/options.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"$schema": "../../schemas/Manual.options.schema.json",
"_comment": "Add a _ before an option name to comment it out and it wont be added to the apworld",
"data": {
"Choice":{
"type": "Choice",
"description": ["This is a Choice"],
"values": {
"start":0,
"test":1
},
"default": 0
},
"TextChoice":{
"type":"TextChoice",
"description": ["This is a TextChoice"],
"values": {
"start":0,
"test":1
},
"default": 0
},
"Range":{
"type": "Range",
"description": ["this is a Range"],
"range_start": 0,
"default": 1,
"range_end": 10
},
"NamedRange":{
"description": ["this is a NamedRange"],
"type": "NamedRange",
"range_start": 0,
"range_end": 10,
"special_range_names": {"test":2}
},
"DefaultOnToggle":{
"description": ["this is a DefaultOnToggle"],
"type": "DefaultOnToggle"
},
"Toggle":{
"description": ["this is a Toggle"],
"type": "Toggle"
},
"FreeText":{
"description": ["this is a FreeText"],
"type": "FreeText"
},


"DLC_enabled":{
"display_name": "DLC Enabled",
"description": ["Is the Dlc enabled?"],
"type": "DefaultOnToggle"
},

"_commented_out":{
"type": "DefaultOnToggle"
}
}

}
Loading