As of November 2023, ChoiceScript officially supports native checkpoints. Softly won’t be supported any longer. Please, refer to the announcement post of this feature or if you prefer, access Softly here.
Softly reads the startup.txt
file of your game and generates a ready-to-go pluggable implementation of a save system. Control it internally at checkpoints or allow players to save at any time.
A soft save system only works for global variables. Another caveat is that the number of variables your game uses scales with the number of save slots available.
If you run this generator again with the same file, remember to remove the previously generated variables from startup.txt
.
There was a bug in ChoiceScript in the past that prevented *redirect_scene
from working properly. It has been fixed already, however, DashingDon and CSIDE might still be using an outdated version of the engine. Check this thread to understand better. The best way to circumvent this issue is to upload a compiled file to DashingDon, instead of the game text files and use an updated version of ChoiceScript to compile the game instead of compiling through CSIDE. You should still use CSIDE for development.
Softly is not compatible with the recent *create_array
command and I don’t have plans for now to support it.
Further instructions
Generating a Save System
- Upload only the
startup.txt
file. Softly will read it and extract all declarations of global variables. - Next you can choose a prefix that will be added to all generated variables. It can be a single letter or another word. By default, it’s
savesys
. It has to follow the rules of variable naming of ChoiceScript, that is it must start with a letter and can only contain letters, numbers, and underscore. The purpose of the prefix is to avoid name collision. - Set the number of slots to be generated, the minimum value is
1
. Keep in mind that the total number of variables in your game will increase proportionally to the number of slots. - You can also select out some variables that won’t change throughout the game, such as the character’s name and pronouns so that they don’t need backing up.
- Softly will generate two files:
variables.txt
andsavesys.txt
. Copy the contents ofvariables.txt
into yourstartup.txt
. Dropsavesys.txt
into your game’s scenes folder. - If you add more variables to your game, you’ll need to regenerate the save system. When doing it, remember to remove the previously generated variables from the
startup.txt
How to use
There are four public functions: save
, load
, clear
and clear_all
. The function init
should not be called externally as it is meant for internal usage.
The module only deals with global variables. Because of that, it is best used as checkpoints at the very end or very beginning of a chapter/scene, or when there are no temporary variables in use. When the player reaches a checkpoint, call the save function as a subroutine.
*gosub_scene savesys save [slot] (scene) (label)
Replace [slot]
with the number of the slot. This is necessary even if there’s only one slot. The parameters scene
and label
are optional. When passed to the function they’ll be saved to the generated variables {prefix}_slot_{slot}_scene
and {prefix}_slot_{slot}_label
, and can be used to manually redirect the player to a point in the game.
Likewise, in order to load a save, make a call to the load function, passing the slot as a parameter.
*gosub_scene savesys load [slot]
Example:
__ chapter_1.txt (start of file) __
*gosub_scene savesys save 1 "chapter_1" "start"
*label start
---------------
__ choicescript_stats.txt (choice to load) __
Do you want to play from the last checkpoint?
*fake_choice
# Yes.
*gosub_scene savesys load 1
*redirect_scene {savesys_slot_1_scene} {savesys_slot_1_label}
# No.
*finish
The management of which slot to use when saving or loading, as well as the actual redirecting of game flow, is left to the author/developer.
Each slot group has a variable to control availability. The name of the variable is the {prefix}_slot_{slot}
. For example, savesys_slot_1
.
When the slot is used, this variable is assigned a timestamp. When a slot is “cleared”, this variable is reset to 0, but none of the other variables gets changed.
*gosub_scene savesys clear [slot]
*gosub_scene savesys clear_all
To check if a slot is available, you can check if the control variable is equal to 0. For example:
*temp slot_1_available (savesys_slot_1 = 0)
These variables can be helpful if the game has more than one save slot and the author wants to expose the choice to the player:
--- Saving ---
*choice
*selectable_if (savesys_slot_1 = 0) # Slot 1
...
*selectable_if (savesys_slot_2 = 0) # Slot 2
...
*selectable_if (savesys_slot_3 = 0) # Slot 3
...
*selectable_if (savesys_slot_4 = 0) # Slot 4
...
*selectable_if (savesys_slot_5 = 0) # Slot 5
...
--- Loading ---
*choice
*selectable_if (savesys_slot_1 != 0) # Slot 1
...
*selectable_if (savesys_slot_2 != 0) # Slot 2
...
*selectable_if (savesys_slot_3 != 0) # Slot 3
...
*selectable_if (savesys_slot_4 != 0) # Slot 4
...
*selectable_if (savesys_slot_5 != 0) # Slot 5
...
Finally, there’s one more variable included with the generated bundle. It’s {prefix}_load_used
. For example, savesys_load_used
. This variable is not set when saving. It is set, when loading, with a timestamp. In order to check if the load function was not called, you can check if the variable is equal to 0. For example:
*temp load_never_used (savesys_load_used = 0)
This might be helpful if there’s an achievement for never using the save system in a playthrough.
Necessary Adaptations
The ChoiceScript Engine has some internal states that is outside of the purview of what can be accomplished with pure ChoiceScript code. For example, the Engine keeps track of which choice has already been selected previously in order to properly implement *disable_reuse
and *hide_reuse
.
Because a soft save system does not interfere with the Engine internals this can create unexpected behaviour. For example, dialogue options being reset after loading a save. This can be avoided with clever placement of your checkpoints, for example, at the very beginning or very end of a scene.
In any case, in order to circumvent this issue, you won’t be able to rely on some native functionalities. Luckily, they can be mostly replicated manually, albeit with some extra effort. You only need this if you want to allow players to save and load at any point in the game. You don’t need this if you use checkpoints at the beginning of a scene.
*disable_reuse
To replicate this feature, you’ll have to create a global variable for each choice option which you want to disable, then use *selectable_if
checking this variable. In the body of the option, set the variable to false
so the option becomes unavailable.
-- startup.txt --
*create option_1 true
*create option_2 true
*create option_3 true
*label disable_reuse_example
*choice
*selectable_if (option_1) # Option 1
*set option_1 false
*goto disable_reuse_example
*selectable_if (option_2) # Option 2
*set option_2 false
*goto disable_reuse_example
*selectable_if (option_3) # Option 3
*set option_3 false
*goto disable_reuse_example
*hide_reuse
To replicate *hide_reuse
, instructions are similar to *disable_reuse
above, but instead of *selectable_if
you should use a simple *if
command.
-- startup.txt --
*create option_1 true
*create option_2 true
*create option_3 true
*label hide_reuse_example
*choice
*if (option_1) # Option 1
*set option_1 false
*goto disable_reuse_example
*if (option_2) # Option 2
*set option_2 false
*goto disable_reuse_example
*if (option_3) # Option 3
*set option_3 false
*goto disable_reuse_example
*goto_random_scene
For this one, you’ll need to keep track of which scenes have already.
You can copy and adapt the code below.
-- startup.txt --
*create scene_1 true
*create scene_2 true
*create scene_3 true
*create scenes_remaining 3
*label choose_random_scene
*temp random_scene
*rand random_scene 1 3
*if (scenes_remaining = 0)
*bug All scenes have been visited.
*temp scene_name "scene_" & random_scene
*if ({ scene_name })
*set scenes_remaining -1
*goto_scene {scene_name}
*else
*goto choose_random_scene
Any code generated by this tool, derived from your work, belongs to you.
The generated code is safe to use with implicit control turned off.
Credit is not necessary but appreciated.