-
Notifications
You must be signed in to change notification settings - Fork 685
Interfacing with the Submod Framework
With 0.11.0 came a submod framework. This allows you to register submods with dependencies and have update scripts for different versions to help keep your submods compatible with the current version of MAS.
Additionally a utility to plug functions into either labels or other functions was also implemented.
This page contains instructions on how to use both of these frameworks.
- The
Submod
object is essentially initialized as a header for your submod and must be initialized in aninit -990 python in mas_submod_utils
block. If at any other init level, dependency checks will not run for your submod.
An example initialization is as follows:
init -990 python in mas_submod_utils:
Submod(
author="Monika After Story",
name="Example Submod",
description="This is an example submod.",
version="0.0.1",
dependencies={},
settings_pane=None,
version_updates={}
)
The above code will initialize a Submod
object with the following information:
- Author:
Monika After Story
- Name:
Example Submod
- Description:
This is an example submod.
- Version:
0.0.1
- Dependencies: None
- Settings: No settings
- Version Updates: No update scripts
NOTE: The version number must be recorded using semantic versioning and be passed in as a string.
- Adding dependencies is a simple process.
Hypothetically, let's say our Example Submod
submod required code from another submod (named Required Submod
) to work properly.
To add this submod as a dependency for yours, we want to add a key and a value to the dependencies
dictionary
.
The format for this is as follows: "dependency submod name": ("minimum_version", "maximum_version")
So for our scenario here, we end up with the Submod
header:
init -990 python in mas_submod_utils:
Submod(
author="Monika After Story",
name="Example Submod",
description="This is an example submod.",
version="0.0.1",
dependencies={
"Required Submod": (None, None)
},
settings_pane=None,
version_updates={}
)
Which marks our Example Submod
as requiring a submod named Required Submod
with no specific version range to be initialized, otherwise we cannot load MAS with this code in.
- If there is no applicable minimum version and/or maximum version, they can be passed in as
None
. - Both the minimum version and maximum versions will be passed in like you passed in the version for your submod, semantic versioning as string.
- If a dependency fails, MAS will throw an exception and exit with a traceback, indicating that there is a submod which is failing the dependency.
With submods like these, it wouldn't be ideal to clutter the main menus with submod settings or ways to get to the settings for your submod. This is where the settings_pane
field comes into play.
To create a settings pane, simply create a screen containing the settings for your submod.
This can be done as you would for any other screen initialization in Ren'Py.
To bind this to your submod, pass in the name of the screen as a string to the settings_pane
field.
For example, let's say we made the following screen our settings screen:
#Don't actually name your screen like this. Use something unique
screen submod_screen():
vbox:
box_wrap False
xfill True
xmaximum 1000
hbox:
style_prefix mas_ui.cbx_style_prefix
box_wrap False
textbutton _("Switch setting #1") action NullAction()
textbutton _("Switch setting #2") action NullAction()
textbutton _("Switch setting #3") action NullAction()
Our Submod
header would now look like:
init -990 python in mas_submod_utils:
Submod(
author="Monika After Story",
name="Example Submod",
description="This is an example submod.",
version="0.0.1",
dependencies={},
settings_pane="submod_screen",
version_updates={}
)
You can use tooltips for buttons on your setting pane, and they will be shown on the main submod screen. The submod screen already has a tooltip defined, all we need to do is get the screen, get the tooltip and adjust its value.
This is how we do it in our example:
screen submod_screen():
python:
submods_screen = store.renpy.get_screen("submods", "screens")
if submods_screen:
_tooltip = submods_screen.scope.get("tooltip", None)
else:
_tooltip = None
vbox:
box_wrap False
xfill True
xmaximum 1000
hbox:
style_prefix mas_ui.cbx_style_prefix
box_wrap False
if _tooltip:
textbutton _("Switch setting #1"):
action NullAction()
hovered SetField(_tooltip, "value", "You will see this text while hovering over the button")
unhovered SetField(_tooltip, "value", _tooltip.default)
else:
textbutton _("Switch setting #1"):
action NullAction()
It's quite a big structure, but it allows you to safely change tooltips for buttons on your screen.
As you can see, you'll need to define 2 variants for each button:
- With the tooltip
- Without the tooltip (as a fallback in case we fail to get the screen for some reason). Notice that we change the tooltip via
SetField
.
On the hovered
action, we'll set the tooltip to our value, on unhovered
, we'll set it back to its default value using _tooltip.default
.
Creating update scripts is likely the most complex aspect of the submod framework.
Submod update script labels must be precisely named in order to avoid conflicts due to Submods which are named the same.
The label formatting is as follows (fields corresponding to the values in the Submod
init)
<author>_<name>_v<version>
- Spaces in the
author
andname
parts will be replaced with underscores - All characters in the label will be forced to lowercase
- The periods in the version number will be replaced with underscores
- The
version_updates
dictionary works in a"from_version": "to_version"
approach, and will follow a chain that is present in the updates dictionary. (Starting from the current version number, going to the top) - Submod update labels must accept a parameter called
version
which defaults to the version the update label is for - Update scripts run at init 10
So with our Example Submod
, the formatting will be
monika_after_story_example_submod_v0_0_1(version="0.0.1")
as our initial update label.
If we needed to make changes from 0.0.1 to 0.0.2 that need to be migrated, we would have the following two labels:
monika_after_story_example_submod_v0_0_1(version="v0_0_1"):
return
monika_after_story_example_submod_v0_0_2(version="v0_0_2"):
#Make your changes here
return
Coupled with the start of this chain in the version_updates
dictionary, we have a Submod
init of:
init -990 python in mas_submod_utils:
Submod(
author="Monika After Story",
name="Example Submod",
description="This is an example submod.",
version="0.0.1",
dependencies={},
settings_pane=None,
version_updates={
"monika_after_story_example_submod_v0_0_1": "monika_after_story_example_submod_v0_0_2"
}
)
Keeping in mind the "from_version": "to_version"
approach to these updates, to continue the chain if we were to move from 0.0.2 to 0.0.3 (or any other arbitrary version), we would simply add:
"monika_after_story_example_submod_v0_0_2": "monika_after_story_example_submod_v0_0_3"
after the entry we put above.
- Since there may be non-breaking changes or you may want to add a compatibility functionality for the scenario where another submod is installed, you can use the
mas_submod_utils.isSubmodInstalled()
function.
-
name
: The name of the submod you're checking for -
version
: A minimum version number (This defaults to None. And if None, is not regarded when checking if a submod is installed)
While overrides are useful, they are not always ideal when it comes to maintenance over updates, or especially when it comes to only adding one or two lines to a label or function. To address this, function plugins
was created
This framework allows you to register functions which can be plugged into any label you wish, and additionally other functions as well, if they are set up to run them.
Registering functions is rather flexible, the parameters are as follows:
-
key
- The label (or function name (as string) in which you want your function to be called in -
_function
- A pointer to the function you wish to plug-in -
args
- Arguments to supply to the function (Default:[]
) -
auto_error_handling
- Whether or not the function plugins framework should handle errors (will log them and not let MAS crash) or not. (Set this to False if your function performs arenpy.call
orrenpy.jump
) (Default:True
) -
priority
- The priority order in which functions should be run. These work like init levels, lower is earlier, higher is later. (For functions whichrenpy.call
orrenpy.jump
, usemas_submod_utils.JUMP_CALL_PRIORITY
as they should be done last) (Default:0
)
There are two ways to register functions. One way is using a decorator
on the function you wish to register.
Note that the decorator approach also removes the need to manually pass in a function pointer, as it takes it directly from the function which it's attached to.
The advantage of using this approach is that it takes up fewer lines to register a function and additionally, it does better in terms of self-documentation of your code.
It's done as follows:
init python:
@store.mas_submod_utils.functionplugin("ch30_minute")
def example_function():
"""
This is an example function to demonstrate registering a function plugin using the decorator approach.
"""
renpy.say(m, "I love you!")
Which initializes a function plugin in the ch30_minute
label that has Monika say "I love you!".
However, this works only if you have the function you wish to plug-in contained within your submod script files. If this is not the case, we use the second approach.
Assuming the same example_function
from above:
init python:
store.mas_submod_utils.registerFunction(
"ch30_minute",
example_function
)
Which is equivalent to the previous example.
Let's say you needed to use arguments for your function, the approach is the same, except now all we do is pass the arguments in order as a list
. Let's change example_function
to be this now:
init python:
def example_function(who, what):
"""
This is an example function to demonstrate registering a function but with arguments
"""
renpy.say(who, what)
To make this equivalent to the last example, we would need to pass m
and "I love you"
in as our arguments. Both approaches can be seen demonstrated below:
Decorator: @store.mas_submod_utils.functionplugin("ch30_minute", args=[m, "I love you"])
RegisterFunction():
init python:
store.mas_submod_utils.registerFunction(
"ch30_minute",
example_function,
args=[m, "I love you!"]
)
With args comes the following two functions:
- This function allows you to get the args of a function plugin. Simply pass in the label/function it's assigned to and the function pointer itself, and it will return its args as a list to you.
- This function allows you to set the args of a function plugin. Simply pass in the label/function it's assigned to, the function pointer itself, and a list of new args.
It is also possible to unregister a plugin. Simply use the following function:
- This function will allow you to unregister a function plugin. Simply pass in the label/function it's assigned to and the function pointer itself, and it will no longer be mapped to that label/function.
As mentioned above, it is possible to have function plugins also be mapped to a function.
This is an obscure case but is handled regardless, however not by everything. There is no function within MAS code by default which will run function plugins. If you wish to have them run in a custom function of your own, you may do so by adding a store.mas_submod_utils.getAndRunFunctions()
line.
It will automatically handle all functions plugged into your function.
- Function plugins can be registered at any time after init -980
- Function plugins by default (without
auto_error_handling
set toFalse
) will automatically handle any errors in your function and log it inmas_log.txt
to avoid crashing MAS - Function plugins are run from the global store
- Function plugins will run at the start of the label it is registered in
- Function plugins will run if the label it is bound to is either
call
ed,jump
ed to, or fallen through to
Thanks to the implementation of Function Plugins, there were two new global variables added:
store.mas_submod_utils.current_label
:
- This variable holds the current label we're in
store.mas_submod_utils.last_label
:
- This variable holds the last label we were in.