💾 [TOOL] Softly -- A Soft Save System for ChoiceScript

banner

Photo by Fernando Lavin on Unsplash


Softly :floppy_disk: 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.

:warning: 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.

:warning: Softly is not compatible with the recent *create_array command and I don’t have plans for now to support it.


try it button


Further instructions

Generating a Save System

  1. Upload only the startup.txt file. Softly will read it and extract all declarations of global variables.
  2. 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.
  3. 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.
  4. 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.
  5. Softly will generate two files: variables.txt and savesys.txt. Copy the contents of variables.txt into your startup.txt. Drop savesys.txt into your game’s scenes folder.
  6. 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. :hugs:

104 Likes

This is amazing!

2 Likes

This is really great! I’m definitely using this for the checkpoints in my game :smiley: Thank you! :heart_eyes: :+1: :+1:

3 Likes

That’s the spirit! :heart:

Thank you! :hugs:

4 Likes

I’m trying this out when I get the chance!

1 Like

Thanks for making this! I’ll definitely use it

1 Like

To developers: please advertise if you use this. I don’t buy games without a save function.

10 Likes

Kind of right there with you at this point. I’ve been buying fewer and fewer games because they refuse to implement basic save functions. Tired of having to completely restart games because choices are in no way shape or form what I was expecting them too be. Or just wanting to try a different options along the same route.

I would play choice games much more if they had save functions. Funny that I’ll spend more time playing in progress games then the finished product, simply due to the fact they almost always have save functions during development.

10 Likes

That’s why I buy games on steam as often as possible. Their file system makes it super easy to find everything and editing save files is a breeze. Saving and reloading becomes a simple matter of copying and pasting the contents of the GameState document. Then I can explore every aspect of a game while actually enjoying it.

4 Likes

It’s easy but tedious.

2 Likes

Not if you pin the Common folder and the User Data folder. You still have to find the directory for each game but you have a good place to start, and once you do it enough you’ll get a good idea of where everything is. Then it’s a breeze.

I have a question, can you load a file from a certain checkpoint.

Let’s say you die in chapter 2, or on the way to chapter 2. You’ve saved a file from chapter 1, can it be an option to choose if you restart the story?
Instead of restarting the game, I know we can also give a choice if the player wants to load from a certain checkpoint. I’m just wondering :face_with_raised_eyebrow:

startup.txt

*create name ""
*create money 0

Do you want to load your checkpoints?
*choice
	*selectable_if (savesys_save_1 != 0) #Load Slot 1
		*gosub_scene savesys load 1
		*goto chapter_2
	*selectable_if (savesys_save_2 != 0) #Load Slot 2
		*gosub_scene savesys load 2
		*goto chapter_2
	#Continue.
		*goto chapter_1

*label chapter_1
What's your name?
*input_text name

How much money do you have?
*input_number money 10 100

Do you want to save a checkpoint?
*choice
	#Slot 1
		*gosub_scene savesys save 1
		*goto chapter_2
	#Slot 2
		*gosub_scene savesys save 2
		*goto chapter_2
	#No, thanks.
		*goto chapter_2

*label chapter_2
You better give me 50 or you'll die
*if (money >= 50)
	You're alive
	*goto chapter_3
*else
	You're dead
	*ending

Is it possible to make it this way?

2 Likes

Hi, @snail!

A Soft Save System uses global variables to save the state of the game. They do not generate “save files” unfortunately. This is only a workaround choicescript’s limitation.

So, if you restart the game all variables will be reset, which is to say, any save will be lost.

What you can do is include a scene and label when saving, like so:

*gosub_scene savesys save [slot] (scene) (label)

This will be saved with the slot’s variables and you can call them like so:

*gosub_scene savesys load 1
*goto_scene savesys_slot_1_scene savesys_slot_1_label

Did it answer your question?

2 Likes

Thank you! It did answer my question.

So, it can call when the player is already dead and gave them a choice to load it from a checkpoint, instead of restarting the whole game.

Or maybe if the game has like a time manipulation, it can be a skill to just rewind the time.

1 Like

Sure! Those are interesting use cases, especially the time wind power thing! :grin:

1 Like

Read your post in the thread I made a few days ago, and just now getting around to adding this (busy week, lol).

I think I get it, but just making sure:
I have to put the “save” subroutine at the checkpoint, and replace [slot] with the slot number. Also I put the load stuff at the beginning of my game (beginning of the first scene)?

Thanks in advance.

1 Like

You use the save subroutine whenever you need to save. I suggest this at checkpoints when you know there’s no temporary variables active, such as the very beginning or very end of the scene.

I’m not sure I know what you mean by load stuff. The load subroutine is going to load a state previously saved to one of the slots.

Maybe I should clarify what I was asking, guess the wording on my part was a bit jumbled. I was wondering where to write the code that allows you to save and load.

It really depends on how you want to use it. There’s no hard rule here.

Let’s say the game reaches a checkpoint or the player chooses to save (if you allow for that), then you call the save subroutine:

*comment __chapter_1.txt__

*label checkpoint_10
*gosub_scene savesys save 1 "chapter_1" "checkpoint_10" 

Then, let’s say the player dies or decides to reload:

YOU DIED!
*page_break Try again from the last checkpoint.

*gosub_scene savesys load 1
*goto_scene {savesys_slot_1_scene} {savesys_slot_1_label}

I hope this helps. :grin:
If not, let me know how you plan on using it specifically. I might be able to spin better instructions.

1 Like

I think I get it, but just in case:

Its a soccer game where the player can save during the offseason to prevent them from needing to several hours at once to complete the game.

1 Like