Theoretical save function?

A

Adry

Guest
Theoretical save function:

Hi all! I’d like to preface this by saying that this is more of a “saving” discussion rather than an actual problem to solve. I’ve been working on saving and have a sort of hate / love relationship (mostly love) with it, because it can be very frustrating when working on it and accidently corrupting everything or overwriting things, but I also love it. I love it, because, I love problem solving and it has to be one of the greatest feelings for a game dev when you get the hang of it and start actually saving all your progress for the player. I have to say to me saving can be taken for granted because in all games you can save, and that’s something that just is a part of the gaming experience, but there is a lot involved to get it right.

Sorry to babble, anyways my question for this discussion is that I’m trying to save NPC dialogue, so it doesn’t reset on restart. My initial thought was could I create [parent] that checks if any of the dialogue has progressed via dialogue children. So when the player is saving the [parent] object checks which NPC has changed and saves that to a collective say global variable or some kind of function? This sounds a bit airy fairy so, no worries if this doesn’t really make sense.. Thanks for any and all replies!

This is a snippet on how I'm saving my variables, with JSON.
GML:
ds_map_add(_map, "NPC_dialogue", global.save_dialogue);
 

NightFrost

Member
You seem to imply that you already have a system to track dialogue progress, but you want the state to save and load. The system I've created, to put it in a super simple way, saves those state flags when player saves the game, and restores them on load. The controller object for this is persistent so it can receive flags from save file anytime. Then you just throw player into the correct room to start the game, and dialogue states are as they should.

Actually, since my dialogue system is attached to quest system, dialogue "flags" are actually invisible quests. They get started and completed under the hood in case certain pieces of dialogue need to be flagged as completed. I didn't want to build a parallel dialogue flag system; since it was possible to already use quest progression states to control dialogue flow, it made sense to leave it as the only system. So for example if an NPC needs to greet and introduce themselves only once, the dialogue starts and completes an invisible "met this NPC" quest, which shuts out the greeting dialogue branch next time player talks to them. For simpler systems, just a bunch of enumerated flags on a global array would suffice.
 
A

Adry

Guest
You seem to imply that you already have a system to track dialogue progress, but you want the state to save and load. The system I've created, to put it in a super simple way, saves those state flags when player saves the game, and restores them on load. The controller object for this is persistent so it can receive flags from save file anytime. Then you just throw player into the correct room to start the game, and dialogue states are as they should.

Actually, since my dialogue system is attached to quest system, dialogue "flags" are actually invisible quests. They get started and completed under the hood in case certain pieces of dialogue need to be flagged as completed. I didn't want to build a parallel dialogue flag system; since it was possible to already use quest progression states to control dialogue flow, it made sense to leave it as the only system. So for example if an NPC needs to greet and introduce themselves only once, the dialogue starts and completes an invisible "met this NPC" quest, which shuts out the greeting dialogue branch next time player talks to them. For simpler systems, just a bunch of enumerated flags on a global array would suffice.
NightFrost, thank you so much for you insight! that's a brilliant way to do it, I don't know why I totally spaced on the versatility and utility of having a persistent controller object. Also, how do you set up a flag in your game? I think I get a more or less picture correct me if I'm wrong.. NPC dialogue progresses then is flagged > persistent controller obj receives this info > then obj is saved? > then load etc?

Thanks for sharing this process, I will be re-reading it over to process and see if I can implement a similar strategy, thanks again!
 

NightFrost

Member
I set flags as part of dialogue processing. The way I store dialogue is arrays in arrays. Dialogue contains either text or commands (numbers, as an enumerator). Commands can tell what other things the controller may need to do. The commands can flag quests as in process or as completed. To simplify the dialogue controller's process, when it encounters a text line it just displays it and waits for button press to proceed to next dialogue line, but if it sees a command (it detects a number instead of a string) it jumps to do whatever is requested and then proceeds to next dialogue line. These commands can set quest as in progress or as completed.

For your purposes it might be enough to set up dialogue data like:
GML:
This_NPC_Dialogue[0] = "Hello there.";
This_NPC_Dialogue[1] = "Nice weather we're having, yes?";
This_NPC_Dialogue[2] = Flags.Greeted_This_NPC; // An enumerator value from an enum that contains all your flags.
However to display some dialogue only once you must also set up conditions. For this purpose, I divide NPC dialogue into fragments, each into their own array. Each dialogue fragment is some amount of text and commands that are intended to be displayed and handled without interruption. Each fragment also has conditions that must check true, or the controller will not consider it for display. So the above array would have been placed into another array where the second slot contains conditions. It would have some way of conveying that dialogue flag Flags.Greeted_This_NPC must be false, otherwise the dialogue is not valid.

Finally, as dialogue now has been broken into separate fragments, there must be a way to form coherent dialogue out of them. I mark certain dialogue fragments as starters. When player initiates dialogue with NPC, only starter fragments are noted and their conditions are checked. If a valid starter is found, the controller starts displaying it. To fit these fragments together, the final command in any fragment is either dialogue end command or a jump command that tells which fragment should be processed next.

A short dialogue would then be defined something like this (assuming enums have already been defined):
GML:
Farmer_Joe_Dialogue [
    // Fragment zero.
    [
         // First item in fragment array is fragment type definition.
        Fragment_Type.Starter,
        // Second item in fragment array is an array containing a condition.
        [
            Condition.Must_Be_False, // First item in condition array is condition type.
            Flags.Greeted_Farmer_Joe // Second item in condition array is condition target.
        ],
        // Third item in fragment array is dialogue.
        [
            "Hello, I'm farmer Joe.",
            [
                Command.Set_Flag, // Command array that tells to set a flag to true.
                Flags.Greeted_Farmer_Joe
            ],
            [
                Command.Next_Fragment, // Command array that tells fragment two should be processed next.
                2
            ]
        ]
    ],
    // Fragment one.
    [
        Fragment_Type.Starter,
        [
            Condition.Must_Be_True,
            Flags.Greeted_Farmer_Joe
        ],
        [
            "Hello again, adventurer."
            [
                Command.Next_Fragment,
                2
            ]
        ]
    ],
    // Fragment two.
    [
        Fragment_Type.Follow,
        [
            Condition.None
        ],
        [
            "Nice weather we're having today.",
            [
                Command.Dialogue_End // Tells dialogue controller to go idle and return control to player.
            ]
        ]
    ]
]
 
Last edited:
A

Adry

Guest
I set flags as part of dialogue processing. The way I store dialogue is arrays in arrays. Dialogue contains either text or commands (numbers, as an enumerator). Commands can tell what other things the controller may need to do. The commands can flag quests as in process or as completed. To simplify the dialogue controller's process, when it encounters a text line it just displays it and waits for button press to proceed to next dialogue line, but if it sees a command (it detects a number instead of a string) it jumps to do whatever is requested and then proceeds to next dialogue line. These commands can set quest as in progress or as completed.

For your purposes it might be enough to set up dialogue data like:
GML:
This_NPC_Dialogue[0] = "Hello there.";
This_NPC_Dialogue[1] = "Nice weather we're having, yes?";
This_NPC_Dialogue[2] = Flags.Greeted_This_NPC; // An enumerator value from an enum that contains all your flags.
However to display some dialogue only once you must also set up conditions. For this purpose, I divide NPC dialogue into fragments, each into their own array. Each dialogue fragment is some amount of text and commands that are intended to be displayed and handled without interruption. Each fragment also has conditions that must check true, or the controller will not consider it for display. So the above array would have been placed into another array where the second slot contains conditions. It would have some way of conveying that dialogue flag Flags.Greeted_This_NPC must be false, otherwise the dialogue is not valid.

Finally, as dialogue now has been broken into separate fragments, there must be a way to form coherent dialogue out of them. I mark certain dialogue fragments as starters. When player initiates dialogue with NPC, only starter fragments are noted and their conditions are checked. If a valid starter is found, the controller starts displaying it. To fit these fragments together, the final command in any fragment is either dialogue end command or a jump command that tells which fragment should be processed next.

A short dialogue would then be defined something like this (assuming enums have already been defined):
GML:
Farmer_Joe_Dialogue [
    // Fragment zero.
    [
         // First item in fragment array is fragment type definition.
        Fragment_Type.Starter,
        // Second item in fragment array is an array containing a condition.
        [
            Condition.Must_Be_False, // First item in condition array is condition type.
            Flags.Greeted_Farmer_Joe // Second item in condition array is condition target.
        ],
        // Third item in fragment array is dialogue.
        [
            "Hello, I'm farmer Joe.",
            [
                Command.Set_Flag, // Command array that tells to set a flag to true.
                Flags.Greeted_Farmer_Joe
            ],
            [
                Command.Next_Fragment, // Command array that tells fragment two should be processed next.
                2
            ]
        ]
    ],
    // Fragment one.
    [
        Fragment_Type.Starter,
        [
            Condition.Must_Be_True,
            Flags.Greeted_Farmer_Joe
        ],
        [
            "Hello again, adventurer."
            [
                Command.Next_Fragment,
                2
            ]
        ]
    ],
    // Fragment two.
    [
        Fragment_Type.Follow,
        [
            Condition.None
        ],
        [
            "Nice weather we're having today.",
            [
                Command.Dialogue_End // Tells dialogue controller to go idle and return control to player.
            ]
        ]
    ]
]
So, I've been reading your design, it's a little over my head, I haven't really used the enum function so it's a bit beyond me, but I'm formulating something based around the idea you mentioned of
arrays in arrays
however, I can't seem to get it to work? Also you don't have to feel obligated to respond, but I'm going to share what I had in mind.. So first I save a global variable I'm calling global.dialogue_flags

within a persistent object with this code:

GML:
global.dialogue_flags = ds_list_create();

ds_list_add(global.dialogue_flags,

obj_edison_phone.dialogue_flag

);
when my NPC goes to another array, (E.g: mytext = mytext_2) I set a variable within the obj from 0 to 1, obj_edison_phone.dialogue_flag = 1;

this is how I'm trying to save it:

Code:
ds_map_add(_map, "Dialogue_flags", global.dialogue_flags);
Then I try to load it:

Code:
if (file_exists("save_file.sav"))
{
    var _wrapper = Load_JSON_File("save_file.sav");
    var _list = _wrapper[? "ROOT"];
   
    for (var i = 0; i < ds_list_size(_list); i++)
    {
        var _map = _list[| i];
       
        var _obj = _map[? "obj"];
       
        global.dialogue_flags = _map [? "Dialogue_flags"];
        ds_list_find_value(global.dialogue_flags, 0)
   
    }
   
    ds_map_destroy(_wrapper);
    show_debug_message("Game loaded");
}
but I can't get the darned thing to work.. Any ideas where I might be going wrong?
 

NightFrost

Member
Yeah the example code was to display a more complex approach into controlling what dialogue gets displayed. As for enumerators, they can be thought as of means to give numbers names and context. Since you seem only to needs some flags, I might suggest just using a global array for storing them, in which enums will also be useful.

Let's say you have certain flags you want to track, like "greeted the king," "lied to the sheriff about the herring," and "selected the blue potion." You can track their true/false state in an array and read from there whenever required. But array indices are numbers, so instead of having to always recall the correct number you write an enumerator instead. For these three flags the enumerator would be:
GML:
enum flag {
    greeted_king,
    lied_to_sheriff,
    took_blue_potion,
    // And more flag names here.
    length
}
Each enumerator name is automatically assigned an integer value from zero upwards. You don't need to remember them as you'll always refer to them by their name. Setting and reading the flag array with enum would go like:
Code:
// Initial setup of global flag array. Enumerator has been given "length" item as last item to carry length information to array create command (there are no other ways to figure enumerator sizes in GML).
global.dialogue_flags = array_create(flag.length, 0);

// Setting flag to true when player talks to the king
global.dialogue_flags[flag.greeted_the_king] = true;

// Checking if player has greeted the king
if(global.dialogue_flags[flag.greeted_the_king] == true){
    ....
}
A single array should be pretty straightforward to file write and read. I haven't used the JSON stuff though, I've always just used INI files, so I can't say if there's something going wrong there.
 
Top