Saved information being overwritten very selectively?

Godot Version

4.2.2

Question

Hi!
I’ve been working in godot for a few months now on a personal project, and I’ve recently started to use custom resources to contain possible data for things like Items and NPCs. For the NPCs I’ve got a generic CrewMember resource with arrays of possible names and stats etc. as well as a function which picks things at random and passes them back to a script as a dictionary. Perhaps this isn’t quite how resources are meant to be used, but I’ve already got save/loading working with dictionaries, so I wanted to stick with that.

Weird thing though. As I save the data to my crew dictionary, one of the entries of data gets overwritten (“stats”), not all of the entries in the dictionary, just this one that is a dictionary itself. Every time I roll a new character and save that data to the crew dictionary, the new “stats” overwrites all other characters stats.

Does this have something to do with Resources? Dictionaries? I have this working via a class with arrays of possible data, but switching it to a resource seems to have done something weird. Any insight would be appreciated, or I’ll have to switch back to my old, less-flexible method.

The Function for reference, and it’s printout:

func new_crew(num : int):
	#Create new characters in the amount indicated
	for i in num:
		var crew_member_data = crew_resource.create_character()
		crew_member_data["order"] = str(i)
		var new_name = crew_member_data["name"]
		_characters_SAVE[new_name] = crew_member_data
		print(new_name)
		print("resource ", crew_member_data) # This is correct!
		for key in _characters_SAVE:
			print("saved  : ", _characters_SAVE[key]) # This is correct UNTIL a new character is added, and then it's only correct for the newest character.
McNameFace
resource { "name": "McNameFace", "pronouns": 2, "role": 1, "icon": "res://Icons/buttonB.png", "stats": { "StatA": 0, "StatB": 1, "StatC": 0, "StatD": 0, "StatE": 0 }, "scores": {  }, "order": "0" }
saved  : { "name": "McNameFace", "pronouns": 2, "role": 1, "icon": "res://Icons/buttonB.png", "stats": { "StatA": 0, "StatB": 1, "StatC": 0, "StatD": 0, "StatE": 0 }, "scores": {  }, "order": "0" }
Dr. Name
resource { "name": "Dr. Name", "pronouns": 3, "role": 0, "icon": "res://Icons/buttonA.png", "stats": { "StatA": 1, "StatB": 0, "StatC": 0, "StatD": 0, "StatE": 1 }, "scores": {  }, "order": "1" }
saved  : { "name": "McNameFace", "pronouns": 2, "role": 1, "icon": "res://Icons/buttonB.png", "stats": { "StatA": 1, "StatB": 0, "StatC": 0, "StatD": 0, "StatE": 1 }, "scores": {  }, "order": "0" }
saved  : { "name": "Dr. Name", "pronouns": 3, "role": 0, "icon": "res://Icons/buttonA.png", "stats": { "StatA": 1, "StatB": 0, "StatC": 0, "StatD": 0, "StatE": 1 }, "scores": {  }, "order": "1" }
Namesdottir
resource { "name": "Namesdottir", "pronouns": 1, "role": 8, "icon": "res://Icons/buttonB.png", "stats": { "StatA": 0, "StatB": 1, "StatC": 0, "StatD": 0, "StatE": 0 }, "scores": {  }, "order": "2" }
saved  : { "name": "McNameFace", "pronouns": 2, "role": 1, "icon": "res://Icons/buttonB.png", "stats": { "StatA": 0, "StatB": 1, "StatC": 0, "StatD": 0, "StatE": 0 }, "scores": {  }, "order": "0" }
saved  : { "name": "Dr. Name", "pronouns": 3, "role": 0, "icon": "res://Icons/buttonA.png", "stats": { "StatA": 0, "StatB": 1, "StatC": 0, "StatD": 0, "StatE": 0 }, "scores": {  }, "order": "1" }
saved  : { "name": "Namesdottir", "pronouns": 1, "role": 8, "icon": "res://Icons/buttonB.png", "stats": { "StatA": 0, "StatB": 1, "StatC": 0, "StatD": 0, "StatE": 0 }, "scores": {  }, "order": "2" }
Namey
resource { "name": "Namey", "pronouns": 1, "role": 7, "icon": "res://Icons/buttonB.png", "stats": { "StatA": 1, "StatB": 0, "StatC": 0, "StatD": 0, "StatE": 1 }, "scores": {  }, "order": "3" }
saved  : { "name": "McNameFace", "pronouns": 2, "role": 1, "icon": "res://Icons/buttonB.png", "stats": { "StatA": 1, "StatB": 0, "StatC": 0, "StatD": 0, "StatE": 1 }, "scores": {  }, "order": "0" }
saved  : { "name": "Dr. Name", "pronouns": 3, "role": 0, "icon": "res://Icons/buttonA.png", "stats": { "StatA": 1, "StatB": 0, "StatC": 0, "StatD": 0, "StatE": 1 }, "scores": {  }, "order": "1" }
saved  : { "name": "Namesdottir", "pronouns": 1, "role": 8, "icon": "res://Icons/buttonB.png", "stats": { "StatA": 1, "StatB": 0, "StatC": 0, "StatD": 0, "StatE": 1 }, "scores": {  }, "order": "2" }
saved  : { "name": "Namey", "pronouns": 1, "role": 7, "icon": "res://Icons/buttonB.png", "stats": { "StatA": 1, "StatB": 0, "StatC": 0, "StatD": 0, "StatE": 1 }, "scores": {  }, "order": "3" }

What is wrong? The prints make sense, what do you expect to happen?

Can you show the code that actually creates the stats? I’m wondering if they’re all ending up with a reference to the same dictionary, which then gets edited each time a new character is added.

Here is the Resource script, which is called as ‘crew_resource.create_character’. I think what you’re saying sounds right, but I can’t track down how it’s happening, since I redefine the variable ‘new_stats’ each time. Also it works fine for the other parameters, role, icon, pronouns etc, so I don’t understand why just the stats are holding some a reference but not the others?

extends Resource

enum E_PRONOUNS {RANDOM, SHE, HE, THEY}
enum E_ROLE {RANDOM, ASTRONOMER, POLITICIAN, CEO, INVENTOR, BIOLOGIST, COLONIST, CIVILIAN, PIRATE, COMPANION}

var ICONS = ["res://Icons/button1.png", "res://Icons/button2.png", "res://Icons/button3.png", "res://Icons/buttonA.png", "res://Icons/buttonB.png"]

@export_category("Details")
@export var name : Array[String]
@export var pronouns : E_PRONOUNS
@export var role : E_ROLE
var icon 
var stats = {"StatA" : 0, "StatB" : 0, "StatC" : 0, "StatD" : 0, "StatE" : 0}
var stats_range = [0, 1]

func create_character():
	var new_stats = stats
	for key in new_stats:
		new_stats[key] = stats_range.pick_random()
		
	var new_role = role
	if role == E_ROLE.RANDOM:
		new_role = E_ROLE.values().pick_random()
		
	var new_pronouns = pronouns
	if pronouns == E_PRONOUNS.RANDOM:
		new_pronouns = E_PRONOUNS.values().pick_random()
	
	var unique_name
	if name.size() != 1:
		var i = randi_range(0, name.size() - 1)
		unique_name = name[i]
		name.pop_at(i)
	else:
		unique_name = name[0]
		
	var data_out = {
		"name" : unique_name,
		"pronouns" :new_pronouns,
		"role" : new_role,
		"icon" : ICONS.pick_random(),
		"stats" : new_stats,
		"scores" : {}
	}
	return data_out

Yeah, so, dictionaries are passed by reference, so new_stats = stats just means that new_stats becomes a reference to the same dictionary as stats. You can get around this by doing new_stats = stats.duplicate()

2 Likes

That makes sense, thank you sooooo much!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.