class_name Vine extends Node2D # Grid position data of the petal @export var petal : Petal # Locations and offsets of all vine segments @export var vine_locations : Array[Vector2] @export var bud_resource : PackedScene # Paths for vine segment sprites while active/inactive var img_path_inactive = "res://vines_petals/vine_inactive.png" @export var img_path_active : String # The table from which to choose a status effect at random, including the corresponding vine color const status_data = [ { "name": "Slow", "params" : {}, "img_path": "res://vines_petals/vine_active_green.png" }, { "name": "Vulnerable", "params" : {}, "img_path": "res://vines_petals/vine_active_purple.png" },] # The chosen status and its data var status_name : String var status_params : Dictionary = {} # To which depths vine segments and nodes are active. # When max depth is reached, everything is active from then on. var active_depth = -1 var fully_active = false var max_depth = 50 # Array containings lists of sprites, using their depths as index var vine_data = [] # List of "leaf locations" of the vine tree var vine_end_data = [] # Places a sprite for a vine segment from pos1 to pos2 at a given depth, # which might be activated already, depending on the active depth. func draw_vine(pos1 : Vector2, pos2 : Vector2, depth : int): var sprite = Sprite2D.new() get_tree().get_root().get_node("main").add_child(sprite) if active_depth >= depth or fully_active: sprite.texture = ResourceLoader.load(img_path_active) else: sprite.texture = ResourceLoader.load(img_path_inactive) # If neccessary, extend vine_data to the current depth while depth + 1 > vine_data.size(): vine_data.append([]) vine_data[depth].append(sprite) sprite.position = (pos1 + pos2)/2.0 sprite.rotation = (pos1 - pos2).angle() + PI/2 sprite.scale *= (pos1 - pos2).length() * 1 / sprite.texture.get_width() sprite.scale.x = 3 sprite.z_index = -1 # Grows a sequence of vine segments from grid position 1 to grid position 2, starting at given depth. func grow_vine_sequence(start : GridNode, target: GridNode, grow_bud = false, quick_spawn = false): var depth = min(start.depth, max_depth) # Calculate the number and length of segments from the distance of source and target var dist = (start.position - target.position).length() var num_segments = floor(dist / 96) var segment_length = dist / num_segments # Build a list of vine segment positions placed horizontally equidistant along the line from the source # to the target, with vertical offset having controlled 2nd derivative to prevent sharp edges. var positions = [] positions.append(start.position) var offsets = generate_random_offsets(num_segments, 0.6 * segment_length) for i in range(num_segments - 1): var t = (i + 1) * 1.0/num_segments var center = t * target.position + (1 - t) * start.position var offset = offsets[i + 1] * (target.position - start.position).normalized().rotated(PI/2) positions.append(center + offset) positions.append(target.position) # Draw vine segments along the positions determined previously. # Do so slowly unless quick_spawn is specified. for i in range(num_segments): draw_vine(positions[i], positions[i+1], depth + i + 1) if not quick_spawn: await get_tree().create_timer(0.2).timeout # If growing while active, place buds if active_depth >= depth + num_segments or fully_active: if grow_bud and target.location.y > 0 and target.location.y <= Grid.max_bud_height: spawn_bud(target.location, target.offset, depth + num_segments) else: # Otherwise, remember the spot to spawn a bud later. # Further, note that the previous location is no longer a leaf unless it is the petal. if target.location.y > 0 and target.location.y <= Grid.max_bud_height: for i in range(vine_end_data.size()): if vine_end_data[i].location == start.location and not vine_end_data[i] is Petal: vine_end_data.remove_at(i) break target.depth = depth + num_segments vine_end_data.append(target) # Register the new vine segment in the grid Grid.add_vine_to(self, target.location) vine_locations.append(Global.vec_mod(target.location, Grid.num_collumns, true)) # Generates a random function on (segment_count - 1) # many grid points with bounded second derivative func generate_random_offsets(segment_count, max_second_derivative): # First, randomize the derivative of the desired function var differences = [] var last_diff = 0 for i in range(segment_count): var new_diff = last_diff + randf_range(-max_second_derivative, max_second_derivative) differences.append(new_diff) last_diff = new_diff var sum = 0.0 # Shift that derivative by a constant to add up to 0 for i in range(segment_count): sum += differences[i] var correction = - sum / segment_count for i in range(segment_count): differences[i] += correction # Return the partial sums over the derivative constructed above var ret = [] var next_val = 0 for i in range(segment_count): ret.append(next_val) next_val += differences[i] return ret # Instantiates a bud func spawn_bud(location, offset, depth): var bud = bud_resource.instantiate() bud.location = location bud.offset = offset bud.vine = self bud.depth = depth get_tree().get_root().get_node("main").add_child(bud) # Upon activation start the process of slowly increasing the active depth func activate(): if active_depth < 0: update_active_depth() # Progressively activate the vine by retexturing its sprites and spawning buds if neccessary # Once max_depth is reached, fully activate the vine func update_active_depth(): if active_depth < max_depth: await get_tree().create_timer(0.15).timeout active_depth += 1 if vine_data.size() > active_depth: for sprite in vine_data[active_depth]: sprite.texture = ResourceLoader.load(img_path_active) for node in vine_end_data: if node.depth == active_depth and not node is Petal: spawn_bud(node.location, node.offset, node.depth) update_active_depth() else: fully_active = true # Upon entering the scene, select the applied status, then spawn the inactive vine func _enter_tree() -> void: var data : Dictionary = status_data.pick_random() status_name = data.name status_params = data.params if data.has("params") else {} img_path_active = data.img_path init_random() # Initializes a random vine func init_random(): # First, include the petal Grid.add_vine_to(self, petal.location) vine_locations.append(petal.location) vine_end_data.append(petal) # Attempt to grow a new vine for a total 6 - 12 times var grow_attempts = randi_range(6,12) while grow_attempts > 0: # Attempt to grow from a random end to 1 - 4 random directions var end = vine_end_data.pick_random() for branch in range(min(ceil(randf() * 4), grow_attempts)): grow_attempts -= 1 var dir = [Vector2.UP, Vector2.DOWN, Vector2.RIGHT, Vector2.LEFT].pick_random() var target_location = Global.vec_mod(end.location + dir, Grid.num_collumns, true) if target_location.y <= Grid.max_bud_height + 1 and Grid.get_vines_at(target_location).is_empty(): var target = GridNode.random_at(target_location) grow_vine_sequence(end, target, true, true)