Dictionary data disappears after third time accessing

Godot Version

4.3

Question

So I’ve been using a dictionary to take a string input and give me the corresponding array, but there is a problem where the third time accessing the dictionary, all the key:value pairs disappear.

I swear up and down that there’s nowhere in my code that modifies this dictionary in any way (I’m like 99% certain of this, although if you know of a method that allows me to check whenever the dictionary is modified let me know) so I’m very confused about what’s happening here.


do you manipulate the backup-dictionary?

No, I only used it to try and replace the original when the data disappears, but it seems that both the original and the backup gets their data erased

By the way this issue happened without the backup as well

I assume you are calling the _calculate_stats() method from somewhere? Can you show us that code that calls this function?
Try to look up any code that reads/writes this dictionary and paste it here as well, but please use preformatted text ```

There is no way this dictionary is being cleaned by just calling it 3 times, and you can easily verify it by printing its value e.g. in the _process() method.

The following code is in the same script as the previous code I sent, this is the only place where _calculate_stat() is called

func _calculate_defence(defence_value):
	return _calculate_stat("defence", defence_value)

func _calculate_max_mana():
	return _calculate_stat("max_mana", initial_creature.initial_mana)

func _calculate_max_health(_health):
	return _calculate_stat("max_health", _health)

func _calculate_mana_efficiency():
	return _calculate_stat("mana_efficiency", 1)

func _calculate_combat_end_mana_recovery_percent():
	return _calculate_stat("calculate_combat_end_mana_recovery_percent", 0)

func _calculate_max_mana_based_damage():
	return _calculate_stat("max_mana_based_damage", 0)

func _calculate_max_mana_percent_on_kill():
	return _calculate_stat("max_mana_percent_on_kill", 0)

func _calculate_max_mana_percent_on_deal_damage():
	return _calculate_stat("max_mana_percent_on_deal_damage", 0)

func _calculate_damage():
	return _calculate_stat("damage", initial_creature.initial_damage)

func _calculate_speed(speed_value):
	return _calculate_stat("speed", speed_value)

func _calculate_core_defence(defence_value):
	var defence = _calculate_defence(defence_value)
	return _calculate_stat("core_defence", defence)

func _calculate_combat_end_coin_gain_percent():
	return _calculate_stat("combat_end_coin_gain_percent", 0)

func _calculate_rotation_speed(rotation_speed_value):
	return _calculate_stat("rotation_speed", rotation_speed_value)

I’ll show where these functions are called, for the second and the third time since that would be most relevent. The second time is where _calculate_core_defence() is called, which is in this script:

extends CreatureColBoxScript

#var creature
var death_component: Node
var health: float
var defence: float = 1

@export var stat_calculator: Node

func _ready():
	defence = stat_calculator._calculate_core_defence(defence)

func _attach_dependencies():
	super()
	death_component = creature.Dependencies["death_component"]

func globalOnHit(damage, kb_force_name, kb_force_dir, kb_force_mag, kb_force_duration):
	super(damage, kb_force_name, kb_force_dir, kb_force_mag, kb_force_duration)
	health -= (damage - defence)
	if health <= 0:
		_health_depleted()

func _health_depleted():
	death_component.Death()

and the third time, which is when the dictionary data gets shown as erased, is in this script:

extends Node
class_name CreatureMovementScript

var creature
@onready var creature_transform_basis: Node3D = creature.Dependencies["creature_transform_basis"]
@export var force_component: Node

@export var motion = Vector3()

@export var acceleration: float = 100
var base_accel: float

@export var deceleration: float = 100
var base_decel: float

@export var max_speed: float = 10
var base_speed: float

@export var movement_dir = Vector2()

@export var active_forces = {}
@export var net_active_force: Vector3

@export var creature_inventory: CreatureInventory
@export var stat_calculator: Node

class ActiveForce:
	var direction: Vector3
	var magnitude: float
	func _init(_direction, _magnitude):
		direction = _direction
		magnitude = _magnitude

func _ready():
	base_speed = max_speed
	base_accel = acceleration
	base_decel = deceleration
	
	_recalc_speed()

func _recalc_speed():
	if creature_inventory.speed_mods.is_empty():
		return
	
	max_speed = stat_calculator._calculate_speed(base_speed)
	var speed_difference = max_speed - base_speed
	
	acceleration = base_accel + (speed_difference * 10)
	deceleration = base_decel + (speed_difference * 10)

func globalSetMovementDirection(movement_direction):
	movement_dir = movement_direction

func globalAddActiveForce(force_name, force_dir, force_mag):
	active_forces[force_name] = ActiveForce.new(force_dir, force_mag)

func _sum_active_forces():
	for active_force in active_forces.values():
		net_active_force.x += active_force.direction.x * active_force.magnitude
		net_active_force.z += active_force.direction.y * active_force.magnitude

func _dampen_active_forces(delta):
	for active_force_name in active_forces.keys():
		active_forces[active_force_name].magnitude = move_toward(active_forces[active_force_name].magnitude, 0, deceleration * delta)
		if active_forces[active_force_name].magnitude <= 0:
			active_forces.erase(active_force_name)

func globalClearAllActiveForces():
	active_forces.clear()
	net_active_force = Vector3()

func _draw_movement_debug_overlay(direction):
	DebugOverlay.draw.add_vector(creature.position, creature.position + 3*creature_transform_basis.transform.basis.x, 1, Color.RED, false)
	DebugOverlay.draw.add_vector(creature.position, creature.position + 3*creature_transform_basis.transform.basis.y, 1, Color.GREEN, false)
	DebugOverlay.draw.add_vector(creature.position, creature.position + 3*creature_transform_basis.transform.basis.z, 1, Color.BLUE, false)
	DebugOverlay.draw.add_vector(creature.position, creature.position + 3*direction, 1, Color.MAGENTA, false)
	DebugOverlay.draw.add_vector(creature.position, creature.position + creature.velocity, 1, Color.BLACK, false)

func _physics_process(delta):
	var direction: Vector3 = (creature_transform_basis.transform.basis * Vector3(movement_dir.x, 0, movement_dir.y)).normalized()

	if direction == Vector3():
		motion = motion.move_toward(Vector3(), deceleration * delta)
	else:
		motion += (direction * acceleration * delta)
		motion = motion.limit_length(max_speed)
	
	_sum_active_forces()
	creature.velocity = motion + net_active_force
	_dampen_active_forces(delta)
	net_active_force = Vector3()
	
	_draw_movement_debug_overlay(direction)
	creature.move_and_slide()

There’s also this script, but it doesn’t change the dictionary’s key:value pairs but rather alters the arrays that the values are referencing, for instance appending a mod into an empty array or removing a mod from an array that’s already there.

extends Node

@export var player_inventory: PlayerInventory

@onready var string_to_modarray_dictionary: Dictionary = {
	"damage" : player_inventory.damage_mods,
	"speed" : player_inventory.speed_mods,
	"rotation_speed" : player_inventory.rotation_speed_mods,
	"defence" : player_inventory.defence_mods,
	"core_defence" : player_inventory.core_defence_mods,
	"max_health" : player_inventory.max_health_mods,
	"max_mana" : player_inventory.max_mana_mods,
	"mana_efficiency" : player_inventory.mana_efficiency_mods,
	"max_mana_based_damage" : player_inventory.max_mana_based_damage_mods,
	"max_mana_percent_on_deal_damage" : player_inventory.max_mana_percent_on_deal_damage_mods,
	"max_mana_percent_on_kill" : player_inventory.max_mana_percent_on_kill_mods,
	"combat_end_mana_recovery_percent" : player_inventory.combat_end_mana_recovery_percent_mods,
	"combat_end_coin_gain_percent" : player_inventory.combat_end_coin_gain_percent_mods
}

func _add_item_mods(item: ItemResource):
	for mod in item.mods:
		string_to_modarray_dictionary[mod.mod_type].append(mod)

func _remove_item_mods(item: ItemResource):
	for mod in item.mods:
		string_to_modarray_dictionary[mod.mod_type].erase(mod)

func _add_item(item: ItemResource):
	player_inventory.player_items.append(item)
	_add_item_mods(item)

func _remove_item(item: ItemResource):
	player_inventory.player_items.erase(item)
	_remove_item_mods(item)

func _print_mod_array(mod_array: Array[ModResource]):
	print_debug("start array")
	print()
	for mod in mod_array:
		print_debug(mod.mod_type)
		print_debug(mod.operation)
		print_debug(mod.value)
		print()
	print_debug("finish_array")
	print()

I don’t think _add_item_mods() or _remove_item_mods is being called between the second and third accessing of the dictionary, since the only time that they are being called is in the shop room and the dictionary is only being accessed in the combat room.

anyway, I think I can circumvent this problem by passing in the mod_array rather than passing in a string, so I wouldn’t need a dictionary since I already have different functions for different stats anyway. I think it’s just odd that this issue occurs in the first place.

put a print(string_to_modarray_backup) in the

if string_to_modarray_dictionary.is_empty():
	print(string_to_modarray_backup)
	string_to_modarray_dictionary= string_to_modarray_backup

to see if your backup is actually filled with anything real. the string_to_modarray_dictionary might not be fully made before the _ready is called. it could be a loading order issue.

I’ve already fixed the issue by passing in the mod_array rather than a string to _calculate_stat(). I’ve left the dictionaries in to see if they still get cleared but it seems that they no longer get cleared now that I’m not using them to obtain the mod_array in the _calculate_stat function. Still puzzled, but if it works, it works.

1 Like