diff --git a/animation/generic_anims.res b/animation/generic_anims.res new file mode 100644 index 0000000..8734ec2 Binary files /dev/null and b/animation/generic_anims.res differ diff --git a/scenes/enemies/enemy_base.gd b/scenes/enemies/enemy_base.gd new file mode 100644 index 0000000..e42dc9b --- /dev/null +++ b/scenes/enemies/enemy_base.gd @@ -0,0 +1,144 @@ +class_name EnemyBase +extends CharacterBody2D + +@export var move_speed: float = 100 +@export var max_health: float = 10.0 +@export var default_contact_damage: float = 0.0 +@export var target_distance: float = 6.0 +@export var path_update_interval: float = 0.5 + +@onready var target_cast: RayCast2D = $TargetCast +@onready var animation_player: AnimationPlayer = $AnimationPlayer +@onready var contact_damage_cd: Timer = $ContactDamageCD +@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D +@onready var collision_shape_2d: CollisionShape2D = $CollisionShape2D +@onready var shape_cast_2d: ShapeCast2D = $ShapeCast2D + +var player: Player +var target: Node2D +var god_mode: bool = false +var is_dead: bool = false +var health: float + +var _path_update_timer: float = 0.0 + + +func _ready() -> void: + health = max_health + shape_cast_2d.shape.radius = collision_shape_2d.shape.radius + shape_cast_2d.enabled = false + _find_player() + + +func _find_player(): + if not player: + player = get_tree().get_first_node_in_group(GlobalConst.GROUP_PLAYER) + target = player + + +func _physics_process(delta: float) -> void: + if not target: + return + do_movement(delta) + check_contact_damage() + + +func do_movement(delta: float) -> void: + if not target: + return + if global_position.distance_to(target.global_position) < target_distance: + return + _path_update_timer -= delta + if _has_direct_path(): + shape_cast_2d.enabled = false + _do_simple_movement() + else: + _do_nav_agent_movement() + + +func _has_direct_path(): + target_cast.target_position = to_local(target.global_position) + target_cast.enabled = true + return not target_cast.is_colliding() + + +func _do_simple_movement(): + var direction = global_position.direction_to(target.global_position) + var distance = global_position.distance_to(target.global_position) + if distance > 4: + velocity = direction * move_speed + move_and_slide() + + +func _do_nav_agent_movement(): + if _path_update_timer <= 0.0: + _path_update_timer = path_update_interval + nav_agent.target_position = target.global_position + + if nav_agent.is_navigation_finished(): + return + var next_point = nav_agent.get_next_path_position() + var direction = (next_point - global_position).normalized() + shape_cast_2d.target_position = to_local(next_point) + shape_cast_2d.enabled = true + if shape_cast_2d.is_colliding(): + direction = direction.bounce(shape_cast_2d.get_collision_normal(0)).normalized() + + velocity = direction * move_speed + move_and_slide() + + +func check_contact_damage(): + if default_contact_damage == 0.0: + return + if global_position.distance_to(target.global_position) > target_distance: + return + deal_contact_damage() + + +func deal_contact_damage(): + if target.is_in_group("damagable"): + if contact_damage_cd.is_stopped(): + target.take_damage(default_contact_damage) + contact_damage_cd.start() + + +func take_damage(value: float, is_crit: bool = false): + if god_mode: + return + health -= value + var dm = preload("res://scenes/damage_numbers.tscn").instantiate() + dm.damage_taken = value + dm.critical_damage = is_crit + animation_player.play("generic_anims/take_damage") + add_child(dm) + if health <= 0: + die() + + +func cheer(): + target_distance = 2 + if not animation_player.is_playing(): + animation_player.play("generic_anims/cheer") + + +func die(): + if is_dead: + return + is_dead = true + drop_xp_orb() + target = null + velocity = Vector2.ZERO + animation_player.play("generic_anims/die") + + +func drop_xp_orb() -> void: + var orb: XPOrb = preload("res://scenes/xp_orb.tscn").instantiate() + orb.value = 5 + orb.position = position + get_parent().add_child(orb) + + +func _on_animation_player_animation_finished(anim_name: StringName) -> void: + if is_dead: + queue_free() diff --git a/scenes/enemies/enemy_base.gd.uid b/scenes/enemies/enemy_base.gd.uid new file mode 100644 index 0000000..f0079db --- /dev/null +++ b/scenes/enemies/enemy_base.gd.uid @@ -0,0 +1 @@ +uid://dxn17u7ltuibw diff --git a/scenes/enemies/enemy_base.tscn b/scenes/enemies/enemy_base.tscn new file mode 100644 index 0000000..38f8f0c --- /dev/null +++ b/scenes/enemies/enemy_base.tscn @@ -0,0 +1,75 @@ +[gd_scene load_steps=8 format=3 uid="uid://b7vq8xspnlyeu"] + +[ext_resource type="Script" uid="uid://dxn17u7ltuibw" path="res://scenes/enemies/enemy_base.gd" id="1_qty17"] +[ext_resource type="AnimationLibrary" uid="uid://dos4y853hq1gu" path="res://animation/generic_anims.res" id="2_pkqou"] + +[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_pkqou"] +size = Vector2(32, 32) + +[sub_resource type="CircleShape2D" id="CircleShape2D_satqt"] +radius = 6.0 + +[sub_resource type="Animation" id="Animation_satqt"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(0, 0)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("Sprite2D:modulate") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Color(1, 1, 1, 1)] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_qa0nx"] +_data = { +&"RESET": SubResource("Animation_satqt") +} + +[sub_resource type="CircleShape2D" id="CircleShape2D_pkqou"] + +[node name="EnemyBase" type="CharacterBody2D"] +collision_layer = 2 +collision_mask = 3 +script = ExtResource("1_qty17") + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = SubResource("PlaceholderTexture2D_pkqou") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("CircleShape2D_satqt") + +[node name="TargetCast" type="RayCast2D" parent="." groups=["damagable", "enemy"]] +enabled = false +collision_mask = 3 + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +libraries = { +&"": SubResource("AnimationLibrary_qa0nx"), +&"generic_anims": ExtResource("2_pkqou") +} + +[node name="ContactDamageCD" type="Timer" parent="."] +wait_time = 0.5 + +[node name="NavigationAgent2D" type="NavigationAgent2D" parent="."] + +[node name="ShapeCast2D" type="ShapeCast2D" parent="."] +shape = SubResource("CircleShape2D_pkqou") + +[connection signal="animation_finished" from="AnimationPlayer" to="." method="_on_animation_player_animation_finished"] diff --git a/scenes/enemies/enemy_rat.gd b/scenes/enemies/enemy_rat.gd new file mode 100644 index 0000000..0afcc22 --- /dev/null +++ b/scenes/enemies/enemy_rat.gd @@ -0,0 +1 @@ +extends EnemyBase diff --git a/scenes/enemies/enemy_rat.gd.uid b/scenes/enemies/enemy_rat.gd.uid new file mode 100644 index 0000000..93861e3 --- /dev/null +++ b/scenes/enemies/enemy_rat.gd.uid @@ -0,0 +1 @@ +uid://ddvxp8eada0mw diff --git a/scenes/enemies/enemy_rat.tscn b/scenes/enemies/enemy_rat.tscn new file mode 100644 index 0000000..e183b3c --- /dev/null +++ b/scenes/enemies/enemy_rat.tscn @@ -0,0 +1,13 @@ +[gd_scene load_steps=4 format=3 uid="uid://c7jaqltfjhn5n"] + +[ext_resource type="PackedScene" uid="uid://b7vq8xspnlyeu" path="res://scenes/enemies/enemy_base.tscn" id="1_o1awd"] +[ext_resource type="Texture2D" uid="uid://in5id1p5bxux" path="res://assets/sprites/rat_small.png" id="2_gscll"] +[ext_resource type="Script" uid="uid://ddvxp8eada0mw" path="res://scenes/enemies/enemy_rat.gd" id="2_sari4"] + +[node name="EnemyRat" instance=ExtResource("1_o1awd")] +script = ExtResource("2_sari4") +max_health = 50.0 +default_contact_damage = 5.0 + +[node name="Sprite2D" parent="." index="0"] +texture = ExtResource("2_gscll") diff --git a/scenes/managers/enemy_manager.gd b/scenes/managers/enemy_manager.gd index b403c03..15f15db 100644 --- a/scenes/managers/enemy_manager.gd +++ b/scenes/managers/enemy_manager.gd @@ -7,7 +7,7 @@ extends Node2D @onready var timer: Timer = $Timer -var enemy_scene = preload("res://scenes/enemies/enemy.tscn") +const ENEMY_RAT = preload("res://scenes/enemies/enemy_rat.tscn") func _ready() -> void: @@ -20,7 +20,7 @@ func _on_timer_timeout() -> void: var enemies = get_tree().get_nodes_in_group(GlobalConst.GROUP_ENEMY) GlobalConst.sig_debug_stats_set.emit("enemy_count", "%s" % len(enemies)) if len(enemies) < max_enemies: - var new_enemy = enemy_scene.instantiate() + var new_enemy = ENEMY_RAT.instantiate() new_enemy.position = target.position + Vector2(50, 50) new_enemy.target = target add_child(new_enemy) diff --git a/scenes/player.gd b/scenes/player.gd index 3246a10..3301b20 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -5,6 +5,7 @@ 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] = [] @@ -59,7 +60,8 @@ func take_damage(value: float) -> void: func die(): dead = true remove_from_group("damagable") - get_tree().call_group("enemy", "cheer_anim") + get_tree().call_group("enemy", "cheer") + attack_sword.queue_free() GlobalConst.sig_stop_spawning.emit(true) sprite_2d.z_index += 10 get_taunted() @@ -75,18 +77,19 @@ func death_animation(delta: float): func get_taunted(): - var taunting_enemies: Array[Enemy] = [] + var taunting_enemies: Array[EnemyBase] = [] for body in get_tree().get_nodes_in_group(GlobalConst.GROUP_ENEMY): - if global_position.distance_to(body.global_position) < 500.0: + print_debug("starting taunt: %s" % global_position.distance_to(body.global_position)) + if global_position.distance_to(body.global_position) < 1000.0: taunting_enemies.append(body) + print_debug("getting taunted by %s enemies" % len(taunting_enemies)) for i in range(taunting_enemies.size()): var angle = (TAU / taunting_enemies.size()) * i var target_pos = Vector2(cos(angle), sin(angle)) * 50 - var new_target = StaticBody2D.new() - var collision = CollisionShape2D.new() + var new_target = Marker2D.new() + print_debug("getting taunted by %s" % taunting_enemies[i]) new_target.position = target_pos add_child(new_target) - new_target.add_child(collision) taunting_enemies[i].target = new_target diff --git a/scenes/tiles/tileset.tres b/scenes/tiles/tileset.tres index 26496d4..5c49407 100644 --- a/scenes/tiles/tileset.tres +++ b/scenes/tiles/tileset.tres @@ -1,4 +1,4 @@ -[gd_resource type="TileSet" load_steps=8 format=3 uid="uid://c3clgitssvdlg"] +[gd_resource type="TileSet" load_steps=9 format=3 uid="uid://c3clgitssvdlg"] [ext_resource type="Texture2D" uid="uid://2xgqhe7u6lfq" path="res://assets/sprites/roguelikeSheet_transparent.png" id="1_kkifh"] [ext_resource type="Shader" uid="uid://clpp23h7fpamt" path="res://assets/shaders/water2.gdshader" id="1_n3s2j"] @@ -26,6 +26,12 @@ shader_parameter/noise_tex = SubResource("NoiseTexture2D_m3ddo") shader_parameter/noise_speed = Vector2(0.03, 0.01) shader_parameter/noise_strength = 0.05 +[sub_resource type="NavigationPolygon" id="NavigationPolygon_n3s2j"] +vertices = PackedVector2Array(8, 8, -8, 8, -8, -8, 8, -8) +polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3)]) +outlines = Array[PackedVector2Array]([PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)]) +agent_radius = 0.0 + [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_s7nh2"] texture = ExtResource("1_kkifh") separation = Vector2i(1, 1) @@ -74,10 +80,15 @@ separation = Vector2i(1, 1) 4:0/0/terrains_peering_bit/top_right_corner = 0 5:0/0 = 0 5:0/0/terrain_set = 0 +5:0/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 6:0/0 = 0 +6:0/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 7:0/0 = 0 +7:0/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 8:0/0 = 0 +8:0/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 9:0/0 = 0 +9:0/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 10:0/0 = 0 11:0/0 = 0 12:0/0 = 0 @@ -197,10 +208,15 @@ separation = Vector2i(1, 1) 5:1/0/terrains_peering_bit/top_left_corner = 0 5:1/0/terrains_peering_bit/top_side = 0 5:1/0/terrains_peering_bit/top_right_corner = 0 +5:1/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 6:1/0 = 0 +6:1/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 7:1/0 = 0 +7:1/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 8:1/0 = 0 +8:1/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 9:1/0 = 0 +9:1/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 10:1/0 = 0 11:1/0 = 0 12:1/0 = 0 @@ -311,10 +327,15 @@ separation = Vector2i(1, 1) 4:2/0/terrains_peering_bit/top_side = 1 4:2/0/terrains_peering_bit/top_right_corner = 0 5:2/0 = 0 +5:2/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 6:2/0 = 0 +6:2/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 7:2/0 = 0 +7:2/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 8:2/0 = 0 +8:2/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 9:2/0 = 0 +9:2/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 10:2/0 = 0 11:2/0 = 0 12:2/0 = 0 @@ -368,10 +389,15 @@ separation = Vector2i(1, 1) 3:3/0 = 0 4:3/0 = 0 5:3/0 = 0 +5:3/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 6:3/0 = 0 +6:3/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 7:3/0 = 0 +7:3/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 8:3/0 = 0 +8:3/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 9:3/0 = 0 +9:3/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 10:3/0 = 0 11:3/0 = 0 12:3/0 = 0 @@ -425,10 +451,15 @@ separation = Vector2i(1, 1) 3:4/0 = 0 4:4/0 = 0 5:4/0 = 0 +5:4/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 6:4/0 = 0 +6:4/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 7:4/0 = 0 +7:4/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 8:4/0 = 0 +8:4/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 9:4/0 = 0 +9:4/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 10:4/0 = 0 11:4/0 = 0 12:4/0 = 0 @@ -482,10 +513,15 @@ separation = Vector2i(1, 1) 3:5/0 = 0 4:5/0 = 0 5:5/0 = 0 +5:5/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 6:5/0 = 0 +6:5/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 7:5/0 = 0 +7:5/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 8:5/0 = 0 +8:5/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 9:5/0 = 0 +9:5/0/navigation_layer_0/polygon = SubResource("NavigationPolygon_n3s2j") 10:5/0 = 0 11:5/0 = 0 12:5/0 = 0 @@ -1965,5 +2001,6 @@ terrain_set_0/terrain_0/name = "Grass" terrain_set_0/terrain_0/color = Color(0.5, 0.34375, 0.25, 1) terrain_set_0/terrain_1/name = "Water" terrain_set_0/terrain_1/color = Color(0.5, 0.4375, 0.25, 1) +navigation_layer_0/layers = 1 sources/0 = SubResource("TileSetAtlasSource_s7nh2") pattern_0 = SubResource("TileMapPattern_beh8d")