Legacy GM [SOLVED] Duplicate Player Objects Upon Loading

M

MissCheeseDanish

Guest
Hey, so I'm working on a game demo for college, and for the past few weeks my adviser and I have been stuck on the save/load system. Right now it's getting close to where it should be, but there's still the problem that with every load, a new player object is created.

Save:
Code:
/// scr_save_game

// Make sure the player exists
if (global.player == noone) {
    exit;
}

// Create the save data structure
var save_data = ds_map_create();

var save_player = global.player;
ds_map_add(save_data, "player", save_player);

with (obj_input) {
    ds_map_add(save_data, "room", previous_room);
    ds_map_add(save_data, "x", player_xstart);
    ds_map_add(save_data, "y", player_ystart);
}

with (global.player) {
    ds_map_add(save_data, "hp", hp);
    ds_map_add(save_data, "maxhp", maxhp);
    ds_map_add(save_data, "stamina", stamina);
    ds_map_add(save_data, "maxstamina", maxstamina);
    ds_map_add(save_data, "expr", expr);
    ds_map_add(save_data, "maxexpr", maxexpr);
    ds_map_add(save_data, "level", level);
    ds_map_add(save_data, "attack", attack);
    ds_map_add(save_data, "defense", defense);
    ds_map_add(save_data, "m_attack", m_attack);
    ds_map_add(save_data, "m_defense", m_defense);
    ds_map_add(save_data, "tired", tired);
    ds_map_add(save_data, "dead", dead);
}

with (obj_input) {
    
    ds_map_add(save_data, "day", global.day);
    ds_map_add(save_data, "flans", global.flans);
    
}

var save_string = json_encode(save_data);
ds_map_destroy(save_data);
//save_string = base64_encode(save_string);

var file = file_text_open_write(working_directory + "PROXYsave.txt");
file_text_write_string(file, save_string);
file_text_close(file);
Load:
Code:
/// scr_load_game
var file = file_text_open_read(working_directory + "PROXYsave.txt");
var save_string = "";
while (!file_text_eof(file)) {
    save_string += file_text_readln(file);
    //file_text_readln(file);
}
file_text_close(file);

//save_string = base64_decode(save_string);
var save_data = json_decode(save_string);

var save_room = ds_map_find_value(save_data, "room");
if (room != save_room) {
    room_goto(save_room);
    if (instance_number(global.player) > 1) {
        with (global.player) {
            instance_destroy();
        }
    }
}

var load_player = ds_map_find_value(save_data, "player");
if (load_player != global.player) {
    with (global.player) {
        instance_change(load_player, true);
    }
}

with (obj_input) {
    player_xstart = ds_map_find_value(save_data, "x");
    player_ystart = ds_map_find_value(save_data, "y");
    if (instance_exists(global.player)) {
        global.player.x = player_xstart;
        global.player.y = player_ystart;
        global.player.phy_position_x = player_xstart;
        global.player.phy_position_y = player_ystart;
    } else {
        if (instance_number(global.player) > 1) {
            instance_destroy();
        }
        instance_create(obj_input.player_xstart, obj_input.player_ystart, global.player);
    }
}

with (global.player) {
    hp = ds_map_find_value(save_data, "hp");
    maxhp = ds_map_find_value(save_data, "maxhp");
    stamina = ds_map_find_value(save_data, "stamina");
    maxstamina = ds_map_find_value(save_data, "maxstamina");
    expr = ds_map_find_value(save_data, "expr");
    maxexpr = ds_map_find_value(save_data, "maxexpr");
    level = ds_map_find_value(save_data, "level");
    attack = ds_map_find_value(save_data, "attack");
    defense = ds_map_find_value(save_data, "defense");
    m_attack = ds_map_find_value(save_data, "m_attack");
    m_defense = ds_map_find_value(save_data, "m_defense");
    tired = ds_map_find_value(save_data, "tired");
    dead = ds_map_find_value(save_data, "dead");
}

with (obj_input) {
    
    global.day = ds_map_find_value(save_data, "day");
    global.flans = ds_map_find_value(save_data, "flans");
    
}

ds_map_destroy(save_data);

In the debugger, I think we found that in the load script, it skips over checking if global.player (which is set to one of the player objects, because there's a character select) exists, and goes straight to the else, where another player object is created.

Any help is appreciated!
 

FrostyCat

Redemption Seeker
You need to set global.player = load_player; after instance_change(load_player, true);. There won't be an instance of the original there anymore.
 
M

MissCheeseDanish

Guest
You need to set global.player = load_player; after instance_change(load_player, true);. There won't be an instance of the original there anymore.
Just tried a few different combinations of that (right after the instance_change, after the with, and then after the if) but there were still duplicate players.
 

TheouAegis

Member
What is the value of global.player? If it isn't an object_index like obj_player or whatever but an instance ID, then there will never be more than one.

Furthermore,
Code:
var save_room = ds_map_find_value(save_data, "room");
if (room != save_room) {
   room_goto(save_room);
    if (instance_number(global.player) > 1) {
        with (global.player) {
            instance_destroy();
        }
    }
}
will not delete any instances in save_room, only instances in the current room. Rooms don't change until the code has finished running. Not sure if that's a part of the problem, but I see you doing that a lot in your code, so I felt I should point it out.
 
M

MissCheeseDanish

Guest
What is the value of global.player? If it isn't an object_index like obj_player or whatever but an instance ID, then there will never be more than one.

Furthermore,
Code:
var save_room = ds_map_find_value(save_data, "room");
if (room != save_room) {
   room_goto(save_room);
    if (instance_number(global.player) > 1) {
        with (global.player) {
            instance_destroy();
        }
    }
}
will not delete any instances in save_room, only instances in the current room. Rooms don't change until the code has finished running. Not sure if that's a part of the problem, but I see you doing that a lot in your code, so I felt I should point it out.
global.player is set to one of the player objects in scr_current_player.
Code:
/// scr_current_player()
// Determine which object to use as player
global.player = obj_takako;

if (instance_exists(obj_takako)) {
    global.player = obj_takako;
} else if (instance_exists(obj_mascot)) {
    global.player = obj_mascot;
} else if (instance_exists(obj_plants)) {
    global.player = obj_plants;
}
So what you're saying is I should do something more like this?
Code:
/// scr_current_player()
// Determine which object to use as player
global.player = /*obj_takako instance ID*/;

if (instance_exists(/*obj_takako instance ID*/)) {
    global.player = /*obj_takako instance ID*/;
} else if (instance_exists(/*obj_mascot instance ID*/)) {
    global.player = /*obj_mascot instance ID*/;
} else if (instance_exists(/*obj_plants instance ID*/)) {
    global.player = /*obj_plants instance ID*/;
}
Just a little confused on how that would work with instance_change for the character select...


And with the code you pointed out, at this point I was trying different combinations to see what would work... But in each player object's create event, I also have this:
Code:
if (instance_number(obj_takako) > 1) {
    instance_destroy();
}
(This code is from obj_takako.)
 

TheouAegis

Member
No, I was just wondering what was being stored in global.player.

Code:
/// scr_current_player()
// Determine which object to use as player
global.player = obj_takako;

if (instance_exists(obj_takako)) {
   global.player = obj_takako;
} else if (instance_exists(obj_mascot)) {
    global.player = obj_mascot;
} else if (instance_exists(obj_plants)) {
    global.player = obj_plants;
}
You should default global.player to noone in that first line. Just to play things safe.

Code:
with (obj_input) {
    ....
    if (instance_exists(global.player)) {
        ....    
    } else {

        //// THE BELOW CODE WILL NEVER RUN ////
        if (instance_number(global.player) > 1) {
            instance_destroy();
        }
        //// THE ABOVE CODE WILL NEVER RUN ////

        instance_create(obj_input.player_xstart, obj_input.player_ystart, global.player);
    }
}
I would guess this is from you trying to debug things willy-nilly. The first conditional checks if there are any instances of the object specified in global.player. If there are no instances of that object, then instance_number(global.player) will never be greater than 1, because it's 0. So you may as well delete the part I sectioned off, since it is literally junk code.

Code:
var save_room = ds_map_find_value(save_data, "room");
if (room != save_room) {
   room_goto(save_room);
    if (instance_number(global.player) > 1) {
        with (global.player) {
            instance_destroy();
        }
    }
}
In the code here, your instance_number check is again pointless, but for a different reason. If there is more than one instance of global.player object in the room where the save data is loaded, then you already failed to destroy the instances of that object to begin with, so there is no point having that code. On the other hand, if there is an instance of the global.player object in the saved room (for whatever reason), then that code wouldn't destroy any of the instances. If there is one instance of global.player in the current room, then it won't be destroyed because instance_number is less than 1. If there is one instance of global.player in the saved room, then that won't be destroyed either because instance_number is only counting instances in the current room, not the saved room.


Now onto my core questions:

Where is the code in which you create the player? In the codes you posted here, the only time the player is being created is in the code that targests obj_input.

Anywhere - and I mean anywhere - does a player object already exist because you manually put it in a room in the Room Editor? In other words, is there anywhere in the game where the player exists without an external object spawning player instances?

How many obj_inputs are there? You can check instance_number(obj_input) in the Draw GUI event to track that. If there's more than 1, then you'll have more than 1 player created. Do you create obj_input anywhere in your game?

Worst case scenario, you can upload the whole project, as there could be external issues you and your professor aren't considering that are hard to diagnose over a forum like this.
 
M

MissCheeseDanish

Guest
No, I was just wondering what was being stored in global.player.

Code:
/// scr_current_player()
// Determine which object to use as player
global.player = obj_takako;

if (instance_exists(obj_takako)) {
   global.player = obj_takako;
} else if (instance_exists(obj_mascot)) {
    global.player = obj_mascot;
} else if (instance_exists(obj_plants)) {
    global.player = obj_plants;
}
You should default global.player to noone in that first line. Just to play things safe.

Code:
with (obj_input) {
    ....
    if (instance_exists(global.player)) {
        ....   
    } else {

        //// THE BELOW CODE WILL NEVER RUN ////
        if (instance_number(global.player) > 1) {
            instance_destroy();
        }
        //// THE ABOVE CODE WILL NEVER RUN ////

        instance_create(obj_input.player_xstart, obj_input.player_ystart, global.player);
    }
}
I would guess this is from you trying to debug things willy-nilly. The first conditional checks if there are any instances of the object specified in global.player. If there are no instances of that object, then instance_number(global.player) will never be greater than 1, because it's 0. So you may as well delete the part I sectioned off, since it is literally junk code.

Code:
var save_room = ds_map_find_value(save_data, "room");
if (room != save_room) {
   room_goto(save_room);
    if (instance_number(global.player) > 1) {
        with (global.player) {
            instance_destroy();
        }
    }
}
In the code here, your instance_number check is again pointless, but for a different reason. If there is more than one instance of global.player object in the room where the save data is loaded, then you already failed to destroy the instances of that object to begin with, so there is no point having that code. On the other hand, if there is an instance of the global.player object in the saved room (for whatever reason), then that code wouldn't destroy any of the instances. If there is one instance of global.player in the current room, then it won't be destroyed because instance_number is less than 1. If there is one instance of global.player in the saved room, then that won't be destroyed either because instance_number is only counting instances in the current room, not the saved room.


Now onto my core questions:

Where is the code in which you create the player? In the codes you posted here, the only time the player is being created is in the code that targests obj_input.

Anywhere - and I mean anywhere - does a player object already exist because you manually put it in a room in the Room Editor? In other words, is there anywhere in the game where the player exists without an external object spawning player instances?

How many obj_inputs are there? You can check instance_number(obj_input) in the Draw GUI event to track that. If there's more than 1, then you'll have more than 1 player created. Do you create obj_input anywhere in your game?

Worst case scenario, you can upload the whole project, as there could be external issues you and your professor aren't considering that are hard to diagnose over a forum like this.
Okay, I changed that to noone and removed those pieces of code (since they really were just there as attempts to debug, as you said).

1. A player object exists on the pause menu (last time I checked, it's just really far off screen and frozen in place), so I just do an instance_change to make it the player object that was saved. That line where the player is being created is just in case the player doesn't exist.

2. Yes, my initial room has a player object.

3. There's only one obj_input, and that is placed in the same room as the player object mentioned above, and it's not created in code.


I've been backing up each version to Google Drive, so I can share the most recent link if you think it's best to do so.
 

TheouAegis

Member
Okay, first off, I'm not sure if all of those scr_current_player() calls were your original intent or for debugging this and other problems, but all those scr_current_player() calls completely ruined your save feature. I couldn't even get scr_save_game to get past the first line because scr_current_player was always setting global.player to noone. Why was it doing that? Because it was getting called all the time by obj_input, but when you go to the pause menu you destroy global.player, so on the next step global.player is set to noone and thus scr_save_game aborts prematurely. In addition, since global.player is destroyed upon going to the pause menu, when scr_save_game and scr_load_game try to read and write to global.player, they can't because there is no global.player. And what happens when there is no global.player? A new one is created. Then you exit the pause menu and go back to the previous room - which is persistent. EVERYTHING in a persistent room will be reloaded when you go back to that room, including global.player. So now you have 2 player objects.

So you need to get rid of all those scr_current_player calls, calling it only when necessary and make sure you don't let the player object's get destroyed ever. What this means is either work out a system of keeping the player persistent and deactivating the player when entering a menu and reactivating it before reading/writing its data, or save all the player's variables to global variables so that they will persist even when the player is destroyed.
 
M

MissCheeseDanish

Guest
Okay, first off, I'm not sure if all of those scr_current_player() calls were your original intent or for debugging this and other problems, but all those scr_current_player() calls completely ruined your save feature. I couldn't even get scr_save_game to get past the first line because scr_current_player was always setting global.player to noone. Why was it doing that? Because it was getting called all the time by obj_input, but when you go to the pause menu you destroy global.player, so on the next step global.player is set to noone and thus scr_save_game aborts prematurely. In addition, since global.player is destroyed upon going to the pause menu, when scr_save_game and scr_load_game try to read and write to global.player, they can't because there is no global.player. And what happens when there is no global.player? A new one is created. Then you exit the pause menu and go back to the previous room - which is persistent. EVERYTHING in a persistent room will be reloaded when you go back to that room, including global.player. So now you have 2 player objects.

So you need to get rid of all those scr_current_player calls, calling it only when necessary and make sure you don't let the player object's get destroyed ever. What this means is either work out a system of keeping the player persistent and deactivating the player when entering a menu and reactivating it before reading/writing its data, or save all the player's variables to global variables so that they will persist even when the player is destroyed.
They were my intent, because I would get error messages without them, but I'll try removing them now.

Honestly didn't realize that I was destroying global.player when I was on the pause menu because I thought I remembered seeing the player walking around at one point, but I guess I was thinking of another menu.

Commented out the code where the player persistence is set to false, made it so the player is off screen on the pause menu, and now (besides a small problem with the player's position when clicking resume, but that's easy to fix), it's working! Thank you!
 

TheouAegis

Member
Try deactivating the player in the Pause Menu's Room Start event. Then in your save and load codes, activate the player, save the data or load the player data, then return to the previous room at the end of the script.

Also I can't remember if something was persistent or just global, but going to the pause menu, highlighting an option, leaving the menu, then going back to the menu resulted in the highlighted selection still being highlighted rather than the first option being highlighted every time. You may want to fix that, but it's just a minor concern.
 
Top