bl2mods/Mods/ModUtils/sdkmod.py
2023-03-22 12:57:56 -04:00

462 lines
9.3 KiB
Python

from Mods.ModMenu import (
KeybindManager as KBM,
Options
)
from Mods.ModMenu.ModObjects import EnabledSaveType, Game, ModPriorities, ModTypes, RegisterMod, SDKMod
from Mods.ModMenu.Options import Boolean, Slider, Spinner
from dataclasses import dataclass, field
from unrealsdk import Log
from .misc import combine_flags, is_in_game, manifest_to_flag
from .objects import Player
MOD_VALUES = {
'Name': 'name',
'Author': 'author',
'Description': 'description',
'Version': 'version',
'SupportedGames': 'supported_games',
'Types': 'types',
'Priority': 'priority',
'SaveEnabledState': 'save_enabled_state',
'Status': 'status',
'SettingsInput': 'settings_input',
'Options': 'options',
'Keybinds': 'keybinds'
}
SAVE_STATES = {
'menu': EnabledSaveType.LoadOnMainMenu,
'settings': EnabledSaveType.LoadWithSettings,
'never': EnabledSaveType.NotSaved
}
class SdkMod(SDKMod):
def __new__(cls, *args, **kwargs):
instance = SDKMod.__new__(cls, *args, **kwargs)
instance.Keybinds = KeybindList()
instance.Options = OptionsList()
instance.SettingsInputs = SettingsInputDict()
return instance
def __getattr__(self, key):
if key == 'player':
return Player.default()
elif key == 'world':
return World.default()
return SDKMod.__getattribute__(self, key)
def get_enable_state(self):
return self.SaveEnabledState
def get_games(self):
return self.SupportedGames
def get_priority(self):
return self.Priority
def get_types(self):
return self.Types
def log(self, *data):
message = ' '.join(str(msg) for msg in data)
Log(f'[{self.Name}] {message}')
def register(self):
if is_in_game():
RegisterMod(self)
def set_enable_state(self, state):
if isinstance(state, str):
state = SAVE_STATES.get(state)
if not state:
state = EnabledSaveType[str]
elif not isinstance(state, EnabledSaveType):
raise TypeError(f'Mod enable state must be a str or EnabledSaveType, not {type(priority)}')
self.SaveEnabledState = state
def set_games(self, *values):
items = []
for value in values:
if isinstance(value, str):
value = Game[value]
elif not isinstance(value, Game):
raise TypeError(f'Game type must be a str or Game, not {(type(value))}')
items.append(value)
self.SupportedGames = combine_flags(*items)
def set_priority(self, priority):
if not instance(priority, (int, ModPriorities)):
raise TypeError(f'Priority must be an int or ModPriorities, not {type(priority)}')
self.Priority = priority
def set_types(self, *types):
items = []
for value in types:
if isinstance(value, str):
value = ModTypes[value]
elif not isinstance(value, ModTypes):
raise TypeError(f'Mod type must be a str or ModTypes, not {(type(value))}')
items.append(value)
self.Types = combine_flags(*items)
## Overriding functions and events to add handler functions for easier sub-classing
@classmethod
def NetworkDeserialize(cls, data):
args = cls.handle_network_deserialize(data)
if not args or isinstance(args, str):
return SDKMod.NetworkDeserialize(args)
return args
@classmethod
def NetworkSerialise(cls, args):
data = cls.handle_network_serialize(args)
if not data or isinstance(data, dict):
return SDKMod.NetworkSerialise(data)
return data
def Disable(self):
SDKMod.Disable(self)
self.handle_disable()
def Enable(self):
SDKMod.Enable(self)
self.handle_enable()
def GameInputPressed(self, bind, event):
self.handle_game_input(bind, event)
def ModOptionChanged(self, option, value):
self.handle_option_change(option, value)
def SettingsInputPressed(self, action):
SDKMod.SettingsInputPressed(self, action)
self.handle_settings_input(action)
## New handler methods for built-in events
def handle_disable(self):
pass
def handle_enable(self):
pass
def handle_game_input(self, bind, event):
pass
def handle_network_deserialize(self, data: str):
pass
def handle_network_serialize(self, args: dict):
pass
def handle_option_change(self, option, value):
pass
def handle_settings_input(self, action):
pass
class KeybindList(list):
def __init__(self, *keybinds):
list.__init__(self)
for keybind in keybinds:
self.add(keybind)
def add(self, keybind):
'''
Add an existing Keybind object to the keybind list
'''
if not isinstance(keybind, KBM.Keybind):
raise TypeError('Keybind is not a ModMenu.Keybind object')
if self.get(keybind.Name):
raise ValueError(f'Keybind with id already exists: {keybind.Name}')
self.append(keybind)
def get(self, name):
'''
Get an in-game keybind
'''
for key in self:
if name in {keybind.Name, keybind.Name.replace(' ', '')}:
return key
def new(self, name, key, rebindable=True, hidden=False, handler=None):
'''
Create a new in-game keybind for an action
'''
keybind = KBM.Keybind(
Name = name,
Key = key,
IsRebindable = rebindable,
IsHidden = hidden,
OnPress = handler
)
self.add(keybind)
return key
class OptionsList(list):
def __init__(self, *options):
list.__init__(self)
for opt in options:
self.add(opt)
def add(self, opt, id=None):
if not isinstance(opt, Options.Value):
raise TypeError('Option must be a ModUtils.BaseValue and ModMenu.Options.Value object')
opt.id = id or opt.Caption.replace(' ', '')
self.append(opt)
def get(self, id):
for opt in self:
if id in {opt.id, opt.Caption}:
return opt
def new_boolean(self, name, description, default=False, true_value='On', false_value='Off', id=None):
self.add(Options.Boolean(
Caption = name,
Description = description,
StartingValue = default,
Choices = (false_value, true_value)
), id)
def new_slider(self, name, description, default=1, min=0, max=100, increment=1, id=None):
self.add(Options.Slider(
Caption = name,
Description = description,
StartingValue = default,
MinValue = min,
MaxValue = max,
Increment = increment
), id)
def new_spinner(self, name, descrption, choices: list, default=None, id=None):
self.add(Options.Spinner(
Caption = name,
Description = description,
StartingValue = default or choices[0]
), id)
def set(self, id, value=None):
opt = self.get(id)
opt.CurrentValue = value if value != None else opt.StartingValue
def to_dict(self):
return {opt.id: opt.CurrentValue for opt in self}
class SettingsInputDict(dict):
def __init__(self, **binds):
dict.__init__(self, {'Enter': 'Enable'})
for keybind, name in binds.items():
self.new(name, keybind)
def get_by_name(self, name):
for keybind, keybind_name in self.items():
if keybind_name == name:
return keybind
raise KeyError(name)
def new(self, name, keybind):
if keybind in self:
raise ValueError(f'Settings keybind already exists: {keybind}')
self[keybind] = name
class BaseValue:
id = None
def __new__(cls, *args, **kwargs):
id = kwargs.pop('id')
if issubclass(cls, Options.Boolean):
value = Options.Boolean.__new__(cls, *args, **kwargs)
elif issubclass(cls, Options.Slider):
value = Options.Slider.__new__(cls, *args, **kwargs)
elif issubclass(cls, Options.Spinner):
value = Options.Spinner.__new__(cls, *args, **kwargs)
value.id = id or value.Caption.replace(' ', '')
return value
def get(self):
return self.CurrentValue
def default(self):
self.CurrentValue = self.StartingValue
def set(self, value):
self.CurrentValue = value
class Boolean(BaseValue, Options.Boolean):
def set(self, value):
if not isinstance(value, bool):
value = bool(value)
BaseValue.set(self, value)
class Slider(BaseValue, Options.Slider):
def set(self, value):
if not isinstance(value, int):
value = int(value)
if self.MinValue > value > self.MaxValue:
raise ValueError(f'Value out of range: {self.MinValue}-{self.MaxValue}')
BaseValue.set(self, value)
class Spinner(BaseValue, Options.Slider):
def set(self, value):
if value not in self.Choices:
raise ValueError(f'Invalid choice: {value}')
BaseValue.set(self, value)
# class SdkManifestMod(SDKMod):
# def __init__(self, dirname):
# with Path(__file__).parent.joinpath(f'../{dirname}/manifest.json').open('r') as manifest:
# self.manifest = json.load(manifest)
#
#
# def __getattr__(self, key):
# if key in MOD_VALUES:
# key = MOD_VALUES[key]
#
# return object.__getattribute__(self, key)
#
#
# def __setattr__(self, key, value):
# if key in MOD_VALUES:
# key = MOD_VALUES[key]
#
# object.__setattr__(self, key, value)
#
#
# def __delattr__(self, key):
# if key in MOD_VALUES:
# key = MOD_VALUES[key]
#
# return object.__delattr__(self, key)
#
#
# @property
# def Author(self):
# return self.manifest['authors']
#
#
# @property
# def Description(self):
# return self.manifest.get('tagline', self.manifest['description'])
#
#
# @property
# def Name(self):
# return self.manifest['name']
#
#
# @property
# def Priority(self):
# value = self.manifest['modinfo']['priority']
#
# if isinstance(value, str):
# return ModPriorities[value.title()]
#
# return value
#
#
# @property
# def SaveEnabledState(self):
# return EnabledSaveType[self.manifest['modinfo']['save_state']]
#
#
# @property
# def SupportedGames(self):
# return manifest_to_flag(Game, self.manifest['supports'])
#
#
# @property
# def Types(self):
# return manifest_to_flag(ModTypes, self.manifest['types'])
#
#
# @property
# def Version(self):
# return self.manifest['latest']