diff --git a/buildings/building.gd b/buildings/building.gd index a1cc5d1..58e5d6d 100644 --- a/buildings/building.gd +++ b/buildings/building.gd @@ -17,15 +17,14 @@ func _ready() -> void: await get_tree().create_timer(0.2).timeout if get_node_or_null("ObjectList") != null: - var objects_to_be_placed = $ObjectList.get_children() - $ObjectList.reparent(get_tree().get_root().get_node("main"), false) + var obj_list = $ObjectList + obj_list.reparent(get_tree().get_root().get_node("main"), false) + await get_tree().create_timer(0.2).timeout + var objects_to_be_placed = obj_list.get_children() for object in objects_to_be_placed: var offset = object.global_position; object.global_position = Grid.get_world_position(location, offset) - # some objects, such as petals, require information about their position in the grid. - if "location" in object: object.location = Grid.get_location_from_world_pos(object.global_position) - if "offset" in object: - object.offset = Global.vec_mod(offset, 300) + # The building remembers these objects: If it is destroyed, so are they. if object is Platform or object is Trap or object is Item: objects.append(object) @@ -33,6 +32,14 @@ func _ready() -> void: # This scales platforms hoizontally to make sure they still form a floor without gaps. if(object.has_method("init_at_horizontal_distortion")): object.init_at_horizontal_distortion(object.position.length() / Grid.ground_radius) + grid_entrance_callback(object) + +func grid_entrance_callback(object : Node): + # Objects receive a callback when placed, starting from the deepest children + for child in object.get_children(): + grid_entrance_callback(child) + if object.has_method("_enter_grid"): + object.call_deferred("_enter_grid") func overlaps(other : Building): # heights don't overlap diff --git a/items/generic/item_spawn.gd b/items/generic/item_spawn.gd index 323f508..1d59d75 100644 --- a/items/generic/item_spawn.gd +++ b/items/generic/item_spawn.gd @@ -10,8 +10,10 @@ static var item_pool = ResourceLoader.load("res://items/generic/item_pool.tres", @export var unique_bonus_multiplier = .05 @export var rare_bonus_multiplier = 0 -@export var spawn_petal = false -@export var petal_scene : PackedScene = ResourceLoader.load("res://petal.tscn") +@export var spawn_petal = true +@export var petal_scene : PackedScene = ResourceLoader.load("res://vines_petals/petal.tscn") + +var packed_item_scene : PackedScene var remove_after_spawn = false @@ -42,12 +44,13 @@ func _ready(): # Pick a random pool and a random item from it, then remove it if unique. var pool = choose_pool() var index = randi_range(0, pool.size() - 1) - var packed_scene : PackedScene = pool[index] + packed_item_scene = pool[index] if remove_after_spawn: item_pool.unique.remove_at(index) - + # Place the item, possibly inside a petal - var object = packed_scene.instantiate() + if packed_item_scene == null: return + var object = packed_item_scene.instantiate() add_child.call_deferred(object) object.reparent.call_deferred(get_parent()) if spawn_petal: instantiate_petal(object) diff --git a/items/generic/item_spawn.tscn b/items/generic/item_spawn.tscn index 1fd4397..00b76e4 100644 --- a/items/generic/item_spawn.tscn +++ b/items/generic/item_spawn.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=4 format=3 uid="uid://xj0of571aur1"] [ext_resource type="Script" uid="uid://b8em61mqgdi58" path="res://items/generic/item_spawn.gd" id="1_ms6tn"] -[ext_resource type="PackedScene" uid="uid://bhhhvaqhm3ctc" path="res://petal.tscn" id="2_5to52"] +[ext_resource type="PackedScene" uid="uid://bhhhvaqhm3ctc" path="res://vines_petals/petal.tscn" id="2_5to52"] [ext_resource type="PackedScene" uid="uid://chs0u61f45nau" path="res://utils/earth_aligner.tscn" id="3_5pwuf"] [node name="ItemSpawn" type="Node2D"] diff --git a/player/player.gd b/player/player.gd index 83032f9..6e34a3a 100644 --- a/player/player.gd +++ b/player/player.gd @@ -236,6 +236,6 @@ func play_double_jump_animation() -> void: # If there is any vine nearby, gain the corresponding statusses. func update_vine_statuses(): var location = Grid.get_location_from_world_pos(global_position) - for vine : Vine in Grid.vines_per_node[location.x][location.y]: + for vine : Vine in Grid.get_vines_at(location): if vine.active_depth > 0: #TODO: Properly manage procedural activation Status.apply(vine.status_name, self, 0.1, vine.status_params) diff --git a/traps/morning_star.gd b/traps/morning_star.gd index 43a7e24..ce3913d 100644 --- a/traps/morning_star.gd +++ b/traps/morning_star.gd @@ -6,7 +6,7 @@ var enemy_damage = 10 @onready var dmg_id = Global.next_dmg_id var id_refreshing = false -func _ready() -> void: +func _enter_grid() -> void: # Randomize starting position and rotation direction rotate(randf() * TAU) angular_speed = angular_speed * (2 * randi_range(0,1) - 1) diff --git a/utils/earth_aligner.gd b/utils/earth_aligner.gd index fb3f54a..5bc7312 100644 --- a/utils/earth_aligner.gd +++ b/utils/earth_aligner.gd @@ -17,7 +17,7 @@ var left : Vector2 : get(): return global_from_local(Vector2.LEFT) -func _ready() -> void: +func _enter_grid() -> void: align() func _process(_delta: float) -> void: diff --git a/utils/global.gd b/utils/global.gd index a74e000..fc1526d 100644 --- a/utils/global.gd +++ b/utils/global.gd @@ -7,5 +7,7 @@ static var next_dmg_id : int = 0 : return next_dmg_id # Entrywise modulo -static func vec_mod(vec : Vector2, modulus : float): - return Vector2(fposmod(vec.x, modulus), fposmod(vec.y, modulus)) +static func vec_mod(vec : Vector2, modulus : float, only_x = false, only_y = false): + return Vector2( + fposmod(vec.x, modulus) if not only_y else vec.x, + fposmod(vec.y, modulus) if not only_x else vec.y) diff --git a/vines_petals/bud.gd b/vines_petals/bud.gd index 5c31f3c..db9a246 100644 --- a/vines_petals/bud.gd +++ b/vines_petals/bud.gd @@ -1,7 +1,6 @@ class_name Bud extends VineNode signal opened var img_path -@export var depth : int # Triggers when a bud is hit. Spreads the vine, then removes the bud func _on_opened(): @@ -14,14 +13,11 @@ func _on_opened(): # Spread in all directions where the given vine is not yet present func spread(): for dir in [Vector2.UP, Vector2.DOWN, Vector2.RIGHT, Vector2.LEFT]: - if not vine.vine_locations.has(Global.vec_mod(location + dir, Grid.num_collumns)): + if not vine.vine_locations.has(Global.vec_mod(location + dir, Grid.num_collumns, true)): grow_to_next_bud(dir) # Grow a vine func grow_to_next_bud(dir): - var target_offset = vine.random_offset() - var target = Global.vec_mod(location + dir, Grid.num_collumns) - var pos1 = Grid.get_world_position(location, offset) - var pos2 = Grid.get_world_position(target, target_offset) - var num_seg = floor((pos1-pos2).length() / 96) - await vine.grow_vine_sequence(location, offset, target, target_offset, num_seg, depth, true, false) + var target_location = Global.vec_mod(location + dir, Grid.num_collumns, true) + var target = vine.random_vine_node_at(target_location) + await vine.grow_vine_sequence(self, target, true, false) diff --git a/petal.gd b/vines_petals/petal.gd similarity index 52% rename from petal.gd rename to vines_petals/petal.gd index 71e9c8f..f83b23d 100644 --- a/petal.gd +++ b/vines_petals/petal.gd @@ -1,9 +1,8 @@ -class_name Petal extends Node2D -@onready var location : Vector2 = Grid.get_location_from_world_pos(global_position) -@onready var offset : Vector2 = Grid.get_offset_from_world_pos(global_position) +class_name Petal extends VineNode @export var vine_resource : PackedScene -var vine : Vine var activated = false + +# Adding an item here automatically locks it inside the petal var item : Item: set(item_in): item = item_in @@ -12,15 +11,14 @@ var item : Item: item.monitorable = false item.modulate = Color(1,1,1,0.8) -func _ready() -> void: - await get_tree().create_timer(1).timeout +# Upon being placed inside a building, create a vine +func _enter_grid() -> void: + depth = 0 vine = vine_resource.instantiate() - vine.petal_location = Global.vec_mod(location, Grid.num_collumns) - vine.petal_offset = offset + vine.petal = self get_parent().call_deferred("add_child",vine) - await get_tree().create_timer(1).timeout - vine.init_random() +# Upon interaction, release the item and activate the vine func _on_interaction() -> void: activated = true vine.activate() diff --git a/petal.gd.uid b/vines_petals/petal.gd.uid similarity index 100% rename from petal.gd.uid rename to vines_petals/petal.gd.uid diff --git a/petal.tscn b/vines_petals/petal.tscn similarity index 97% rename from petal.tscn rename to vines_petals/petal.tscn index b2e6e2a..7972f75 100644 --- a/petal.tscn +++ b/vines_petals/petal.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=7 format=3 uid="uid://bhhhvaqhm3ctc"] [ext_resource type="Texture2D" uid="uid://d15c1exwtp7sc" path="res://vines_petals/bud_open.png" id="1_05pvv"] -[ext_resource type="Script" uid="uid://coi26l4ndnaw0" path="res://petal.gd" id="1_ybtdj"] +[ext_resource type="Script" uid="uid://coi26l4ndnaw0" path="res://vines_petals/petal.gd" id="1_ybtdj"] [ext_resource type="PackedScene" uid="uid://b7o82cdfwuqd1" path="res://vines_petals/vine.tscn" id="2_4btcp"] [ext_resource type="PackedScene" uid="uid://ds21d77ruuk4y" path="res://utils/interactable_area.tscn" id="2_ybtdj"] [ext_resource type="PackedScene" uid="uid://chs0u61f45nau" path="res://utils/earth_aligner.tscn" id="5_uxqeq"] diff --git a/vines_petals/vine.gd b/vines_petals/vine.gd index 03e0648..3153b15 100644 --- a/vines_petals/vine.gd +++ b/vines_petals/vine.gd @@ -1,11 +1,16 @@ class_name Vine extends Node2D -@export var petal_location : Vector2 -@export var petal_offset : Vector2 +# 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", @@ -17,20 +22,25 @@ const status_data = [ "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 = [] -var max_depth = 150 + +# List of "leaf locations" of the vine tree var vine_end_data = [] -func _ready() -> 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 - +# 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) @@ -38,6 +48,7 @@ func draw_vine(pos1 : Vector2, pos2 : Vector2, depth : int): 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) @@ -47,37 +58,47 @@ func draw_vine(pos1 : Vector2, pos2 : Vector2, depth : int): sprite.scale.x = 3 sprite.z_index = -1 -func grow_vine_sequence(location1 : Vector2, offset1 : Vector2, location2: Vector2, offset2 : Vector2, num_segments: int, depth: int, grow_bud = false, quick_spawn = false): - depth = min(depth, max_depth) - var pos1 = Grid.get_world_position(location1, offset1) - var pos2 = Grid.get_world_position(location2, offset2) - var positions = [] - positions.append(pos1) +# Grows a sequence of vine segments from grid position 1 to grid position 2, starting at given depth. +func grow_vine_sequence(start : VineNode, target: VineNode, grow_bud = false, quick_spawn = false): + var depth = min(start.depth, max_depth) - var segment_length = (pos1 - pos2).length() / num_segments + # 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 * pos2 + (1 - t) * pos1 - var offset = offsets[i + 1] * (pos2 - pos1).normalized().rotated(PI/2) + 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(pos2) + 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 active_depth >= depth + num_segments: - if grow_bud and location2.y > 0 and location2.y <= Grid.max_bud_height: - spawn_bud(location2, offset2, depth + num_segments) + 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: - if location2.y > 0 and location2.y <= Grid.max_bud_height: + 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 == location1: + if vine_end_data[i].location == start.location: vine_end_data.remove_at(i) break - vine_end_data.append({"location": location2, "offset": offset2, "depth": depth+num_segments}) - Grid.vines_per_node[location2.x][location2.y].append(self) - vine_locations.append(Global.vec_mod(location2, Grid.num_collumns)) + target.depth = depth + num_segments + vine_end_data.append(target) + Grid.add_vine_to(self, target.location) + vine_locations.append(Global.vec_mod(target.location, Grid.num_collumns, true)) func generate_random_offsets(segment_count, max_second_derivative): var differences = [] @@ -122,26 +143,34 @@ func update_active_depth(): spawn_bud(data.location, data.offset, data.depth) update_active_depth() +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() + +func random_vine_node_at(location): + var offset = random_offset() + return VineNode.new(self, location, offset) + func init_random(): - Grid.vines_per_node[petal_location.x][petal_location.y].append(self) - vine_locations.append(petal_location) - vine_end_data.append({"location": petal_location, "offset": petal_offset, "depth": 0}) + Grid.add_vine_to(self, petal.location) + vine_locations.append(petal.location) + vine_end_data.append(petal) for i in range(randi_range(2,2)): var end = vine_end_data.pick_random() var branches_count = 0 for branch in range(ceil(randf() * 4)): var dir = [Vector2.UP, Vector2.DOWN, Vector2.RIGHT, Vector2.LEFT].pick_random() - var target = Global.vec_mod(end.location + dir, Grid.num_collumns) - if target.y <= Grid.max_bud_height + 1 and Grid.vines_per_node[target.x][target.y].is_empty(): - if not (target.y <= 0 or target.y > Grid.max_bud_height): + 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(): + if not (target_location.y <= 0 or target_location.y > Grid.max_bud_height): branches_count += 1 - var target_offset = random_offset() - var pos1 = Grid.get_world_position(end.location, end.offset) - var pos2 = Grid.get_world_position(target, target_offset) - var num_seg = floor((pos1-pos2).length() / 96) - grow_vine_sequence(end.location, end.offset, target, target_offset, num_seg, end.depth, true, true) + var target = random_vine_node_at(target_location) + grow_vine_sequence(end, target, true, true) if i==0 and branches_count == 1: - vine_end_data.append({"location": petal_location, "offset": petal_offset, "depth": 0}) + vine_end_data.append(petal) func random_offset(): return Vector2(randf_range(60, 240), randf_range(90, 270)) diff --git a/vines_petals/vine_node.gd b/vines_petals/vine_node.gd index d3c2a52..a295d91 100644 --- a/vines_petals/vine_node.gd +++ b/vines_petals/vine_node.gd @@ -1,8 +1,40 @@ class_name VineNode extends Node2D - + @export var vine : Vine -@export var location : Vector2 +@export var location : Vector2 : + set(new_loc): + location = Global.vec_mod(new_loc, Grid.num_collumns, true) @export var offset : Vector2 +@export var depth : int -func _ready() -> void: - position = Grid.get_world_position(location, offset) +func _get(property: StringName) -> Variant: + if property == "position": + update_position() + return position + if property == "global_position": + update_position() + return global_position + return null + +func _set(property: StringName, value: Variant) -> bool: + if property == "global_position": + location = Grid.get_location_from_world_pos(value) + offset = Grid.get_offset_from_world_pos(value) + update_position() + return true + if property == "position": + update_position() + return false + return false + +func update_position(): + global_position = Grid.get_world_position(location, offset) + +func _enter_grid() -> void: + update_position() + +func _init(_vine = null, _location = Vector2.ZERO, _offset = Vector2.ZERO, _depth = 0): + vine = _vine + location = _location + offset = _offset + depth = _depth diff --git a/world/grid.gd b/world/grid.gd index b39d05c..dcf1abb 100644 --- a/world/grid.gd +++ b/world/grid.gd @@ -11,7 +11,7 @@ extends Node2D var buildings : Array[Building] = [] var max_bud_height = 8 -var vines_per_node : Array = [] +var vines_per_node : Array func _ready() -> void: reset() @@ -75,7 +75,17 @@ func reset(): for j in range(max_bud_height + 2): arr.append([]) vines_per_node.append(arr) - + +func get_vines_at(location) -> Array: + location = Global.vec_mod(location, num_collumns, true) + return vines_per_node[location.x][location.y] + +func add_vine_to(vine, location) -> void: + if location.y > max_bud_height + 1 or location.y < 0: + return + location = Global.vec_mod(location, num_collumns, true) + vines_per_node[location.x][location.y].append(vine) + # for testing #func _ready() -> void: #