Custom resource - export weighted probability for array?

Godot Version

4.2.2

Question

Context: I’m making a loot table to control item drops from a central location. Basically, I have an array of custom resource called “ItemPickupLocation” that each in turn have an array of “Item” resources for possible item drops. Pretty simple stuff.

I’d like to be able to control the probability of the drops of each Item. So for example, if I have an array [Item1, Item2, Item3] by default each would have a 33.333% chance of being returned, but by changing a variable in ItemPickupLocation I could make Item1 have a 90%, Item2 have a 8%, and Item3 a 2%.
The nearest solution I’ve thought of is creating an Array[float], manually adding in numbers for each Item, and then doing some math to normalize the floats to become percentages. This seems particularly prone to user error, makes editing the loot table later a hassle, and is generally clunky. (Plus it doesn’t allow for “guaranteed drops” but that’s probably beyond the scope of this question.)

Is there a simpler way to weigh each Item’s probability of being returned?

Hi!

The nearest solution I’ve thought of is creating an Array[float], manually adding in numbers for each Item, and then doing some math to normalize the floats to become percentages.

Sorry if this doesn’t help you, but this would be the way I’d do it. Heres the code I usually use for this:

static func normalize_probabilities(probs : Array[float]) -> Array[float]:
	var sum: float = probs.reduce(func(i: float, accu:float) -> float: return accu + i)

	var normalized_probs: Array[float] = []
	for i in probs.size():
		normalized_probs.append(probs[i] / sum)
	
	return normalized_probs
	
static func pick_index_by_probabilities(normalized_probs: Array[float]) -> int:
	var rand_value: float = randf()
	var accu_sum: float = 0;
	for i in normalized_probs.size():
		accu_sum = accu_sum + normalized_probs[i]
		if rand_value < accu_sum:
			return i
		
	return -1

And an example:

@export var drop_probabilities: Array[float] = [0.1, 0.8, 0.3]

func get_next_drop() -> int:
	var norm_probs: Array[float] = normalize_probabilities(drop_probabilities)
	var chosen_index: int = pick_index_by_probabilities(norm_probs)

	# lets update the probabilities
	drop_probabilities[chosen_index] = max(drop_probabilities[chosen_index] - 0.1, 0.05)
	norm_probs = normalize_probabilities(drop_probabilities)

	return chosen_index	

Note that you would probably use a dictionary for the drop probabilities so you can return something meaningful instead of an index.

If you wanted to have better control over the loot table, you could also make it a tool script and call the normalize_probabilities() right after changing the inputs.

I’m also curious if someone has a better solution

edit: changed the probabilities update so that it does decreases the probability of the item that was just dropped, instead of increasing it

1 Like

Thanks so much! For some reason I didn’t realize @tool worked with resources, I think I got it mixed up with another annotation. This works out quite well, thanks! :smiley:

1 Like

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