Skip to content

Conversation

@Jowan-Spooner
Copy link
Member

@Jowan-Spooner Jowan-Spooner commented Nov 23, 2025

Note: this is not about the Save subsystem API, which might get a cleanup as well in the future. This is about how dialogic stores and restores its state internally.

This restructures how dialogic stores its state. It completely removes the current_state_info dictionary in favor of proper properties on the subsystems. This makes it easier to know what will be in the state dictionary at any point and more reliable what type those infos have. It also puts the burden of keeping track of the state more directly on the subsystems.

Pros:

  • save logic is more modular, more predictable (most properties have a fixed type, less unpredictable dictionary stuff)
  • it's easier to get the state of just one module

The new save flow is like this:

  • Dialogic.get_full_state()
    • Stores timeline & index
    • Packs each subsystems @exported properties
    • Packs each subsystems _pack_extra_state() returns
      • Some subsystems use this to store dynamic info, e.g. custom portrait state, textbox visibility

The load flow is the opposite

  • Dialogic.load_full_state(state)
    • clears all previous state
    • Unpacks each subsystems @exported properties
    • Calls each subsystems _load_state() (Style subsystem first)
      • Some subsystems use get_extra_state() to restore dynamic info, e.g. custom portrait state, textbox visibility
    • loads timeline/index

As you might have noticed I implemented a few things previously not possible:

  • Custom Portrait & Custom Background state. They now have a _get_state and _load_state method. This is implemented on the layered portrait.
  • Textbox visibility. This was a bit of a monster. Textboxes can now have an identifier. If two textboxes should not share the textbox visibility, they have to have two different identifiers. The DialogText node now has start_hidden and auto_visibility properties. Auto visibility is basically the same as hide_when_empty, but works on the subsystem instead of directly on the node (which made the visibility state really hard to keep track of).

From my tests this fixes a bunch of issues with saving/loading and personally I find it easier to work with this new system, so it should be easier to fix issues in the future.

Compatibility breakage

This breaks compatibility in many ways:

  • save compatibility. Obviously saves created with previous dialogic versions won't work anymore after this.
  • custom code or custom subsystems that accessed custom_state_info break. This is more annoying, but hopefully not too many people out there 🙈
  • a bunch of methods have been renamed, especially in subsystems, I've tried making a full list here:
    • Subsystem.pause() and resume() are now _pause() and _resume()
    • Subsystem.clear_game_state() is now _clear_state()
    • Subsystem.load_game_state() is now _load_state()
    • Subsystem.post_install() is now _post_install()
    • Text.update_textbox() has been removed in favor of Text.textbox_update_visibility(visible, instant, identifier) and Text.textbox_handle_auto_visibility(text, identifier)
    • Dialogic.get_full_state() now returns a DialogicSaveState resource
    • Dialogic.load_full_state() now takes a DialogicSaveState resource

This makes the minimum godot version required 4.4

Other Stuff

  • removes the pre alpha 17 audio save conversion code
  • adds get_background_node() to the Background Subsystem
  • adds debug_draw to the PortraitContainer subsystem, which draws outlines around portrait containers and their names
  • reorganizes the text subsystem to group related methods closer together
  • adds Timeline.get_text_line_from_index() which tries to reverse map the index of an event to the line it's written on. This won't work properly on multiline events, but is still useful in error messages.

Introduces a new DialogicSaveState resource. This is the first step in many to make the saving logic more modular and completely remove Dialogic.current_state_info.
For easier debugging, the save system saves this as a .tres for now, which makes it impossible for encryption to work, this can be changed back later.

Subsystems now have the pack_state() and unpack_state() methods, which collect it's exported variables into a dictionary.
This changes the full_event_history to be stored as texts at runtime. I don't know how people use this rn, so idk if this is a problem. It makes the code cleaner for now, we'll see.
clear_game_state -> _clear_state
post_install -> _post_install
pause, resume -> _pause, _resume
Dialogic.print_debug_moment is now a little cleaner and includes the line in the textfile, where the current event is.
Restructures the text subsystem for better organization and readability by grouping related functions into regions.
Allows subsystems to store extra_state on save and use it on load. This is mainly intended for state outside the subsystem script, e.g. on nodes in the style or custom parts like portraits, backgrounds, etc.

The textbox visibility is now handled per "textbox-identifier", which can be specified on the dialog_text node. A new "active_textbox" can be changed via script.
New property Dialogic.PortraitContainers.debug_draw.
These shouldn't be saved and were shouting some errors on load, now they are properly decoupled.
Adds _get_state and _load_state to the DialogicPortrait and implements them on the layered portrait.
A little scared about this one, but I think it makes sense.
@Jowan-Spooner Jowan-Spooner added Enhance ⚡ Improve a feature's workflow. Compatibility Breaking ⚠️ This PR breaks compatibility in some way labels Nov 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Compatibility Breaking ⚠️ This PR breaks compatibility in some way Enhance ⚡ Improve a feature's workflow.

Projects

None yet

1 participant