If you have a question that is not answered here, you can search on Discord, and if you do not find it, ask there.
Page Contents
Follow the steps described on the main page.
Yes. Usual compatibility concerns still apply, like if several mods change the same variables they will interfere with each other. This is the case regardless of whether the mods are PythonSDK mods or text mods.
Text mods can only modify objects once; when run. PythonSDK mods can modify dynamically generated objects and run arbitrary game functions whenever they please.
python-sdk.log
in your Binaries/Win32
folder may contain clues as to what went wrong when asking for further support.If you are on Linux: PythonSDK does not yet work natively on Linux, but it seems to work well under SteamPlay/Proton and Wine. See The README section on Linux.
If you are running a mod or modpack that modifies skills (such as Skill Randomizer, Exodus), it will crash if you try to make a new character with those mods enabled. First make the new character, then enable those mods.
If that is not your situation, try to narrow down the mod that causes the issue by disabling all other mods and restarting to see if it still happens, and when you find the culprit: contact the mod author, if it’s a large modpack they may have a discord server you can ask on, or create an issue on their mod’s repository if there is one.
Auto enabling is something the mod author needs to turn on themselves. There might be a reason they’ve chosen to explicitly disable it. Alternatively, if the mod’s last update was a while ago, it’s possible auto enabling wasn’t implemented yet, and you could ask the author to enable it.
__init__.py
within it with the following basic template:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import unrealsdk
from Mods import ModMenu
class MyMod(ModMenu.SDKMod):
Name: str = "My Mod Name"
Author: str = "My Name"
Description: str = "My Mod Description"
Version: str = "1.0.0"
SupportedGames: ModMenu.Game = ModMenu.Game.BL2 | ModMenu.Game.TPS # Either BL2 or TPS; bitwise OR'd together
Types: ModMenu.ModTypes = ModMenu.ModTypes.Utility # One of Utility, Content, Gameplay, Library; bitwise OR'd together
instance = MyMod()
ModMenu.RegisterMod(instance)
python-sdk.log
(which can be found in your Win32
folder, one folder up from the Mods folder) should give a clue as to why.Add the following to your __init__.py
before RegisterMod is called:
1
2
3
4
5
6
7
8
9
10
11
12
if __name__ == "__main__":
unrealsdk.Log(f"[{instance.Name}] Manually loaded")
for mod in ModMenu.Mods:
if mod.Name == instance.Name:
if mod.IsEnabled:
mod.Disable()
ModMenu.Mods.remove(mod)
unrealsdk.Log(f"[{instance.Name}] Removed last instance")
# Fixes inspect.getfile()
instance.__class__.__module__ = mod.__class__.__module__
break
Now you should be able to reload your mod by running pyexec ModFolderName/__init__.py
(change ModFolderName to the name of the folder your mod is in).
Note that this code snippet is a workaround more than anything, and could break in certain situations. Restarting your mod like this is not the same as restarting the game, as it does not restart game state, so it will not be a clean slate and your mod could potentially behave differently than if you were to restart.
Override the Enable
instance method, for example:
1
2
3
4
5
class MyMod(ModMenu.SDKMod):
...
def Enable(self) -> None:
super().Enable()
unrealsdk.Log("I ARISE!")
super().Enable()
calls the base class Enable
method, which registers any hooks or network methods, so you should call that first.
Log
will log a message to the console. Now when you launch the game and enable your mod you should see “I ARISE!” in the console output. Replace that with whatever you want to do upon enable.
Cleanup functionality can go in the Disable
instance method:
1
2
3
def Disable(self) -> None:
unrealsdk.Log("I sleep.")
super().Disable()
super().Disable()
calls the base class Disable
method, which removes any hooks or network methods.
Now when you disable your mod you should see “I sleep.” in the console output. Replace that with whatever you want to do upon disable.
A good practice is to restore anything you’ve changed back to what it was in the Disable
method.
For other functions you can override, check the SDKMod
base class, defined in ModMenu/ModObjects.py
in the Mods folder.
SaveEnabledState
allows you to indicate whether your mod should be automatically enabled on subsequent runs if the user enabled it.
Values it can be set to:
NotSaved
: The enabled state is not saved.LoadWithSettings
: The enabled state is saved, and the mod is enabled when the mod settings are loaded.LoadOnMainMenu
: The enabled state is saved, and the mod is enabled upon reaching the main menu - after hotfixes are all setup and all the normal packages are loaded.For example, to set it to LoadWithSettings
add the following class variable to your mod class:
1
SaveEnabledState: ModMenu.EnabledSaveType = ModMenu.EnabledSaveType.LoadWithSettings
This will save your mod’s enabled state in settings.json, and the mod will be enabled when the mod settings are loaded.
Instantiate your options and add them to the Options
instance variable of your mod class.
See ModMenu/Options.py
for available option classes you can use.
Here is an example for adding a Boolean
option:
1
2
3
4
5
6
7
8
9
10
11
12
class MyMod(ModMenu.SDKMod):
def __init__(self) -> None:
self.MyBoolean = ModMenu.Options.Boolean(
Caption="Set My Boolean",
Description="Whether My Boolean should be on.",
StartingValue=True,
Choices=["No", "Yes"] # False, True
)
self.Options = [
self.MyBoolean,
]
This Boolean
should now appear in the options menu, under the name of your mod (provided the mod is enabled). You can get the value from it by accessing self.MyBoolean.CurrentValue
, which, since it’s a boolean, will be True or False.
To handle changes to this value in real time, you can override the method ModOptionChanged
. For example:
1
2
3
4
5
6
def ModOptionChanged(self, option: ModMenu.Options.Base, new_value) -> None:
if option == self.MyBoolean:
if new_value:
unrealsdk.Log("You turned on My Boolean")
else:
unrealsdk.Log("You turned off My Boolean")
Note that this function is called before the change to CurrentValue occurred, so we check new_value
and not self.MyBoolean.CurrentValue
.
Also note that for backwards-compatibility reasons, upon enable of your mod this function will be called for every option that is not in a Nested
. Do not rely on this functionality, as it may be removed in future. Logic addressing settings values on startup should be placed in the Enable
method instead.
You can change the values of options programmatically, but make sure to call ModMenu.SaveModSettings(mod: ModObjects.SDKMod)
afterwards (passing an instance of your mod, or self
if called from within your mod class) or the new values will not be updated in the mod’s settings.json
.
See ModMenu/KeybindManager.py
.
Instantiate your keybinds and add them to the Keybinds
instance variable of your mod class.
Here is an example:
1
2
3
4
5
6
7
8
9
...
def sayHi() -> None:
unrealsdk.Log("hi")
class MyMod(ModMenu.SDKMod):
...
Keybinds = [
ModMenu.Keybind("Say hi", "F3", OnPress=sayHi),
]
Now “hi” will be outputted to the console whenever F3 is pressed while ingame.
Any bindings can be customised by the user in Options > Keyboard / Mouse > Modded Key Bindings.
If the function you give OnPress
takes an argument, it will be passed a ModMenu.InputEvent
enum with the input event type, which can be Pressed
(0), Released
(1), Repeat
(2), DoubleClick
(3), or Axis
(4). This allows you to perform different actions depending on the input type, which you can use, for instance, to do something while a button is held by watching for Pressed
and Released
.
Alternatively, there is also the ModMenu.SDKMod
method GameInputPressed(self, bind: ModMenu.Keybind, event: ModMenu.InputEvent)
which you can override. It will be called when any key event is performed on one of the mod’s Keybinds
. Instead of passing an OnPress
method when you define the Keybind
, you can perform the associated action in this method instead.
1
2
3
4
5
6
7
8
9
10
11
12
13
...
class MyMod(ModMenu.SDKMod):
...
HiBind = ModMenu.Keybind("Say hi", "F3")
Keybinds = [
HiBind,
]
def GameInputPressed(self, bind: ModMenu.Keybind, event: ModMenu.InputEvent) -> None:
if bind == self.HiBind and event == ModMenu.InputEvent.Pressed:
unrealsdk.Log("hi")
You can have bindings the mod performs when a key is pressed in the mods menu, just like the default Enable
by pressing Enter
, by modifying SettingsInput
instance variable of your mod class. This is a dictionary mapping key
: action
, where both are str
. Handle the different actions by overriding SettingsInputPressed
. See the SDKMod
base class in ModMenu/ModObjects.py
.
Here is an example:
1
2
3
4
5
6
7
8
9
10
11
12
13
def sayHi() -> None:
unrealsdk.Log("hi")
class MyMod(ModMenu.SDKMod):
...
SettingsInputs = ModMenu.SDKMod.SettingsInputs.copy()
SettingsInputs["H"] = "Say hi"
def SettingsInputPressed(self, action: str) -> None:
if action == "Say hi":
sayHi()
else:
super().SettingsInputPressed(action)
Now “hi” will be outputted to the console whenever H is pressed while the mod is selected in the mod manager.
You can use the @Hook
decorator. PythonSDK will handle the registering and removing of hooks itself, so long as you remember to call the base class Enable/Disable if you override those methods.
The function you hook must have the signature:
([self,] caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct)
Example:
1
2
3
4
5
6
class MyMod(ModMenu.SDKMod):
...
@ModMenu.Hook("WillowGame.WillowPlayerController.SpawningProcessComplete")
def onSpawn(self, caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool:
unrealsdk.Log("onSpawn called")
return True
If you do not return True the function you hooked into will not continue executing as normal, which is sometimes desired, but if you do not want that remember to return True. If I forget return True in this case, I spawn in a weird place, don’t have any money, eridium, or anything in my inventory, among other things, because we diverted the logic ordinarily handling all that.
Alternatively, it is also possible to register hooks manually with unrealsdk.RunHook(funcName: str, hookName: str, funcHook: object)
, where
funcName
: a string of the game function to hook,hookName
: a name it can be referenced by when you remove it,funcHook
: the function you want to have called,or unrealsdk.RegisterHook(..)
(same arguments),
and remove them with unrealsdk.RemoveHook(funcName: str, hookName: str)
.
It is preferrable to use RunHook
over RegisterHook
, since the former ensures the hook is not registered twice.
You can discuss what you’re trying to do on the Discord, and people will probably chime in to help you out!
Upload your mod(s) to a public repository, then to add it to this site follow the steps on Adding to the Database in the main page.
Note that you should not commit settings.json
file nor __pycache__
, as they are automatically generated.