Difference between revisions of "Modding"
m |
m |
||
Line 1: | Line 1: | ||
{{message box warning|This API is depreciated: the features are incomplete and will not be updated for future versions. '''USAGE IS HIGHLY DISCOURAGED.'''}} | {{message box warning|This API is depreciated: the features are incomplete and will not be updated for future versions. '''USAGE IS HIGHLY DISCOURAGED.'''}} | ||
− | The modding of the game is based on the Summertime Saga API. It is recommended to read the [[Summertime Saga API|corresponding article]] beforehand in order to familiarize yourself with the specificities of the code. | + | The {{strong|modding of the game}} is based on the Summertime Saga API. It is recommended to read the [[Summertime Saga API|corresponding article]] beforehand in order to familiarize yourself with the specificities of the code. |
{{TOC limit|3}} | {{TOC limit|3}} | ||
Latest revision as of 18:04, 11 July 2020
This API is depreciated: the features are incomplete and will not be updated for future versions. USAGE IS HIGHLY DISCOURAGED. |
The modding of the game is based on the Summertime Saga API. It is recommended to read the corresponding article beforehand in order to familiarize yourself with the specificities of the code.
Hooking into the game
Registration and enabling of the mod
In an init -9 python or later, use the class method "register" of the ModManager class to register your mod to the game. This registration makes the mod show up in the (upcoming) Mods menu on the main menu so the players have the choice to enable or disable the mod for the game.
Example:
init python: ModManager.register("ikarumod")
A manifest file named modname_manifest.json
must be added in the scripts/data
folder.
Manifest file
The manifest file details in which labels the mod should hook into the game, and which screens. It also defines the name of the main function you wish to use to hook into the game.main()
function, if any.
Preferred mod load order
Adding a key named load_order
to the manifest will allow your mod to be loaded after a specific amount of mods, or before.
The value of this key must be a string leading with either >
, <
or =
followed by an integer. Additionally, the values inf
and -inf
can be used to set the load order as last, and absolutely first. If several mods are defined with -inf
in load_order
, then a random order is chosen. The default value is inf
, which means the mods are added last to the list in no particular order.
Main function name
This key should be a string containing the name of the main function of your mod. The function is searched for ini the globals()
of the game (global namespace)
Init label name
This key should give the name of a label your mod uses at init time, which means after the game is fully initialized, either at the start of the game or after the load of the game.
This label is called in a new context, and it must return, otherwise other mods won’t be loaded
Adding to the game’s json files
Keys in the manifest named items
, achievements
and text_messages
can be used to add data to the game json files. Each of these keys expects a full json dictionary formatted in the same way as their respective models.
Example of manifest:
{ "name":"ikarumod", "version": "0.1.0", "load_order": ">3", "main_function_name": "ikarumod_main", "init_label_name":"ikarumod_init_label", "text_filter":"ikarumod_text_filter", "items": { "item1":{ ... }, "item2":{ ... } }, "text_messages":{ "text_message1":{ ... } }, "screen_hooks":{ "upstairs_bedroom":"upstairs_bedroom_ikarumod", "bedroom":"bedroom_ikarumod" }, "label_hooks":{ "bedroom":"bedroom_ikarumod" }, "achievements":{ "angler-2"{ ... } } }
Mod init order
Mods are assumed to be in an initial random order. |
For every mod that is enabled, create a Mod instance with that mod’s name, parse the load order from the manifest, then insert that mod in the ModManager.mods
list in the proper position. Then, for every mod in the ModManager.mods
list, call their init label, if defined. Finally, update the game stores (i.e. achievements, items and text_messages) with every data of the mod. Updates overwrite the keys, so the load order can be used to overwrite the game’s (although ill‐advised) or another mod’s items/text_messages/achievements.
Text filter
The text filter key in the manifest allows you to create your own filter function without overwriting other mods.
If undefined, this defaults to lambda text:text
, otherwise the value of this key should be a string of the name of the function you wish to pass in as a text filter. If the function cannot be found in the global namespace (i.e. the globals()
dictionary), then a ModLoaderError("No Function named {text_filter} found in the global namespace. Is it defined properly?")
exception will be raised.
Screens
Create your screens with the following convention: modname
+ _
+ in‐game screen name
(the Ren’Py definition name). Screens are being included into the main game screens with the use statement. If you wish to add new locations, you have to define a screen for it, in which case, you can inspire yourself with the existing screens in the game.
To ensure proper game progression, you shoul only add imagebuttons. Please refer to Summertime Saga API § User-defined screen actions for more information on which screen actions the game defines.
The mod screen hook is quite simple as it uses RenPy’s use
statement for every modded screen. The screen hook is called at the end of each screen, so background set up and imagebuttons for the game are already properly shown and defined. You can then add your own imagebuttons, and other screen features to it.
- Hookable screens are, at least for now, only the location screens, and do not include the user interface.
Example:
init python: ModManager.register_main(ikarumod_main)
In this example, ikarumod_main
is assumed to be a callable, which should be a python function. The function is called with no arguments at the end of game.main()
method.
Hooking into the main function is usually useful for code you want executed every time the game returns to a main screen, i.e. at the end of location labels for instance. This is where you can repeatedly check if the condition for an achievement has been fulfilled. If the provided function is not a callable, a ModLoaderError exception will be raised.
Imported modules and directory structure
Third‐party modules
Platform agnostic
- os
- pygame
- sys
- from time : time, clock
- from copy : copy, deepcopy
- datetime
- re
- random
- math
- from collections : defaultdict, OrderedDict and Counter
- weakref
- codecs
- hashlib
- json
- itertools
- operator
- textwrap
- deuces
Desktop builds
- certifi
- requests
Mobile builds
- android
- pyjnius
Directory structure
game
audio
: SFX and musicsfonts
: fonts for the game.images
achievements
: achievement‐related images.cookie_jar
: cookie jar buttons, popups and thumbnails.backgrounds
: backgrounds and close‐ups.boxes
: popups and general purpose buttons (like the go back button).buttons
: most minigame assets, and menu buttons.cellphone
: cellphone images assets.characters
: character poses. One folder per character.map
: map locations and map background.objects
: character buttons, item buttons, doors, etc.vfx
: special visual effects (like the rain in Roxxy’s trailing scene).
python-packages
: third party python modules.scripts
characters
: one folder for each character, containing an fsm file, a character.rpy file for miscellaneous stuff, and a layeredimage definition file. May contain a file for the character’s button and the according dialogues.core
: core files are put there, mostly what has been documented in the Modding API section of this manifesto.data
: JSON files that contain data about the game. Items, achievements, keymap or text messages are defined here.defines
: general image definitions, transforms, etc.locations
: one folder for every location, sorted in a tree‐like structure. Each location has a main file, a screen file and a dialogues file.minigames
: one folder for every minigame, the minigame dialogues and screen files are in that folder.script.rpy
pregnancy_announcements.rpy
changelog.txt
pledge_list.txt
Translating the game
Label calling in the game
The game offers a function in the Game class to select different dialogues based on the language class attribute defined there. To change that attribute, it’s just a matter of writing this piece of code in a separate ".rpy" file.
Example:
init 1 python: Game.language = "es" # for Spanish for instance, "fr" for French, etc
Every time a dialogue is called, the game looks for the label name and the language string at the end. For instance, the label bank_liu_account_info
has the English version in; and to overwrite that dialogue, you’d have bank_liu_account_info_fr
or bank_liu_account_info_es
depending on the value of the language string.
Any dialogue can be translated with this method. Keeping a consistent directory structure is recommended, because of the large number of dialogues in the game. You also have to copy over the posing. For your convenience, every dialogue label is stored in a dialogues.rpy
file for that location. Just copy the file and edit the dialogue and the label name.
Cutscenes and minigame instructions
You can’t edit the cutscenes directly. Well, you could, but you shouldn’t! Using the config.say_menu_text_filter
variable is the best way. Just register a function to that variable with one argument, text
, which contains the text of the displayable. Then edit the text how you see fit.
Example of translation:
init 10 python: fr_translations = {"Using the key and stool, I was able to get into our attic.\nI had never been up there before.\nI was filled with excitement wondering what treasures {b}[deb_name]{/b} and dad had stashed away.": "En utilisant la clé et le tabouret, je pus aller dans le grenier.\nJe n'y avais jamais été auparavant.\nUn sentiment d'excitation m'envahissait alors que je me demandais quels trésors {b}[deb_name]{/b} et papa avaient caché là-haut."} def fr_text_filter(text): if text in fr_translations.keys(): return fr_translations[text] else: return text config.say_menu_text_filter = fr_text_filter
Keep in mind however that Ren’Py engine doesn’t handle large dictionaries. elif
statements can be used to split the content into several dictionaries if necessary.
Example:
def fr_text_filter(text): if text in fr_translations_1.keys(): return fr_translations_1[text] elif text in fr_translations_2.keys(): return fr_translations_2[text] else: return text
Location names
In an init 1 python
(or later) block, you can set the display name of any location object in the game. This allows renaming and translation of any game location, thus displaying that name in relevant parts of the user interface.
Example:
init -1 python: L_map.display_name = "New Map Name"