from Options import PerGameCommonOptions, FreeText, Toggle, DefaultOnToggle, Choice, TextChoice, Range, NamedRange, DeathLink, \
OptionGroup, StartInventoryPool, Visibility, item_and_loc_options, Option
from .hooks.Options import before_options_defined, after_options_defined, before_option_groups_created, after_option_groups_created
from .Data import category_table, game_table, option_table
from .Helpers import convert_to_long_string, format_to_valid_identifier
from .Locations import victory_names
from .Items import item_table
from .Game import starting_items
from dataclasses import make_dataclass
from typing import List, Any, Type
import logging
class FillerTrapPercent(Range):
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}
def convertOptionVisibility(input) -> Visibility:
visibility = Visibility.all
if isinstance(input, list):
visibility = Visibility.none
for type in input:
visibility |= Visibility[type.lower()]
elif isinstance(input,str):
if input.startswith('0b'):
visibility = Visibility(int(input, base=0))
else:
visibility = Visibility[input.lower()]
elif isinstance(input, int):
visibility = Visibility(input)
return visibility
def getOriginalOptionArguments(option: Type[Option[Any]]) -> dict:
args = {}
args['default'] = option.default
if hasattr(option, 'display_name'): args['display'] = option.display_name
args['rich_text_doc'] = option.rich_text_doc
args['default'] = option.default
args['visibility'] = option.visibility
return args
manual_option_groups: dict[str, List[Type[Option[Any]]]] = {}
def addOptionToGroup(option_name: str, group: str):
if group not in manual_option_groups.keys():
manual_option_groups[group] = []
if manual_options.get(option_name) and manual_options[option_name] not in manual_option_groups[group]:
manual_option_groups[group].append(manual_options[option_name])
manual_options: dict[str, Type[Option[Any]]] = before_options_defined({})
manual_options["start_inventory_from_pool"] = StartInventoryPool
if len(victory_names) > 1:
if manual_options.get('goal'):
logging.warning("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")
goal = {'option_' + v: i for i, v in enumerate(victory_names)}
manual_options['goal'] = type('goal', (Choice,), dict(goal))
manual_options['goal'].__doc__ = "Choose your victory condition."
if any(item.get('trap') for item in item_table):
manual_options["filler_traps"] = FillerTrapPercent
if game_table.get("death_link"):
manual_options["death_link"] = DeathLink
for option_name, option in option_table.get('core', {}).items():
if option_name.startswith('_'): continue
option_display_name = option_name
option_name = format_to_valid_identifier(option_name)
if manual_options.get(option_name):
original_option: Type[Option] = manual_options[option_name]
original_doc = str(original_option.__doc__)
if issubclass(original_option, Toggle):
if option.get('default', None) is not None:
option_type = DefaultOnToggle if option['default'] else Toggle
if original_option.__base__ != option_type: args = getOriginalOptionArguments(original_option)
manual_options[option_name] = type(option_name, (option_type,), dict(args)) logging.debug(f"Manual: Option.json converted option '{option_display_name}' into a {option_type}")
elif issubclass(original_option, Choice):
if option.get("values"):
raise Exception(f"You cannot modify the values of the '{option_display_name}' option since they cannot have their value changed by Option.json")
if option.get('aliases'):
for alias, value in option['aliases'].items():
original_option.aliases[alias] = value
original_option.options.update(original_option.aliases)
logging.debug(f"Manual: Option.json modified option '{option_display_name}''s aliases")
elif issubclass(original_option, Range):
if option.get('values'): args = getOriginalOptionArguments(original_option)
args['special_range_names'] = {}
if issubclass(original_option, NamedRange):
args['special_range_names'] = dict(original_option.special_range_names)
args['special_range_names']['default'] = option.get('default', args['special_range_names'].get('default', args['default']))
args['range_start'] = original_option.range_start
args['range_end'] = original_option.range_end
args['special_range_names'] = {**args['special_range_names'], **{l.lower(): v for l, v in option['values'].items()}}
manual_options[option_name] = type(option_name, (NamedRange,), dict(args))
logging.debug(f"Manual: Option.json converted option '{option_display_name}' into a {NamedRange}")
manual_options[option_name].display_name = option.get('display_name', option_display_name) manual_options[option_name].__doc__ = convert_to_long_string(option.get('description', original_doc))
if option.get('rich_text_doc'):
manual_options[option_name].rich_text_doc = option["rich_text_doc"]
if option.get('default'):
manual_options[option_name].default = option['default']
if option.get('hidden'):
manual_options[option_name].visibility = Visibility.none
elif option.get('visibility'):
manual_options[option_name].visibility = convertOptionVisibility(option['visibility'])
else:
logging.debug(f"Manual: Option.json just tried to modify the option '{option_display_name}' but it doesn't currently exists")
supported_option_types = ["Toggle", "Choice", "Range"]
for option_name, option in option_table.get('user', {}).items():
if option_name.startswith('_'): continue
option_display_name = option_name
option_name = format_to_valid_identifier(option_name)
if manual_options.get(option_name):
logging.warning(f"Manual: An option with the name '{option_display_name}' cannot be added since it already exists in Manual Core Options. \nTo modify an existing option move it to the 'core' section of Option.json")
else:
option_type = option.get('type', "").title()
if option_type not in supported_option_types:
raise Exception(f'Option {option_display_name} in options.json has an invalid type of "{option["type"]}".\nIt must be one of the folowing: {supported_option_types}')
args = {'display_name': option.get('display_name', option_display_name)}
option_class: Type[Option] = DefaultOnToggle if option.get('default', False) else Toggle
if option_type == "Choice":
args = {**args, **createChoiceOptions(option.get('values'), option.get('aliases', {}))}
option_class = TextChoice if option.get("allow_custom_value", False) else Choice
elif option_type == "Range":
args['range_start'] = option.get('range_start', 0)
args['range_end'] = option.get('range_end', 1)
if option.get('values'):
args['special_range_names'] = {l.lower(): v for l, v in option['values'].items()}
args['special_range_names']['default'] = option.get('default', args['range_start'])
option_class = NamedRange if option.get('values') else Range
if option.get('default'):
args['default'] = option['default']
if option.get('rich_text_doc',None) is not None:
args["rich_text_doc"] = option["rich_text_doc"]
if option.get('hidden'):
args['visibility'] = Visibility.none
elif option.get('visibility'):
args['visibility'] = convertOptionVisibility(option['visibility'])
manual_options[option_name] = type(option_name, (option_class,), args ) manual_options[option_name].__doc__ = convert_to_long_string(option.get('description', "an Option"))
if option.get('group'):
addOptionToGroup(option_name, option['group'])
for category in category_table:
for option_name in category_table[category].get("yaml_option", []):
if option_name[0] == "!":
option_name = option_name[1:]
option_name = format_to_valid_identifier(option_name)
if option_name not in manual_options:
manual_options[option_name] = type(option_name, (DefaultOnToggle,), {"default": True})
manual_options[option_name].__doc__ = "Should items/locations linked to this option be enabled?"
if starting_items:
for starting_items in starting_items:
if starting_items.get("yaml_option"):
for option_name in starting_items["yaml_option"]:
if option_name[0] == "!":
option_name = option_name[1:]
option_name = format_to_valid_identifier(option_name)
if option_name not in manual_options:
manual_options[option_name] = type(option_name, (DefaultOnToggle,), {"default": True})
manual_options[option_name].__doc__ = "Should items/locations linked to this option be enabled?"
def make_options_group() -> list[OptionGroup]:
global manual_option_groups
manual_option_groups = before_option_groups_created(manual_option_groups)
option_groups: List[OptionGroup] = []
base_item_loc_group = item_and_loc_options
if manual_option_groups:
if 'Item & Location Options' in manual_option_groups.keys():
base_item_loc_group = manual_option_groups.pop('Item & Location Options') base_item_loc_group.extend(item_and_loc_options)
for group, options in manual_option_groups.items():
option_groups.append(OptionGroup(group, options))
option_groups.append(OptionGroup('Item & Location Options', base_item_loc_group, True))
return after_option_groups_created(option_groups)
manual_options_data = make_dataclass('ManualOptionsClass', manual_options.items(), bases=(PerGameCommonOptions,))
after_options_defined(manual_options_data)