Compare commits

...

6 commits

16 changed files with 131 additions and 113 deletions

View file

@ -2,6 +2,8 @@ extends TextureRect
@export var colors : Array[Color] = [Color(0.3, 1, 1) * 0.7, Color(1, 0.6, 0.6) * 0.7, Color(1, 1, 1) * 0.7]
# Modulate the background with an interpolation of the colors in the list,
# depending on the players position on the earth.
func _process(_delta: float) -> void:
var index : int = floor((%Player.position.angle() + PI)/ TAU * colors.size())
var diff = (%Player.position.angle() + PI)/ TAU * colors.size() - index

View file

@ -15,31 +15,9 @@ func _ready() -> void:
if blocks_area:
Grid.buildings.append(self)
await get_tree().create_timer(0.2).timeout
if get_node_or_null("ObjectList") != null:
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)
# 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)
if "building" in object: object.building = self
# 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")
Grid.call_deferred("place_object_list", obj_list, self)
func overlaps(other : Building):
# heights don't overlap

View file

@ -102,7 +102,7 @@ func destroy_below():
func wave():
# Raise a wave from the water at the boundary of the screen and move it towards the center.
var angle = player.position.angle
var angle = player.position.angle()
var dir = randi_range(0, 1) * 2 - 1
var speed = 3000 / water.radius_base
water.create_tsunami(angle - speed * dir * TAU/30, dir, speed)

View file

@ -14,7 +14,7 @@
[ext_resource type="Script" uid="uid://cpaskpj67pnaj" path="res://enemies/boss/boss_spawner.gd" id="10_efxa6"]
[ext_resource type="PackedScene" uid="uid://cqn67nwyrtq3k" path="res://ui/journal/journal.tscn" id="10_w48qg"]
[ext_resource type="PackedScene" uid="uid://cpe4s6vsn0ujd" path="res://enemies/boss/boss.tscn" id="11_efxa6"]
[ext_resource type="Script" uid="uid://gul4u5tw1vxk" path="res://bg_image.gd" id="13_vivmo"]
[ext_resource type="Script" uid="uid://gul4u5tw1vxk" path="res://background/bg_image.gd" id="13_vivmo"]
[node name="main" type="Node2D"]

View file

@ -1,20 +1,24 @@
extends Control
@onready var item_list : ItemList = $ItemList
var item_list_no_dupes = []
# Add items from each pool to journal. TODO: Deal with multiplicities.
# Add items from each pool to journal.
func _ready() -> void:
await get_tree().create_timer(0.3).timeout
for item_scene in ItemSpawn.item_pool.common:
add_item_to_journal(item_scene.instantiate())
add_item_to_journal(item_scene)
for item_scene in ItemSpawn.item_pool.rare:
add_item_to_journal(item_scene.instantiate())
add_item_to_journal(item_scene)
for item_scene in ItemSpawn.item_pool.unique:
add_item_to_journal(item_scene.instantiate())
add_item_to_journal(item_scene)
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("journal"):
visible = not visible
func add_item_to_journal(item: Item):
item_list.add_item(item.item_name, item.icon)
# Adds an item to the journal if it was not yet added
func add_item_to_journal(item_scene):
if not item_list_no_dupes.has(item_scene):
item_list_no_dupes.append(item_scene)
var item = item_scene.instantiate()
item_list.add_item(item.item_name, item.icon)

View file

@ -1,40 +1,37 @@
class_name VineNode extends Node2D
@export var vine : Vine
class_name GridNode extends Node2D
# Setting location and offset automatically adjusts position
@export var location : Vector2 :
set(new_loc):
location = Global.vec_mod(new_loc, Grid.num_collumns, true)
@export var offset : Vector2
update_position()
@export var offset : Vector2 :
set(new_offset):
offset = new_offset
update_position()
@export var depth : int
func _get(property: StringName) -> Variant:
if property == "position":
update_position()
return position
if property == "global_position":
update_position()
return global_position
return null
# Setting the global position automatically adjusts location and offset
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
# Generates position from location and offset
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
# Constructor for Grid Nodes
func _init(_location = Vector2.ZERO, _offset = Vector2.ZERO):
location = _location
offset = _offset
depth = _depth
static func random_at(_location):
var rand_offset = Vector2(randf_range(60, 240), randf_range(90, 270))
return GridNode.new(_location, rand_offset)

View file

@ -1,14 +1,18 @@
class_name Bud extends VineNode
class_name Bud extends GridNode
signal opened
var img_path
@export var vine : Vine
# Triggers when a bud is hit. Spreads the vine, then removes the bud
func _on_opened():
$EnemyHurtbox.monitorable = false
$EnemyHurtbox.monitoring = false
$AnimatedSprite2D.play("open")
opened.emit()
spread()
await $AnimatedSprite2D.animation_finished
queue_free()
for child in get_children():
queue_free()
# Spread in all directions where the given vine is not yet present
func spread():
@ -19,5 +23,5 @@ func spread():
# Grow a vine
func grow_to_next_bud(dir):
var target_location = Global.vec_mod(location + dir, Grid.num_collumns, true)
var target = vine.random_vine_node_at(target_location)
var target = GridNode.random_at(target_location)
await vine.grow_vine_sequence(self, target, true, false)

View file

@ -1,4 +1,5 @@
class_name Petal extends VineNode
class_name Petal extends GridNode
@export var vine : Vine
@export var vine_resource : PackedScene
var activated = false

View file

@ -44,7 +44,7 @@ var vine_end_data = []
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:
if active_depth >= depth or fully_active:
sprite.texture = ResourceLoader.load(img_path_active)
else:
sprite.texture = ResourceLoader.load(img_path_inactive)
@ -59,7 +59,7 @@ func draw_vine(pos1 : Vector2, pos2 : Vector2, depth : int):
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 : VineNode, target: VineNode, grow_bud = false, quick_spawn = false):
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
@ -86,21 +86,29 @@ func grow_vine_sequence(start : VineNode, target: VineNode, grow_bud = false, qu
if not quick_spawn:
await get_tree().create_timer(0.2).timeout
if active_depth >= depth + num_segments:
# 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:
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):
@ -108,11 +116,15 @@ func generate_random_offsets(segment_count, 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):
@ -120,6 +132,7 @@ func generate_random_offsets(segment_count, max_second_derivative):
next_val += differences[i]
return ret
# Instantiates a bud
func spawn_bud(location, offset, depth):
var bud = bud_resource.instantiate()
bud.location = location
@ -128,9 +141,13 @@ func spawn_bud(location, offset, depth):
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():
update_active_depth()
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
@ -138,11 +155,13 @@ func update_active_depth():
if vine_data.size() > active_depth:
for sprite in vine_data[active_depth]:
sprite.texture = ResourceLoader.load(img_path_active)
for data in vine_end_data:
if data.depth == active_depth:
spawn_bud(data.location, data.offset, data.depth)
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
@ -150,27 +169,21 @@ func _enter_tree() -> void:
img_path_active = data.img_path
init_random()
func random_vine_node_at(location):
var offset = random_offset()
return VineNode.new(self, location, offset)
# 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)
for i in range(randi_range(2,2)):
# 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()
var branches_count = 0
for branch in range(ceil(randf() * 4)):
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():
if not (target_location.y <= 0 or target_location.y > Grid.max_bud_height):
branches_count += 1
var target = random_vine_node_at(target_location)
var target = GridNode.random_at(target_location)
grow_vine_sequence(end, target, true, true)
if i==0 and branches_count == 1:
vine_end_data.append(petal)
func random_offset():
return Vector2(randf_range(60, 240), randf_range(90, 270))

View file

@ -6,40 +6,43 @@ class_name BuildingGenerator extends Node
@export var only_on_first_load = false
static var first_load = true
func random_oppostite_collumn() -> int:
var playerpos = %Player.position
var player_angle = atan2(playerpos.y, playerpos.x)
var offset = randf_range(TAU/3, 2*TAU/3)
var spawn_angle = player_angle + offset
var collumn = int(spawn_angle / TAU * Grid.num_collumns + Grid.num_collumns) % Grid.num_collumns
# Determine the player's column, then choose one in the opposite third of the circle
func random_opposite_collumn() -> int:
var player_location = Grid.get_location_from_world_pos(%Player.position)
var offset = randi_range(int(Grid.num_collumns / 3.0), int(2 * Grid.num_collumns / 3.0))
var collumn = player_location.x + offset
return collumn
func random_collumn() -> int:
return randi_range(0, Grid.num_collumns - 1)
func _ready():
# The initial buildings in the main menu are only to be spawned upon first loading
if not (only_on_first_load and not first_load):
first_load = false
for i in range(initial_buildings):
# For each building, attempt spawns a few times.
# Spawns are not viable if they are within the player spawn protection
# or overlap with existing buildings
for j in range(spawn_attempts):
var collumn = random_collumn()
if initial_spawn_protection and 43 <= collumn and collumn <= 49:
continue
var building = randomize_building()
building.z_index = -2
var building = random_building()
if Grid.add_building_to_collumn(building, collumn):
break
# Each time the building generator's timer runs out, generate a new building
func _on_timer_timeout() -> void:
for i in range(spawn_attempts):
var collumn = random_oppostite_collumn()
var building : Building = randomize_building()
building.z_index = -2
var collumn = random_opposite_collumn()
var building : Building = random_building()
if Grid.add_building_to_collumn(building, collumn):
break
func randomize_building() -> Building:
# Picks a random building from the list. Sets the z_index.
func random_building() -> Building:
var index = randi() % Grid.packed_buildings.size()
return Grid.packed_buildings[index].instantiate()
var ret = Grid.packed_buildings[index].instantiate()
ret.z_index = -2
return ret

View file

@ -1,5 +0,0 @@
extends Node2D
@export var radius : float;
func _draw():
draw_circle(Vector2.ZERO, radius, Color.BLACK, true, -1.0, true)

View file

@ -1 +0,0 @@
uid://b5fhsy1xlreco

View file

@ -3,7 +3,7 @@ extends Node2D
@export var grass : PackedScene
func _ready() -> void:
ItemSpawn.item_pool = ResourceLoader.load("res://items/generic/item_pool.tres","",ResourceLoader.CACHE_MODE_IGNORE)
# Place grass
for column in range(Grid.num_collumns):
var grass_placed : Building = grass.instantiate()
grass_placed.location = Vector2(column, 0)

View file

@ -42,15 +42,33 @@ func add_building_to_collumn(building : Building, collumn : int):
building.free()
return false
add_child(building)
return true
# Adds children of given node to the grid and their building.
func place_object_list(obj_list : Node2D, building):
obj_list.reparent(self, false)
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(building.location, offset)
# The building remembers these objects: If it is destroyed, so are they.
if object is Platform or object is Trap or object is Item:
building.objects.append(object)
if "building" in object: object.building = self
# 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)
# Returns the global position for a given grid location and offset
func get_world_position (location: Vector2, offset: Vector2 = Vector2.ZERO) -> Vector2:
var height = ground_radius + location.y * cell_height - offset.y # currently assumes anchor is bottom left
var angle = (location.x + offset.x / cell_height) * TAU / num_collumns
return height * Vector2.from_angle(angle)
# Returns the grid location for a given global position
func get_location_from_world_pos(pos : Vector2):
var angle = fposmod(pos.angle(), TAU)
var x = floor(num_collumns * angle / TAU)
@ -58,6 +76,7 @@ func get_location_from_world_pos(pos : Vector2):
var y = ceil((height - ground_radius)/cell_height)
return Vector2(x, y)
# Returns the offset with respect to the grid location for a given global position
func get_offset_from_world_pos(pos : Vector2):
var angle = pos.angle()
var x = fposmod(num_collumns * angle / TAU, 1) * cell_height
@ -65,7 +84,10 @@ func get_offset_from_world_pos(pos : Vector2):
var y = fposmod(-(height - ground_radius)/cell_height, 1) * cell_height
return Vector2(x, y)
# Resets objects in the grids and the item pool.
# Also initializes vines_per_node.
func reset():
ItemSpawn.item_pool = ResourceLoader.load("res://items/generic/item_pool.tres","",ResourceLoader.CACHE_MODE_IGNORE)
for obj in get_children():
obj.free()
buildings = []
@ -76,22 +98,22 @@ func reset():
arr.append([])
vines_per_node.append(arr)
# Returns all vines present at given grid location.
func get_vines_at(location) -> Array:
location = Global.vec_mod(location, num_collumns, true)
return vines_per_node[location.x][location.y]
# Registers a vine at a given grid location.
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:
#
#
#
#for i in range(100):
#var test_building = packed_buildings[0].instantiate()
#var collumn = randi() % 60
#add_building_to_collumn(test_building, collumn)
# Calls _enter_grid on all children.
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")