Compare commits
No commits in common. "42ea2d3d751d8979cbdd26fc8a0abb2e6dd8e92e" and "d3a46ee77904dde749b64ac7a4a166fe2d40d075" have entirely different histories.
42ea2d3d75
...
d3a46ee779
67 changed files with 410 additions and 946 deletions
5
background/background.gd
Normal file
5
background/background.gd
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
extends Node2D
|
||||||
|
|
||||||
|
#func _process(delta: float) -> void:
|
||||||
|
#for p : Parallax2D in get_children():
|
||||||
|
#p.scroll_offset += Vector2.RIGHT * 100 * delta * p.scroll_scale
|
||||||
1
background/background.gd.uid
Normal file
1
background/background.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://c6q1tgl7kag67
|
||||||
|
|
@ -4,42 +4,33 @@ class_name Building extends Node2D
|
||||||
@export var dimension : Vector2i = Vector2(2, 1) # same as above
|
@export var dimension : Vector2i = Vector2(2, 1) # same as above
|
||||||
@export var blocks_area = true
|
@export var blocks_area = true
|
||||||
|
|
||||||
|
@onready var grid : Grid = get_parent()
|
||||||
|
|
||||||
var objects = []
|
var objects = []
|
||||||
var destroyed = false
|
var destroyed = false
|
||||||
|
|
||||||
# make sure location is set before adding a building to the scene tree
|
# make sure location is set before adding a building to the scene tree
|
||||||
# also make sure that the buildings are instantiated as children of the grid
|
# also make sure that the buildings are instantiated as children of the grid
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
global_position = Grid.get_world_position(location)
|
assert(grid != null)
|
||||||
|
position = grid.get_world_position(location)
|
||||||
if blocks_area:
|
if blocks_area:
|
||||||
Grid.buildings.append(self)
|
grid.buildings.append(self)
|
||||||
|
|
||||||
await get_tree().create_timer(0.2).timeout
|
await get_tree().create_timer(0.2).timeout
|
||||||
if get_node_or_null("ObjectList") != null:
|
if get_node_or_null("EnemyList") != null:
|
||||||
var obj_list = $ObjectList
|
var enemies = $EnemyList.get_children()
|
||||||
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.
|
$EnemyList.reparent(get_tree().get_root().get_node("main"), false)
|
||||||
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):
|
for enemy in enemies:
|
||||||
# Objects receive a callback when placed, starting from the deepest children
|
var oldpos = enemy.position;
|
||||||
for child in object.get_children():
|
enemy.position = grid.get_world_position(location, oldpos)
|
||||||
grid_entrance_callback(child)
|
if enemy is Platform or enemy is Trap or enemy is Item:
|
||||||
if object.has_method("_enter_grid"):
|
objects.append(enemy)
|
||||||
object.call_deferred("_enter_grid")
|
if "building" in enemy: enemy.building = self
|
||||||
|
if(enemy.has_method("init_at_horizontal_distortion")):
|
||||||
|
enemy.init_at_horizontal_distortion(enemy.position.length() / grid.ground_radius)
|
||||||
|
|
||||||
func overlaps(other : Building):
|
func overlaps(other : Building):
|
||||||
# heights don't overlap
|
# heights don't overlap
|
||||||
|
|
@ -47,17 +38,16 @@ func overlaps(other : Building):
|
||||||
if location.y + dimension.y <= other.location.y: return false # other is above
|
if location.y + dimension.y <= other.location.y: return false # other is above
|
||||||
|
|
||||||
# angles overlap. We can now assume heights overlap
|
# angles overlap. We can now assume heights overlap
|
||||||
var relative_other_loc = (other.location.x - location.x + Grid.num_collumns) % Grid.num_collumns
|
var relative_other_loc = (other.location.x - location.x + grid.num_collumns) % grid.num_collumns
|
||||||
if dimension.x > relative_other_loc: return true
|
if dimension.x > relative_other_loc: return true
|
||||||
if relative_other_loc + other.dimension.x > Grid.num_collumns: return true
|
if relative_other_loc + other.dimension.x > grid.num_collumns: return true
|
||||||
|
|
||||||
# If we get here, angles do not overlap
|
# If we get here, angles do not overlap
|
||||||
return false
|
return false
|
||||||
|
|
||||||
func destroy():
|
func destroy():
|
||||||
if not destroyed:
|
if not destroyed:
|
||||||
# On destruction, the building is removed from the list and destroyed together with all its objects.
|
grid.buildings.remove_at(grid.buildings.find(self))
|
||||||
Grid.buildings.remove_at(Grid.buildings.find(self))
|
|
||||||
for object in objects:
|
for object in objects:
|
||||||
if object != null and not ("collected" in object and object.collected):
|
if object != null and not ("collected" in object and object.collected):
|
||||||
object.queue_free()
|
object.queue_free()
|
||||||
|
|
|
||||||
|
|
@ -31,36 +31,36 @@ texture = ExtResource("5_012sa")
|
||||||
script = ExtResource("4_505qw")
|
script = ExtResource("4_505qw")
|
||||||
grid_offset = Vector2i(0, -1)
|
grid_offset = Vector2i(0, -1)
|
||||||
|
|
||||||
[node name="ObjectList" type="Node2D" parent="."]
|
[node name="EnemyList" type="Node2D" parent="."]
|
||||||
|
|
||||||
[node name="MorningStar" parent="ObjectList" instance=ExtResource("6_qwyfo")]
|
[node name="MorningStar" parent="EnemyList" instance=ExtResource("6_qwyfo")]
|
||||||
position = Vector2(238, -149)
|
position = Vector2(238, -149)
|
||||||
|
|
||||||
[node name="Platform" parent="ObjectList" instance=ExtResource("8_evf2t")]
|
[node name="Platform" parent="EnemyList" instance=ExtResource("8_evf2t")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(75, -295)
|
position = Vector2(75, -295)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform2" parent="ObjectList" instance=ExtResource("8_evf2t")]
|
[node name="Platform2" parent="EnemyList" instance=ExtResource("8_evf2t")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(225, -295)
|
position = Vector2(225, -295)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform3" parent="ObjectList" instance=ExtResource("8_evf2t")]
|
[node name="Platform3" parent="EnemyList" instance=ExtResource("8_evf2t")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(375, -295)
|
position = Vector2(375, -295)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform4" parent="ObjectList" instance=ExtResource("8_evf2t")]
|
[node name="Platform4" parent="EnemyList" instance=ExtResource("8_evf2t")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(525, -295)
|
position = Vector2(525, -295)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform5" parent="ObjectList" instance=ExtResource("8_evf2t")]
|
[node name="Platform5" parent="EnemyList" instance=ExtResource("8_evf2t")]
|
||||||
position = Vector2(431, -150)
|
position = Vector2(431, -150)
|
||||||
scale = Vector2(2.688, 3)
|
scale = Vector2(2.688, 3)
|
||||||
|
|
||||||
[node name="BearTrap" parent="ObjectList" instance=ExtResource("9_c7qov")]
|
[node name="BearTrap" parent="EnemyList" instance=ExtResource("9_c7qov")]
|
||||||
position = Vector2(270, -9)
|
position = Vector2(270, -9)
|
||||||
|
|
||||||
[node name="DebugSprite" type="Sprite2D" parent="."]
|
[node name="DebugSprite" type="Sprite2D" parent="."]
|
||||||
|
|
|
||||||
|
|
@ -31,36 +31,36 @@ texture = ExtResource("5_hu6aj")
|
||||||
script = ExtResource("4_2shqy")
|
script = ExtResource("4_2shqy")
|
||||||
grid_offset = Vector2i(0, -1)
|
grid_offset = Vector2i(0, -1)
|
||||||
|
|
||||||
[node name="ObjectList" type="Node2D" parent="."]
|
[node name="EnemyList" type="Node2D" parent="."]
|
||||||
|
|
||||||
[node name="Platform" parent="ObjectList" instance=ExtResource("8_y6yyb")]
|
[node name="Platform" parent="EnemyList" instance=ExtResource("8_y6yyb")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(75, -295)
|
position = Vector2(75, -295)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform2" parent="ObjectList" instance=ExtResource("8_y6yyb")]
|
[node name="Platform2" parent="EnemyList" instance=ExtResource("8_y6yyb")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(225, -295)
|
position = Vector2(225, -295)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform3" parent="ObjectList" instance=ExtResource("8_y6yyb")]
|
[node name="Platform3" parent="EnemyList" instance=ExtResource("8_y6yyb")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(375, -295)
|
position = Vector2(375, -295)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform4" parent="ObjectList" instance=ExtResource("8_y6yyb")]
|
[node name="Platform4" parent="EnemyList" instance=ExtResource("8_y6yyb")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(525, -295)
|
position = Vector2(525, -295)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform5" parent="ObjectList" instance=ExtResource("8_y6yyb")]
|
[node name="Platform5" parent="EnemyList" instance=ExtResource("8_y6yyb")]
|
||||||
position = Vector2(300, -150)
|
position = Vector2(300, -150)
|
||||||
scale = Vector2(2.688, 3)
|
scale = Vector2(2.688, 3)
|
||||||
|
|
||||||
[node name="ItemSpawn" parent="ObjectList" instance=ExtResource("9_jmdjr")]
|
[node name="ItemSpawn" parent="EnemyList" instance=ExtResource("9_jmdjr")]
|
||||||
position = Vector2(300, -200)
|
position = Vector2(300, -200)
|
||||||
|
|
||||||
[node name="Giant_Leech" parent="ObjectList" instance=ExtResource("8_r3b86")]
|
[node name="Giant_Leech" parent="EnemyList" instance=ExtResource("8_r3b86")]
|
||||||
position = Vector2(400, -340)
|
position = Vector2(400, -340)
|
||||||
|
|
||||||
[node name="DebugSprite" type="Sprite2D" parent="."]
|
[node name="DebugSprite" type="Sprite2D" parent="."]
|
||||||
|
|
|
||||||
|
|
@ -25,25 +25,25 @@ scale = Vector2(25, 25)
|
||||||
texture = ExtResource("3_uv7v8")
|
texture = ExtResource("3_uv7v8")
|
||||||
script = ExtResource("4_bl5jt")
|
script = ExtResource("4_bl5jt")
|
||||||
|
|
||||||
[node name="ObjectList" type="Node2D" parent="."]
|
[node name="EnemyList" type="Node2D" parent="."]
|
||||||
|
|
||||||
[node name="Ghost" parent="ObjectList" instance=ExtResource("5_23fi7")]
|
[node name="Ghost" parent="EnemyList" instance=ExtResource("5_23fi7")]
|
||||||
position = Vector2(150, -300)
|
position = Vector2(150, -300)
|
||||||
|
|
||||||
[node name="Platform" parent="ObjectList" instance=ExtResource("6_e6j05")]
|
[node name="Platform" parent="EnemyList" instance=ExtResource("6_e6j05")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(75, -595)
|
position = Vector2(75, -595)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform2" parent="ObjectList" instance=ExtResource("6_e6j05")]
|
[node name="Platform2" parent="EnemyList" instance=ExtResource("6_e6j05")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(225, -595)
|
position = Vector2(225, -595)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform3" parent="ObjectList" instance=ExtResource("6_e6j05")]
|
[node name="Platform3" parent="EnemyList" instance=ExtResource("6_e6j05")]
|
||||||
position = Vector2(290, -431)
|
position = Vector2(290, -431)
|
||||||
|
|
||||||
[node name="Platform4" parent="ObjectList" instance=ExtResource("6_e6j05")]
|
[node name="Platform4" parent="EnemyList" instance=ExtResource("6_e6j05")]
|
||||||
position = Vector2(62, -184)
|
position = Vector2(62, -184)
|
||||||
|
|
||||||
[node name="DebugSprite" type="Sprite2D" parent="."]
|
[node name="DebugSprite" type="Sprite2D" parent="."]
|
||||||
|
|
|
||||||
|
|
@ -27,38 +27,38 @@ scale = Vector2(25, 25)
|
||||||
texture = ExtResource("3_elmbw")
|
texture = ExtResource("3_elmbw")
|
||||||
script = ExtResource("4_1cnhw")
|
script = ExtResource("4_1cnhw")
|
||||||
|
|
||||||
[node name="ObjectList" type="Node2D" parent="."]
|
[node name="EnemyList" type="Node2D" parent="."]
|
||||||
|
|
||||||
[node name="Ghost" parent="ObjectList" instance=ExtResource("5_rh5oo")]
|
[node name="Ghost" parent="EnemyList" instance=ExtResource("5_rh5oo")]
|
||||||
position = Vector2(-38, -481)
|
position = Vector2(-38, -481)
|
||||||
|
|
||||||
[node name="Platform" parent="ObjectList" instance=ExtResource("6_caaff")]
|
[node name="Platform" parent="EnemyList" instance=ExtResource("6_caaff")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(75, -595)
|
position = Vector2(75, -595)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform2" parent="ObjectList" instance=ExtResource("6_caaff")]
|
[node name="Platform2" parent="EnemyList" instance=ExtResource("6_caaff")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(225, -595)
|
position = Vector2(225, -595)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform3" parent="ObjectList" instance=ExtResource("6_caaff")]
|
[node name="Platform3" parent="EnemyList" instance=ExtResource("6_caaff")]
|
||||||
position = Vector2(5, -251)
|
position = Vector2(5, -251)
|
||||||
|
|
||||||
[node name="Platform4" parent="ObjectList" instance=ExtResource("6_caaff")]
|
[node name="Platform4" parent="EnemyList" instance=ExtResource("6_caaff")]
|
||||||
position = Vector2(286, -138)
|
position = Vector2(286, -138)
|
||||||
|
|
||||||
[node name="Platform5" parent="ObjectList" instance=ExtResource("6_caaff")]
|
[node name="Platform5" parent="EnemyList" instance=ExtResource("6_caaff")]
|
||||||
position = Vector2(269, -435)
|
position = Vector2(269, -435)
|
||||||
scale = Vector2(2.49, 3.1)
|
scale = Vector2(2.49, 3.1)
|
||||||
|
|
||||||
[node name="ItemSpawn" parent="ObjectList" instance=ExtResource("7_elmbw")]
|
[node name="ItemSpawn" parent="EnemyList" instance=ExtResource("7_elmbw")]
|
||||||
position = Vector2(149, -645)
|
position = Vector2(149, -645)
|
||||||
|
|
||||||
[node name="MorningStar" parent="ObjectList" instance=ExtResource("8_1cnhw")]
|
[node name="MorningStar" parent="EnemyList" instance=ExtResource("8_1cnhw")]
|
||||||
position = Vector2(39, -552)
|
position = Vector2(39, -552)
|
||||||
|
|
||||||
[node name="Ghost2" parent="ObjectList" instance=ExtResource("5_rh5oo")]
|
[node name="Ghost2" parent="EnemyList" instance=ExtResource("5_rh5oo")]
|
||||||
position = Vector2(301, -39)
|
position = Vector2(301, -39)
|
||||||
|
|
||||||
[node name="DebugSprite" type="Sprite2D" parent="."]
|
[node name="DebugSprite" type="Sprite2D" parent="."]
|
||||||
|
|
|
||||||
|
|
@ -27,30 +27,30 @@ scale = Vector2(25, 25)
|
||||||
texture = ExtResource("3_0yjll")
|
texture = ExtResource("3_0yjll")
|
||||||
script = ExtResource("4_ri5b7")
|
script = ExtResource("4_ri5b7")
|
||||||
|
|
||||||
[node name="ObjectList" type="Node2D" parent="."]
|
[node name="EnemyList" type="Node2D" parent="."]
|
||||||
|
|
||||||
[node name="Platform" parent="ObjectList" instance=ExtResource("6_kom4b")]
|
[node name="Platform" parent="EnemyList" instance=ExtResource("6_kom4b")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(75, -595)
|
position = Vector2(75, -595)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform2" parent="ObjectList" instance=ExtResource("6_kom4b")]
|
[node name="Platform2" parent="EnemyList" instance=ExtResource("6_kom4b")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(225, -595)
|
position = Vector2(225, -595)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform5" parent="ObjectList" instance=ExtResource("6_kom4b")]
|
[node name="Platform5" parent="EnemyList" instance=ExtResource("6_kom4b")]
|
||||||
position = Vector2(85, -287)
|
position = Vector2(85, -287)
|
||||||
scale = Vector2(2.49, 3.1)
|
scale = Vector2(2.49, 3.1)
|
||||||
|
|
||||||
[node name="ItemSpawn" parent="ObjectList" instance=ExtResource("7_sr858")]
|
[node name="ItemSpawn" parent="EnemyList" instance=ExtResource("7_sr858")]
|
||||||
position = Vector2(149, -645)
|
position = Vector2(149, -645)
|
||||||
rarity_bonus = 0.5
|
rarity_bonus = 0.5
|
||||||
|
|
||||||
[node name="MorningStar" parent="ObjectList" instance=ExtResource("8_ta0fd")]
|
[node name="MorningStar" parent="EnemyList" instance=ExtResource("8_ta0fd")]
|
||||||
position = Vector2(39, -552)
|
position = Vector2(39, -552)
|
||||||
|
|
||||||
[node name="BearTrap" parent="ObjectList" instance=ExtResource("8_pww4b")]
|
[node name="BearTrap" parent="EnemyList" instance=ExtResource("8_pww4b")]
|
||||||
position = Vector2(165, -7)
|
position = Vector2(165, -7)
|
||||||
|
|
||||||
[node name="DebugSprite" type="Sprite2D" parent="."]
|
[node name="DebugSprite" type="Sprite2D" parent="."]
|
||||||
|
|
|
||||||
|
|
@ -33,42 +33,42 @@ texture = ExtResource("5_pfkkr")
|
||||||
script = ExtResource("4_xr4t5")
|
script = ExtResource("4_xr4t5")
|
||||||
grid_offset = Vector2i(0, -1)
|
grid_offset = Vector2i(0, -1)
|
||||||
|
|
||||||
[node name="ObjectList" type="Node2D" parent="."]
|
[node name="EnemyList" type="Node2D" parent="."]
|
||||||
|
|
||||||
[node name="MorningStar" parent="ObjectList" instance=ExtResource("5_xr4t5")]
|
[node name="MorningStar" parent="EnemyList" instance=ExtResource("5_xr4t5")]
|
||||||
position = Vector2(397, -3)
|
position = Vector2(397, -3)
|
||||||
|
|
||||||
[node name="Ghost" parent="ObjectList" instance=ExtResource("7_35wcg")]
|
[node name="Ghost" parent="EnemyList" instance=ExtResource("7_35wcg")]
|
||||||
position = Vector2(118, -125)
|
position = Vector2(118, -125)
|
||||||
|
|
||||||
[node name="Platform" parent="ObjectList" instance=ExtResource("8_sifiv")]
|
[node name="Platform" parent="EnemyList" instance=ExtResource("8_sifiv")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(75, -295)
|
position = Vector2(75, -295)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform2" parent="ObjectList" instance=ExtResource("8_sifiv")]
|
[node name="Platform2" parent="EnemyList" instance=ExtResource("8_sifiv")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(225, -295)
|
position = Vector2(225, -295)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform3" parent="ObjectList" instance=ExtResource("8_sifiv")]
|
[node name="Platform3" parent="EnemyList" instance=ExtResource("8_sifiv")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(375, -295)
|
position = Vector2(375, -295)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform4" parent="ObjectList" instance=ExtResource("8_sifiv")]
|
[node name="Platform4" parent="EnemyList" instance=ExtResource("8_sifiv")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(525, -295)
|
position = Vector2(525, -295)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform5" parent="ObjectList" instance=ExtResource("8_sifiv")]
|
[node name="Platform5" parent="EnemyList" instance=ExtResource("8_sifiv")]
|
||||||
position = Vector2(300, -150)
|
position = Vector2(300, -150)
|
||||||
scale = Vector2(2.688, 3)
|
scale = Vector2(2.688, 3)
|
||||||
|
|
||||||
[node name="ItemSpawn" parent="ObjectList" instance=ExtResource("9_i1qmw")]
|
[node name="ItemSpawn" parent="EnemyList" instance=ExtResource("9_i1qmw")]
|
||||||
position = Vector2(300, -200)
|
position = Vector2(300, -200)
|
||||||
|
|
||||||
[node name="Leech" parent="ObjectList" instance=ExtResource("10_ibnxs")]
|
[node name="Leech" parent="EnemyList" instance=ExtResource("10_ibnxs")]
|
||||||
position = Vector2(240, -340)
|
position = Vector2(240, -340)
|
||||||
|
|
||||||
[node name="DebugSprite" type="Sprite2D" parent="."]
|
[node name="DebugSprite" type="Sprite2D" parent="."]
|
||||||
|
|
|
||||||
|
|
@ -33,39 +33,39 @@ texture = ExtResource("5_v4fh6")
|
||||||
script = ExtResource("4_h84o2")
|
script = ExtResource("4_h84o2")
|
||||||
grid_offset = Vector2i(0, -1)
|
grid_offset = Vector2i(0, -1)
|
||||||
|
|
||||||
[node name="ObjectList" type="Node2D" parent="."]
|
[node name="EnemyList" type="Node2D" parent="."]
|
||||||
|
|
||||||
[node name="Platform" parent="ObjectList" instance=ExtResource("6_me65q")]
|
[node name="Platform" parent="EnemyList" instance=ExtResource("6_me65q")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(75, -295)
|
position = Vector2(75, -295)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform2" parent="ObjectList" instance=ExtResource("6_me65q")]
|
[node name="Platform2" parent="EnemyList" instance=ExtResource("6_me65q")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(225, -295)
|
position = Vector2(225, -295)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform3" parent="ObjectList" instance=ExtResource("6_me65q")]
|
[node name="Platform3" parent="EnemyList" instance=ExtResource("6_me65q")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(375, -295)
|
position = Vector2(375, -295)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="Platform4" parent="ObjectList" instance=ExtResource("6_me65q")]
|
[node name="Platform4" parent="EnemyList" instance=ExtResource("6_me65q")]
|
||||||
visible = false
|
visible = false
|
||||||
position = Vector2(525, -295)
|
position = Vector2(525, -295)
|
||||||
collision_layer = 41
|
collision_layer = 41
|
||||||
|
|
||||||
[node name="ItemSpawn" parent="ObjectList" instance=ExtResource("7_crruu")]
|
[node name="ItemSpawn" parent="EnemyList" instance=ExtResource("7_crruu")]
|
||||||
position = Vector2(137, -329)
|
position = Vector2(137, -329)
|
||||||
rarity_bonus = 0.25
|
rarity_bonus = 0.25
|
||||||
|
|
||||||
[node name="BearTrap" parent="ObjectList" instance=ExtResource("8_fkxmk")]
|
[node name="BearTrap" parent="EnemyList" instance=ExtResource("8_fkxmk")]
|
||||||
position = Vector2(465, -301)
|
position = Vector2(465, -301)
|
||||||
|
|
||||||
[node name="Ghost" parent="ObjectList" instance=ExtResource("9_6hrl3")]
|
[node name="Ghost" parent="EnemyList" instance=ExtResource("9_6hrl3")]
|
||||||
position = Vector2(301, -49)
|
position = Vector2(301, -49)
|
||||||
|
|
||||||
[node name="Leech" parent="ObjectList" instance=ExtResource("10_7e5ul")]
|
[node name="Leech" parent="EnemyList" instance=ExtResource("10_7e5ul")]
|
||||||
position = Vector2(176, -340)
|
position = Vector2(176, -340)
|
||||||
|
|
||||||
[node name="DebugSprite" type="Sprite2D" parent="."]
|
[node name="DebugSprite" type="Sprite2D" parent="."]
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,26 @@
|
||||||
extends Node2D
|
extends Node2D
|
||||||
|
|
||||||
|
|
||||||
var moving = false
|
var moving = false
|
||||||
@onready var player = get_tree().get_root().get_node("main/Player")
|
@onready var player = get_tree().get_root().get_node("main/Player")
|
||||||
var speed = 500
|
|
||||||
|
|
||||||
# The target is set on initialization and never updated.
|
|
||||||
@onready var target = player.position
|
@onready var target = player.position
|
||||||
|
var speed = 500
|
||||||
|
|
||||||
var reached = false
|
var reached = false
|
||||||
signal target_reached
|
signal target_reached
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
|
||||||
# Wait one second before firing
|
|
||||||
await get_tree().create_timer(1).timeout
|
await get_tree().create_timer(1).timeout
|
||||||
moving = true
|
moving = true
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
|
|
||||||
# Fire towards the center of the cross
|
|
||||||
if moving:
|
if moving:
|
||||||
position += (target - global_position).normalized().rotated(-get_parent().rotation) * speed * delta
|
position += (target - global_position).normalized().rotated(-get_parent().rotation) * speed * delta
|
||||||
|
|
||||||
# When reaching the center, inform parent node
|
|
||||||
if((global_position - target).length() < 40):
|
if((global_position - target).length() < 40):
|
||||||
moving = false
|
moving = false
|
||||||
if not reached:
|
if not reached:
|
||||||
target_reached.emit()
|
target_reached.emit()
|
||||||
reached = true
|
reached = true
|
||||||
|
|
||||||
# Damage the player on contact
|
|
||||||
if has_node("Area2D") and $Area2D.overlaps_body(player):
|
if has_node("Area2D") and $Area2D.overlaps_body(player):
|
||||||
player.hurt(1, global_position-player.position)
|
player.hurt(1, global_position-player.position)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,7 @@ var angular_speed = TAU/8
|
||||||
var lifetime = 5
|
var lifetime = 5
|
||||||
|
|
||||||
var ready_blobs = 0
|
var ready_blobs = 0
|
||||||
|
var num_blobs = 4
|
||||||
var num_blobs = 0
|
|
||||||
|
|
||||||
var particles_ended = false
|
var particles_ended = false
|
||||||
|
|
||||||
|
|
@ -14,20 +13,16 @@ func _on_target_reached():
|
||||||
ready_blobs += 1
|
ready_blobs += 1
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
# Count those children that have a "target reached" signal and connect them.
|
|
||||||
for child in get_children():
|
for child in get_children():
|
||||||
if "target_reached" in child:
|
if "target_reached" in child:
|
||||||
num_blobs += 1
|
|
||||||
child.connect("target_reached", _on_target_reached)
|
child.connect("target_reached", _on_target_reached)
|
||||||
$SplashSound.play()
|
$SplashSound.play()
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
# Once all child blobs have reached their target, start moving as one while slowly rotating.
|
|
||||||
if ready_blobs == num_blobs:
|
if ready_blobs == num_blobs:
|
||||||
position += (player.position - position).normalized() * speed * delta
|
position += (player.position - position).normalized() * speed * delta
|
||||||
rotate(angular_speed * delta)
|
rotate(angular_speed * delta)
|
||||||
lifetime -= delta
|
lifetime -= delta
|
||||||
# In the end, clear all child blobs, then the rest.
|
|
||||||
if lifetime < 0 and not particles_ended:
|
if lifetime < 0 and not particles_ended:
|
||||||
particles_ended = true
|
particles_ended = true
|
||||||
for child in get_children():
|
for child in get_children():
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,22 @@
|
||||||
extends CharacterBody2D
|
extends CharacterBody2D
|
||||||
|
@onready var earthaligner = $EarthAligner
|
||||||
@onready var player = get_tree().get_root().get_node("main/Player")
|
@onready var player = get_tree().get_root().get_node("main/Player")
|
||||||
var moves = ["slam", "wave", "water_rise", "splash"]
|
var moves = ["slam", "wave", "water_rise", "splash"]
|
||||||
@export var big_blob : PackedScene
|
@export var big_blob : PackedScene
|
||||||
@onready var water : Water = get_tree().get_root().get_node("main/Water")
|
@onready var water : Water = get_tree().get_root().get_node("main/Water")
|
||||||
|
|
||||||
# How often water has been risen.
|
|
||||||
var risen = 0
|
var risen = 0
|
||||||
|
|
||||||
# Managing idle behavior between attacks.
|
|
||||||
var attack_ready = true
|
var attack_ready = true
|
||||||
var idle_dir : Vector2 = Vector2.ZERO
|
var idle_dir : Vector2 = Vector2.ZERO
|
||||||
var idle_dir_remaining = 0
|
var idle_dir_remaining = 0
|
||||||
var idle_move = true
|
var idle_move = true
|
||||||
var target_pos = Vector2.ZERO
|
var target_pos = Vector2.ZERO
|
||||||
|
|
||||||
|
|
||||||
var damage = 1
|
var damage = 1
|
||||||
var dead = false
|
var dead = false
|
||||||
signal grounded
|
signal grounded
|
||||||
signal slam_step_finished
|
signal slam_step_finished
|
||||||
|
|
||||||
func choose_next_move() -> String:
|
func choose_next_move() -> String:
|
||||||
# Water rises at 75% and 50% of remaining HP.
|
|
||||||
if $EnemyHurtbox.hp <= 3 * $EnemyHurtbox.max_hp / 4 and risen == 0:
|
if $EnemyHurtbox.hp <= 3 * $EnemyHurtbox.max_hp / 4 and risen == 0:
|
||||||
risen += 1
|
risen += 1
|
||||||
return "water_rise"
|
return "water_rise"
|
||||||
|
|
@ -30,15 +25,10 @@ func choose_next_move() -> String:
|
||||||
return "water_rise"
|
return "water_rise"
|
||||||
|
|
||||||
var pool = ["splash"]
|
var pool = ["splash"]
|
||||||
|
if not (position.length() - water.radius < 300 and randf()<0.75):
|
||||||
# Heavily decrease slam probability if boss height is low.
|
|
||||||
if not (position.length() - water.radius < 450 and randf()<0.75):
|
|
||||||
pool.append("slam")
|
pool.append("slam")
|
||||||
|
|
||||||
# Heavily decrease wave probability if player is very high up.
|
|
||||||
if not (player.position.length() > water.radius + 900 and randf()<0.75):
|
if not (player.position.length() > water.radius + 900 and randf()<0.75):
|
||||||
pool.append("wave")
|
pool.append("wave")
|
||||||
|
|
||||||
return ["slam", "wave", "splash"].pick_random()
|
return ["slam", "wave", "splash"].pick_random()
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
|
|
@ -49,7 +39,7 @@ func _process(_delta: float) -> void:
|
||||||
|
|
||||||
func _physics_process(delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
if dead: return
|
if dead: return
|
||||||
up_direction = $EarthAligner.up
|
up_direction = earthaligner.global_from_local(Vector2.UP)
|
||||||
if(is_on_floor()):
|
if(is_on_floor()):
|
||||||
grounded.emit()
|
grounded.emit()
|
||||||
if idle_move: move_idle(delta)
|
if idle_move: move_idle(delta)
|
||||||
|
|
@ -58,18 +48,15 @@ func _physics_process(delta: float) -> void:
|
||||||
move_and_slide()
|
move_and_slide()
|
||||||
|
|
||||||
func move_idle(delta : float):
|
func move_idle(delta : float):
|
||||||
# Pick a random target roughly above the player's head every 0.5 seconds.
|
|
||||||
idle_dir_remaining -= delta
|
idle_dir_remaining -= delta
|
||||||
if(idle_dir_remaining <= 0):
|
if(idle_dir_remaining <= 0):
|
||||||
target_pos = player.position + player.get_node("EarthAligner").up * 400
|
target_pos = player.position + player.earth_aligner.global_from_local(Vector2.UP) * 400
|
||||||
target_pos += randf_range(0, max(200, (target_pos - global_position).length())*0.25) * Vector2.from_angle(randf_range(0,TAU))
|
target_pos += randf_range(0, max(200, (target_pos - global_position).length())*0.25) * Vector2.from_angle(randf_range(0,TAU))
|
||||||
idle_dir = (target_pos - global_position).normalized()* max(200, (target_pos - global_position).length()) * 0.4
|
idle_dir = (target_pos - global_position).normalized()* max(200, (target_pos - global_position).length()) * 0.4
|
||||||
idle_dir_remaining = 0.5
|
idle_dir_remaining = 0.5
|
||||||
velocity = idle_dir
|
velocity = idle_dir
|
||||||
|
|
||||||
func slam():
|
func slam():
|
||||||
# Move up, Slam Down, Repeat. Afterwards, linger for a moment.
|
|
||||||
# The slam destroys buildings.
|
|
||||||
idle_move = false
|
idle_move = false
|
||||||
velocity = up_direction * 500
|
velocity = up_direction * 500
|
||||||
await get_tree().create_timer(0.6).timeout
|
await get_tree().create_timer(0.6).timeout
|
||||||
|
|
@ -84,8 +71,6 @@ func slam():
|
||||||
attack_ready = true
|
attack_ready = true
|
||||||
|
|
||||||
func slam_step():
|
func slam_step():
|
||||||
# End a downslam after ground is reached or 1.5 seconds have passed.
|
|
||||||
# Then destroy buildings hit.
|
|
||||||
damage = 2
|
damage = 2
|
||||||
velocity = up_direction * -1500
|
velocity = up_direction * -1500
|
||||||
grounded.connect(func(): slam_step_finished.emit())
|
grounded.connect(func(): slam_step_finished.emit())
|
||||||
|
|
@ -101,24 +86,21 @@ func destroy_below():
|
||||||
if(body.has_method("destroy")): body.destroy()
|
if(body.has_method("destroy")): body.destroy()
|
||||||
|
|
||||||
func wave():
|
func wave():
|
||||||
# Raise a wave from the water at the boundary of the screen and move it towards the center.
|
var angle = atan2(player.position.y, player.position.x)
|
||||||
var angle = player.position.angle
|
|
||||||
var dir = randi_range(0, 1) * 2 - 1
|
var dir = randi_range(0, 1) * 2 - 1
|
||||||
var speed = 3000 / water.radius_base
|
var speed = 3000/water.radius_base
|
||||||
water.create_tsunami(angle - speed * dir * TAU/30, dir, speed)
|
water.create_tsunami(angle - speed * dir*TAU/30, dir, speed)
|
||||||
await get_tree().create_timer(0.5).timeout
|
await get_tree().create_timer(0.5).timeout
|
||||||
$Wave.play()
|
$Wave.play()
|
||||||
await get_tree().create_timer(3.5).timeout
|
await get_tree().create_timer(3.5).timeout
|
||||||
attack_ready = true
|
attack_ready = true
|
||||||
|
|
||||||
func water_rise():
|
func water_rise():
|
||||||
# Increase the water level by 1 room height.
|
|
||||||
water.rise_water()
|
water.rise_water()
|
||||||
await get_tree().create_timer(5).timeout
|
await get_tree().create_timer(5).timeout
|
||||||
attack_ready = true
|
attack_ready = true
|
||||||
|
|
||||||
func splash():
|
func splash():
|
||||||
# Form four small blobs around the player which merge into one.
|
|
||||||
var blob_instance = big_blob.instantiate()
|
var blob_instance = big_blob.instantiate()
|
||||||
get_tree().get_root().get_node("main").add_child(blob_instance)
|
get_tree().get_root().get_node("main").add_child(blob_instance)
|
||||||
blob_instance.position = player.position
|
blob_instance.position = player.position
|
||||||
|
|
@ -128,14 +110,13 @@ func splash():
|
||||||
|
|
||||||
|
|
||||||
func die():
|
func die():
|
||||||
# Clear everything else, then wait for Audio Players to make sure sounds are not cut off.
|
|
||||||
dead = true
|
dead = true
|
||||||
for child in get_children():
|
for child in get_children():
|
||||||
if not child is AudioStreamPlayer2D:
|
if not child is AudioStreamPlayer2D:
|
||||||
child.queue_free()
|
child.queue_free()
|
||||||
$DeathSound.play()
|
$DeathSound.play()
|
||||||
await $DeathSound.finished
|
await $DeathSound.finished
|
||||||
await get_tree().create_timer(1).timeout
|
await get_tree().create_timer(3).timeout
|
||||||
get_tree().change_scene_to_file("res://ui/victory_screen/victory_screen.tscn")
|
get_tree().change_scene_to_file("res://ui/victory_screen/victory_screen.tscn")
|
||||||
queue_free()
|
queue_free()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,7 @@ func _ready() -> void:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
func _on_water_water_reached_max_height() -> void:
|
func _on_water_water_reached_max_height() -> void:
|
||||||
# The boss spawns once the water has risen to its maximum height.
|
|
||||||
var node = boss.instantiate()
|
var node = boss.instantiate()
|
||||||
add_sibling(node)
|
add_sibling(node)
|
||||||
node.position = %Player.position + %Player.get_node("EarthAligner").up * 1000;
|
node.position = %Player.position + %Player.earth_aligner.local_from_global(Vector2.UP) * 1000;
|
||||||
queue_free()
|
queue_free()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
extends Area2D
|
extends Area2D
|
||||||
|
|
||||||
# Attach this to an object to make it destructible
|
|
||||||
func destroy():
|
func destroy():
|
||||||
get_parent().queue_free()
|
get_parent().queue_free()
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,17 @@
|
||||||
extends Area2D
|
extends Area2D
|
||||||
@onready var player : CharacterBody2D = get_tree().get_root().get_node_or_null("main/Player")
|
@onready var earth_aligner = $EarthAligner
|
||||||
var target : CharacterBody2D
|
|
||||||
|
|
||||||
# Base stats
|
|
||||||
var speed = 100
|
var speed = 100
|
||||||
var damage = 1
|
var damage = 1
|
||||||
|
var player : CharacterBody2D
|
||||||
# Aggro range determines at which distance the ghost targets the player,
|
var target : CharacterBody2D
|
||||||
# Chase range determines at which distance the ghost stops pursuing the player.
|
|
||||||
var aggro_range = 900
|
|
||||||
var chase_range = 1400
|
|
||||||
|
|
||||||
var current_knockback = Vector2.ZERO
|
var current_knockback = Vector2.ZERO
|
||||||
var knockback_weight = 800
|
var knockback_weight = 800
|
||||||
|
|
||||||
|
var aggro_range = 900
|
||||||
|
var chase_range = 1400
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
player = get_tree().get_root().get_node_or_null("main/Player")
|
||||||
$AnimatedSprite2D.play("default")
|
$AnimatedSprite2D.play("default")
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
|
|
@ -22,18 +19,15 @@ func _process(delta: float) -> void:
|
||||||
return
|
return
|
||||||
|
|
||||||
var dist = (position - player.position).length()
|
var dist = (position - player.position).length()
|
||||||
# Set target based on aggression range and whether target is already set
|
|
||||||
if(dist > chase_range):
|
if(dist > chase_range):
|
||||||
target = null
|
target = null
|
||||||
elif(target == null and dist <= aggro_range):
|
elif(target == null and dist <= aggro_range):
|
||||||
target = player
|
target = player
|
||||||
|
|
||||||
# Move towards the target at constant speed
|
|
||||||
if(target!=null):
|
if(target!=null):
|
||||||
var motion = -(position - target.position).normalized() * speed
|
var motion = -(position - target.position).normalized() * speed
|
||||||
self.position += motion * delta * min(1, dist/(motion.length()*delta))
|
self.position += motion * delta * min(1, dist/(motion.length()*delta))
|
||||||
|
|
||||||
# Process knockback
|
|
||||||
self.position += current_knockback * delta
|
self.position += current_knockback * delta
|
||||||
current_knockback = current_knockback/pow(1.3, 60*delta)
|
current_knockback = current_knockback/pow(1.3, 60*delta)
|
||||||
|
|
||||||
|
|
@ -41,13 +35,12 @@ func _process(delta: float) -> void:
|
||||||
player.hurt(damage, self.global_position-player.global_position)
|
player.hurt(damage, self.global_position-player.global_position)
|
||||||
|
|
||||||
func _on_death():
|
func _on_death():
|
||||||
# First free all other children, then wait for Death Sound to finish
|
|
||||||
for child in get_children():
|
for child in get_children():
|
||||||
if not child is AudioStreamPlayer2D:
|
if not child is AudioStreamPlayer2D:
|
||||||
child.queue_free()
|
child.queue_free()
|
||||||
await $HurtSound.finished
|
await $AudioStreamPlayer2D.finished
|
||||||
self.queue_free()
|
self.queue_free()
|
||||||
|
|
||||||
func _on_damage_taken(_damage : int, dir: Vector2, _id):
|
func _on_damage_taken(_damage : int, dir: Vector2, _id):
|
||||||
current_knockback = - dir * knockback_weight
|
current_knockback = - dir * knockback_weight
|
||||||
$HurtSound.play()
|
$AudioStreamPlayer2D.play()
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ shape = SubResource("CircleShape2D_6attn")
|
||||||
|
|
||||||
[node name="EarthAligner" parent="." instance=ExtResource("3_obmiq")]
|
[node name="EarthAligner" parent="." instance=ExtResource("3_obmiq")]
|
||||||
|
|
||||||
[node name="HurtSound" type="AudioStreamPlayer2D" parent="."]
|
[node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="."]
|
||||||
stream = ExtResource("7_eqcb8")
|
stream = ExtResource("7_eqcb8")
|
||||||
volume_db = 15.0
|
volume_db = 15.0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,20 +49,20 @@ scale = Vector2(2, 2)
|
||||||
|
|
||||||
[node name="EarthAligner" parent="." instance=ExtResource("3_vk62e")]
|
[node name="EarthAligner" parent="." instance=ExtResource("3_vk62e")]
|
||||||
|
|
||||||
[node name="StepChecker" type="Area2D" parent="."]
|
[node name="RayCast2D" type="Area2D" parent="."]
|
||||||
position = Vector2(248, 31.2)
|
position = Vector2(248, 31.2)
|
||||||
collision_layer = 0
|
collision_layer = 0
|
||||||
collision_mask = 8
|
collision_mask = 8
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="StepChecker"]
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="RayCast2D"]
|
||||||
position = Vector2(0, 25)
|
position = Vector2(0, 25)
|
||||||
shape = SubResource("RectangleShape2D_cq6dk")
|
shape = SubResource("RectangleShape2D_cq6dk")
|
||||||
|
|
||||||
[node name="GroundSensor" type="Area2D" parent="."]
|
[node name="RayCast2D2" type="Area2D" parent="."]
|
||||||
collision_layer = 0
|
collision_layer = 0
|
||||||
collision_mask = 8
|
collision_mask = 8
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="GroundSensor"]
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="RayCast2D2"]
|
||||||
position = Vector2(0, 25)
|
position = Vector2(0, 25)
|
||||||
shape = SubResource("RectangleShape2D_cq6dk")
|
shape = SubResource("RectangleShape2D_cq6dk")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
extends EnemyHurtbox
|
extends EnemyHurtbox
|
||||||
# The distance from one end to the other while standing
|
|
||||||
@export var broadth = 250
|
@export var broadth = 250
|
||||||
var move_dir = [-1, 1].pick_random()
|
@export var move_dir = -1
|
||||||
const angular_speed = 0.25
|
@export var angular_speed = 0.25
|
||||||
|
|
||||||
# The angle from the base to the moving end
|
|
||||||
var angle = 0.0 if move_dir == 1 else PI
|
var angle = 0.0 if move_dir == 1 else PI
|
||||||
|
|
||||||
@onready var segments : Array[Node] = $Segments.get_children()
|
@onready var segments : Array[Node] = $Segments.get_children()
|
||||||
|
|
@ -13,37 +10,25 @@ var dead = false
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
if dead: return
|
if dead: return
|
||||||
# Fall slowly while not grounded
|
if not $RayCast2D2.has_overlapping_bodies():
|
||||||
if not $GroundSensor.has_overlapping_bodies():
|
position += 200 * delta * $EarthAligner.global_from_local(Vector2.DOWN)
|
||||||
position += 200 * delta * $EarthAligner.down
|
|
||||||
|
|
||||||
var y = position.length()
|
var y = position.length()
|
||||||
var ratio = - move_dir * broadth / (2 * y)
|
var ratio = - move_dir * broadth / (2 * y)
|
||||||
|
|
||||||
# Due to the curvature of the ground, the leech can not just prescribe a semicircle.
|
|
||||||
# 2 * rot_angle determines how much further than 180° the moving leg has to move.
|
|
||||||
# This agrees with the angle of the arc spanned by the leeches standing ends.
|
|
||||||
var rot_angle = - 2 * asin(ratio)
|
var rot_angle = - 2 * asin(ratio)
|
||||||
angle -= TAU * delta * angular_speed * move_dir
|
angle -= TAU * delta * angular_speed * move_dir
|
||||||
|
|
||||||
# Once the rotation has finished, the other leg is used as the leeches center.
|
|
||||||
# The leeches position has to be updated accordingly.
|
|
||||||
if(angle > PI + abs(rot_angle) / 2 or angle < - abs(rot_angle) / 2):
|
if(angle > PI + abs(rot_angle) / 2 or angle < - abs(rot_angle) / 2):
|
||||||
angle = fmod(angle + PI, PI)
|
angle = fmod(angle + PI, PI)
|
||||||
position = position.rotated(rot_angle)
|
position = position.rotated(rot_angle)
|
||||||
|
if dead:
|
||||||
# StepChecker determines whether there is ground for the next step.
|
return
|
||||||
# Place the StepChecker on the side of the next step to be made.
|
|
||||||
if(move_dir == 1 and angle < 1 or move_dir == -1 and angle > PI - 1):
|
if(move_dir == 1 and angle < 1 or move_dir == -1 and angle > PI - 1):
|
||||||
$StepChecker.global_position = position.rotated(rot_angle)
|
$RayCast2D.global_position = position.rotated(rot_angle)
|
||||||
$StepChecker.rotation = rot_angle
|
$RayCast2D.rotation = rot_angle
|
||||||
|
|
||||||
# At some point of the movement, check whether there is ground to step on, turn around otherwise.
|
|
||||||
if(move_dir == 1 and angle < 0.5 or move_dir == -1 and angle > PI - 0.5):
|
if(move_dir == 1 and angle < 0.5 or move_dir == -1 and angle > PI - 0.5):
|
||||||
if(not $StepChecker.has_overlapping_bodies()):
|
if(not $RayCast2D.has_overlapping_bodies()):
|
||||||
move_dir *= -1
|
move_dir *= -1
|
||||||
|
if dead:
|
||||||
# Update the position and rotation according to the end's position
|
return
|
||||||
for i in range(segment_count):
|
for i in range(segment_count):
|
||||||
var segment_pos_data = calculate_segment_location_and_rotation(i)
|
var segment_pos_data = calculate_segment_location_and_rotation(i)
|
||||||
if not is_instance_valid(segments[i]):
|
if not is_instance_valid(segments[i]):
|
||||||
|
|
@ -53,11 +38,7 @@ func _process(delta: float) -> void:
|
||||||
|
|
||||||
func calculate_segment_location_and_rotation (i) -> Dictionary:
|
func calculate_segment_location_and_rotation (i) -> Dictionary:
|
||||||
var aerial_end_location = Vector2.from_angle(-angle) * broadth
|
var aerial_end_location = Vector2.from_angle(-angle) * broadth
|
||||||
|
|
||||||
# Compute the gravicenter of the standing end, the moving end and the apex of the movement.
|
|
||||||
var gravicenter = (aerial_end_location + Vector2(0, -broadth) + Vector2.ZERO) / 3
|
var gravicenter = (aerial_end_location + Vector2(0, -broadth) + Vector2.ZERO) / 3
|
||||||
|
|
||||||
# Compute the circle through the above gravicenter, the standing end and the moving end.
|
|
||||||
var ax = gravicenter.x
|
var ax = gravicenter.x
|
||||||
var ay = gravicenter.y
|
var ay = gravicenter.y
|
||||||
var bx = aerial_end_location.x
|
var bx = aerial_end_location.x
|
||||||
|
|
@ -67,46 +48,31 @@ func calculate_segment_location_and_rotation (i) -> Dictionary:
|
||||||
var d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by))
|
var d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by))
|
||||||
var ux = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d
|
var ux = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d
|
||||||
var uy = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d
|
var uy = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d
|
||||||
var circum_center = Vector2(ux, uy)
|
var center = Vector2(ux, uy)
|
||||||
var radius = circum_center.length()
|
var radius = center.length()
|
||||||
|
|
||||||
# Determine the direction of the correct arc between the standing and the moving end
|
|
||||||
var switch_arc_dir = false
|
var switch_arc_dir = false
|
||||||
# When the moving end crosses above the standing end, the circumcenter jumps
|
|
||||||
# from one side of the leech to the other, reverting the direction of the arc
|
|
||||||
if ux < 0:
|
if ux < 0:
|
||||||
switch_arc_dir = !switch_arc_dir
|
switch_arc_dir = !switch_arc_dir
|
||||||
|
if center.angle() > 1:
|
||||||
# For sufficiently large size of circum_center.angle() it can happen that
|
|
||||||
# the sign of the (circum_center - aerial_end_location).angle() flips while
|
|
||||||
# that of circum_center.angle() doesn't, which has to be counteracted.
|
|
||||||
if circum_center.angle() > 1:
|
|
||||||
switch_arc_dir = !switch_arc_dir
|
switch_arc_dir = !switch_arc_dir
|
||||||
|
|
||||||
var angle1 = - PI + circum_center.angle()
|
var angle1 = - PI + center.angle()
|
||||||
var angle2 = - PI + (circum_center - aerial_end_location).angle()
|
var angle2 = - PI + (center-aerial_end_location).angle()
|
||||||
if(switch_arc_dir):
|
if(switch_arc_dir):
|
||||||
angle1 += TAU
|
angle1 += TAU
|
||||||
|
|
||||||
# In the edge case where the leech is almost a straight line, the circum_center
|
if radius < 10000000:
|
||||||
# and radius approach infty, leading to numerical errors producing flickering.
|
return {"position": center + radius * Vector2.from_angle((i * angle1 + (segment_count - 1 - i) * angle2)/(segment_count - 1)),
|
||||||
# This is ruled out by treating these cases as actual straight lines.
|
"rotation": (i * angle1 + (segment_count - 1 - i) * angle2)/ (segment_count - 1) + sign(ux) * PI/2}
|
||||||
if radius > 1000000:
|
|
||||||
|
else:
|
||||||
return {"position" : Vector2.UP * broadth * i / (segment_count - 1),
|
return {"position" : Vector2.UP * broadth * i / (segment_count - 1),
|
||||||
"rotation" : 3 * PI / 2}
|
"rotation" : 3 * PI / 2}
|
||||||
|
|
||||||
# Otherwise, the segments are distributed regularly along the arc.
|
|
||||||
else:
|
|
||||||
var arc_angle = (i * angle1 + (segment_count - 1 - i) * angle2)/(segment_count - 1)
|
|
||||||
return {"position": circum_center + radius * Vector2.from_angle(arc_angle),
|
|
||||||
"rotation": arc_angle + sign(ux) * PI/2}
|
|
||||||
|
|
||||||
|
|
||||||
func _on_damage_taken(_damage, _dir, _id):
|
func _on_damage_taken(_damage, _dir, _id):
|
||||||
$AudioStreamPlayer2D.play()
|
$AudioStreamPlayer2D.play()
|
||||||
|
|
||||||
func _on_death():
|
func _on_death():
|
||||||
# Free all other children while waiting for the death sound to play.
|
|
||||||
dead = true
|
dead = true
|
||||||
for child in get_children():
|
for child in get_children():
|
||||||
if not child is AudioStreamPlayer2D:
|
if not child is AudioStreamPlayer2D:
|
||||||
|
|
|
||||||
|
|
@ -39,20 +39,20 @@ scale = Vector2(-1, -1)
|
||||||
|
|
||||||
[node name="EarthAligner" parent="." instance=ExtResource("3_0r7dp")]
|
[node name="EarthAligner" parent="." instance=ExtResource("3_0r7dp")]
|
||||||
|
|
||||||
[node name="StepChecker" type="Area2D" parent="."]
|
[node name="RayCast2D" type="Area2D" parent="."]
|
||||||
position = Vector2(248, 31.2)
|
position = Vector2(248, 31.2)
|
||||||
collision_layer = 0
|
collision_layer = 0
|
||||||
collision_mask = 8
|
collision_mask = 8
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="StepChecker"]
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="RayCast2D"]
|
||||||
position = Vector2(0, 14.8)
|
position = Vector2(0, 14.8)
|
||||||
shape = SubResource("RectangleShape2D_cq6dk")
|
shape = SubResource("RectangleShape2D_cq6dk")
|
||||||
|
|
||||||
[node name="GroundSensor" type="Area2D" parent="."]
|
[node name="RayCast2D2" type="Area2D" parent="."]
|
||||||
collision_layer = 0
|
collision_layer = 0
|
||||||
collision_mask = 8
|
collision_mask = 8
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="GroundSensor"]
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="RayCast2D2"]
|
||||||
position = Vector2(0, 8.8)
|
position = Vector2(0, 8.8)
|
||||||
shape = SubResource("RectangleShape2D_cq6dk")
|
shape = SubResource("RectangleShape2D_cq6dk")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@ extends Area2D
|
||||||
@onready var player = get_tree().get_root().get_node_or_null("main/Player")
|
@onready var player = get_tree().get_root().get_node_or_null("main/Player")
|
||||||
var damage = 1
|
var damage = 1
|
||||||
|
|
||||||
|
#signal segment_damaged
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
if (player != null and overlaps_body(player)):
|
if (player != null and overlaps_body(player)):
|
||||||
player.hurt(damage, self.global_position-player.global_position)
|
player.hurt(damage, self.global_position-player.global_position)
|
||||||
|
|
||||||
# Forward taken damage to the parent leech.
|
|
||||||
func _on_hurtbox_damaged(dmg : int, dir : Vector2, id):
|
func _on_hurtbox_damaged(dmg : int, dir : Vector2, id):
|
||||||
|
#segment_damaged.emit(dmg, dir)
|
||||||
get_parent().get_parent().hurt(dmg, dir, id)
|
get_parent().get_parent().hurt(dmg, dir, id)
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,14 @@ extends ActiveItem
|
||||||
func actually_collect():
|
func actually_collect():
|
||||||
player.set_cooldown(cooldown)
|
player.set_cooldown(cooldown)
|
||||||
|
|
||||||
|
|
||||||
func activate():
|
func activate():
|
||||||
player.activate_cooldown()
|
player.activate_cooldown()
|
||||||
|
|
||||||
# Spawn an arrow on activation
|
|
||||||
var arrow : Area2D = arrow_scene.instantiate()
|
var arrow : Area2D = arrow_scene.instantiate()
|
||||||
get_tree().get_root().add_child(arrow)
|
get_tree().get_root().add_child(arrow)
|
||||||
arrow.position = player.position
|
arrow.position = player.position
|
||||||
arrow.rotation = player.rotation
|
arrow.rotation = player.rotation
|
||||||
arrow.direction = player.get_node("EarthAligner").right * player.facing
|
arrow.direction = player.earth_aligner.global_from_local(Vector2(player.facing, 0))
|
||||||
|
|
||||||
# Make sure the arrow sprite faces the right direction
|
|
||||||
if(player.facing == -1):
|
if(player.facing == -1):
|
||||||
arrow.get_node("Sprite2D").scale.x = - arrow.get_node("Sprite2D").scale.x
|
arrow.get_node("Sprite2D").scale.x = - arrow.get_node("Sprite2D").scale.x
|
||||||
$SoundBowRelease.play()
|
$SoundBowRelease.play()
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@ extends Item
|
||||||
|
|
||||||
func collect() -> bool:
|
func collect() -> bool:
|
||||||
if(player.current_hp < player.max_hp):
|
if(player.current_hp < player.max_hp):
|
||||||
player.current_hp += heal_amount
|
player.current_hp = min(player.max_hp, player.current_hp + heal_amount)
|
||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,13 @@ extends ActiveItem
|
||||||
@export var cooldown = 0.2
|
@export var cooldown = 0.2
|
||||||
var dash_time = 0.15
|
var dash_time = 0.15
|
||||||
var dash_timer : SceneTreeTimer
|
var dash_timer : SceneTreeTimer
|
||||||
var dash_velocity
|
var dash_dir
|
||||||
var dash_speed = 1600
|
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
super(delta)
|
super(delta)
|
||||||
# While the dash is active, move the player in the
|
|
||||||
# (absolute!) direction fixed at the start of the dash
|
|
||||||
if dash_timer != null and dash_timer.time_left > 0:
|
if dash_timer != null and dash_timer.time_left > 0:
|
||||||
player.reset_to_velocity = player.get_node("EarthAligner").local_from_global(dash_velocity)
|
player.reset_to_velocity = player.earth_aligner.local_from_global(dash_dir)
|
||||||
|
|
||||||
|
|
||||||
func actually_collect():
|
func actually_collect():
|
||||||
player.set_cooldown(cooldown)
|
player.set_cooldown(cooldown)
|
||||||
|
|
@ -18,15 +16,12 @@ func actually_collect():
|
||||||
func activate():
|
func activate():
|
||||||
$DashSound.play()
|
$DashSound.play()
|
||||||
player.activate_cooldown()
|
player.activate_cooldown()
|
||||||
# The dash refills one air jump if possible and provides iframes.
|
player.air_jumps_current = min(player.air_jumps_current + 1, player.air_jumps_max)
|
||||||
player.air_jumps_current += 1
|
|
||||||
dash_timer = get_tree().create_timer(dash_time)
|
dash_timer = get_tree().create_timer(dash_time)
|
||||||
dash_velocity = player.get_node("EarthAligner").right * player.facing * dash_speed
|
dash_dir = player.earth_aligner.global_from_local(Vector2.RIGHT * player.facing * 1600)
|
||||||
player.get_node("IFrames").start(dash_time)
|
player.inv_time = max(player.inv_time, dash_time)
|
||||||
|
|
||||||
func remove(reset_player_active = true):
|
func remove(reset_player_active = true):
|
||||||
# If the item is removed during the dash, clear the active item slot,
|
|
||||||
# but only remove the item from the scene afterwards.
|
|
||||||
if(dash_timer != null):
|
if(dash_timer != null):
|
||||||
if reset_player_active:
|
if reset_player_active:
|
||||||
player.active_item = null
|
player.active_item = null
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,12 @@ func actually_collect():
|
||||||
func activate():
|
func activate():
|
||||||
$UpdashSound.play()
|
$UpdashSound.play()
|
||||||
player.activate_cooldown()
|
player.activate_cooldown()
|
||||||
# Drop the player for a moment, then boost upwards.
|
|
||||||
player.reset_to_velocity = Vector2(0,1)
|
player.reset_to_velocity = Vector2(0,1)
|
||||||
timer = get_tree().create_timer(0.1)
|
timer = get_tree().create_timer(0.1)
|
||||||
await timer.timeout
|
await timer.timeout
|
||||||
player.reset_to_velocity = Vector2(0, -2400)
|
player.reset_to_velocity = Vector2(0, -2400)
|
||||||
|
|
||||||
func remove(reset_player_active = true):
|
func remove(reset_player_active = true):
|
||||||
# If the item is removed during the dash startup, clear the active item slot,
|
|
||||||
# but only remove the item from the scene afterwards.
|
|
||||||
if reset_player_active:
|
if reset_player_active:
|
||||||
player.active_item = null
|
player.active_item = null
|
||||||
if timer != null and timer.time_left > 0:
|
if timer != null and timer.time_left > 0:
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,6 @@ class_name ActiveItem extends Item
|
||||||
@export var uses = 1:
|
@export var uses = 1:
|
||||||
set(new_uses):
|
set(new_uses):
|
||||||
uses = new_uses
|
uses = new_uses
|
||||||
refresh_uses_ui()
|
|
||||||
|
|
||||||
func refresh_uses_ui():
|
|
||||||
# Set the amount and type of child nodes active_item_uses has according to num_uses.
|
|
||||||
if active_item_uses != null:
|
if active_item_uses != null:
|
||||||
while active_item_uses.get_children().size() > uses:
|
while active_item_uses.get_children().size() > uses:
|
||||||
active_item_uses.remove_child(active_item_uses.get_child(0))
|
active_item_uses.remove_child(active_item_uses.get_child(0))
|
||||||
|
|
@ -18,17 +14,15 @@ func refresh_uses_ui():
|
||||||
while active_item_uses.get_children().size() < uses:
|
while active_item_uses.get_children().size() < uses:
|
||||||
active_item_uses.add_child(uses_left_icon.instantiate())
|
active_item_uses.add_child(uses_left_icon.instantiate())
|
||||||
|
|
||||||
# Called when the active item is touched. It will only be collected if
|
|
||||||
# the player has space, in which case actually_collect() is called.
|
|
||||||
func collect() -> bool:
|
func collect() -> bool:
|
||||||
if (player.active_item == null or (player.active_item.item_name == item_name and player.active_item.uses < uses)):
|
if (player.active_item == null or (player.active_item.item_name == item_name and player.active_item.uses < uses)):
|
||||||
player.active_item = self
|
player.active_item = self
|
||||||
uses = uses
|
uses = uses
|
||||||
actually_collect()
|
actually_collect()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# Intended to be overridden by item classes.
|
|
||||||
func actually_collect():
|
func actually_collect():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -39,11 +33,9 @@ func trigger_activation():
|
||||||
if uses == 0:
|
if uses == 0:
|
||||||
remove()
|
remove()
|
||||||
|
|
||||||
# Intended to be overridden by item classes.
|
|
||||||
func activate():
|
func activate():
|
||||||
pass
|
assert(false)
|
||||||
|
|
||||||
# When removed, also removes the reference from the player and removes the uses.
|
|
||||||
func remove(reset_player_active = true):
|
func remove(reset_player_active = true):
|
||||||
if player.active_item == self:
|
if player.active_item == self:
|
||||||
uses = 0
|
uses = 0
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,6 @@ var collected = false
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
if(is_instance_valid(player) and overlaps_body(player)):
|
if(is_instance_valid(player) and overlaps_body(player)):
|
||||||
# Attempt to collect the item. If successful, play the collect animation
|
|
||||||
# and attach the item to the player.
|
|
||||||
if(self.has_method("collect") and collect()):
|
if(self.has_method("collect") and collect()):
|
||||||
set_deferred("monitoring", false)
|
set_deferred("monitoring", false)
|
||||||
set_deferred("monitorable", false)
|
set_deferred("monitorable", false)
|
||||||
|
|
@ -15,12 +13,10 @@ func _process(_delta: float) -> void:
|
||||||
collect_animation()
|
collect_animation()
|
||||||
collected = true
|
collected = true
|
||||||
|
|
||||||
# Placeholder for a proper animation.
|
|
||||||
func collect_animation():
|
func collect_animation():
|
||||||
self.visible = false
|
self.visible = false
|
||||||
if self.has_node("AudioStreamPlayer2D"): $AudioStreamPlayer2D.play()
|
if self.has_node("AudioStreamPlayer2D"): $AudioStreamPlayer2D.play()
|
||||||
|
|
||||||
# Intended to be overridden by item classes.
|
|
||||||
func collect():
|
func collect():
|
||||||
push_error("Please specify item collection behavior")
|
push_error("Please specify item collection behavior")
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
|
@ -10,47 +10,26 @@ static var item_pool = ResourceLoader.load("res://items/generic/item_pool.tres",
|
||||||
@export var unique_bonus_multiplier = .05
|
@export var unique_bonus_multiplier = .05
|
||||||
@export var rare_bonus_multiplier = 0
|
@export var rare_bonus_multiplier = 0
|
||||||
|
|
||||||
@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
|
var remove_after_spawn = false
|
||||||
|
|
||||||
# Choose the item pool this spawn location draws from
|
|
||||||
func choose_pool() -> Array[PackedScene]:
|
func choose_pool() -> Array[PackedScene]:
|
||||||
spawn_petal = false
|
|
||||||
var unique_chance = unique_base_chance + unique_bonus_multiplier * rarity_bonus
|
var unique_chance = unique_base_chance + unique_bonus_multiplier * rarity_bonus
|
||||||
var rare_chance = rare_base_chance + rare_bonus_multiplier * rarity_bonus
|
var rare_chance = rare_base_chance + rare_bonus_multiplier * rarity_bonus
|
||||||
|
|
||||||
var random = randf()
|
var random = randf()
|
||||||
if random < unique_chance && item_pool.unique.size() > 0:
|
if random < unique_chance && item_pool.unique.size() > 0:
|
||||||
# Unique items are removed from the pool when picked and spawn a petal
|
|
||||||
remove_after_spawn = true
|
remove_after_spawn = true
|
||||||
spawn_petal = true
|
|
||||||
return item_pool.unique
|
return item_pool.unique
|
||||||
elif random < unique_chance + rare_chance || guarantee_rare:
|
elif random < unique_chance + rare_chance || guarantee_rare:
|
||||||
return item_pool.rare
|
return item_pool.rare
|
||||||
return item_pool.common
|
return item_pool.common
|
||||||
|
|
||||||
# Places a petal holding this item
|
|
||||||
func instantiate_petal(item):
|
|
||||||
var petal : Petal = petal_scene.instantiate()
|
|
||||||
get_parent().call_deferred("add_child", petal)
|
|
||||||
petal.item = item
|
|
||||||
petal.global_position = global_position
|
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
# Pick a random pool and a random item from it, then remove it if unique.
|
|
||||||
var pool = choose_pool()
|
var pool = choose_pool()
|
||||||
var index = randi_range(0, pool.size() - 1)
|
var index = randi_range(0, pool.size() - 1)
|
||||||
packed_item_scene = pool[index]
|
var packed_scene : PackedScene = pool[index]
|
||||||
if remove_after_spawn:
|
if remove_after_spawn:
|
||||||
item_pool.unique.remove_at(index)
|
item_pool.unique.remove_at(index)
|
||||||
|
var object = packed_scene.instantiate()
|
||||||
# Place the item, possibly inside a petal
|
|
||||||
if packed_item_scene == null: return
|
|
||||||
var object = packed_item_scene.instantiate()
|
|
||||||
add_child.call_deferred(object)
|
add_child.call_deferred(object)
|
||||||
object.reparent.call_deferred(get_parent())
|
object.reparent.call_deferred(get_parent())
|
||||||
if spawn_petal: instantiate_petal(object)
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
[gd_scene load_steps=4 format=3 uid="uid://xj0of571aur1"]
|
[gd_scene load_steps=3 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="Script" uid="uid://b8em61mqgdi58" path="res://items/generic/item_spawn.gd" id="1_ms6tn"]
|
||||||
[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"]
|
[ext_resource type="PackedScene" uid="uid://chs0u61f45nau" path="res://utils/earth_aligner.tscn" id="3_5pwuf"]
|
||||||
|
|
||||||
[node name="ItemSpawn" type="Node2D"]
|
[node name="ItemSpawn" type="Node2D"]
|
||||||
script = ExtResource("1_ms6tn")
|
script = ExtResource("1_ms6tn")
|
||||||
petal_scene = ExtResource("2_5to52")
|
|
||||||
metadata/_custom_type_script = "uid://b8em61mqgdi58"
|
metadata/_custom_type_script = "uid://b8em61mqgdi58"
|
||||||
|
|
||||||
[node name="EarthAligner" parent="." instance=ExtResource("3_5pwuf")]
|
[node name="EarthAligner" parent="." instance=ExtResource("3_5pwuf")]
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,5 @@ extends Item
|
||||||
|
|
||||||
func collect() -> bool:
|
func collect() -> bool:
|
||||||
player.max_hp += max_health_increase
|
player.max_hp += max_health_increase
|
||||||
player.current_hp += max_health_increase
|
player.current_hp = min(player.max_hp, player.current_hp + heal_amount)
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ extends Item
|
||||||
@export var sword : PackedScene
|
@export var sword : PackedScene
|
||||||
|
|
||||||
func collect() -> bool:
|
func collect() -> bool:
|
||||||
# Give the player a new sword facing towards th back.
|
|
||||||
var sword_instance = sword.instantiate()
|
var sword_instance = sword.instantiate()
|
||||||
player.add_child(sword_instance)
|
player.add_child(sword_instance)
|
||||||
sword_instance.facing_mult = -1
|
sword_instance.facing_mult = -1
|
||||||
|
|
|
||||||
21
main.tscn
21
main.tscn
|
|
@ -1,4 +1,4 @@
|
||||||
[gd_scene load_steps=16 format=3 uid="uid://cxo6bq26huau7"]
|
[gd_scene load_steps=18 format=3 uid="uid://cxo6bq26huau7"]
|
||||||
|
|
||||||
[ext_resource type="PackedScene" uid="uid://cmaovvr15b3qk" path="res://player/player.tscn" id="2_1bvp3"]
|
[ext_resource type="PackedScene" uid="uid://cmaovvr15b3qk" path="res://player/player.tscn" id="2_1bvp3"]
|
||||||
[ext_resource type="Texture2D" uid="uid://d3fpq76anm4t7" path="res://world/Background Prototype/Background prototype.png" id="3_kek77"]
|
[ext_resource type="Texture2D" uid="uid://d3fpq76anm4t7" path="res://world/Background Prototype/Background prototype.png" id="3_kek77"]
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
[ext_resource type="PackedScene" uid="uid://cqn67nwyrtq3k" path="res://ui/journal/journal.tscn" id="10_w48qg"]
|
[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="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://bg_image.gd" id="13_vivmo"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dhxnae8wbhp3u" path="res://vines_petals/bud.tscn" id="16_2cqfq"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b7o82cdfwuqd1" path="res://vines_petals/vine.tscn" id="17_yaehf"]
|
||||||
|
|
||||||
[node name="main" type="Node2D"]
|
[node name="main" type="Node2D"]
|
||||||
|
|
||||||
|
|
@ -104,6 +106,23 @@ colors = Array[Color]([Color(0, 0.6441987, 0.6693053, 1), Color(0.90588236, 0.15
|
||||||
script = ExtResource("10_efxa6")
|
script = ExtResource("10_efxa6")
|
||||||
boss = ExtResource("11_efxa6")
|
boss = ExtResource("11_efxa6")
|
||||||
|
|
||||||
|
[node name="Vine" parent="." instance=ExtResource("17_yaehf")]
|
||||||
|
|
||||||
|
[node name="Bud" parent="Vine" node_paths=PackedStringArray("vine") instance=ExtResource("16_2cqfq")]
|
||||||
|
position = Vector2(1320, -3484)
|
||||||
|
vine = NodePath("..")
|
||||||
|
location = Vector2(46, 1)
|
||||||
|
offset = Vector2(150, 150)
|
||||||
|
|
||||||
|
[node name="Node2D2" type="Node2D" parent="Vine"]
|
||||||
|
position = Vector2(963, -3171)
|
||||||
|
|
||||||
|
[node name="Node2D4" type="Node2D" parent="Vine"]
|
||||||
|
position = Vector2(1035, -3518)
|
||||||
|
|
||||||
|
[node name="Node2D5" type="Node2D" parent="Vine"]
|
||||||
|
position = Vector2(1551, -2955)
|
||||||
|
|
||||||
[connection signal="active_item_changed" from="Player" to="UIOverlay/ItemUI" method="_on_player_active_item_changed"]
|
[connection signal="active_item_changed" from="Player" to="UIOverlay/ItemUI" method="_on_player_active_item_changed"]
|
||||||
[connection signal="health_changed" from="Player" to="UIOverlay/Healthbar" method="_on_player_health_changed"]
|
[connection signal="health_changed" from="Player" to="UIOverlay/Healthbar" method="_on_player_health_changed"]
|
||||||
[connection signal="max_hp_changed" from="Player" to="UIOverlay/Healthbar" method="_on_player_max_hp_changed"]
|
[connection signal="max_hp_changed" from="Player" to="UIOverlay/Healthbar" method="_on_player_max_hp_changed"]
|
||||||
|
|
|
||||||
159
player/player.gd
159
player/player.gd
|
|
@ -1,4 +1,10 @@
|
||||||
class_name Player extends CharacterBody2D
|
class_name Player extends CharacterBody2D
|
||||||
|
# Child Nodes
|
||||||
|
@onready var camera : Camera2D = $Camera2D;
|
||||||
|
@onready var anim_sprite : AnimatedSprite2D = $AnimatedSprite2D;
|
||||||
|
@onready var earth_aligner : Node2D = $EarthAligner;
|
||||||
|
@onready var sword : Area2D = $Sword;
|
||||||
|
@onready var active_item_cooldown : Timer = $ActiveItemCooldown
|
||||||
|
|
||||||
@export var double_jump_animation : PackedScene
|
@export var double_jump_animation : PackedScene
|
||||||
|
|
||||||
|
|
@ -12,40 +18,27 @@ var gravity = 100;
|
||||||
|
|
||||||
# Movement
|
# Movement
|
||||||
var facing = -1;
|
var facing = -1;
|
||||||
var base_hspeed = 150.0;
|
var hspeed = 150;
|
||||||
var ground_jump_strength = 1400;
|
var ground_jump_strength = 1400;
|
||||||
var air_jump_strength = 1100;
|
var air_jump_strength = 1100;
|
||||||
var air_jumps_max = 1;
|
var air_jumps_max = 1;
|
||||||
var air_jumps_current = 1:
|
var air_jumps_current = 1;
|
||||||
set(air_jumps_new):
|
|
||||||
air_jumps_current = min(air_jumps_new, air_jumps_max)
|
|
||||||
|
|
||||||
# HP and Iframes
|
# HP and Iframes
|
||||||
signal health_changed(new_health : int)
|
signal health_changed(new_health : int)
|
||||||
signal max_hp_changed(new_max_hp : int)
|
signal max_hp_changed(new_max_hp : int)
|
||||||
signal player_died
|
signal player_died
|
||||||
|
|
||||||
var hit_iframes = 0.8
|
|
||||||
var current_hp = 5:
|
var current_hp = 5:
|
||||||
set(new_hp):
|
set(new_hp):
|
||||||
# HP can't be increased above Max HP or reduced below 0
|
|
||||||
# When reduced to 0, the player dies.
|
|
||||||
new_hp = min(new_hp, max_hp)
|
|
||||||
if new_hp <= 0:
|
|
||||||
new_hp = 0
|
|
||||||
die()
|
|
||||||
if new_hp != current_hp:
|
|
||||||
current_hp = new_hp
|
current_hp = new_hp
|
||||||
health_changed.emit(current_hp)
|
health_changed.emit(current_hp)
|
||||||
@export var max_hp = 5:
|
@export var max_hp = 5:
|
||||||
# When Max HP is reduced below current health, current health is adjusted.
|
|
||||||
set(new_max_hp):
|
set(new_max_hp):
|
||||||
max_hp = new_max_hp
|
max_hp = new_max_hp
|
||||||
if max_hp <= 0:
|
|
||||||
max_hp = 0
|
|
||||||
if current_hp > max_hp:
|
|
||||||
current_hp = max_hp
|
|
||||||
max_hp_changed.emit(max_hp)
|
max_hp_changed.emit(max_hp)
|
||||||
|
var hit_invulnerability = 0.8
|
||||||
|
var inv_time = 0;
|
||||||
var dead = false;
|
var dead = false;
|
||||||
|
|
||||||
# Received Knockback
|
# Received Knockback
|
||||||
|
|
@ -55,7 +48,10 @@ var damage_knockup = 500
|
||||||
@export var friction = 0.5
|
@export var friction = 0.5
|
||||||
|
|
||||||
# Attack Handling
|
# Attack Handling
|
||||||
|
var atk_cooldown = 0.35
|
||||||
|
var atk_timer = 0
|
||||||
var can_upslash = false
|
var can_upslash = false
|
||||||
|
|
||||||
signal attack
|
signal attack
|
||||||
|
|
||||||
# Active Item
|
# Active Item
|
||||||
|
|
@ -67,61 +63,53 @@ var active_item : ActiveItem = null:
|
||||||
|
|
||||||
|
|
||||||
func set_cooldown(cooldown):
|
func set_cooldown(cooldown):
|
||||||
$ActiveItemCooldown.wait_time = cooldown
|
active_item_cooldown.wait_time = cooldown
|
||||||
|
|
||||||
func activate_cooldown():
|
func activate_cooldown():
|
||||||
$ActiveItemCooldown.start()
|
active_item_cooldown.start()
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
# Update the Health Bar initially
|
|
||||||
max_hp_changed.emit(max_hp)
|
max_hp_changed.emit(max_hp)
|
||||||
health_changed.emit(current_hp)
|
health_changed.emit(current_hp)
|
||||||
|
|
||||||
func _physics_process(delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
# Velocity management uses the physics framework, hence runs at fix 60FPS
|
|
||||||
manage_velocity(delta)
|
manage_velocity(delta)
|
||||||
move_and_slide()
|
move_and_slide()
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
# All non-velocity management can run without FPS cap
|
manage_iframes(delta)
|
||||||
update_vine_statuses()
|
|
||||||
manage_movement_options()
|
manage_movement_options()
|
||||||
manage_interaction()
|
manage_active(delta)
|
||||||
manage_animation()
|
manage_animation()
|
||||||
if handle_input:
|
manage_attack(delta)
|
||||||
manage_active()
|
|
||||||
manage_attack()
|
|
||||||
|
|
||||||
func manage_attack():
|
func manage_iframes(delta: float):
|
||||||
# If an attack is possible, a signal is sent which the weapon can connect to
|
if(inv_time > 0):
|
||||||
if(Input.is_action_just_pressed("attack") and $AttackCooldown.time_left <= 0):
|
inv_time = max(0, inv_time-delta)
|
||||||
|
|
||||||
|
func manage_attack(delta : float):
|
||||||
|
atk_timer = max(0, atk_timer-delta)
|
||||||
|
if handle_input:
|
||||||
|
if(Input.is_action_just_pressed("attack") and atk_timer <= 0):
|
||||||
if Input.is_action_pressed("up") and can_upslash:
|
if Input.is_action_pressed("up") and can_upslash:
|
||||||
attack.emit("up")
|
attack.emit("up")
|
||||||
else:
|
else:
|
||||||
attack.emit("horizontal")
|
attack.emit("horizontal")
|
||||||
$Sprite.play("attack")
|
$AnimatedSprite2D.play("attack")
|
||||||
$SwordSwingAudio.play()
|
$SwordSwingAudio.play()
|
||||||
$AttackCooldown.start()
|
atk_timer = atk_cooldown
|
||||||
|
|
||||||
func manage_active():
|
func manage_active(_delta : float):
|
||||||
# Activate or remove items. Cooldown + use management is handled by the item.
|
if(active_item != null and Input.is_action_just_pressed("item") and active_item_cooldown.is_stopped()):
|
||||||
if(active_item != null and Input.is_action_just_pressed("item") and $ActiveItemCooldown.is_stopped()):
|
|
||||||
active_item.trigger_activation()
|
active_item.trigger_activation()
|
||||||
if(Input.is_action_just_pressed("drop_item") and active_item != null):
|
if(Input.is_action_just_pressed("drop_item")):
|
||||||
active_item.remove()
|
active_item.remove()
|
||||||
|
|
||||||
func manage_movement_options() -> void:
|
func manage_movement_options() -> void:
|
||||||
# Reset Air Jumps when grounded
|
|
||||||
if(is_on_floor()):
|
if(is_on_floor()):
|
||||||
air_jumps_current = air_jumps_max
|
air_jumps_current = air_jumps_max
|
||||||
|
|
||||||
func manage_interaction():
|
|
||||||
# Interacts with all overlapping interactables on button press
|
|
||||||
if Input.is_action_just_pressed("interact"):
|
|
||||||
for area in $InteractBox.get_overlapping_areas():
|
|
||||||
if area.has_method("interact"): area.interact()
|
|
||||||
|
|
||||||
func manage_animation() -> void:
|
func manage_animation() -> void:
|
||||||
var walk_dir = 0
|
var walk_dir = 0
|
||||||
if(handle_input):
|
if(handle_input):
|
||||||
|
|
@ -130,81 +118,67 @@ func manage_animation() -> void:
|
||||||
if(Input.is_action_pressed("move_left")):
|
if(Input.is_action_pressed("move_left")):
|
||||||
walk_dir -= 1
|
walk_dir -= 1
|
||||||
|
|
||||||
# Set the direction the player faces
|
|
||||||
if walk_dir != 0:
|
|
||||||
facing = walk_dir
|
|
||||||
$Sprite.scale.x = - abs($Sprite.scale.x) * facing
|
|
||||||
|
|
||||||
# Play the walk or idle animation when appropriate
|
|
||||||
if(walk_dir != 0):
|
if(walk_dir != 0):
|
||||||
if(is_on_floor() and not $Sprite.is_playing()):
|
facing = walk_dir
|
||||||
$Sprite.play("walk")
|
anim_sprite.scale.x = - abs(anim_sprite.scale.x) * facing
|
||||||
|
if(is_on_floor() and not $AnimatedSprite2D.is_playing()):
|
||||||
|
anim_sprite.play("walk")
|
||||||
else:
|
else:
|
||||||
if $Sprite.animation == "walk":
|
if anim_sprite.animation == "walk":
|
||||||
$Sprite.stop()
|
anim_sprite.stop()
|
||||||
|
|
||||||
|
|
||||||
func manage_velocity(delta: float) -> void:
|
func manage_velocity(delta: float) -> void:
|
||||||
up_direction = $EarthAligner.up
|
up_direction = (position - earth_center).normalized();
|
||||||
# Convert the current velocity into local coordinates, then compute changes there.
|
var old_local_velocity = earth_aligner.local_from_global(velocity)
|
||||||
# This is important for capped values such as fall speed.
|
|
||||||
var old_local_velocity = $EarthAligner.local_from_global(velocity)
|
|
||||||
# Apply friction horizontally, exponentially. Factor 1 - friction per frame at 60FPS.
|
|
||||||
var local_velocity = Vector2(old_local_velocity.x * pow(1 - friction,60*delta), old_local_velocity.y);
|
var local_velocity = Vector2(old_local_velocity.x * pow(1 - friction,60*delta), old_local_velocity.y);
|
||||||
local_velocity += Vector2(0, gravity)
|
local_velocity += Vector2(0, gravity)
|
||||||
|
|
||||||
if handle_input:
|
if handle_input:
|
||||||
# Apply Slow Status if present.
|
|
||||||
var hspeed = base_hspeed if not Status.affects("Slow", self) else base_hspeed * get_node("Slow").params.slow_factor
|
|
||||||
|
|
||||||
# Change the local velocity by the movement speed, or more if moving in the opposite direction,
|
|
||||||
# for more responsive turning around.
|
|
||||||
if(Input.is_action_pressed("move_right")):
|
if(Input.is_action_pressed("move_right")):
|
||||||
if local_velocity.x > - 3 * hspeed:
|
if local_velocity.x > -700:
|
||||||
local_velocity += Vector2(hspeed, 0)
|
local_velocity += Vector2(hspeed, 0)
|
||||||
else:
|
else:
|
||||||
local_velocity += Vector2(2 * hspeed, 0)
|
local_velocity += Vector2(hspeed/2.0, 0)
|
||||||
if(Input.is_action_pressed("move_left")):
|
if(Input.is_action_pressed("move_left")):
|
||||||
if local_velocity.x < 3 * hspeed:
|
if local_velocity.x < 700:
|
||||||
local_velocity += Vector2(-hspeed, 0)
|
local_velocity += Vector2(-hspeed, 0)
|
||||||
else:
|
else:
|
||||||
local_velocity += Vector2(-2 * hspeed, 0)
|
local_velocity += Vector2(-hspeed/2.0, 0)
|
||||||
|
|
||||||
if(Input.is_action_just_pressed("jump") and (is_on_floor() or air_jumps_current > 0)):
|
if(Input.is_action_just_pressed("jump") and (is_on_floor() or air_jumps_current > 0)):
|
||||||
# If the player holds drop, just move through the platform
|
var dropped = false
|
||||||
if Input.is_action_pressed("drop"):
|
if(not is_on_floor()):
|
||||||
self.position += 12 * $EarthAligner.down
|
air_jumps_current -= 1;
|
||||||
else:
|
play_double_jump_animation()
|
||||||
# Otherwise, either jump from the ground or perform a double jump.
|
elif (Input.is_action_pressed("drop")):
|
||||||
|
dropped = true
|
||||||
|
self.position += earth_aligner.global_from_local(Vector2(0,12))
|
||||||
|
if(not dropped):
|
||||||
if is_on_floor():
|
if is_on_floor():
|
||||||
local_velocity.y = -ground_jump_strength
|
local_velocity.y = -ground_jump_strength
|
||||||
else:
|
else:
|
||||||
air_jumps_current -= 1;
|
|
||||||
play_double_jump_animation()
|
|
||||||
local_velocity.y = -air_jump_strength
|
local_velocity.y = -air_jump_strength
|
||||||
|
|
||||||
if(local_velocity.y > max_fall_speed):
|
if(local_velocity.y > max_fall_speed):
|
||||||
local_velocity.y = max_fall_speed
|
local_velocity.y = max_fall_speed
|
||||||
|
|
||||||
# When knockback is applied, momentum is reset to the knockback value instead.
|
|
||||||
if(reset_to_velocity.x != 0):
|
if(reset_to_velocity.x != 0):
|
||||||
local_velocity.x = reset_to_velocity.x
|
local_velocity.x = reset_to_velocity.x
|
||||||
if(reset_to_velocity.y != 0):
|
if(reset_to_velocity.y != 0):
|
||||||
local_velocity.y = reset_to_velocity.y
|
local_velocity.y = reset_to_velocity.y
|
||||||
reset_to_velocity = Vector2.ZERO
|
reset_to_velocity = Vector2.ZERO
|
||||||
|
velocity = earth_aligner.global_from_local(local_velocity)
|
||||||
# Return to world coordinates and apply the changes to velocity.
|
|
||||||
velocity = $EarthAligner.global_from_local(local_velocity)
|
|
||||||
|
|
||||||
|
|
||||||
func hurt(dmg: int, dir: Vector2 = Vector2.ZERO):
|
func hurt(dmg: int, dir: Vector2 = Vector2.ZERO):
|
||||||
# If the player has no iframes, apply knockback and damage and start iframes.
|
if(inv_time <= 0):
|
||||||
if $IFrames.time_left <= 0:
|
|
||||||
if Status.affects("Vulnerable", self): dmg += 1
|
|
||||||
$AudioStreamPlayer2D.play()
|
$AudioStreamPlayer2D.play()
|
||||||
current_hp -= dmg
|
current_hp -= dmg
|
||||||
$IFrames.start(hit_iframes)
|
if(current_hp <= 0):
|
||||||
reset_to_velocity = Vector2(-sign($EarthAligner.local_from_global(dir).x)*knockback_strength, -damage_knockup)
|
die()
|
||||||
|
inv_time = hit_invulnerability
|
||||||
|
reset_to_velocity = Vector2(-sign(earth_aligner.local_from_global(dir).x)*knockback_strength, -damage_knockup)
|
||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
@ -213,29 +187,18 @@ func die():
|
||||||
player_died.emit()
|
player_died.emit()
|
||||||
dead = true
|
dead = true
|
||||||
|
|
||||||
# Connected to the signal marking the end of an attack.
|
|
||||||
# Returns to default animation.
|
|
||||||
func _on_attack_end():
|
func _on_attack_end():
|
||||||
if($Sprite.animation != "idle"):
|
if($AnimatedSprite2D.animation != "idle"):
|
||||||
$Sprite.play("idle")
|
$AnimatedSprite2D.play("idle")
|
||||||
$Sprite.stop()
|
$AnimatedSprite2D.stop()
|
||||||
|
|
||||||
# Take away player control while the Death Screen is active.
|
|
||||||
func _on_death_screen_visibility_changed() -> void:
|
func _on_death_screen_visibility_changed() -> void:
|
||||||
handle_input = !handle_input
|
handle_input = !handle_input
|
||||||
|
|
||||||
func play_double_jump_animation() -> void:
|
func play_double_jump_animation() -> void:
|
||||||
# Instantiate a sprite which plays a double jump animation, then deletes itself.
|
|
||||||
$AirJumpAudio.play()
|
$AirJumpAudio.play()
|
||||||
var node = double_jump_animation.instantiate()
|
var node = double_jump_animation.instantiate()
|
||||||
add_child(node)
|
add_child(node)
|
||||||
node.position = Vector2(0, 5)
|
node.position = Vector2(0, 5)
|
||||||
node.scale = .5 * Vector2.ONE
|
node.scale = .5 * Vector2.ONE
|
||||||
node.reparent(get_parent())
|
node.reparent(get_parent())
|
||||||
|
|
||||||
# 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.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)
|
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ position = Vector2(0.2, 6)
|
||||||
scale = Vector2(0.7, 0.72)
|
scale = Vector2(0.7, 0.72)
|
||||||
shape = SubResource("RectangleShape2D_e7oew")
|
shape = SubResource("RectangleShape2D_e7oew")
|
||||||
|
|
||||||
[node name="Sprite" type="AnimatedSprite2D" parent="."]
|
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."]
|
||||||
scale = Vector2(0.37, 0.37)
|
scale = Vector2(0.37, 0.37)
|
||||||
sprite_frames = SubResource("SpriteFrames_dw050")
|
sprite_frames = SubResource("SpriteFrames_dw050")
|
||||||
animation = &"attack"
|
animation = &"attack"
|
||||||
|
|
@ -100,14 +100,6 @@ animation = &"attack"
|
||||||
[node name="ActiveItemCooldown" type="Timer" parent="."]
|
[node name="ActiveItemCooldown" type="Timer" parent="."]
|
||||||
one_shot = true
|
one_shot = true
|
||||||
|
|
||||||
[node name="IFrames" type="Timer" parent="."]
|
|
||||||
wait_time = 0.8
|
|
||||||
one_shot = true
|
|
||||||
|
|
||||||
[node name="AttackCooldown" type="Timer" parent="."]
|
|
||||||
wait_time = 0.35
|
|
||||||
one_shot = true
|
|
||||||
|
|
||||||
[node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="."]
|
[node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="."]
|
||||||
stream = ExtResource("11_2ieo8")
|
stream = ExtResource("11_2ieo8")
|
||||||
volume_db = 15.0
|
volume_db = 15.0
|
||||||
|
|
@ -121,13 +113,4 @@ pitch_scale = 1.62
|
||||||
stream = ExtResource("17_tqiix")
|
stream = ExtResource("17_tqiix")
|
||||||
volume_db = -12.0
|
volume_db = -12.0
|
||||||
|
|
||||||
[node name="InteractBox" type="Area2D" parent="."]
|
[connection signal="animation_finished" from="AnimatedSprite2D" to="." method="_on_attack_end"]
|
||||||
collision_layer = 0
|
|
||||||
collision_mask = 64
|
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="InteractBox"]
|
|
||||||
position = Vector2(0.2, 6)
|
|
||||||
scale = Vector2(0.7, 0.72)
|
|
||||||
shape = SubResource("RectangleShape2D_e7oew")
|
|
||||||
|
|
||||||
[connection signal="animation_finished" from="Sprite" to="." method="_on_attack_end"]
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
extends Area2D
|
extends Area2D
|
||||||
|
var anim_sprite: AnimatedSprite2D
|
||||||
|
var slash_duration = 0.1
|
||||||
|
var slash_timer = 0
|
||||||
var damage = 20
|
var damage = 20
|
||||||
var facing = -1
|
var facing = -1
|
||||||
var facing_mult = 1
|
var facing_mult = 1
|
||||||
|
|
@ -7,44 +10,36 @@ var swing_knockback = 1000
|
||||||
var apply_swing_knockback = true
|
var apply_swing_knockback = true
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
anim_sprite = $AnimatedSprite2D
|
||||||
get_parent().attack.connect(swing)
|
get_parent().attack.connect(swing)
|
||||||
|
|
||||||
func swing(dir_str) -> void:
|
func swing(dir_str) -> void:
|
||||||
# Each swing is a new damage instance
|
|
||||||
dmg_id = Global.next_dmg_id
|
dmg_id = Global.next_dmg_id
|
||||||
facing = get_parent().facing * facing_mult
|
facing = - get_parent().facing * facing_mult
|
||||||
# Adjust the orientation depending on direction of the player,
|
|
||||||
# direction of the sword and whether it was an upward strike
|
|
||||||
if dir_str == "up":
|
if dir_str == "up":
|
||||||
scale.x = abs(scale.x)
|
scale.x = abs(scale.x)
|
||||||
scale.y = abs(scale.y) * -facing
|
scale.y = abs(scale.y) * facing
|
||||||
rotation = PI/2 * facing_mult
|
rotation = PI/2 * facing_mult
|
||||||
else:
|
else:
|
||||||
scale.x = abs(scale.x) * -facing
|
scale.x = abs(scale.x) * facing
|
||||||
scale.y = abs(scale.y)
|
scale.y = abs(scale.y)
|
||||||
rotation = 0
|
rotation = 0
|
||||||
|
anim_sprite.visible = true
|
||||||
# Sprite is visible during swing
|
|
||||||
$Sprite.visible = true
|
|
||||||
|
|
||||||
# Wait for the physics to register the new orientation of the sword before starting the swing
|
|
||||||
await get_tree().physics_frame
|
await get_tree().physics_frame
|
||||||
await get_tree().physics_frame
|
await get_tree().physics_frame
|
||||||
# Upslashes do not knockback you when you hit
|
|
||||||
if dir_str == "up":
|
if dir_str == "up":
|
||||||
apply_swing_knockback = false
|
apply_swing_knockback = false
|
||||||
$SlashTimer.start()
|
slash_timer = slash_duration
|
||||||
|
|
||||||
func end_slash():
|
func _process(delta: float) -> void:
|
||||||
# Reset swing-specific values to default
|
if slash_timer > 0:
|
||||||
$Sprite.visible = false
|
slash_timer = max(0, slash_timer-delta)
|
||||||
apply_swing_knockback = true
|
if(slash_timer == 0):
|
||||||
|
anim_sprite.visible = false
|
||||||
func _process(_delta: float) -> void:
|
|
||||||
if $SlashTimer.time_left > 0:
|
|
||||||
# Hurt all overlapping enemies
|
|
||||||
for area in get_overlapping_areas():
|
for area in get_overlapping_areas():
|
||||||
var hurt_dir = -get_parent().get_node("EarthAligner").global_from_local(Vector2(facing, 0)).rotated(-facing*rotation)
|
var hurt_dir = -get_parent().earth_aligner.global_from_local(Vector2(-facing, 0)).rotated(facing*rotation)
|
||||||
if area.hurt(damage, hurt_dir, dmg_id) and apply_swing_knockback:
|
if area.hurt(damage, hurt_dir, dmg_id) and apply_swing_knockback:
|
||||||
get_parent().reset_to_velocity += Vector2(swing_knockback * -facing, 0)
|
get_parent().reset_to_velocity += Vector2(swing_knockback * facing, 0)
|
||||||
apply_swing_knockback = false
|
apply_swing_knockback = false
|
||||||
|
else:
|
||||||
|
apply_swing_knockback = true
|
||||||
|
|
|
||||||
|
|
@ -28,15 +28,9 @@ rotation = 1.5708
|
||||||
scale = Vector2(-0.635, -1.1)
|
scale = Vector2(-0.635, -1.1)
|
||||||
shape = SubResource("CapsuleShape2D_e4ynd")
|
shape = SubResource("CapsuleShape2D_e4ynd")
|
||||||
|
|
||||||
[node name="Sprite" type="AnimatedSprite2D" parent="."]
|
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."]
|
||||||
visible = false
|
visible = false
|
||||||
modulate = Color(1, 1, 1, 0.6862745)
|
modulate = Color(1, 1, 1, 0.6862745)
|
||||||
position = Vector2(-25.644447, 0.61333346)
|
position = Vector2(-25.644447, 0.61333346)
|
||||||
scale = Vector2(-0.385, 0.231)
|
scale = Vector2(-0.385, 0.231)
|
||||||
sprite_frames = SubResource("SpriteFrames_fahsa")
|
sprite_frames = SubResource("SpriteFrames_fahsa")
|
||||||
|
|
||||||
[node name="SlashTimer" type="Timer" parent="."]
|
|
||||||
wait_time = 0.1
|
|
||||||
one_shot = true
|
|
||||||
|
|
||||||
[connection signal="timeout" from="SlashTimer" to="." method="end_slash"]
|
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,6 @@ run/main_scene="uid://dpkr8yoobtej6"
|
||||||
config/features=PackedStringArray("4.5", "Forward Plus")
|
config/features=PackedStringArray("4.5", "Forward Plus")
|
||||||
config/icon="res://icon.svg"
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
[autoload]
|
|
||||||
|
|
||||||
Grid="*res://world/grid.tscn"
|
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
window/size/viewport_width=1920
|
window/size/viewport_width=1920
|
||||||
|
|
@ -92,13 +88,6 @@ journal={
|
||||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":4,"pressure":0.0,"pressed":true,"script":null)
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":4,"pressure":0.0,"pressed":true,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
interact={
|
|
||||||
"deadzone": 0.2,
|
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
|
|
||||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":86,"key_label":0,"unicode":118,"location":0,"echo":false,"script":null)
|
|
||||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":9,"pressure":0.0,"pressed":true,"script":null)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
[layer_names]
|
[layer_names]
|
||||||
|
|
||||||
|
|
@ -107,7 +96,6 @@ interact={
|
||||||
2d_physics/layer_4="SolidGround"
|
2d_physics/layer_4="SolidGround"
|
||||||
2d_physics/layer_5="EnemyHurtBoxNonTrap"
|
2d_physics/layer_5="EnemyHurtBoxNonTrap"
|
||||||
2d_physics/layer_6="TrulySolidGround"
|
2d_physics/layer_6="TrulySolidGround"
|
||||||
2d_physics/layer_7="InteractionArea"
|
|
||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
# Status inherits from Timer: The time until the status expires.
|
|
||||||
class_name Status extends Timer
|
|
||||||
var params = {}
|
|
||||||
const default_parameters = {
|
|
||||||
# Vulnerable: Player Status. Increases Damage taken by 1 per Hit
|
|
||||||
"Vulnerable": {},
|
|
||||||
# Slow: Player Status. Decreases movement speed by speed_factor.
|
|
||||||
# Can theoretically also be used to increase speed.
|
|
||||||
"Slow": {
|
|
||||||
"slow_factor": 0.6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
timeout.connect(expire)
|
|
||||||
one_shot = true
|
|
||||||
|
|
||||||
# Applies the given Status as a child to the target node for the given duration.
|
|
||||||
# Was intended to work through statusses inheriting from Status, but Godot inheritance interacts terribly with static things.
|
|
||||||
# If that status is already present on the node, duration is reset and reapply is called.
|
|
||||||
static func apply(status_name : String, target : Node, duration : float, params_in : Dictionary = {}):
|
|
||||||
if not target.has_node(status_name):
|
|
||||||
var child = Status.new()
|
|
||||||
child.name = status_name
|
|
||||||
child.params = get_default_parameters(status_name).duplicate(true)
|
|
||||||
child.params.merge(params_in.duplicate(true))
|
|
||||||
target.add_child(child)
|
|
||||||
child.start(duration)
|
|
||||||
else:
|
|
||||||
var child : Status = target.get_node(status_name)
|
|
||||||
child.reapply(duration)
|
|
||||||
|
|
||||||
# Checks whether the target is affected by the given status.
|
|
||||||
static func affects(status_name : String, target : Node) -> bool:
|
|
||||||
return target.has_node(status_name)
|
|
||||||
|
|
||||||
# Returns the status on the target, or null if the status is not present.
|
|
||||||
static func get_status(status_name : String, target : Node) -> Status:
|
|
||||||
if affects(status_name, target):
|
|
||||||
return target.get_node(status_name)
|
|
||||||
else:
|
|
||||||
return null
|
|
||||||
|
|
||||||
# By default, reapplying a status resets the duration and merges the new parameters into the given ones.
|
|
||||||
func reapply(duration_new : float, params_new : Dictionary = {}):
|
|
||||||
self.start(max(duration_new, time_left))
|
|
||||||
merge_params(params_new)
|
|
||||||
|
|
||||||
# Controls the override behavior of each status
|
|
||||||
func merge_params(params_new):
|
|
||||||
if name == "Slow" and params.has("slow_factor") and params_new.has("slow_factor"):
|
|
||||||
params.slow_factor = min(params.slow_factor, params_new.slow_factor)
|
|
||||||
|
|
||||||
func expire():
|
|
||||||
queue_free()
|
|
||||||
|
|
||||||
static func get_default_parameters(status_name : String) -> Dictionary:
|
|
||||||
return default_parameters.get(status_name, {})
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
uid://c574sege4i20i
|
|
||||||
|
|
@ -6,23 +6,25 @@ extends Trap
|
||||||
var closed = false
|
var closed = false
|
||||||
|
|
||||||
func damage_target(target: CollisionObject2D):
|
func damage_target(target: CollisionObject2D):
|
||||||
# Checks whether the player or an enemy has been hit
|
|
||||||
if(target.get_collision_layer_value(3)):
|
if(target.get_collision_layer_value(3)):
|
||||||
target.hurt(player_damage, Vector2.ZERO)
|
target.hurt(player_damage, Vector2.ZERO)
|
||||||
else:
|
else:
|
||||||
target.hurt(enemy_damage, Vector2.ZERO)
|
target.hurt(enemy_damage, Vector2.ZERO)
|
||||||
|
|
||||||
# Bear Traps are static, hence it is sufficient to check for others entering the trap
|
|
||||||
func _on_area_2d_area_entered(area: Area2D) -> void:
|
func _on_area_2d_area_entered(area: Area2D) -> void:
|
||||||
close_on(area)
|
|
||||||
|
|
||||||
func _on_area_2d_body_entered(body: Node2D) -> void:
|
|
||||||
close_on(body)
|
|
||||||
|
|
||||||
# Play the animation and assign the damage
|
|
||||||
func close_on(target):
|
|
||||||
if not closed:
|
if not closed:
|
||||||
damage_target(target)
|
damage_target(area)
|
||||||
|
closed = true
|
||||||
|
$AnimatedSprite2D.play("close")
|
||||||
|
await $AnimatedSprite2D.animation_finished
|
||||||
|
await get_tree().create_timer(0.1).timeout
|
||||||
|
queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_area_2d_body_entered(body: Node2D) -> void:
|
||||||
|
if not closed:
|
||||||
|
damage_target(body)
|
||||||
closed = true
|
closed = true
|
||||||
$AnimatedSprite2D.play("close")
|
$AnimatedSprite2D.play("close")
|
||||||
await $AnimatedSprite2D.animation_finished
|
await $AnimatedSprite2D.animation_finished
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
extends Trap
|
extends Trap
|
||||||
@onready var ball : Area2D = $Area2D
|
@onready var ball : Area2D = $Area2D
|
||||||
var angular_speed = 0.3
|
var anglespeed = 0.3
|
||||||
var player_damage = 1
|
var player_damage = 1
|
||||||
var enemy_damage = 10
|
var enemy_damage = 10
|
||||||
@onready var dmg_id = Global.next_dmg_id
|
@onready var dmg_id = Global.next_dmg_id
|
||||||
var id_refreshing = false
|
var id_refreshing = false
|
||||||
|
|
||||||
func _enter_grid() -> void:
|
func _ready() -> void:
|
||||||
# Randomize starting position and rotation direction
|
|
||||||
rotate(randf() * TAU)
|
rotate(randf() * TAU)
|
||||||
angular_speed = angular_speed * (2 * randi_range(0,1) - 1)
|
anglespeed = anglespeed * (2 * randi_range(0,1) - 1)
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
rotate(angular_speed * delta * TAU)
|
rotate(anglespeed * delta * TAU)
|
||||||
for target in ball.get_overlapping_areas():
|
for target in ball.get_overlapping_areas():
|
||||||
damage_target(target)
|
damage_target(target)
|
||||||
|
|
||||||
|
|
@ -20,9 +19,16 @@ func _process(delta: float) -> void:
|
||||||
damage_target(target)
|
damage_target(target)
|
||||||
|
|
||||||
func damage_target(target: CollisionObject2D):
|
func damage_target(target: CollisionObject2D):
|
||||||
# Targets are knocked away from the ball
|
|
||||||
var dir = (ball.global_position - target.global_position).normalized()
|
var dir = (ball.global_position - target.global_position).normalized()
|
||||||
if(target.get_collision_layer_value(3)):
|
if(target.get_collision_layer_value(3)):
|
||||||
target.hurt(player_damage, dir)
|
target.hurt(player_damage, dir)
|
||||||
else:
|
else:
|
||||||
target.hurt(enemy_damage, 2.5*dir, dmg_id)
|
target.hurt(enemy_damage, 2.5*dir, dmg_id)
|
||||||
|
refresh_id()
|
||||||
|
|
||||||
|
func refresh_id():
|
||||||
|
if not id_refreshing:
|
||||||
|
id_refreshing = true
|
||||||
|
await get_tree().create_timer(0.2).timeout
|
||||||
|
id_refreshing = false
|
||||||
|
dmg_id = Global.next_dmg_id
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,4 @@ func _on_player_player_died() -> void:
|
||||||
|
|
||||||
func _on_button_retry_pressed() -> void:
|
func _on_button_retry_pressed() -> void:
|
||||||
Engine.time_scale = 1.
|
Engine.time_scale = 1.
|
||||||
Grid.reset()
|
get_tree().reload_current_scene()
|
||||||
get_tree().call_deferred("reload_current_scene")
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,13 @@ extends HBoxContainer
|
||||||
|
|
||||||
@export var packedHeart : PackedScene;
|
@export var packedHeart : PackedScene;
|
||||||
var current_max_hp = 0
|
var current_max_hp = 0
|
||||||
|
var current_health = 0
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
#for i in range(initial_health):
|
||||||
|
#add_child(packedHeart.instantiate())
|
||||||
|
pass
|
||||||
|
|
||||||
# Set number of hearts to red corresponding to input health
|
|
||||||
func set_health(health : int):
|
func set_health(health : int):
|
||||||
var i = 0
|
var i = 0
|
||||||
for heart : Heart in get_children():
|
for heart : Heart in get_children():
|
||||||
|
|
@ -12,8 +17,8 @@ func set_health(health : int):
|
||||||
heart.restore()
|
heart.restore()
|
||||||
else:
|
else:
|
||||||
heart.fade()
|
heart.fade()
|
||||||
|
current_health = health
|
||||||
|
|
||||||
# Adjust the number of children to max HP
|
|
||||||
func set_maxhealth(new_max_hp : int):
|
func set_maxhealth(new_max_hp : int):
|
||||||
if new_max_hp > current_max_hp:
|
if new_max_hp > current_max_hp:
|
||||||
for i in range(new_max_hp - current_max_hp):
|
for i in range(new_max_hp - current_max_hp):
|
||||||
|
|
@ -23,11 +28,16 @@ func set_maxhealth(new_max_hp : int):
|
||||||
for i in range(children.size()):
|
for i in range(children.size()):
|
||||||
if new_max_hp < i+1:
|
if new_max_hp < i+1:
|
||||||
children[i].queue_free()
|
children[i].queue_free()
|
||||||
current_max_hp = new_max_hp
|
current_health = min(current_health, current_max_hp)
|
||||||
|
else: pass
|
||||||
|
|
||||||
|
current_max_hp = new_max_hp
|
||||||
|
set_health(current_max_hp)
|
||||||
|
|
||||||
|
|
||||||
# Update display on (max) HP changes for the player
|
|
||||||
func _on_player_health_changed(new_health: int) -> void:
|
func _on_player_health_changed(new_health: int) -> void:
|
||||||
set_health(new_health)
|
set_health(new_health)
|
||||||
|
|
||||||
|
|
||||||
func _on_player_max_hp_changed(new_max_hp: int) -> void:
|
func _on_player_max_hp_changed(new_max_hp: int) -> void:
|
||||||
set_maxhealth(new_max_hp)
|
set_maxhealth(new_max_hp)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ extends MarginContainer
|
||||||
@export var player : Player;
|
@export var player : Player;
|
||||||
@onready var timer : Timer = player.get_node("ActiveItemCooldown")
|
@onready var timer : Timer = player.get_node("ActiveItemCooldown")
|
||||||
|
|
||||||
# Refresh icon and cooldown on active item change
|
|
||||||
func _on_player_active_item_changed(newitem: ActiveItem) -> void:
|
func _on_player_active_item_changed(newitem: ActiveItem) -> void:
|
||||||
timer.stop()
|
timer.stop()
|
||||||
if(newitem != null):
|
if(newitem != null):
|
||||||
|
|
@ -11,9 +10,10 @@ func _on_player_active_item_changed(newitem: ActiveItem) -> void:
|
||||||
else:
|
else:
|
||||||
%ItemTexture.texture = null
|
%ItemTexture.texture = null
|
||||||
|
|
||||||
# Display the cooldown progress
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
|
# visible = timer.time_left != 0;
|
||||||
if not visible: return;
|
if not visible: return;
|
||||||
|
|
||||||
var percentage = timer.time_left / timer.wait_time;
|
var percentage = timer.time_left / timer.wait_time;
|
||||||
$CooldownOverlay.material.set_shader_parameter("percentage", percentage);
|
$CooldownOverlay.material.set_shader_parameter("percentage", percentage);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ extends Control
|
||||||
|
|
||||||
@onready var item_list : ItemList = $ItemList
|
@onready var item_list : ItemList = $ItemList
|
||||||
|
|
||||||
# Add items from each pool to journal. TODO: Deal with multiplicities.
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
await get_tree().create_timer(0.3).timeout
|
await get_tree().create_timer(0.3).timeout
|
||||||
for item_scene in ItemSpawn.item_pool.common:
|
for item_scene in ItemSpawn.item_pool.common:
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@ extends Node
|
||||||
|
|
||||||
|
|
||||||
func _on_button_start_pressed() -> void:
|
func _on_button_start_pressed() -> void:
|
||||||
Grid.reset()
|
get_tree().change_scene_to_file("res://main.tscn")
|
||||||
get_tree().call_deferred("change_scene_to_file","res://main.tscn")
|
|
||||||
|
|
||||||
|
|
||||||
func _on_button_quit_pressed() -> void:
|
func _on_button_quit_pressed() -> void:
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@ zoom = Vector2(0.12, 0.12)
|
||||||
script = ExtResource("2_d3a7t")
|
script = ExtResource("2_d3a7t")
|
||||||
initial_buildings = 200
|
initial_buildings = 200
|
||||||
initial_spawn_protection = false
|
initial_spawn_protection = false
|
||||||
only_on_first_load = true
|
|
||||||
|
|
||||||
[node name="Timer" type="Timer" parent="Building Generator"]
|
[node name="Timer" type="Timer" parent="Building Generator"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,4 @@ extends Button
|
||||||
|
|
||||||
|
|
||||||
func _on_pressed() -> void:
|
func _on_pressed() -> void:
|
||||||
get_tree().call_deferred("change_scene_to_file","res://ui/main_menu/main_menu.tscn")
|
get_tree().change_scene_to_file("res://ui/main_menu/main_menu.tscn")
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,22 @@
|
||||||
extends Node2D
|
extends Node2D
|
||||||
|
|
||||||
|
var parent : Node2D
|
||||||
@export var center = Vector2.ZERO
|
@export var center = Vector2.ZERO
|
||||||
var angle = 0
|
var angle = 0
|
||||||
|
|
||||||
# Quick access to relative cardinal directions
|
func _ready() -> void:
|
||||||
var up : Vector2 :
|
parent = get_parent()
|
||||||
get():
|
|
||||||
return global_from_local(Vector2.UP)
|
|
||||||
var down : Vector2 :
|
|
||||||
get():
|
|
||||||
return global_from_local(Vector2.DOWN)
|
|
||||||
var right : Vector2 :
|
|
||||||
get():
|
|
||||||
return global_from_local(Vector2.RIGHT)
|
|
||||||
var left : Vector2 :
|
|
||||||
get():
|
|
||||||
return global_from_local(Vector2.LEFT)
|
|
||||||
|
|
||||||
func _enter_grid() -> void:
|
|
||||||
align()
|
align()
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
align()
|
align()
|
||||||
|
|
||||||
# Aligns the parent to the center
|
|
||||||
func align():
|
func align():
|
||||||
angle = -(get_parent().position - center).angle_to(Vector2.UP)
|
angle = -(parent.position - center).angle_to(Vector2.UP)
|
||||||
get_parent().rotation = angle;
|
parent.rotation = angle;
|
||||||
|
|
||||||
# Converts directions in local coordinates into global coordinates
|
|
||||||
func global_from_local (_velocity: Vector2) -> Vector2:
|
func global_from_local (_velocity: Vector2) -> Vector2:
|
||||||
return _velocity.rotated(angle)
|
return _velocity.rotated(angle)
|
||||||
|
|
||||||
# Converts directions in global coordinates into local coordinates
|
|
||||||
func local_from_global (_velocity: Vector2) -> Vector2:
|
func local_from_global (_velocity: Vector2) -> Vector2:
|
||||||
return _velocity.rotated(-angle)
|
return _velocity.rotated(-angle)
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,43 @@
|
||||||
class_name EnemyHurtbox extends Area2D
|
class_name EnemyHurtbox extends Area2D
|
||||||
|
|
||||||
# HP Management
|
|
||||||
@export var max_hp : int
|
@export var max_hp : int
|
||||||
@onready var hp = max_hp :
|
@onready var hp = max_hp
|
||||||
set(new_hp):
|
|
||||||
if new_hp > max_hp:
|
|
||||||
new_hp = max_hp
|
|
||||||
if new_hp < 0 :
|
|
||||||
new_hp = 0
|
|
||||||
hp = new_hp
|
|
||||||
if hp == 0:
|
|
||||||
die()
|
|
||||||
@export var flash_duration = 0.2
|
@export var flash_duration = 0.2
|
||||||
@export var canvasItem : CanvasItem
|
@export var canvasItem : CanvasItem
|
||||||
@export var flashColor : Color = Color(1.5, 1.5, 1.5)
|
@export var flashColor : Color = Color(1.5, 1.5, 1.5)
|
||||||
|
|
||||||
# Instance-Specific Damage Cooldown
|
|
||||||
@export var id_block_time = 0.12
|
@export var id_block_time = 0.12
|
||||||
|
var flash_time = 0
|
||||||
var blocked_damage_ids : Array[int] = []
|
var blocked_damage_ids : Array[int] = []
|
||||||
|
|
||||||
signal damage_taken(damage, dir, id)
|
signal damage_taken(damage, dir, id)
|
||||||
signal died
|
signal died
|
||||||
|
|
||||||
# Reset Hit Flash
|
func _process(delta: float) -> void:
|
||||||
func _on_flash_ended():
|
flash_time = max(flash_time-delta, 0)
|
||||||
if canvasItem != null:
|
if(flash_time <= 0 and canvasItem != null):
|
||||||
canvasItem.modulate = Color(1,1,1)
|
canvasItem.modulate = Color(1,1,1)
|
||||||
|
|
||||||
func hurt(damage : int, dir : Vector2 = Vector2.ZERO, id : int = -1) -> bool:
|
func hurt(damage : int, dir : Vector2 = Vector2.ZERO, id : int = -1) -> bool:
|
||||||
if not id in blocked_damage_ids:
|
if not id in blocked_damage_ids:
|
||||||
block_id(id)
|
block_id(id)
|
||||||
if canvasItem != null: canvasItem.modulate = flashColor
|
if canvasItem != null: canvasItem.modulate = flashColor
|
||||||
if has_node("FlashTimer"): $FlashTimer.start(flash_duration)
|
flash_time = flash_duration
|
||||||
hp -= damage
|
hp = max(hp-damage, 0)
|
||||||
damage_taken.emit(damage, dir, id)
|
damage_taken.emit(damage, dir, id)
|
||||||
|
if(hp <= 0):
|
||||||
|
die()
|
||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# When taking damage, prevent damage from that instance for id_block_time.
|
|
||||||
func block_id(id : int):
|
func block_id(id : int):
|
||||||
blocked_damage_ids.append(id)
|
blocked_damage_ids.append(id)
|
||||||
await get_tree().create_timer(id_block_time).timeout
|
await get_tree().create_timer(id_block_time).timeout
|
||||||
var id_pos = blocked_damage_ids.find(id)
|
var id_pos = blocked_damage_ids.find(id)
|
||||||
if not id_pos == -1: blocked_damage_ids.remove_at(id_pos)
|
if not id_pos == -1: blocked_damage_ids.remove_at(id_pos)
|
||||||
|
|
||||||
|
|
||||||
func die():
|
func die():
|
||||||
died.emit()
|
died.emit()
|
||||||
|
|
||||||
func destroy():
|
func destroy():
|
||||||
hurt(10 * max_hp)
|
hurt(9999)
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,3 @@
|
||||||
[node name="EnemyHurtbox" type="Area2D"]
|
[node name="EnemyHurtbox" type="Area2D"]
|
||||||
collision_layer = 18
|
collision_layer = 18
|
||||||
script = ExtResource("1_wa58b")
|
script = ExtResource("1_wa58b")
|
||||||
|
|
||||||
[node name="FlashTimer" type="Timer" parent="."]
|
|
||||||
wait_time = 0.2
|
|
||||||
one_shot = true
|
|
||||||
|
|
||||||
[connection signal="timeout" from="FlashTimer" to="." method="_on_flash_ended"]
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,6 @@
|
||||||
class_name Global
|
class_name Global
|
||||||
|
|
||||||
# Returns the next damage instance (and increases by 1)
|
|
||||||
static var next_dmg_id : int = 0 :
|
static var next_dmg_id : int = 0 :
|
||||||
get:
|
get:
|
||||||
next_dmg_id += 1
|
next_dmg_id += 1
|
||||||
return next_dmg_id
|
return next_dmg_id
|
||||||
|
|
||||||
# Entrywise modulo
|
|
||||||
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)
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
# Simple area that emits a signal when the player interacts while overlapping
|
|
||||||
extends Area2D
|
|
||||||
|
|
||||||
@export var oneshot = true
|
|
||||||
@export var cooldown = 10
|
|
||||||
var already_used = false
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
$Cooldown.wait_time = cooldown
|
|
||||||
|
|
||||||
signal interaction
|
|
||||||
|
|
||||||
func interact():
|
|
||||||
if not $Cooldown.time_left > 0 and not (oneshot and already_used):
|
|
||||||
already_used = true
|
|
||||||
$Cooldown.start()
|
|
||||||
interaction.emit()
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
uid://bgp6nutttqhpb
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
[gd_scene load_steps=2 format=3 uid="uid://ds21d77ruuk4y"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://bgp6nutttqhpb" path="res://utils/interactable_area.gd" id="1_rua2f"]
|
|
||||||
|
|
||||||
[node name="InteractableArea" type="Area2D"]
|
|
||||||
collision_layer = 64
|
|
||||||
script = ExtResource("1_rua2f")
|
|
||||||
|
|
||||||
[node name="Cooldown" type="Timer" parent="."]
|
|
||||||
|
|
@ -2,15 +2,12 @@ class_name Platform extends StaticBody2D
|
||||||
var building
|
var building
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
# Choose a platform design at random
|
|
||||||
if(randf() > 0.5):
|
if(randf() > 0.5):
|
||||||
$Sprite2D.visible = true
|
$Sprite2D.visible = true
|
||||||
$Sprite2D2.visible = false
|
$Sprite2D2.visible = false
|
||||||
|
|
||||||
# Stretch platforms depending on their height to prevent gaps in the ceiling
|
|
||||||
func init_at_horizontal_distortion(distortion : float):
|
func init_at_horizontal_distortion(distortion : float):
|
||||||
scale.x *= distortion
|
scale.x *= distortion
|
||||||
|
|
||||||
# When a platform is destroyed, destroy the whole building
|
|
||||||
func destroy():
|
func destroy():
|
||||||
building.destroy()
|
building.destroy()
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ class_name Bud extends VineNode
|
||||||
signal opened
|
signal opened
|
||||||
var img_path
|
var img_path
|
||||||
|
|
||||||
# Triggers when a bud is hit. Spreads the vine, then removes the bud
|
|
||||||
func _on_opened():
|
func _on_opened():
|
||||||
$AnimatedSprite2D.play("open")
|
$AnimatedSprite2D.play("open")
|
||||||
opened.emit()
|
opened.emit()
|
||||||
|
|
@ -10,14 +9,18 @@ func _on_opened():
|
||||||
await $AnimatedSprite2D.animation_finished
|
await $AnimatedSprite2D.animation_finished
|
||||||
queue_free()
|
queue_free()
|
||||||
|
|
||||||
# Spread in all directions where the given vine is not yet present
|
|
||||||
func spread():
|
func spread():
|
||||||
for dir in [Vector2.UP, Vector2.DOWN, Vector2.RIGHT, Vector2.LEFT]:
|
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, true)):
|
if not vine.vine_locations.has(vec_mod(location + dir, grid.num_collumns)):
|
||||||
|
vine.vine_locations.append(vec_mod(location + dir, grid.num_collumns))
|
||||||
grow_to_next_bud(dir)
|
grow_to_next_bud(dir)
|
||||||
|
|
||||||
# Grow a vine
|
|
||||||
func grow_to_next_bud(dir):
|
func grow_to_next_bud(dir):
|
||||||
var target_location = Global.vec_mod(location + dir, Grid.num_collumns, true)
|
var target_offset = Vector2(randf_range(60, 240), randf_range(90, 270))
|
||||||
var target = vine.random_vine_node_at(target_location)
|
var pos1 = grid.get_world_position(location, offset)
|
||||||
await vine.grow_vine_sequence(self, target, true, false)
|
var pos2 = grid.get_world_position(location + dir, target_offset)
|
||||||
|
var num_seg = floor((pos1-pos2).length() / 96)
|
||||||
|
await vine.grow_vine_sequence(location, offset, location + dir, target_offset, num_seg, true)
|
||||||
|
|
||||||
|
func vec_mod(vec : Vector2, modulus : float):
|
||||||
|
return Vector2(fposmod(vec.x, modulus), fposmod(vec.y, modulus))
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
class_name Petal extends VineNode
|
|
||||||
@export var vine_resource : PackedScene
|
|
||||||
var activated = false
|
|
||||||
|
|
||||||
# Adding an item here automatically locks it inside the petal
|
|
||||||
var item : Item:
|
|
||||||
set(item_in):
|
|
||||||
item = item_in
|
|
||||||
if not activated and item is Item:
|
|
||||||
item.monitoring = false
|
|
||||||
item.monitorable = false
|
|
||||||
item.modulate = Color(1,1,1,0.8)
|
|
||||||
|
|
||||||
# Upon being placed inside a building, create a vine
|
|
||||||
func _enter_grid() -> void:
|
|
||||||
depth = 0
|
|
||||||
vine = vine_resource.instantiate()
|
|
||||||
vine.petal = self
|
|
||||||
get_parent().call_deferred("add_child",vine)
|
|
||||||
|
|
||||||
# Upon interaction, release the item and activate the vine
|
|
||||||
func _on_interaction() -> void:
|
|
||||||
activated = true
|
|
||||||
vine.activate()
|
|
||||||
if item != null:
|
|
||||||
item.monitorable = true
|
|
||||||
item.monitoring = true
|
|
||||||
item.modulate = Color.WHITE
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
uid://coi26l4ndnaw0
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
[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://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"]
|
|
||||||
|
|
||||||
[sub_resource type="CircleShape2D" id="CircleShape2D_4btcp"]
|
|
||||||
radius = 64.381676
|
|
||||||
|
|
||||||
[node name="Petal" type="Node2D"]
|
|
||||||
script = ExtResource("1_ybtdj")
|
|
||||||
vine_resource = ExtResource("2_4btcp")
|
|
||||||
|
|
||||||
[node name="BudOpen" type="Sprite2D" parent="."]
|
|
||||||
modulate = Color(18.892157, 1.8483434, 18.892157, 1)
|
|
||||||
z_index = -1
|
|
||||||
scale = Vector2(5, 5)
|
|
||||||
texture = ExtResource("1_05pvv")
|
|
||||||
|
|
||||||
[node name="InteractableArea" parent="." instance=ExtResource("2_ybtdj")]
|
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="InteractableArea"]
|
|
||||||
position = Vector2(-1, 12)
|
|
||||||
shape = SubResource("CircleShape2D_4btcp")
|
|
||||||
|
|
||||||
[node name="EarthAligner" parent="." instance=ExtResource("5_uxqeq")]
|
|
||||||
|
|
||||||
[connection signal="interaction" from="InteractableArea" to="." method="_on_interaction"]
|
|
||||||
|
|
@ -1,104 +1,39 @@
|
||||||
class_name Vine extends Node2D
|
class_name Vine extends Node2D
|
||||||
# Grid position data of the petal
|
var petal_location : Vector2
|
||||||
@export var petal : Petal
|
|
||||||
|
|
||||||
# Locations and offsets of all vine segments
|
|
||||||
@export var vine_locations : Array[Vector2]
|
@export var vine_locations : Array[Vector2]
|
||||||
@export var bud_resource : PackedScene
|
@export var bud_resource : PackedScene
|
||||||
|
@onready var grid : Grid = get_tree().get_root().get_node("main/Earth/Grid")
|
||||||
|
var img_path = "res://vines_petals/vine_active_green.png"
|
||||||
|
|
||||||
# Paths for vine segment sprites while active/inactive
|
func draw_vine(pos1 : Vector2, pos2 : Vector2):
|
||||||
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()
|
var sprite = Sprite2D.new()
|
||||||
get_tree().get_root().get_node("main").add_child(sprite)
|
get_tree().get_root().get_node("main").add_child(sprite)
|
||||||
if active_depth >= depth:
|
sprite.texture = ResourceLoader.load(img_path)
|
||||||
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.position = (pos1 + pos2)/2.0
|
||||||
sprite.rotation = (pos1 - pos2).angle() + PI/2
|
sprite.rotation = (pos1 - pos2).angle() + PI/2
|
||||||
sprite.scale *= (pos1 - pos2).length() * 1 / sprite.texture.get_width()
|
sprite.scale *= (pos1 - pos2).length() * 1 / sprite.texture.get_width()
|
||||||
sprite.scale.x = 3
|
sprite.scale.x = 3
|
||||||
sprite.z_index = -1
|
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(location1 : Vector2, offset1 : Vector2, location2: Vector2, offset2 : Vector2, num_segments: int, grow_bud = false):
|
||||||
func grow_vine_sequence(start : VineNode, target: VineNode, grow_bud = false, quick_spawn = false):
|
var pos1 = grid.get_world_position(location1, offset1)
|
||||||
var depth = min(start.depth, max_depth)
|
var pos2 = grid.get_world_position(location2, offset2)
|
||||||
|
|
||||||
# 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 = []
|
var positions = []
|
||||||
positions.append(start.position)
|
positions.append(pos1)
|
||||||
|
|
||||||
|
var segment_length = (pos1 - pos2).length() / num_segments
|
||||||
var offsets = generate_random_offsets(num_segments, 0.6 * segment_length)
|
var offsets = generate_random_offsets(num_segments, 0.6 * segment_length)
|
||||||
for i in range(num_segments - 1):
|
for i in range(num_segments - 1):
|
||||||
var t = (i + 1) * 1.0/num_segments
|
var t = (i + 1) * 1.0/num_segments
|
||||||
var center = t * target.position + (1 - t) * start.position
|
var center = t * pos2 + (1 - t) * pos1
|
||||||
var offset = offsets[i + 1] * (target.position - start.position).normalized().rotated(PI/2)
|
var offset = offsets[i + 1] * (pos2 - pos1).normalized().rotated(PI/2)
|
||||||
positions.append(center + offset)
|
positions.append(center + offset)
|
||||||
positions.append(target.position)
|
positions.append(pos2)
|
||||||
|
|
||||||
# Draw vine segments along the positions determined previously.
|
|
||||||
# Do so slowly unless quick_spawn is specified.
|
|
||||||
for i in range(num_segments):
|
for i in range(num_segments):
|
||||||
draw_vine(positions[i], positions[i+1], depth + i + 1)
|
draw_vine(positions[i], positions[i+1])
|
||||||
if not quick_spawn:
|
|
||||||
await get_tree().create_timer(0.2).timeout
|
await get_tree().create_timer(0.2).timeout
|
||||||
|
if grow_bud and location2.y > 0:
|
||||||
if active_depth >= depth + num_segments:
|
spawn_bud(location2, offset2)
|
||||||
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 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:
|
|
||||||
vine_end_data.remove_at(i)
|
|
||||||
break
|
|
||||||
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):
|
func generate_random_offsets(segment_count, max_second_derivative):
|
||||||
var differences = []
|
var differences = []
|
||||||
|
|
@ -120,57 +55,9 @@ func generate_random_offsets(segment_count, max_second_derivative):
|
||||||
next_val += differences[i]
|
next_val += differences[i]
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
func spawn_bud(location, offset, depth):
|
func spawn_bud(location, offset):
|
||||||
var bud = bud_resource.instantiate()
|
var bud = bud_resource.instantiate()
|
||||||
bud.location = location
|
bud.location = location
|
||||||
bud.offset = offset
|
bud.offset = offset
|
||||||
bud.vine = self
|
bud.vine = self
|
||||||
bud.depth = depth
|
|
||||||
get_tree().get_root().get_node("main").add_child(bud)
|
get_tree().get_root().get_node("main").add_child(bud)
|
||||||
|
|
||||||
func activate():
|
|
||||||
update_active_depth()
|
|
||||||
|
|
||||||
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 data in vine_end_data:
|
|
||||||
if data.depth == 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.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_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)
|
|
||||||
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))
|
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,9 @@
|
||||||
class_name VineNode extends Node2D
|
class_name VineNode extends Node2D
|
||||||
|
|
||||||
@export var vine : Vine
|
@export var vine : Vine
|
||||||
@export var location : Vector2 :
|
@onready var grid : Grid = get_tree().get_root().get_node("main/Earth/Grid")
|
||||||
set(new_loc):
|
@export var location : Vector2
|
||||||
location = Global.vec_mod(new_loc, Grid.num_collumns, true)
|
|
||||||
@export var offset : Vector2
|
@export var offset : Vector2
|
||||||
@export var depth : int
|
|
||||||
|
|
||||||
func _get(property: StringName) -> Variant:
|
func _ready() -> void:
|
||||||
if property == "position":
|
position = grid.get_world_position(location, offset)
|
||||||
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
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
class_name BuildingGenerator extends Node
|
class_name BuildingGenerator extends Node
|
||||||
|
|
||||||
|
@onready var grid : Grid = %Earth.get_grid()
|
||||||
@export var initial_buildings : int;
|
@export var initial_buildings : int;
|
||||||
@export var initial_spawn_protection = true
|
@export var initial_spawn_protection = true
|
||||||
@export var spawn_attempts = 5
|
@export var spawn_attempts = 5
|
||||||
@export var only_on_first_load = false
|
|
||||||
static var first_load = true
|
|
||||||
|
|
||||||
func random_oppostite_collumn() -> int:
|
func random_oppostite_collumn() -> int:
|
||||||
var playerpos = %Player.position
|
var playerpos = %Player.position
|
||||||
|
|
@ -12,16 +11,14 @@ func random_oppostite_collumn() -> int:
|
||||||
|
|
||||||
var offset = randf_range(TAU/3, 2*TAU/3)
|
var offset = randf_range(TAU/3, 2*TAU/3)
|
||||||
var spawn_angle = player_angle + offset
|
var spawn_angle = player_angle + offset
|
||||||
var collumn = int(spawn_angle / TAU * Grid.num_collumns + Grid.num_collumns) % Grid.num_collumns
|
var collumn = int(spawn_angle / TAU * grid.num_collumns + grid.num_collumns) % grid.num_collumns
|
||||||
|
|
||||||
return collumn
|
return collumn
|
||||||
|
|
||||||
func random_collumn() -> int:
|
func random_collumn() -> int:
|
||||||
return randi_range(0, Grid.num_collumns - 1)
|
return randi_range(0, grid.num_collumns - 1)
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
if not (only_on_first_load and not first_load):
|
|
||||||
first_load = false
|
|
||||||
for i in range(initial_buildings):
|
for i in range(initial_buildings):
|
||||||
for j in range(spawn_attempts):
|
for j in range(spawn_attempts):
|
||||||
var collumn = random_collumn()
|
var collumn = random_collumn()
|
||||||
|
|
@ -29,7 +26,7 @@ func _ready():
|
||||||
continue
|
continue
|
||||||
var building = randomize_building()
|
var building = randomize_building()
|
||||||
building.z_index = -2
|
building.z_index = -2
|
||||||
if Grid.add_building_to_collumn(building, collumn):
|
if grid.add_building_to_collumn(building, collumn):
|
||||||
break
|
break
|
||||||
|
|
||||||
func _on_timer_timeout() -> void:
|
func _on_timer_timeout() -> void:
|
||||||
|
|
@ -37,9 +34,9 @@ func _on_timer_timeout() -> void:
|
||||||
var collumn = random_oppostite_collumn()
|
var collumn = random_oppostite_collumn()
|
||||||
var building : Building = randomize_building()
|
var building : Building = randomize_building()
|
||||||
building.z_index = -2
|
building.z_index = -2
|
||||||
if Grid.add_building_to_collumn(building, collumn):
|
if grid.add_building_to_collumn(building, collumn):
|
||||||
break
|
break
|
||||||
|
|
||||||
func randomize_building() -> Building:
|
func randomize_building() -> Building:
|
||||||
var index = randi() % Grid.packed_buildings.size()
|
var index = randi() % grid.packed_buildings.size()
|
||||||
return Grid.packed_buildings[index].instantiate()
|
return grid.packed_buildings[index].instantiate()
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@ extends Node2D
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
ItemSpawn.item_pool = ResourceLoader.load("res://items/generic/item_pool.tres","",ResourceLoader.CACHE_MODE_IGNORE)
|
ItemSpawn.item_pool = ResourceLoader.load("res://items/generic/item_pool.tres","",ResourceLoader.CACHE_MODE_IGNORE)
|
||||||
for column in range(Grid.num_collumns):
|
for column in range($Grid.num_collumns):
|
||||||
var grass_placed : Building = grass.instantiate()
|
var grass_placed : Building = grass.instantiate()
|
||||||
grass_placed.location = Vector2(column, 0)
|
grass_placed.location = Vector2(column, 0)
|
||||||
Grid.add_child(grass_placed)
|
$Grid.add_child(grass_placed)
|
||||||
|
|
||||||
|
func get_grid() -> Grid:
|
||||||
|
return $Grid
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
[gd_scene load_steps=7 format=3 uid="uid://jjoyj1ldafkf"]
|
[gd_scene load_steps=15 format=3 uid="uid://jjoyj1ldafkf"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://vgxh2xdevat7" path="res://world/earth.gd" id="1_wxnww"]
|
[ext_resource type="Script" uid="uid://vgxh2xdevat7" path="res://world/earth.gd" id="1_wxnww"]
|
||||||
[ext_resource type="PackedScene" uid="uid://xrbh432lrjge" path="res://world/grass.tscn" id="2_abvrx"]
|
[ext_resource type="PackedScene" uid="uid://xrbh432lrjge" path="res://world/grass.tscn" id="2_abvrx"]
|
||||||
|
[ext_resource type="Script" uid="uid://m3vyyfk8gnma" path="res://world/grid.gd" id="3_2bhor"]
|
||||||
[ext_resource type="Shader" path="res://world/earth.gdshader" id="3_640fc"]
|
[ext_resource type="Shader" path="res://world/earth.gdshader" id="3_640fc"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://djawvtdwp423v" path="res://buildings/room_temple.tscn" id="3_nihcy"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://cmofmd0vf3hx3" path="res://buildings/room_haunted_house.tscn" id="4_ml5no"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dliwqqmrxldjh" path="res://buildings/room_bear_trap.tscn" id="4_r4pw8"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://6y637jp2tbma" path="res://buildings/room_pedastal.tscn" id="6_640fc"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dt827qxyycg8n" path="res://buildings/room_pedastal_mean.tscn" id="7_abvrx"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://c7ddsyd8kcjji" path="res://buildings/room_wide_item_above.tscn" id="9_ej0af"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://oflm2yjjwhf" path="res://buildings/room_giant_leech.tscn" id="10_640fc"]
|
||||||
|
|
||||||
[sub_resource type="CircleShape2D" id="CircleShape2D_5i67w"]
|
[sub_resource type="CircleShape2D" id="CircleShape2D_5i67w"]
|
||||||
radius = 3000.0
|
radius = 3000.0
|
||||||
|
|
@ -28,3 +36,12 @@ z_index = 3
|
||||||
material = SubResource("ShaderMaterial_abvrx")
|
material = SubResource("ShaderMaterial_abvrx")
|
||||||
scale = Vector2(6000, 6000)
|
scale = Vector2(6000, 6000)
|
||||||
mesh = SubResource("QuadMesh_ej0af")
|
mesh = SubResource("QuadMesh_ej0af")
|
||||||
|
|
||||||
|
[node name="Grid" type="Node2D" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
script = ExtResource("3_2bhor")
|
||||||
|
ground_radius = 3000.0
|
||||||
|
cell_height = 300.0
|
||||||
|
num_collumns = 60
|
||||||
|
packed_buildings = Array[PackedScene]([ExtResource("4_r4pw8"), ExtResource("4_ml5no"), ExtResource("6_640fc"), ExtResource("7_abvrx"), ExtResource("3_nihcy"), ExtResource("9_ej0af"), ExtResource("10_640fc")])
|
||||||
|
metadata/_custom_type_script = "uid://m3vyyfk8gnma"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
extends Node2D
|
class_name Grid extends Node2D
|
||||||
|
|
||||||
@export var ground_radius : float
|
@export var ground_radius : float
|
||||||
@export var cell_height : float
|
@export var cell_height : float
|
||||||
|
|
@ -10,12 +10,6 @@ extends Node2D
|
||||||
|
|
||||||
var buildings : Array[Building] = []
|
var buildings : Array[Building] = []
|
||||||
|
|
||||||
var max_bud_height = 8
|
|
||||||
var vines_per_node : Array
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
reset()
|
|
||||||
|
|
||||||
func _draw() -> void:
|
func _draw() -> void:
|
||||||
if !debug:
|
if !debug:
|
||||||
return
|
return
|
||||||
|
|
@ -51,41 +45,6 @@ func get_world_position (location: Vector2, offset: Vector2 = Vector2.ZERO) -> V
|
||||||
var angle = (location.x + offset.x / cell_height) * TAU / num_collumns
|
var angle = (location.x + offset.x / cell_height) * TAU / num_collumns
|
||||||
return height * Vector2.from_angle(angle)
|
return height * Vector2.from_angle(angle)
|
||||||
|
|
||||||
func get_location_from_world_pos(pos : Vector2):
|
|
||||||
var angle = fposmod(pos.angle(), TAU)
|
|
||||||
var x = floor(num_collumns * angle / TAU)
|
|
||||||
var height = pos.length()
|
|
||||||
var y = ceil((height - ground_radius)/cell_height)
|
|
||||||
return Vector2(x, y)
|
|
||||||
|
|
||||||
func get_offset_from_world_pos(pos : Vector2):
|
|
||||||
var angle = pos.angle()
|
|
||||||
var x = fposmod(num_collumns * angle / TAU, 1) * cell_height
|
|
||||||
var height = pos.length()
|
|
||||||
var y = fposmod(-(height - ground_radius)/cell_height, 1) * cell_height
|
|
||||||
return Vector2(x, y)
|
|
||||||
|
|
||||||
func reset():
|
|
||||||
for obj in get_children():
|
|
||||||
obj.free()
|
|
||||||
buildings = []
|
|
||||||
vines_per_node = []
|
|
||||||
for i in range(num_collumns):
|
|
||||||
var arr = []
|
|
||||||
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
|
# for testing
|
||||||
#func _ready() -> void:
|
#func _ready() -> void:
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
[gd_scene load_steps=9 format=3 uid="uid://dqg5yjkbklfcf"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://m3vyyfk8gnma" path="res://world/grid.gd" id="1_qw8xp"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://dliwqqmrxldjh" path="res://buildings/room_bear_trap.tscn" id="2_ryti2"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://cmofmd0vf3hx3" path="res://buildings/room_haunted_house.tscn" id="3_5v6ap"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://6y637jp2tbma" path="res://buildings/room_pedastal.tscn" id="4_fbocv"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://dt827qxyycg8n" path="res://buildings/room_pedastal_mean.tscn" id="5_lkrwq"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://djawvtdwp423v" path="res://buildings/room_temple.tscn" id="6_8w44a"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://c7ddsyd8kcjji" path="res://buildings/room_wide_item_above.tscn" id="7_ntdb8"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://oflm2yjjwhf" path="res://buildings/room_giant_leech.tscn" id="8_um4di"]
|
|
||||||
|
|
||||||
[node name="Grid" type="Node2D"]
|
|
||||||
script = ExtResource("1_qw8xp")
|
|
||||||
ground_radius = 3000.0
|
|
||||||
cell_height = 300.0
|
|
||||||
num_collumns = 60
|
|
||||||
packed_buildings = Array[PackedScene]([ExtResource("2_ryti2"), ExtResource("3_5v6ap"), ExtResource("4_fbocv"), ExtResource("5_lkrwq"), ExtResource("6_8w44a"), ExtResource("7_ntdb8"), ExtResource("8_um4di")])
|
|
||||||
metadata/_custom_type_script = "uid://m3vyyfk8gnma"
|
|
||||||
Loading…
Add table
Reference in a new issue