diff --git a/global_const.gd b/global_const.gd index 4040af5..47e5fbf 100644 --- a/global_const.gd +++ b/global_const.gd @@ -18,6 +18,7 @@ const GROUP_DAMAGEABLE = "damagable" const GROUP_PLAYER = "player" const GROUP_XP_ORB = "xp_orb" const GROUP_PICKUP = "pickup" +const GROUP_PROJ_MANAGER = "proj_manager" enum ModRarity { LEGENDARY, EPIC, RARE, NORMAL } diff --git a/project.godot b/project.godot index ce843c5..64cf7f8 100644 --- a/project.godot +++ b/project.godot @@ -33,6 +33,7 @@ enemy="Group containing all enemies" damagable="Can be damaged using take_damage" xp_orb="Experience orbs" pickup="Items that can be picked up" +proj_manager="Parent node for all projectiles" [input] diff --git a/scenes/attacks/attack_sword.gd b/scenes/attacks/attack_sword.gd deleted file mode 100644 index 9e3cfae..0000000 --- a/scenes/attacks/attack_sword.gd +++ /dev/null @@ -1,102 +0,0 @@ -extends Node2D - -@export var default_attack_time: float = 0.5 -@export var player: Player = null - -@onready var trigger_area: Area2D = $TriggerArea -@onready var trigger_collision: CollisionShape2D = $TriggerArea/TriggerCollision -@onready var timer: Timer = $Timer -@onready var attack_path: Path2D = $AttackPath -@onready var path_follow_2d: PathFollow2D = $AttackPath/PathFollow2D -@onready var attack_area: Area2D = $AttackPath/PathFollow2D/Sprite2D/AttackArea - -var base_damage: float = 5.0 -var current_target: Node2D = null -var current_progress: float = 0.0 -var damaged_this_attack: Array = [] -var is_attacking: bool = false - - -func _ready() -> void: - attack_path.visible = false - - -func _process(delta: float) -> void: - if timer.is_stopped() and current_progress == 0.0: - timer.start() - if current_progress > 0.95: - reset_attack() - if ( - current_target - and is_instance_valid(current_target) - and not current_target.is_queued_for_deletion() - ): - track_target(current_target) - is_attacking = true - attack_path.visible = true - - if is_attacking: - # Do attack animation - current_progress += delta / default_attack_time - current_progress = clampf(current_progress, 0.0, 1.0) - path_follow_2d.progress_ratio = current_progress - - -func reset_attack() -> void: - current_target = null - attack_path.visible = false - current_progress = 0.0 - damaged_this_attack = [] - is_attacking = false - position = Vector2.ZERO - rotation = 0.0 - - -func set_target(body: Node2D): - current_target = body - is_attacking = true - - -func track_target(body: Node2D): - var mid_distance = attack_path.curve.get_baked_length() / 2 - var mid_point: Vector2 = attack_path.curve.sample_baked(mid_distance) - var offset = body.global_position - to_global(mid_point) - - var desired_dir = (body.global_position - to_global(mid_point)).normalized() - var start_point_global = attack_path.to_global(attack_path.curve.sample_baked(0)) - var end_point_global = attack_path.to_global( - attack_path.curve.sample_baked(attack_path.curve.get_baked_length()) - ) - var curve_dir = (start_point_global - end_point_global).normalized() - var angle_diff = curve_dir.angle_to(desired_dir) - if rotation == 0.0: - rotation = curve_dir.angle_to(desired_dir) - position += offset - - -func _on_timer_timeout() -> void: - if current_target: - if trigger_area.has_overlapping_areas(): - if current_target not in trigger_area.get_overlapping_bodies(): - current_target = null - return - if trigger_area.has_overlapping_bodies(): - for body in trigger_area.get_overlapping_bodies(): - if body.is_in_group(GlobalConst.GROUP_ENEMY): - set_target(body) - - -func _on_attack_area_body_entered(body: Node2D) -> void: - if not attack_path.visible: - return - if body in damaged_this_attack: - return - if body.is_in_group(GlobalConst.GROUP_ENEMY) and body.is_in_group(GlobalConst.GROUP_DAMAGEABLE): - var crit_chance = player.player_stats.get_final("crit_chance", player.modifiers) - var damage_dealt = base_damage - var is_crit = randf() >= 1 - crit_chance - if is_crit: - damage_dealt *= player.player_stats.get_final("crit_multiplier", player.modifiers) - - body.take_damage(damage_dealt, is_crit) - damaged_this_attack.append(body) diff --git a/scenes/attacks/attack_sword.gd.uid b/scenes/attacks/attack_sword.gd.uid deleted file mode 100644 index c62e791..0000000 --- a/scenes/attacks/attack_sword.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://db326gu8abue5 diff --git a/scenes/attacks/attack_sword.tscn b/scenes/attacks/attack_sword.tscn deleted file mode 100644 index f8310d7..0000000 --- a/scenes/attacks/attack_sword.tscn +++ /dev/null @@ -1,53 +0,0 @@ -[gd_scene load_steps=6 format=3 uid="uid://cdojqe2m4kxx1"] - -[ext_resource type="Texture2D" uid="uid://dycw7c3484dir" path="res://assets/sprites/sword.png" id="1_3fwwl"] -[ext_resource type="Script" uid="uid://db326gu8abue5" path="res://scenes/attacks/attack_sword.gd" id="1_frsqi"] - -[sub_resource type="Curve2D" id="Curve2D_frsqi"] -bake_interval = 2.0 -_data = { -"points": PackedVector2Array(0, 0, 0, 0, 0, 0, 0, 0, 25, 10, 200, 0) -} -point_count = 2 - -[sub_resource type="RectangleShape2D" id="RectangleShape2D_frsqi"] -size = Vector2(13.9997, 46.999) - -[sub_resource type="CircleShape2D" id="CircleShape2D_3fwwl"] -radius = 267.002 - -[node name="AttackSword" type="Node2D"] -script = ExtResource("1_frsqi") - -[node name="AttackPath" type="Path2D" parent="."] -curve = SubResource("Curve2D_frsqi") - -[node name="PathFollow2D" type="PathFollow2D" parent="AttackPath"] -loop = false - -[node name="Sprite2D" type="Sprite2D" parent="AttackPath/PathFollow2D"] -position = Vector2(0.322462, -0.946582) -rotation = 0.328329 -texture = ExtResource("1_3fwwl") - -[node name="AttackArea" type="Area2D" parent="AttackPath/PathFollow2D/Sprite2D"] -collision_layer = 0 -collision_mask = 2 - -[node name="AttackCollision" type="CollisionShape2D" parent="AttackPath/PathFollow2D/Sprite2D/AttackArea"] -position = Vector2(-0.0328934, -4.50646) -shape = SubResource("RectangleShape2D_frsqi") - -[node name="TriggerArea" type="Area2D" parent="."] -visible = false -collision_layer = 0 -collision_mask = 2 - -[node name="TriggerCollision" type="CollisionShape2D" parent="TriggerArea"] -shape = SubResource("CircleShape2D_3fwwl") - -[node name="Timer" type="Timer" parent="."] -one_shot = true - -[connection signal="body_entered" from="AttackPath/PathFollow2D/Sprite2D/AttackArea" to="." method="_on_attack_area_body_entered"] -[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"] diff --git a/scenes/enemies/enemy_base.gd b/scenes/enemies/enemy_base.gd index ddf9fc9..f4ecb1b 100644 --- a/scenes/enemies/enemy_base.gd +++ b/scenes/enemies/enemy_base.gd @@ -55,6 +55,7 @@ func do_movement(delta: float) -> void: else: _do_nav_agent_movement() + func _has_direct_path(): target_cast.target_position = to_local(target.global_position) target_cast.enabled = true diff --git a/scenes/main.gd b/scenes/main.gd index 66e3316..8e7151a 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -14,6 +14,7 @@ func _process(delta: float) -> void: elapsed_time += delta main_ui.player_ui.set_elapsed_time(elapsed_time) + func _unhandled_input(event: InputEvent) -> void: if event.is_action_pressed("ui_cancel"): main_ui.pause_ui.toggle_pause_ui() diff --git a/scenes/main.tscn b/scenes/main.tscn index f3ce5e3..a017e5d 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=8 format=3 uid="uid://bjg50n7aab3ng"] +[gd_scene load_steps=9 format=3 uid="uid://bjg50n7aab3ng"] [ext_resource type="Script" uid="uid://brb4ssksmtq8k" path="res://scenes/main.gd" id="1_jyhfs"] [ext_resource type="PackedScene" uid="uid://4xha2nhf8fya" path="res://scenes/test_level.tscn" id="1_o5qli"] @@ -7,6 +7,7 @@ [ext_resource type="PackedScene" uid="uid://dy73qrxcgrwg3" path="res://scenes/managers/enemy_manager.tscn" id="5_tbgi4"] [ext_resource type="PackedScene" uid="uid://bbev8m5g0p3a3" path="res://scenes/pickups/pickup_magnet.tscn" id="6_tefeu"] [ext_resource type="PackedScene" uid="uid://cr8gj1dlloamp" path="res://scenes/pickups/pickup_hp.tscn" id="7_o6xl0"] +[ext_resource type="PackedScene" uid="uid://c2o1lpm4pimpr" path="res://scenes/managers/projectile_manager.tscn" id="8_tipki"] [node name="Main" type="Node2D"] script = ExtResource("1_jyhfs") @@ -32,3 +33,5 @@ position = Vector2(1697, 414) [node name="PickupHP" parent="." instance=ExtResource("7_o6xl0")] position = Vector2(1678, 939) + +[node name="ProjectileManager" parent="." instance=ExtResource("8_tipki")] diff --git a/scenes/managers/enemy_manager.gd b/scenes/managers/enemy_manager.gd index 4a65011..7ef1b50 100644 --- a/scenes/managers/enemy_manager.gd +++ b/scenes/managers/enemy_manager.gd @@ -26,11 +26,13 @@ func _on_timer_timeout() -> void: new_enemy.target = target add_child(new_enemy) + func _on_stop_spawning(val: bool): if val: timer.stop() elif timer.is_stopped(): timer.start() + func _on_set_spawn_rate(val: float): timer.wait_time = 1 / val diff --git a/scenes/managers/projectile_manager.tscn b/scenes/managers/projectile_manager.tscn new file mode 100644 index 0000000..d38fcfd --- /dev/null +++ b/scenes/managers/projectile_manager.tscn @@ -0,0 +1,3 @@ +[gd_scene format=3 uid="uid://c2o1lpm4pimpr"] + +[node name="ProjectileManager" type="Node2D"] diff --git a/scenes/player.gd b/scenes/player.gd index 3301b20..a03ec1b 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -5,7 +5,6 @@ extends CharacterBody2D @export var main_ui: MainUI @onready var sprite_2d: Sprite2D = $Sprite2D -@onready var attack_sword: Node2D = $AttackSword var player_stats: PlayerStats = PlayerStats.new() var modifiers: Array[PlayerStatsModifier] = [] @@ -61,7 +60,6 @@ func die(): dead = true remove_from_group("damagable") get_tree().call_group("enemy", "cheer") - attack_sword.queue_free() GlobalConst.sig_stop_spawning.emit(true) sprite_2d.z_index += 10 get_taunted() diff --git a/scenes/player.tscn b/scenes/player.tscn index 0493cf5..dc276a5 100644 --- a/scenes/player.tscn +++ b/scenes/player.tscn @@ -2,7 +2,7 @@ [ext_resource type="Texture2D" uid="uid://5x5wimok8uw2" path="res://assets/sprites/roguelikeChar_transparent.png" id="1_3vyb7"] [ext_resource type="Script" uid="uid://cvqaxckx4num3" path="res://scenes/player.gd" id="1_g2els"] -[ext_resource type="PackedScene" uid="uid://cdojqe2m4kxx1" path="res://scenes/attacks/attack_sword.tscn" id="3_qhqgy"] +[ext_resource type="PackedScene" uid="uid://dfikvj27k01tu" path="res://scenes/weapons/weapon_sword.tscn" id="3_qhqgy"] [sub_resource type="CircleShape2D" id="CircleShape2D_3vyb7"] radius = 8.0 @@ -22,9 +22,6 @@ region_rect = Rect2(0, 104, 16, 14) [node name="CollisionShape2D" type="CollisionShape2D" parent="."] shape = SubResource("CircleShape2D_3vyb7") -[node name="AttackSword" parent="." node_paths=PackedStringArray("player") instance=ExtResource("3_qhqgy")] -player = NodePath("..") - [node name="PickupArea" type="Area2D" parent="."] collision_layer = 0 collision_mask = 24 @@ -33,4 +30,6 @@ monitorable = false [node name="CollisionShape2D" type="CollisionShape2D" parent="PickupArea"] shape = SubResource("CircleShape2D_qhqgy") +[node name="WeaponSword" parent="." instance=ExtResource("3_qhqgy")] + [connection signal="area_entered" from="PickupArea" to="." method="_on_pickup_area_area_entered"] diff --git a/scenes/weapons/weapon_base.gd b/scenes/weapons/weapon_base.gd new file mode 100644 index 0000000..32249d7 --- /dev/null +++ b/scenes/weapons/weapon_base.gd @@ -0,0 +1,42 @@ +class_name WeaponBase +extends Node2D + +@export var attack_cd: float +@export var attack_damage: float +@export var attack_aoe: float +@export var attack_duration: float +@export var attack_range: float + + +func _on_attack_cd_timer_timeout() -> void: + do_attack() + + +func do_attack() -> void: + push_error("%s does not implement do_attack" % self) + + +func find_target_in_radius() -> EnemyBase: + var space_state: PhysicsDirectSpaceState2D = get_world_2d().direct_space_state + var shape := CircleShape2D.new() + shape.radius = attack_range + + var query := PhysicsShapeQueryParameters2D.new() + query.shape = shape + query.transform = Transform2D(0, global_position) + query.collision_mask = 2 + query.collide_with_bodies = true + var results := space_state.intersect_shape(query) + if len(results) < 1: + return null + var closest: PhysicsBody2D = results[0]["collider"] + for r in results: + var c: PhysicsBody2D = r["collider"] + if not c.is_in_group(GlobalConst.GROUP_ENEMY): + continue + if ( + c.global_position.distance_to(global_position) + < closest.global_position.distance_to(global_position) + ): + closest = c + return closest diff --git a/scenes/weapons/weapon_base.gd.uid b/scenes/weapons/weapon_base.gd.uid new file mode 100644 index 0000000..bfee5fc --- /dev/null +++ b/scenes/weapons/weapon_base.gd.uid @@ -0,0 +1 @@ +uid://d6nwfhyethdw diff --git a/scenes/weapons/weapon_base.tscn b/scenes/weapons/weapon_base.tscn new file mode 100644 index 0000000..6f020a2 --- /dev/null +++ b/scenes/weapons/weapon_base.tscn @@ -0,0 +1,11 @@ +[gd_scene load_steps=2 format=3 uid="uid://i8mtky2req41"] + +[ext_resource type="Script" uid="uid://d6nwfhyethdw" path="res://scenes/weapons/weapon_base.gd" id="1_v4xn6"] + +[node name="WeaponBase" type="Node2D"] +script = ExtResource("1_v4xn6") + +[node name="AttackCDTimer" type="Timer" parent="."] +autostart = true + +[connection signal="timeout" from="AttackCDTimer" to="." method="_on_attack_cd_timer_timeout"] diff --git a/scenes/weapons/weapon_sword.gd b/scenes/weapons/weapon_sword.gd new file mode 100644 index 0000000..657482d --- /dev/null +++ b/scenes/weapons/weapon_sword.gd @@ -0,0 +1,42 @@ +class_name WeaponSword +extends WeaponBase + +const WEAPON_SWORD_PROJECTILE = preload("res://scenes/weapons/weapon_sword_projectile.tscn") + +@onready var targeting_range: Area2D = $TargetingRange +@onready var targeting_range_shape: CollisionShape2D = $TargetingRange/CollisionShape2D + +signal projectile_hit(projectile: WeaponSwordProjectile, enemy: EnemyBase) + +var _player: Player + + +func _ready() -> void: + targeting_range_shape.shape.radius = attack_range + projectile_hit.connect(_on_projectile_hit) + _player = get_tree().get_first_node_in_group(GlobalConst.GROUP_PLAYER) + + +func do_attack() -> void: + var target: EnemyBase = find_target_in_radius() + if not target: + return + + var projectile = WEAPON_SWORD_PROJECTILE.instantiate() + projectile.damage = attack_damage + projectile.target = target + projectile.on_hit_sig = projectile_hit + add_child(projectile) + + +func deal_damage(enemy: EnemyBase): + var crit_chance = _player.player_stats.get_final("crit_chance", _player.modifiers) + var damage_dealt = attack_damage + var is_crit = randf() >= 1 - crit_chance + if is_crit: + damage_dealt *= _player.player_stats.get_final("crit_multiplier", _player.modifiers) + enemy.take_damage(damage_dealt, is_crit) + + +func _on_projectile_hit(projectile: WeaponSwordProjectile, enemy: EnemyBase): + deal_damage(enemy) diff --git a/scenes/weapons/weapon_sword.gd.uid b/scenes/weapons/weapon_sword.gd.uid new file mode 100644 index 0000000..4855f1c --- /dev/null +++ b/scenes/weapons/weapon_sword.gd.uid @@ -0,0 +1 @@ +uid://b072d866r4usq diff --git a/scenes/weapons/weapon_sword.tscn b/scenes/weapons/weapon_sword.tscn new file mode 100644 index 0000000..3b66c5f --- /dev/null +++ b/scenes/weapons/weapon_sword.tscn @@ -0,0 +1,22 @@ +[gd_scene load_steps=4 format=3 uid="uid://dfikvj27k01tu"] + +[ext_resource type="PackedScene" uid="uid://i8mtky2req41" path="res://scenes/weapons/weapon_base.tscn" id="1_2dti5"] +[ext_resource type="Script" uid="uid://b072d866r4usq" path="res://scenes/weapons/weapon_sword.gd" id="2_ruf80"] + +[sub_resource type="CircleShape2D" id="CircleShape2D_ruf80"] + +[node name="WeaponSword" instance=ExtResource("1_2dti5")] +script = ExtResource("2_ruf80") +attack_cd = 1.0 +attack_damage = 5.0 +attack_aoe = 1.0 +attack_duration = 1.0 +attack_range = 150.0 + +[node name="TargetingRange" type="Area2D" parent="." index="1"] +collision_layer = 0 +collision_mask = 2 +monitorable = false + +[node name="CollisionShape2D" type="CollisionShape2D" parent="TargetingRange" index="0"] +shape = SubResource("CircleShape2D_ruf80") diff --git a/scenes/weapons/weapon_sword_projectile.gd b/scenes/weapons/weapon_sword_projectile.gd new file mode 100644 index 0000000..0e46c2f --- /dev/null +++ b/scenes/weapons/weapon_sword_projectile.gd @@ -0,0 +1,40 @@ +class_name WeaponSwordProjectile +extends Node2D + +@export var speed: float = 500.0 +@export var range: float = 200.0 +@export var target: Node2D +@export var damage: float +@export var on_hit_sig: Signal + +var _direction: Vector2 +var _traveled: float = 0.0 +var _already_hit: Array[PhysicsBody2D] = [] + + +func _ready() -> void: + _direction = global_position.direction_to(target.global_position).normalized() + _traveled = 0.0 + rotation = _direction.angle() + if _direction.x < 0: + rotation += PI + var proj_manager = get_tree().get_first_node_in_group(GlobalConst.GROUP_PROJ_MANAGER) + if not proj_manager: + return + reparent(proj_manager, true) + + +func _physics_process(delta: float) -> void: + var step = _direction * speed * delta + global_position += step + _traveled += step.length() + + if _traveled >= range: + queue_free() + + +func _on_area_2d_body_entered(body: Node2D) -> void: + if body in _already_hit: + return + on_hit_sig.emit(self, body) + _already_hit.append(body) diff --git a/scenes/weapons/weapon_sword_projectile.gd.uid b/scenes/weapons/weapon_sword_projectile.gd.uid new file mode 100644 index 0000000..c8b95ef --- /dev/null +++ b/scenes/weapons/weapon_sword_projectile.gd.uid @@ -0,0 +1 @@ +uid://c40iaqdubwl0p diff --git a/scenes/weapons/weapon_sword_projectile.tscn b/scenes/weapons/weapon_sword_projectile.tscn new file mode 100644 index 0000000..c21113f --- /dev/null +++ b/scenes/weapons/weapon_sword_projectile.tscn @@ -0,0 +1,24 @@ +[gd_scene load_steps=4 format=3 uid="uid://bv5f47x3ishmf"] + +[ext_resource type="Script" uid="uid://c40iaqdubwl0p" path="res://scenes/weapons/weapon_sword_projectile.gd" id="1_asuu4"] +[ext_resource type="Texture2D" uid="uid://dycw7c3484dir" path="res://assets/sprites/sword.png" id="2_pxap4"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_ygc1t"] +size = Vector2(16, 46) + +[node name="WeaponSwordProjectile" type="Node2D"] +script = ExtResource("1_asuu4") + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = ExtResource("2_pxap4") + +[node name="Area2D" type="Area2D" parent="."] +collision_layer = 0 +collision_mask = 2 +monitorable = false + +[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] +position = Vector2(0, -5) +shape = SubResource("RectangleShape2D_ygc1t") + +[connection signal="body_entered" from="Area2D" to="." method="_on_area_2d_body_entered"]