• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

[Solved] Why do scripts break my ds_list saving system?

L

Leon Hurley

Guest
[Currently using GM 1.4]

I have a simple saving system that uses 0s and 1s to record if the player interacts with objects (opens a door, picks something up etc). To avoid coding each save individually I pass arguments from a save object in each room to a script, defining the save file name, the ds_list name, the room the player's in, and the number of values needing to be saved:

current_save_file = "save_data" + string(global.current_save) + ".ini"
ds_list_name = "r1_save_data"
current_room = "r1"
list_size = 3

scr_basic_save(current_save_file, ds_list_name, current_room, current_room, list_size);
A script then takes all that and creates, save or loads the values as needed:

//Arguments
save_file = argument0
save_ds_list_name = argument1
save_key = argument2
save_rm = argument3
save_ds_list_size = argument4

//save the current room
ini_open(save_file)
ini_write_real("Main data", "room", save_rm);
ini_close();

//Create the list
save_ds_list_name = ds_list_create();

//Set all the save values to zero or load them
ini_open(save_file)

if !ini_key_exists(save_file, save_rm)
{
for (i=0;i<save_ds_list_size;i+=1)
{
ds_list_add(save_ds_list_name, 0);

save = ds_list_write(save_ds_list_name)

ini_open(save_file)
ini_write_string(string("savedata"), save_rm, save);
ini_close();
}
}
else if ini_key_exists(save_file, save_rm)
{
load = ini_read_string(string("savedata"), save_rm, "")

ds_list_read(save_ds_list_name, load);
}

ini_close();

return save_ds_list_name;
This works perfectly when moving between a room and the pause menu (another room without a save object currently) but seems to reset when I move between two gameplay rooms where there are different save objects.

I worked perfectly when all the code was in its respective objects, but using scripts is obviously doing something I don't understand.

What am I getting wrong?

Thanks
 
Last edited by a moderator:
L

Leon Hurley

Guest
[Currently using GM 1.4]

I have a simple saving system that uses 0s and 1s to record if the player interacts with objects (opens a door, picks something up etc). To avoid coding each save individually I pass arguments from a save object in each room to a script, defining the save file name, the ds_list name, the room the player's in, and the number of values needing to be saved:



A script then takes all that and creates, save or loads the values as needed:



This works perfectly when moving between a room and the pause menu (another room without a save object currently) but seems to reset when I move between two gameplay rooms where there are different save objects.

I worked perfectly when all the code was in its respective objects, but using scripts is obviously doing something I don't understand.

What am I getting wrong?

Thanks
I realise I have a save_key and save_rm argument that are the same value and then I don't actually use one. I just structured it that way when I was trying to keep thing's clear in the script, as the room is both the location of the player and the key in the ini file everything's saved to.
 
L

Leon Hurley

Guest
Still no idea with this and I can't find anything to help.
 

Neptune

Member
Could be a larger underlying reason, but at first glance:
- Make sure your list is stored in a global. variable, so it isnt lost between rooms.
- You don't have semi-colons on a lot of those lines (bad habit).
- When combining strings together (even if the data type is already string, use the string() function).


if global.current_save is a reference to your ds_list, make sure you write it to a string.
var my_list_str = ds_list_write(global.current_save); //converts your list into a string of characters.

On a different note, I highly recommend a ds_map over a ds_list for saving data.
https://forum.yoyogames.com/index.php?threads/turn-based-rpg-overworld-to-battle-help.27191/

If you scroll down toward the bottom of this post -- I've posted a detailed way to use data structures for saving some data between rooms.


This video will give you even more detail, for an advanced save system:
 
Last edited:
ds_list_name = "r1_save_data"
current_room = "r1"
Are you converting these strings to variable names somehow? Otherwise I can't see how this would work. A room index is not a string, and a ds_list should be stored as a variable name, using the string as a reference directly won't work.
 
L

Leon Hurley

Guest
Are you converting these strings to variable names somehow? Otherwise I can't see how this would work. A room index is not a string, and a ds_list should be stored as a variable name, using the string as a reference directly won't work.
They should just be names the script uses for variables, not actual variables themselves? The [ds_list_name = "r1_save_data"] bit is the name used create/write to the ds_list. The [current_room = "r1"] is just used to name a key in the ini file it's all saved to.

The idea was that I could reuse the code for every room that needs to save and only change the arguments being passed to the script.

It works perfectly when I move from a room that runs the code to one that doesn't and back again. It's moving between two rooms that run the code that doesn't work.
 
L

Leon Hurley

Guest
Could be a larger underlying reason, but at first glance:
- Make sure your list is stored in a global. variable, so it isnt lost between rooms.
- You don't have semi-colons on a lot of those lines (bad habit).
- When combining strings together (even if the data type is already string, use the string() function).


if global.current_save is a reference to your ds_list, make sure you write it to a string.
var my_list_str = ds_list_write(global.current_save); //converts your list into a string of characters.

On a different note, I highly recommend a ds_map over a ds_list for saving data.
https://forum.yoyogames.com/index.php?threads/turn-based-rpg-overworld-to-battle-help.27191/

If you scroll down toward the bottom of this post -- I've posted a detailed way to use data structures for saving some data between rooms.


This video will give you even more detail, for an advanced save system:
Thanks. I'll work through all that. I thought my system would be okay because I only have a few things in each room that need to be saved: 3-4 on average - open/closed doors, picked up objects etc. The plan was the save object for a room would check an ini for the room name as a key and if it wasn't there it would create a ds_list and initialise it with zeroes and then write ones in whenever you opened a door, picked up a thing etc. If the ini key was there then it would load that up instead. Every time the ds_list changed it would write it to the ini and, on leaving the room, destroy it all together - with so little to save it didn't seem practical to have data structures that lived the entire time the game was running.

With a ds_map are you suggesting creating one single map that stores the entire game at all times?

What's frustrating it that this all works perfectly as long as I move to a room that isn't running the code and back again. It only breaks when I go to another room running it and back.

And, yes, terrible with the semi colons...
 
L

Leon Hurley

Guest
I've realised the problem - the script is not returning the ds_list once it's done. There's a "return save_ds_list_name;" call at the end but I'm guessing that's not doing the right thing. There's only a call to destroy the list when moving to the next playable room, but not the pause room, which is why you can pause and come back without any problems - the list is still there.

All the code in the script worked when it was individual objects but I'm trying to create something I can apply to all rooms without having to code each one separately. The script is working in as far as creating the ds_list and saving it and, I'm assuming, loading. It's just not passing the list back to the game once it's run.
 
Was away from home all weekend, just got a chance to reply now. I'm a bit tired from the holiday, so please forgive if I've misunderstood anything, I'll just write down where I see some problem points.

Code:
current_save_file = "save_data" + string(global.current_save) + ".ini"
ds_list_name = "r1_save_data"
current_room = "r1"
list_size = 3

scr_basic_save(current_save_file, ds_list_name, current_room, current_room, list_size);
What does global.current_save represent and when do you change it/ set it?

Code:
//Arguments
save_file = argument0
save_ds_list_name = argument1
save_key = argument2
save_rm = argument3
save_ds_list_size = argument4

//save the current room
ini_open(save_file)
ini_write_real("Main data", "room", save_rm);
ini_close();
So you're saving the current room to the same section and key for every save. This will overwrite whatever was there before it. I take it that's what you want?

I presume since you are changing the file name with global.current_save, you have planned it so you won't get any problems from this.

Code:
//Create the list
save_ds_list_name = ds_list_create();
You passed in a string value "r1_save_data" and assigned it to save_ds_list_name. Now you are overwriting the string value and assigning a newly created ds_list to save_ds_list_name. What is the logic for this?

Code:
//Set all the save values to zero or load them
ini_open(save_file)

if !ini_key_exists(save_file, save_rm)
{
So now you are trying to use the name of the ini file as the Section key to check if the value "save_rm" exists.

I don't see why you would use the file name as a section key, and I don't see where you set this previously.

The function arguments are : ini_key_exists(section, key);

So if my reading is correct, the following code will always run because the key isn't found.
Code:
    for (i=0;i<save_ds_list_size;i+=1)
    {
Where are you calculating save_ds_list_size? The correct function for this would be ds_list_size(save_ds_list_name) given your previous creation of the ds_list.

Code:
        ds_list_add(save_ds_list_name, 0);

        save = ds_list_write(save_ds_list_name)

        ini_open(save_file)
Ok, so you've just opened the already opened ini file from above...not good practise but possibly not causing an error at the moment because its the same file.
Code:
        ini_write_string(string("savedata"), save_rm, save);
        ini_close();
    }
Hmm...so here...you're progressively writing the string generated by ds_list_write() function to the key "savedata" which is a string already so doesn't need the string() function wrapped around it.

Would be better just to do this once after the loop finishes and you've completed building your ds_list.

Code:
}
else if ini_key_exists(save_file, save_rm)
{
    load = ini_read_string(string("savedata"), save_rm, "")

    ds_list_read(save_ds_list_name, load);
}

ini_close();
return save_ds_list_name;
Ah ...ignore comment way above, I see you are setting the key here using the save file name.

Ok these are just my first comments - will have another look once rested! :)

Edit: sorry...I just saw you are passing the list size into the script! Shouldn't do this when I'm tired!
 
Last edited:
L

Leon Hurley

Guest
Was away from home all weekend, just got a chance to reply now. I'm a bit tired from the holiday, so please forgive if I've misunderstood anything, I'll just write down where I see some problem points.

Code:
current_save_file = "save_data" + string(global.current_save) + ".ini"
ds_list_name = "r1_save_data"
current_room = "r1"
list_size = 3

scr_basic_save(current_save_file, ds_list_name, current_room, current_room, list_size);
What does global.current_save represent and when do you change it/ set it?

Code:
//Arguments
save_file = argument0
save_ds_list_name = argument1
save_key = argument2
save_rm = argument3
save_ds_list_size = argument4

//save the current room
ini_open(save_file)
ini_write_real("Main data", "room", save_rm);
ini_close();
So you're saving the current room to the same section and key for every save. This will overwrite whatever was there before it. I take it that's what you want?

I presume since you are changing the file name with global.current_save, you have planned it so you won't get any problems from this.

Code:
//Create the list
save_ds_list_name = ds_list_create();
You passed in a string value "r1_save_data" and assigned it to save_ds_list_name. Now you are overwriting the string value and assigning a newly created ds_list to save_ds_list_name. What is the logic for this?

Code:
//Set all the save values to zero or load them
ini_open(save_file)

if !ini_key_exists(save_file, save_rm)
{
So now you are trying to use the name of the ini file as the Section key to check if the value "save_rm" exists.

I don't see why you would use the file name as a section key, and I don't see where you set this previously.

The function arguments are : ini_key_exists(section, key);

So if my reading is correct, the following code will always run because the key isn't found.
Code:
    for (i=0;i<save_ds_list_size;i+=1)
    {
Where are you calculating save_ds_list_size? The correct function for this would be ds_list_size(save_ds_list_name) given your previous creation of the ds_list.

Code:
        ds_list_add(save_ds_list_name, 0);

        save = ds_list_write(save_ds_list_name)

        ini_open(save_file)
Ok, so you've just opened the already opened ini file from above...not good practise but possibly not causing an error at the moment because its the same file.
Code:
        ini_write_string(string("savedata"), save_rm, save);
        ini_close();
    }
Hmm...so here...you're progressively writing the string generated by ds_list_write() function to the key "savedata" which is a string already so doesn't need the string() function wrapped around it.

Would be better just to do this once after the loop finishes and you've completed building your ds_list.

Code:
}
else if ini_key_exists(save_file, save_rm)
{
    load = ini_read_string(string("savedata"), save_rm, "")

    ds_list_read(save_ds_list_name, load);
}

ini_close();
return save_ds_list_name;
Ah ...ignore comment way above, I see you are setting the key here using the save file name.

Ok these are just my first comments - will have another look once rested! :)
Wow, thanks for looking at all that so hard! And...

THIS:
So now you are trying to use the name of the ini file as the Section key to check if the value "save_rm" exists.

I don't see why you would use the file name as a section key, and I don't see where you set this previously.

The function arguments are : ini_key_exists(section, key);

So if my reading is correct, the following code will always run because the key isn't found.
I'm an idiot. I had perfectly working code when it was in an object and just messed up turning it into a script. Replacing that section key with "savedate" makes it work!

I cannot thank you enough!

To answer the other questions:

Q What does global.current_save represent and when do you change it/ set it?
A That's just a value of '1' set on a placeholder start menu for the time being. Eventually you'll be able to change it and have different save files.

Q So you're saving the current room to the same section and key for every save. This will overwrite whatever was there before it. I take it that's what you want?
A That's a different file to just track the last room you were in. It's not actually doing anything yet.

Q You passed in a string value "r1_save_data" and assigned it to save_ds_list_name. Now you are overwriting the string value and assigning a newly created ds_list to save_ds_list_name. What is the logic for this?
A I'm assuming that the string value will be used as the name of the created list?

Q Where are you calculating save_ds_list_size? The correct function for this would be ds_list_size(save_ds_list_name) given your previous creation of the ds_list.
A The ds_list_size comes from the list_size = 3 argument passed over. I only have three things I want to save in this room:
0 = key dropped
1 = key taken
2 = door opened
These are the only things the play changes and the 0/1 state of each controls everything else.

Q Ok, so you've just opened the already opened ini file from above...not good practise but possibly not causing an error at the moment because its the same file.
A Yep, good point. I'm always a bit unsure where to open ini files - in every if/else option or once at the start before any of it. That's probably left over from previous uncertainty.

Q Hmm...so here...you're progressively writing the string generated by ds_list_write() function to the key "savedata" which is a string already so doesn't need the string() function wrapped around it. Would be better just to do this once after the loop finishes and you've completed building your ds_list.
A Absolutely. Made that change.

Again, can't thank you enough. After all the searching and looking stuff up it was just a typo [facepalm]. It all seems to work perfectly now what ever room I move between.
 
That's really great to hear that it's working now! Don't change it now if it's working, but I will just comment on that ds_list creation thing.

Q You passed in a string value "r1_save_data" and assigned it to save_ds_list_name. Now you are overwriting the string value and assigning a newly created ds_list to save_ds_list_name. What is the logic for this?
A I'm assuming that the string value will be used as the name of the created list?
I'm pretty sure a ds_list doesn't have a name as such. And even if it did, this method is not going to give it that name.

Basically you are doing this:

Code:
my_list = "name of my list" // my_list is set to a string variable

my_list = ds_list_create() // You have just overwritten the string variable (basically destroyed it, it the string no longer exists) with a handle/id/pointer (call it what you like, handle is appropriate for GameMaker) that points to the ds_list you just created.
Just to be clear, here's your code:
Code:
ds_list_name = "r1_save_data"

// Then you pass that string into the script and assign it as follows:
save_ds_list_name = argument1 // So now save_ds_list_name = "r1_save_data"

// Then almost straight after, you overwrite the string variable with a ds_list handle
//Create the list
save_ds_list_name = ds_list_create(); // This "throws-away" the string variable and assigns the handle to the list to the same variable
 
Top