Nested Data of a Unique Object Affected by Previous Value Assignments

Godot Version

4.5.1

Question

I am trying to make a placeholder battle calculator for armies engaging in battle. The Army contains a Units array that holds unique data to each UnitData Object, which contains a num_troops integer. When i loop through each and simply subtract 1 from the number of troops, it will subtract exactly the number of instances in that array from all instances. The other army is not affected by the other, so there is some pass by reference or pass by value issue, maybe in the for loop? See image:

func calculate_battle(attacking_army: Army, defending_army: Army) -> Army:
	var total_defenders = 0
	var total_attackers = 0
	
	# var defender_bonus_per_unit = 1.1
	
	var attackers_to_remove = []
	var defenders_to_remove = []
	
	for unit: UnitData in attacking_army.units:
		total_attackers += unit.num_troops
	for unit: UnitData in defending_army.units:
		total_defenders += unit.num_troops
	
	if total_defenders <= 0:
		print("attackers win! (no defenders)")
		return attacking_army
	var attacker_to_defender_ratio: float = float(total_attackers) / float(total_defenders)
	
	if attacker_to_defender_ratio < 1.1 and attacker_to_defender_ratio > 0.9:
		var i = attacking_army.units.size() - 1
		while i > 0:
			attacking_army.units[i].set_troops(attacking_army.units[i].num_troops - 1)
			print(attacking_army.units[i].num_troops)
			i -= 1
		#for unit: UnitData in attacking_army.units:
			#unit.set_troops(unit.num_troops - 1)
			#
			#if !unit.check_health():
				#attackers_to_remove.append(unit)

The commented out for loop code was what i thought caused an issue by passing by value, but apparently the docs say it should pass by ref anyway, but for redundancy i tried another way, which is to use a while loop and access each Object exclusively.

Strangely, the issue still arises. It will subtract the number of instances even though it should just subtract 1, from each of the num_troops of each object. (Also it only subtracts of the same Class, i proved this by using another class of another type, and the number of those will affect the number subtracted from their class objects only in the array and not affect the other type of unit.)

I printed the output and this is what comes out when all units start at 100:
image2

So each one of that type will all have 96 units left, instead of the 99 it should.

So when i have 4 units of a particular type it subtracts a total of 4 from those types, and if i have 2 units of another type, it subtracts a total of 2 from those types. So it somehow carries the same value to the next iteration just in that function call! Even though it should (in theory) be accessing the current troop count of each unit uniquely.

Not sure how this is even happening.

First, please post your code inside ```, one at the top and one of the bottom of your code and paste the code inside. Reading screenshots is annoying, and more so, if one wants to modify or notate your code the only way to do so is to re-type all your code.

Second, are you clear on what pass by reference means? It means that when you make a variable assignment, you are not assigning the value of the object to a new object (pass by value), but you are passing the memory address of the object - a reference to it. In other words, you are creating another name for the same object - you are not copying the values inside the object to a new object. You only have one object.

So in your code, your calculate_battle() function is taking its two arguments as Objects - specifically of type Army. All Objects are passed by Reference. Always. So, whatever changes you make to the attacking_army variable, you are directly making to the original Army object you passed from wherever you called this function.

Having said all that, you are using this code correctly. It should be passing by reference and should be modifying the value at the address it resides. You want it to change.

The solution is not in the code you posted. It is somewhere related to the code that calls the calculate_battle() function, and where those variables are declared. Somehow, you are creating all your armies to reference the same object. You need to create a new Army object for each army that you would like to have. Probably by doing something like var my_army = Army.new().

1 Like

Thank you for the reply!
Sorry about the screenshot, i edited the post.

I wasn’t sure if pass by reference was only referring to variable assignments, so i gotta do some homework. Seems like its not an issue here.

The Armies are unique and unaffected from this battle function from other armies. There is also a defending army that is edited in the same way, and has it’s own values as well. So each army is uniquely instantiated. I don’t include that in the code here as it is redundant and suffers the same type of problem, but its values are separate and unique. So if it starts with 100 troops per unit then i subtract 2 for the defending army that has 4 total units, all its units of that type will be subtracted a total of 8 from all 4.

The calculate_battle() func is only called by the attacking army when two armies meet (on area entered), passing in itself and the defending army. This ensures the function is called once.

func _on_army_selection_area_area_entered(area: Area2D) -> void:
    if area.is_in_group("actual_army_node") and area != $ActualArmy and is_moving:
		battle_option(area.get_parent(), self)
		


func battle_option(defending_army: Army, attacking_army: Army):
	if defending_army.faction.nation_name == MapPlayer.player_faction.nation_name or attacking_army.faction.nation_name == MapPlayer.player_faction.nation_name:
		print("Player army")
		attacking_army.stop_moving()
		defending_army.stop_moving()
	elif attacking_army.wishes_to_engage(defending_army) or defending_army.wishes_to_engage(attacking_army):
		
		if self == attacking_army:
			print("attacking")
			attacking_army.stop_moving()
			defending_army.stop_moving()
			Utils.calculate_battle(attacking_army, defending_army)

I know some code in here is bad practice lol but essentially the area is entered by another defending army and battle option passes in the defender and attacker, then passes it again in the Global autoload function Utils.calculate_battle()

Is the UnitData object a Resource object? If so, then you need to make sure that each time you use the resource you make it unique - otherwise you are passing it by Reference and that’s why all your numbers are the same.

Found it!

UnitData actually inherits from Node, but I guess I should convert that eventually to Resources.

Turns out in my recruitment system, I was adding the data directly into the Units array, and never actually called UnitData.new(), which creates unique ID’s, which explains why the objects in the array were all pointing to the same object.

So this was caused by all the units of a class to have the same ObjectID! Oops! Silly mistake! So basically I put in UnitData.new({“my_unit_properties”: are_added_here})

Thanks @dragonforge-dev

1 Like

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