462 lines
9.3 KiB
Python
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']
|