You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			153 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			GDScript
		
	
		
		
			
		
	
	
			153 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			GDScript
		
	
| 
								 
											2 years ago
										 
									 | 
							
								@tool
							 | 
						||
| 
								 | 
							
								class_name RandomizedComposite extends Composite
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const WEIGHTS_PREFIX = "Weights/"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Sets a predicable seed
							 | 
						||
| 
								 | 
							
								@export var random_seed: int = 0:
							 | 
						||
| 
								 | 
							
									set(rs):
							 | 
						||
| 
								 | 
							
										random_seed = rs
							 | 
						||
| 
								 | 
							
										if random_seed != 0:
							 | 
						||
| 
								 | 
							
											seed(random_seed)
							 | 
						||
| 
								 | 
							
										else:
							 | 
						||
| 
								 | 
							
											randomize()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Wether to use weights for every child or not.
							 | 
						||
| 
								 | 
							
								@export var use_weights: bool:
							 | 
						||
| 
								 | 
							
									set(value):
							 | 
						||
| 
								 | 
							
										use_weights = value
							 | 
						||
| 
								 | 
							
										if use_weights:
							 | 
						||
| 
								 | 
							
											_update_weights(get_children())
							 | 
						||
| 
								 | 
							
											_connect_children_changing_signals()
							 | 
						||
| 
								 | 
							
										notify_property_list_changed()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var _weights: Dictionary
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func _ready():
							 | 
						||
| 
								 | 
							
									_connect_children_changing_signals()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func _connect_children_changing_signals():
							 | 
						||
| 
								 | 
							
									if not child_entered_tree.is_connected(_on_child_entered_tree):
							 | 
						||
| 
								 | 
							
										child_entered_tree.connect(_on_child_entered_tree)
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									if not child_exiting_tree.is_connected(_on_child_exiting_tree):
							 | 
						||
| 
								 | 
							
										child_exiting_tree.connect(_on_child_exiting_tree)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func get_shuffled_children() -> Array[Node]:
							 | 
						||
| 
								 | 
							
									var children_bag: Array[Node] = get_children().duplicate()
							 | 
						||
| 
								 | 
							
									if use_weights:
							 | 
						||
| 
								 | 
							
										var weights: Array[int]
							 | 
						||
| 
								 | 
							
										weights.assign(children_bag.map(func (child): return _weights[child.name]))
							 | 
						||
| 
								 | 
							
										children_bag.assign(_weighted_shuffle(children_bag, weights))
							 | 
						||
| 
								 | 
							
									else:
							 | 
						||
| 
								 | 
							
										children_bag.shuffle()
							 | 
						||
| 
								 | 
							
									return children_bag
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Returns a shuffled version of a given array using the supplied array of weights. 
							 | 
						||
| 
								 | 
							
								## Think of weights as the chance of a given item being the first in the array.
							 | 
						||
| 
								 | 
							
								func _weighted_shuffle(items: Array, weights: Array[int]) -> Array:
							 | 
						||
| 
								 | 
							
									if len(items) != len(weights):
							 | 
						||
| 
								 | 
							
										push_error("items and weights size mismatch: expected %d weights, got %d instead." % [len(items), len(weights)])
							 | 
						||
| 
								 | 
							
										return items
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									# This method is based on the weighted random sampling algorithm 
							 | 
						||
| 
								 | 
							
									# by Efraimidis, Spirakis; 2005. This runs in O(n log(n)).
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									# For each index, it will calculate random_value^(1/weight).
							 | 
						||
| 
								 | 
							
									var chance_calc = func(i): return [i, randf() ** (1.0 / weights[i])]
							 | 
						||
| 
								 | 
							
									var random_distribuition = range(len(items)).map(chance_calc)
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									# Now we just have to order by the calculated value, descending.
							 | 
						||
| 
								 | 
							
									random_distribuition.sort_custom(func(a, b): return a[1] > b[1])
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									return random_distribuition.map(func(dist): return items[dist[0]])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func _get_property_list():
							 | 
						||
| 
								 | 
							
									var properties = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if use_weights:
							 | 
						||
| 
								 | 
							
										for key in _weights.keys():
							 | 
						||
| 
								 | 
							
											properties.append({
							 | 
						||
| 
								 | 
							
												"name": WEIGHTS_PREFIX + key,
							 | 
						||
| 
								 | 
							
												"type": TYPE_INT,
							 | 
						||
| 
								 | 
							
												"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR,
							 | 
						||
| 
								 | 
							
												"hint": PROPERTY_HINT_RANGE,
							 | 
						||
| 
								 | 
							
												"hint_string": "1,100"
							 | 
						||
| 
								 | 
							
											})
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
									return properties
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
								func _set(property: StringName, value: Variant) -> bool:
							 | 
						||
| 
								 | 
							
									if property.begins_with(WEIGHTS_PREFIX):
							 | 
						||
| 
								 | 
							
										var weight_name = property.trim_prefix(WEIGHTS_PREFIX)
							 | 
						||
| 
								 | 
							
										_weights[weight_name] = value
							 | 
						||
| 
								 | 
							
										return true
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									return false
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func _get(property: StringName):
							 | 
						||
| 
								 | 
							
									if property.begins_with(WEIGHTS_PREFIX):
							 | 
						||
| 
								 | 
							
										var weight_name = property.trim_prefix(WEIGHTS_PREFIX)
							 | 
						||
| 
								 | 
							
										return _weights[weight_name]
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									return null
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func _update_weights(children: Array[Node]) -> void:
							 | 
						||
| 
								 | 
							
									var new_weights = {}
							 | 
						||
| 
								 | 
							
									for c in children:
							 | 
						||
| 
								 | 
							
										if _weights.has(c.name):
							 | 
						||
| 
								 | 
							
											new_weights[c.name] = _weights[c.name]
							 | 
						||
| 
								 | 
							
										else:
							 | 
						||
| 
								 | 
							
											new_weights[c.name] = 1
							 | 
						||
| 
								 | 
							
									_weights = new_weights
							 | 
						||
| 
								 | 
							
									notify_property_list_changed()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func _on_child_entered_tree(node: Node):
							 | 
						||
| 
								 | 
							
									_update_weights(get_children())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									var renamed_callable = _on_child_renamed.bind(node.name, node)
							 | 
						||
| 
								 | 
							
									if not node.renamed.is_connected(renamed_callable):
							 | 
						||
| 
								 | 
							
										node.renamed.connect(renamed_callable)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func _on_child_exiting_tree(node: Node):
							 | 
						||
| 
								 | 
							
									var renamed_callable = _on_child_renamed.bind(node.name, node)
							 | 
						||
| 
								 | 
							
									if node.renamed.is_connected(renamed_callable):
							 | 
						||
| 
								 | 
							
										node.renamed.disconnect(renamed_callable)
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									var children = get_children()
							 | 
						||
| 
								 | 
							
									children.erase(node)
							 | 
						||
| 
								 | 
							
									_update_weights(children)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func _on_child_renamed(old_name: String, renamed_child: Node):
							 | 
						||
| 
								 | 
							
									if old_name == renamed_child.name:
							 | 
						||
| 
								 | 
							
										return # No need to update the weights.
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									# Disconnect signal with old name...
							 | 
						||
| 
								 | 
							
									renamed_child.renamed\
							 | 
						||
| 
								 | 
							
											.disconnect(_on_child_renamed.bind(old_name, renamed_child))
							 | 
						||
| 
								 | 
							
									# ...and connect with the new name.
							 | 
						||
| 
								 | 
							
									renamed_child.renamed\
							 | 
						||
| 
								 | 
							
											.connect(_on_child_renamed.bind(renamed_child.name, renamed_child))
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									var original_weight = _weights[old_name]
							 | 
						||
| 
								 | 
							
									_weights.erase(old_name)
							 | 
						||
| 
								 | 
							
									_weights[renamed_child.name] = original_weight
							 | 
						||
| 
								 | 
							
									notify_property_list_changed()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func get_class_name() -> Array[StringName]:
							 | 
						||
| 
								 | 
							
									var classes := super()
							 | 
						||
| 
								 | 
							
									classes.push_back(&"RandomizedComposite")
							 | 
						||
| 
								 | 
							
									return classes
							 |