Question about multiple save and load slots

Godot Version

Godot 4.2.1

Question

Hi. I am new to Godot, and I am having troubles with creating a working save/load system with 5 slots. I am not even sure this is the best method for saving. In any case, with my work there are two problems: 1) whenever I load, all of the previous savings titles disappear, substituted by “Write the saving name here.” which is the default visible name for all empty slots. 2) I do not know why, but whatever number of savings have been made, and whatever of them I load, I only load the last saving.
Can anybody tell me the cause of my error?
Here my actual code in the main scene (delo.tscn).

extends Node2D

func save() → Dictionary:
return {
“filename”: “res://delo.tscn”,
“parent”: get_parent().get_path(),
“slot_name”: “”,
“mese”: global.mese,
“giornomese”: global.giornomese
}
var message_queue =
var current_popup = null

@onready var popup_menu = $CanvasLayer/Panel_menu/MenuButton.get_popup()
@onready var status_label = $CanvasLayer/Label
@onready var save_load_panel = $CanvasLayer/SaveLoadPanel
@onready var slot_names = [“Write here the name of the new save”, “Write here the name of the new save”, “Write here the name of the new save”, “Write here the name of the new save”, “Write here the name of the new save”]

@onready var pannello_informazioni = $CanvasLayer/Panello_informazioni_economiche
@onready var pannello_quests= $CanvasLayer/Pannello_quests

Called when the node enters the scene tree for the first time.

func _ready():

print_all_save_slots()
#menu
popup_menu.add_item("Save", 0)
popup_menu.add_item("Load", 1)
popup_menu.add_item("Exit", 2)
popup_menu.connect("id_pressed", _on_menu_option_selected)

_init_save_load_panel()
save_load_panel.visible = false
    global.init_popup()
   $CanvasLayer/PopupTemplate/Button.connect("pressed", Callable(self, "_on_popup_close"))

func _on_menu_option_selected(id):
match id:
0:
_show_save_load_panel(“save”)
1:
_show_save_load_panel(“load”)
2:
_on_exit_pressed()

func _show_save_load_panel(mode: String):
save_load_panel.visible = true
$CanvasLayer/SaveLoadPanel/Label.text = “Choose a slot to %s” % mode

var vbox = save_load_panel.get_node("VBoxContainer")
for i in range(vbox.get_child_count()):
	var hbox = vbox.get_child(i)
	
	if hbox == null:
		print("Warning: HBoxContainer node not found at index %d" % i)
		continue

	var button = hbox.get_node("Button(save_load)")
	var line_edit = hbox.get_node("LineEdit")
	var delete_button = hbox.get_node("Button(delete)")  # Nuovo pulsante di cancellazione

	if button != null:
		button.text = mode.capitalize()
		# Create a Callable object for the method with binding parameters
		var callable = Callable(self, "_on_slot_button_pressed").bind(mode, i, line_edit)
		# Verifica e scollega il segnale se già connesso
		if button.is_connected("pressed", callable):
			button.disconnect("pressed", callable)

		# Connette il segnale al pulsante con i parametri
		button.connect("pressed", Callable(self, "_on_slot_button_pressed").bind(mode, i, line_edit))
		print("Connected button for slot %d with mode %s" % [i, mode])
	else:
		print("Warning: Button node not found in HBox %d" % i)

	if line_edit != null:
		line_edit.custom_minimum_size = Vector2(300, 30)  # Imposta larghezza a 200 e altezza a 30
		if i < slot_names.size():
			if slot_names[i] == "":
				line_edit.placeholder_text = "..."
				line_edit.text = ""
			else:
				line_edit.text = slot_names[i]
				line_edit.placeholder_text = ""  # Nascondi il testo segnaposto
		else:
			line_edit.placeholder_text = "..."
			line_edit.text = ""
	
	else:
		print("Warning: LineEdit node not found in HBox %d" % i)
	
	if delete_button == null:
		print("Warning: Delete Button node not found in HBox %d" % i)
		continue
				# Connect the delete button
	delete_button.connect("pressed", Callable(self, "_on_delete_slot_button_pressed").bind(i, line_edit))
	delete_button.text = "Delete"

func _on_slot_button_pressed(mode: String, slot_index: int, line_edit: LineEdit):
if mode == “save”:
global._save_game(slot_index, line_edit.text)
print(“Saved game in slot: %d with name: %s” % [slot_index, line_edit.text])

	# Aggiorna il nome dello slot nella UI
	if slot_index < slot_names.size():
		slot_names[slot_index] = line_edit.text
	else:
		slot_names.append(line_edit.text)
	
	# Rinfresca i nomi degli slot nel pannello di salvataggio/caricamento
	_refresh_save_load_panel()
elif mode == "load":
	global._load_game(slot_index, line_edit.text)
	print("Loaded game from slot: %d with name: %s" % [slot_index, line_edit.text])
	
save_load_panel.visible = false
$CanvasLayer/PopupTemplate.visible = false

func _init_save_load_panel():
var vbox = save_load_panel.get_node(“VBoxContainer”)

# Assicurati che ci siano abbastanza nomi di slot per coprire gli HBox
var hbox_count = vbox.get_child_count()

if slot_names.size() < hbox_count:
	print("Errore: slot_names non ha abbastanza elementi! Aggiungendo elementi vuoti.")
	# Aggiungi elementi vuoti per riempire il gap
	while slot_names.size() < hbox_count:
		slot_names.append("")

for i in range(hbox_count):
	var hbox = vbox.get_child(i)

	# Controlla se hbox è effettivamente un HBoxContainer
	if not hbox is HBoxContainer:
		print("Warning: il nodo %d non è un HBoxContainer!" % i)
		continue

	var label = hbox.get_node("Label")
	var button = hbox.get_node("Button(save_load)")
	var line_edit = hbox.get_node("LineEdit")
	
	# Controlla se i nodi sono stati trovati
	if label == null:
		print("Warning: Label node not found in HBox %d" % i)
		continue
	if line_edit == null:
		print("Warning: LineEdit node not found in HBox %d" % i)
		continue
	if button == null:
		print("Warning: Button node not found in HBox %d" % i)
		continue

	# Imposta il nome dello slot iniziale
	label.text = "Slot %d" % (i + 1)
	
	# Imposta il testo del LineEdit
	if i < slot_names.size():
		line_edit.text = slot_names[i]
	else:
		line_edit.text = "Empty Slot"

	# Connette il pulsante per il salvataggio/caricamento
	button.connect("pressed", Callable(self, "_on_slot_button_pressed").bind("save", i, line_edit))

func _refresh_save_load_panel():
var vbox = save_load_panel.get_node(“VBoxContainer”)
for i in range(vbox.get_child_count()):
var hbox = vbox.get_child(i)
if hbox == null:
continue

	var line_edit = hbox.get_node("LineEdit")
	if line_edit != null:
		if i < slot_names.size():
			line_edit.text = slot_names[i]
			line_edit.placeholder_text = ""  # Nascondi il testo segnaposto
		else:
			line_edit.text = ""
			line_edit.placeholder_text = "..."

func _on_save_pressed():
ricovero.save_game(ricovero.slot_name)
_show_status(“Game Saved”)

func _on_load_pressed():
ricovero.load_game(ricovero.slot_name)
_show_status(“Game Loaded”)

func _on_exit_pressed():
get_tree().quit()

func _show_status(message):
status_label.text = message
status_label.show()
await get_tree().create_timer(2).timeout
status_label.hide()

#mi sa devo creare pure un pannello salvataggi e caricamenti

func _on_button_close_saveload_pressed():
$CanvasLayer/SaveLoadPanel.visible = false
pass # Replace with function body.

And this here is my saving and loading functions, written in a singleton called global.

func _save_game(slot_index: int, slot_name: String):
global._save_global_state()

# Save Dialogic data to the slot
var dialogic_extra_info = {
	"slot_name": slot_name,
	"timestamp": Time.get_datetime_string_from_system()
}
Dialogic.Save.save(slot_name, false, Dialogic.Save.ThumbnailMode.STORE_ONLY, dialogic_extra_info)
var save_file_path = "user://save_slot_%d.save" % slot_index
var file = FileAccess.open(save_file_path, FileAccess.WRITE)

# Save the slot name
file.store_line(slot_name)
#

var save_nodes = get_tree().get_nodes_in_group("Persist")
for node in save_nodes:
	if node.scene_file_path.is_empty():
		print("Persistent node '%s' is not an instanced scene, skipped" % node.name)
		continue

	if !node.has_method("save"):
		print("Persistent node '%s' is missing a save() function, skipped" % node.name)
		continue

	var node_data = node.call("save")
	node_data["slot_name"] = slot_name  # Save the slot name in the data

	var json_string = JSON.stringify(node_data)
	print("Saving node data for '%s': %s" % [node.name, json_string])  # Print node data being saved
	file.store_line(json_string)

file.close()
print("Game saved to slot %d!" % slot_index)

func _load_game(slot_index: int, slot_name: String):
global._load_global_state()

# Check if Dialogic save slot exists and load Dialogic data
if Dialogic.Save.has_slot(slot_name):
	Dialogic.Save.load(slot_name)
else:
	print("No Dialogic save file found for slot %d!" % slot_index)
	
var save_file_path = "user://save_slot_%d.save" % slot_index
if FileAccess.file_exists(save_file_path):
	var file = FileAccess.open(save_file_path, FileAccess.READ)
	while not file.eof_reached():
		var line = file.get_line()
		print("Save file content line: ", line)
	file.close()
else:
	print("Save file not found for slot %d" % slot_index)

# Revert the game state before loading
var save_nodes = get_tree().get_nodes_in_group("Persist")
for node in save_nodes:
	node.queue_free()

var file = FileAccess.open(save_file_path, FileAccess.READ)
while file.get_position() < file.get_length():
	var json_string = file.get_line()
	var json = JSON.new()
	var parse_result = json.parse(json_string)

	if parse_result != OK:
		print("JSON Parse Error: ", json.get_error_message(), " in ", json_string, " at line ", json.get_error_line())
		continue

	var node_data = json.get_data()
	var new_object = load(node_data["filename"]).instantiate()
	get_node(node_data["parent"]).add_child(new_object)
	#new_object.position = Vector2(node_data["pos_x"], node_data["pos_y"])

	for key in node_data.keys():
		if key == "filename" or key == "parent" or key == "pos_x" or key == "pos_y":
			continue
		new_object.set(key, node_data[key])

file.close()
print("Game loaded from slot %d!" % slot_index)

func _save_global_state():
var lista_quest_dict =
for oggetto_quest in lista_quest:
lista_quest_dict.append(quest_to_dict(oggetto_quest))
var global_data = {
“ermaisti”: global.ermaisti, …
}

Save global data using Dialogic’s global save method

for key in global_data.keys():
	Dialogic.Save.set_global_info(key, global_data[key])

var file = FileAccess.open("user://global_state.save", FileAccess.WRITE)
var json_string = JSON.stringify(global_data)
file.store_line(json_string)
file.close() 

func _load_global_state():
if not FileAccess.file_exists(“user://global_state.save”):
print(“No global state file found!”)
return

var file = FileAccess.open("user://global_state.save", FileAccess.READ)
var json_string = file.get_line()
var json = JSON.new()
var parse_result = json.parse(json_string)

if parse_result != OK:
	print("JSON Parse Error: ", json.get_error_message(), " in ", json_string, " at line ", json.get_error_line())
	return

var global_data = json.get_data()

# Carica la lista di quest
global.lista_quest.clear()
for quest_dict in global_data.get("lista_quest", []):
	var oggetto_quest = dict_to_quest(quest_dict)
	global.lista_quest.append(oggetto_quest)  

global.ermaisti = global_data["ermaisti"]

#many others like this one

Load global data using Dialogic’s global load method

for key in global_data.keys():
	if Dialogic.Save.get_global_info(key, global_data[key]):
		global_data[key] = Dialogic.Save.get_global_info(key, global_data[key])
	else:
		print("Global data not found for key: ", key)

file.close()

Funzione per convertire un oggetto quest in un dizionario

func quest_to_dict(oggetto_quest):
var quest_dict = {
“nome_quest”: oggetto_quest.nome_quest,
“descrizione”: oggetto_quest.descrizione,
“destinazione”: oggetto_quest.destinazione,
“ricompensa”: oggetto_quest.ricompensa,
“completata”: oggetto_quest.completata,
}

if oggetto_quest is quest.quest_libro:
	quest_dict["quest_type"] = "quest_libro"
	quest_dict["nome_libro"] = oggetto_quest.nome_libro
	quest_dict["nome_autore"] = oggetto_quest.nome_autore
	quest_dict["iniziata"] = oggetto_quest.iniziata
	quest_dict["libro_trovato"] = oggetto_quest.libro_trovato
elif oggetto_quest is quest.quest_trasporto:
	quest_dict["quest_type"] = "quest_trasporto"
	quest_dict["nome_oggetto"] = oggetto_quest.nome_oggetto
	quest_dict["disponibile"] = oggetto_quest.disponibile
	quest_dict["attivata"] = oggetto_quest.attivata
	quest_dict["cancellata"] = oggetto_quest.cancellata

return quest_dict

Funzione per convertire un dizionario in un oggetto quest

func dict_to_quest(dict: Dictionary):
var oggetto_quest

if dict.get("quest_type") == "quest_libro":
	oggetto_quest = quest.quest_libro.new(
		dict.get("nome_quest", ""),
		dict.get("descrizione", ""),
		dict.get("nome_libro", ""),
		dict.get("nome_autore", ""),
		dict.get("ricompensa", 0),
		dict.get("iniziata", false),
		dict.get("libro_trovato", false),
		dict.get("completata", false),
		dict.get("destinazione", "")
	)
elif dict.get("quest_type") == "quest_trasporto":
	oggetto_quest = quest.quest_trasporto.new(
		dict.get("nome_quest", ""),
		dict.get("descrizione", ""),
		dict.get("nome_oggetto", ""),
		dict.get("destinazione", ""),
		dict.get("ricompensa", 0),
		dict.get("disponibile", false),
		dict.get("attivata", false),
		dict.get("completata", false),
		dict.get("cancellata", false)
	)

return oggetto_quest

Dialogic is just a plugin, and as of now it works well.

This situation is really complicated!
But I have some recommendations based on your general situation:

  1. Check the program progress:
    1- It needs a load at runtime
    2- Before saving the file, it should load it once, change it and then save it
  2. Use a standard storage system, I suggest this:
    GitHub - Subject-Team/SLib: Useful Codes For GDScript