How to return last key / value from dictionary (checkpoint, when loading game)

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By anssiko

Hi,

I’m trying to use visited checkpoints to load the correct level and place the player at the last visited checkpoint. For this I’m saving the key (level name) and value (checkpoint name) in a dictionary on config file.

I figure I could return the last Level name and last Checkpoint name to archieve this, but how? (back() should work with an array, but how to do this with a dictionary?)

Or is there a better way to archieve what I’m after?

Code below. Many thanks in advance for any suggestions.

Game (including dictionary) initiates level loader:

extends Node

export(PackedScene) var StartLevel := preload("res://src/Levels/Level1.tscn")

const SAVE_PATH = "res://config.cfg"

var level: Node2D = null
var config = ConfigFile.new()
var _savedata = {
	"SAVEDATA": {
		"VisitedCheckpoints": { }
	}
}

func _ready() -> void:
	load_checkpoints()
	LevelLoader.setup(self, $Player, StartLevel)
	Events.connect("checkpoint_visited", self, "_on_Events_checkpoint_visited")

Level loader:

extends Node

onready var scene_tree := get_tree()

var _game: Node = null
var _player: Player = null
var _level: Node2D = null

func setup(game: Node, player: Player, Level: PackedScene) -> void:
	_game = game
	_player = player
	trigger(Level)

func trigger(NewLevel: PackedScene, portal_name: String = "") -> void:
	_game.remove_child(_player)
	
	if _level:
		scene_tree.paused = true
		_level.queue_free() #poistaa levelin
		yield(_level, "tree_exited")
	
	_level = NewLevel.instance()
	
	var player_position_node: Node2D = (
		_level.get_node("Checkpoints").get_child(0)
		if portal_name.empty()
		else _level.get_node("Portals/%s" % portal_name))
	_player.global_position = player_position_node.global_position
	
	for checkpoint_name in _game._savedata["SAVEDATA"]["VisitedCheckpoints"].get(_level.name, []):
		var checkpoint: Area2D = _level.get_node("Checkpoints/%s" % checkpoint_name)
		checkpoint.is_visited = true

	_game.level = _level
	_game.add_child(_level)
	_game.add_child(_player)
	
	scene_tree.paused = false

Never use “res://” for saving it wont work once you export your game

supper_raptor | 2020-04-09 19:28

:bust_in_silhouette: Reply From: supper_raptor

Never use “res://” for saving it wont work once you export your game , use “user://”
Use these function to save / load dictionary

func save_data(save_path : String, data) -> void:
    var data_string = JSON.print(data)
    var file = File.new()
    var json_error = validate_json(data_string)
    if json_error:
        print_debug("JSON IS NOT VALID FOR: " + data_string)
        print_debug("error: " + json_error)
        return
    file.open(save_path, file.WRITE)
    file.store_string(data_string)
    file.close()


func load_data(save_path : String):
    var file : File = File.new()
    if not file.file_exists(save_path):
        print_debug('file [%s] does not exist; creating' % save_path)
        save_data(save_path, {})
    file.open(save_path, file.READ)
    var json : String = file.get_as_text()
    var data = parse_json(json)
    file.close()

    return data

Load / save data

#save data
save_data(SAVE_PATH , _savedata)

#load data
_savedata = load_data(SAVE_PATH)

Thank you for your suggestion. If I understand correctly, you’re suggesting a better way of save/load (json), but my main problem still stands: how to return the last key and value from a dictionary?

anssiko | 2020-04-13 18:26

data is a dictionary

supper_raptor | 2020-04-14 05:54

I’m sorry, but I don’t get your meaning.

anssiko | 2020-04-14 17:51

i mean the variable data is godot dictionary
for example

# godot dictionary
var x = {
	name = "bla",
	hp = 100,
}

#to save it to "user://save.sav"

save_data("user://save.sav", x)

#to load data

var new_x = load_data("user://save.sav")

#you can access data like you do for dictionary

print(new_x.name) #will print "bla"

supper_raptor | 2020-04-14 18:59

anssiko,

Godot dictionary keys are ordered, according to the documentation (which is not always true in other language).

Godot’s JSON serialization / parsing seems to keep them too. But I would advise you to use an Array instead of storing your checkpoint as a Dictionary, as you may lose key order at some point. It could be an Array of Dictionary of course.

Accessing the last element of a Dictionary could be accomplished that way:

var my_last_k = new_dic.keys()[-1]
var last_v = new_dic[my_last_k]

You get all the key name’s array, and pickup the last element.

You could also timestamp your checkpoint record, and use the timestamp as the key. And also store the extra value of the last timestamp as a checkpoint reference to fetch.

The JSON could looks like:

{
  "last_checkpoint": 1234325432,
  "checkpoints": {
    "1234325430": {
      "player_pos": "(123, 345)",
      "player_weapon": "BFG",
      "player_heath": "30%"
    },
    "1234325432": {
      "player_pos": "(678, 345)",
      "player_weapon": "BFG",
      "player_heath": "28%"
    }
  }
}

But for sure, ensure that you don’t rely on Dictonary ordered keys nor on JSON keeping Dictionary order too.

Hope that helps.
Sylvain.

Sylvain22 | 2020-05-18 06:17