Colours and practise dummy
This commit is contained in:
@@ -11,7 +11,7 @@ damage = 20.0
|
|||||||
attack_range = 3.5
|
attack_range = 3.5
|
||||||
attack_cooldown = 0.6
|
attack_cooldown = 0.6
|
||||||
knockback_force = 12.0
|
knockback_force = 12.0
|
||||||
startup_time = 0.12
|
startup_time = 0.5
|
||||||
active_time = 1.0
|
active_time = 1.0
|
||||||
mesh_scene = ExtResource("1_gdc1w")
|
mesh_scene = ExtResource("1_gdc1w")
|
||||||
weight = 2.0
|
weight = 2.0
|
||||||
|
|||||||
@@ -47,54 +47,54 @@ _character = NodePath("../..")
|
|||||||
animation_player = NodePath("../AnimationPlayer")
|
animation_player = NodePath("../AnimationPlayer")
|
||||||
|
|
||||||
[node name="Skeleton3D" parent="LilguyRigged/Armature" index="0"]
|
[node name="Skeleton3D" parent="LilguyRigged/Armature" index="0"]
|
||||||
bones/0/position = Vector3(-0.32852697, 2.914154, -546.76843)
|
bones/0/position = Vector3(-0.32859802, 2.9141626, -546.76843)
|
||||||
bones/0/rotation = Quaternion(-0.6608288, 0.28933647, -0.19178489, 0.6654384)
|
bones/0/rotation = Quaternion(-0.6608289, 0.28933656, -0.19178493, 0.66543835)
|
||||||
bones/1/position = Vector3(0.054167695, 63.219894, -3.33786e-06)
|
bones/1/position = Vector3(0.054167695, 63.219894, -3.33786e-06)
|
||||||
bones/1/rotation = Quaternion(0.015321622, 0.025352472, 0.09471857, 0.99506325)
|
bones/1/rotation = Quaternion(0.015321612, 0.025352655, 0.0947179, 0.99506336)
|
||||||
bones/2/position = Vector3(-1.8112361e-05, 73.7566, -1.621247e-05)
|
bones/2/position = Vector3(-1.8112361e-05, 73.7566, -1.621247e-05)
|
||||||
bones/2/rotation = Quaternion(0.03465983, 0.05047194, 0.051301006, 0.99680465)
|
bones/2/rotation = Quaternion(0.034659874, 0.050472155, 0.051301125, 0.99680465)
|
||||||
bones/3/position = Vector3(-3.0510128e-05, 84.29319, 9.059899e-06)
|
bones/3/position = Vector3(-3.0510128e-05, 84.29319, 9.059899e-06)
|
||||||
bones/3/rotation = Quaternion(0.029244617, 0.05379084, -0.051849358, 0.9967763)
|
bones/3/rotation = Quaternion(0.029244598, 0.05379088, -0.051849354, 0.99677634)
|
||||||
bones/4/position = Vector3(3.8038404e-05, 94.83001, 1.9073414e-06)
|
bones/4/position = Vector3(3.8038404e-05, 94.83001, 1.9073414e-06)
|
||||||
bones/4/rotation = Quaternion(0.0006216668, 0.08164933, 0.020402616, 0.99645215)
|
bones/4/rotation = Quaternion(0.0006217413, 0.08164966, 0.020404326, 0.9964521)
|
||||||
bones/5/position = Vector3(-0.25257444, 72.84532, -7.644296e-06)
|
bones/5/position = Vector3(-0.25257444, 72.84532, -7.644296e-06)
|
||||||
bones/5/rotation = Quaternion(0.03735582, 0.19943666, -0.04159315, 0.97831464)
|
bones/5/rotation = Quaternion(0.037355006, 0.19943582, -0.04159446, 0.9783148)
|
||||||
bones/6/position = Vector3(-0.606337, 174.89494, 7.152558e-06)
|
bones/6/position = Vector3(-0.606337, 174.89494, 7.152558e-06)
|
||||||
bones/7/position = Vector3(-0.19949026, 76.75483, 52.286175)
|
bones/7/position = Vector3(-0.19949026, 76.75483, 52.286175)
|
||||||
bones/7/rotation = Quaternion(0.8036073, -0.09628729, 0.106725484, 0.57754105)
|
bones/7/rotation = Quaternion(0.80360717, -0.09628771, 0.10672592, 0.57754105)
|
||||||
bones/8/position = Vector3(4.5403274e-05, 110.91907, 9.404198e-05)
|
bones/8/position = Vector3(4.5403274e-05, 110.91907, 9.404198e-05)
|
||||||
bones/8/rotation = Quaternion(0.25522032, -0.08967137, 0.029357875, 0.962268)
|
bones/8/rotation = Quaternion(0.25522023, -0.08967148, 0.029356971, 0.9622681)
|
||||||
bones/9/position = Vector3(2.3064584e-05, 173.66367, 5.063071e-05)
|
bones/9/position = Vector3(2.3064584e-05, 173.66367, 5.063071e-05)
|
||||||
bones/9/rotation = Quaternion(0.0878422, -0.16096674, 0.2433837, 0.9524378)
|
bones/9/rotation = Quaternion(0.08784258, -0.16096693, 0.24338366, 0.95243776)
|
||||||
bones/10/position = Vector3(-2.2947788e-05, 166.48767, -1.2734416e-05)
|
bones/10/position = Vector3(-2.2947788e-05, 166.48767, -1.2734416e-05)
|
||||||
bones/11/position = Vector3(0.23053212, 76.75536, -52.28617)
|
bones/11/position = Vector3(0.23053212, 76.75536, -52.28617)
|
||||||
bones/11/rotation = Quaternion(0.14271575, -0.5852634, 0.782287, 0.15851216)
|
bones/11/rotation = Quaternion(0.14271267, -0.5852636, 0.7822879, 0.15851)
|
||||||
bones/12/position = Vector3(1.532285e-05, 110.91911, 4.0430357e-05)
|
bones/12/position = Vector3(1.532285e-05, 110.91911, 4.0430357e-05)
|
||||||
bones/12/rotation = Quaternion(0.32196987, 0.13412467, 0.27112576, 0.8971269)
|
bones/12/rotation = Quaternion(0.32197043, 0.13412262, 0.2711233, 0.89712787)
|
||||||
bones/13/position = Vector3(1.5523525e-05, 173.6661, 0.00010698747)
|
bones/13/position = Vector3(1.5523525e-05, 173.6661, 0.00010698747)
|
||||||
bones/13/rotation = Quaternion(0.09037647, 0.101556264, -0.39819276, 0.90717196)
|
bones/13/rotation = Quaternion(0.090376236, 0.10155637, -0.39819276, 0.90717196)
|
||||||
bones/14/position = Vector3(-2.0682812e-05, 166.48976, 3.939679e-05)
|
bones/14/position = Vector3(-2.0682812e-05, 166.48976, 3.939679e-05)
|
||||||
bones/15/position = Vector3(0.6496186, -35.1185, 49.84838)
|
bones/15/position = Vector3(0.6496186, -35.1185, 49.84838)
|
||||||
bones/15/rotation = Quaternion(0.38543195, 0.163806, 0.8217493, 0.3864424)
|
bones/15/rotation = Quaternion(0.38543156, 0.16380574, 0.82174975, 0.38644233)
|
||||||
bones/16/position = Vector3(8.771768e-06, 312.91962, 7.4840264e-06)
|
bones/16/position = Vector3(8.771768e-06, 312.91962, 7.4840264e-06)
|
||||||
bones/16/rotation = Quaternion(-0.05300442, 0.17209676, 0.3905684, 0.90278995)
|
bones/16/rotation = Quaternion(-0.053004134, 0.17209636, 0.39056766, 0.90279037)
|
||||||
bones/17/position = Vector3(-1.8137518e-05, 301.05597, -2.1670077e-05)
|
bones/17/position = Vector3(-1.8137518e-05, 301.05597, -2.1670077e-05)
|
||||||
bones/17/rotation = Quaternion(0.24982396, 0.64725155, -0.6711768, 0.2611037)
|
bones/17/rotation = Quaternion(0.2498236, 0.64725155, -0.67117685, 0.26110402)
|
||||||
bones/18/position = Vector3(-3.026353e-05, 14.185886, -1.4917823e-06)
|
bones/18/position = Vector3(-3.026353e-05, 14.185886, -1.4917823e-06)
|
||||||
bones/18/rotation = Quaternion(0.11539763, 0.017187234, -0.0100022685, 0.9931203)
|
bones/18/rotation = Quaternion(0.11539694, 0.017187497, -0.010002339, 0.9931204)
|
||||||
bones/19/position = Vector3(-4.351055e-06, 11.391233, -2.5032205e-06)
|
bones/19/position = Vector3(-4.351055e-06, 11.391233, -2.5032205e-06)
|
||||||
bones/20/position = Vector3(0.014209064, -35.118507, -49.848385)
|
bones/20/position = Vector3(0.014209064, -35.118507, -49.848385)
|
||||||
bones/20/rotation = Quaternion(-0.07370265, -0.18747172, 0.9412239, 0.2711456)
|
bones/20/rotation = Quaternion(-0.07370261, -0.18747209, 0.94122386, 0.27114522)
|
||||||
bones/21/position = Vector3(2.8756085e-05, 312.91974, 5.14377e-06)
|
bones/21/position = Vector3(2.8756085e-05, 312.91974, 5.14377e-06)
|
||||||
bones/21/rotation = Quaternion(-0.03735361, -0.04220169, 0.46135134, 0.8854257)
|
bones/21/rotation = Quaternion(-0.037353504, -0.04220154, 0.46134973, 0.88542664)
|
||||||
bones/22/position = Vector3(2.2092872e-05, 301.0575, 1.8114511e-05)
|
bones/22/position = Vector3(2.2092872e-05, 301.0575, 1.8114511e-05)
|
||||||
bones/22/rotation = Quaternion(0.7933202, 0.12858748, -0.3622723, 0.47208822)
|
bones/22/rotation = Quaternion(0.79332, 0.1285891, -0.36227074, 0.47208923)
|
||||||
bones/23/position = Vector3(1.3624241e-05, 15.034077, 9.790485e-06)
|
bones/23/position = Vector3(1.3624241e-05, 15.034077, 9.790485e-06)
|
||||||
bones/23/rotation = Quaternion(0.11885707, 0.009521758, -0.0077993367, 0.9928351)
|
bones/23/rotation = Quaternion(0.11885707, 0.009522018, -0.0077985795, 0.9928351)
|
||||||
bones/24/position = Vector3(-2.4847686e-06, 11.913359, -6.198885e-06)
|
bones/24/position = Vector3(-2.4847686e-06, 11.913359, -6.198885e-06)
|
||||||
|
|
||||||
[node name="WeaponPoint" type="BoneAttachment3D" parent="LilguyRigged/Armature/Skeleton3D" index="1"]
|
[node name="WeaponPoint" type="BoneAttachment3D" parent="LilguyRigged/Armature/Skeleton3D" index="1"]
|
||||||
transform = Transform3D(-0.43292555, -0.61284775, 0.6610542, 0.7782953, 0.11585887, 0.61711675, -0.45478758, 0.78166103, 0.42681834, -352.38528, -73.5694, -531.96124)
|
transform = Transform3D(-0.4329258, -0.61284786, 0.6610537, 0.7782944, 0.11585927, 0.6171174, -0.45478824, 0.78166056, 0.42681772, -352.385, -73.56995, -531.9614)
|
||||||
bone_name = "mixamorig_RightHand"
|
bone_name = "mixamorig_RightHand"
|
||||||
bone_idx = 14
|
bone_idx = 14
|
||||||
|
|
||||||
@@ -102,13 +102,16 @@ bone_idx = 14
|
|||||||
transform = Transform3D(36.6912, 297.2667, 16.921356, 46.72698, 11.0892515, -296.13126, -294.05847, 38.85366, -44.94499, 24.08223, -7.4241333, 7.098694)
|
transform = Transform3D(36.6912, 297.2667, 16.921356, 46.72698, 11.0892515, -296.13126, -294.05847, 38.85366, -44.94499, 24.08223, -7.4241333, 7.098694)
|
||||||
|
|
||||||
[node name="OffhandPoint" type="BoneAttachment3D" parent="LilguyRigged/Armature/Skeleton3D" index="2"]
|
[node name="OffhandPoint" type="BoneAttachment3D" parent="LilguyRigged/Armature/Skeleton3D" index="2"]
|
||||||
transform = Transform3D(0.6212382, -0.004605584, -0.7836083, -0.620316, 0.60813713, -0.49535576, 0.47882265, 0.7938187, 0.37494114, 135.65903, 334.35764, -511.2708)
|
transform = Transform3D(0.62123704, -0.004605159, -0.7836091, -0.62031674, 0.6081372, -0.49535444, 0.4788229, 0.79381835, 0.3749406, 135.65929, 334.35745, -511.27094)
|
||||||
bone_name = "mixamorig_LeftHand"
|
bone_name = "mixamorig_LeftHand"
|
||||||
bone_idx = 10
|
bone_idx = 10
|
||||||
|
|
||||||
[node name="OffhandContainer" type="Node3D" parent="LilguyRigged/Armature/Skeleton3D/OffhandPoint"]
|
[node name="OffhandContainer" type="Node3D" parent="LilguyRigged/Armature/Skeleton3D/OffhandPoint"]
|
||||||
transform = Transform3D(-17.74905, -295.46814, -48.82108, 21.019196, -50.01525, 295.05362, -298.73593, 14.035805, 23.660797, 0.005859375, 0.39337158, 0.06616211)
|
transform = Transform3D(-17.74905, -295.46814, -48.82108, 21.019196, -50.01525, 295.05362, -298.73593, 14.035805, 23.660797, 0.005859375, 0.39337158, 0.06616211)
|
||||||
|
|
||||||
|
[node name="AnimationPlayer" parent="LilguyRigged" index="1"]
|
||||||
|
speed_scale = 2.0
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||||
transform = Transform3D(2, 0, 0, 0, 2, 0, 0, 0, 2, -0.066, 1.647685, 0.01)
|
transform = Transform3D(2, 0, 0, 0, 2, 0, 0, 0, 2, -0.066, 1.647685, 0.01)
|
||||||
shape = SubResource("CapsuleShape3D_yxyay")
|
shape = SubResource("CapsuleShape3D_yxyay")
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
[gd_scene load_steps=7 format=3 uid="uid://dif4t1y3c07ax"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://level/scripts/practice_dummy.gd" id="1_dummy"]
|
||||||
|
[ext_resource type="Script" uid="uid://bj3uepduxvgju" path="res://level/scripts/hurt_box.gd" id="2_hurtbox"]
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_dummy"]
|
||||||
|
albedo_color = Color(0.8, 0.6, 0.4, 1)
|
||||||
|
metallic = 0.2
|
||||||
|
roughness = 0.8
|
||||||
|
|
||||||
|
[sub_resource type="CapsuleMesh" id="CapsuleMesh_dummy"]
|
||||||
|
material = SubResource("StandardMaterial3D_dummy")
|
||||||
|
|
||||||
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_body"]
|
||||||
|
|
||||||
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_hurtbox"]
|
||||||
|
radius = 0.6
|
||||||
|
height = 2.2
|
||||||
|
|
||||||
|
[node name="PracticeDummy" type="CharacterBody3D"]
|
||||||
|
transform = Transform3D(1.5, 0, 0, 0, 1.5, 0, 0, 0, 1.5, 0, 0, 0)
|
||||||
|
collision_mask = 2
|
||||||
|
script = ExtResource("1_dummy")
|
||||||
|
detection_range = 0.0
|
||||||
|
is_aggressive = false
|
||||||
|
respawn_delay = 5.0
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||||
|
mesh = SubResource("CapsuleMesh_dummy")
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||||
|
shape = SubResource("CapsuleShape3D_body")
|
||||||
|
|
||||||
|
[node name="HurtBox" type="Area3D" parent="." node_paths=PackedStringArray("owner_entity")]
|
||||||
|
collision_layer = 16
|
||||||
|
collision_mask = 0
|
||||||
|
script = ExtResource("2_hurtbox")
|
||||||
|
owner_entity = NodePath("..")
|
||||||
|
|
||||||
|
[node name="HurtBoxShape" type="CollisionShape3D" parent="HurtBox"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||||
|
shape = SubResource("CapsuleShape3D_hurtbox")
|
||||||
|
|
||||||
|
[node name="HealthLabel" type="Label3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, 0)
|
||||||
|
billboard = 1
|
||||||
|
text = "HP: 100/100"
|
||||||
|
font_size = 24
|
||||||
|
outline_size = 8
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
[gd_scene load_steps=20 format=3 uid="uid://dugaivbj1o66n"]
|
[gd_scene load_steps=21 format=3 uid="uid://dugaivbj1o66n"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://d0dgljwwl463n" path="res://level/scripts/level.gd" id="1_e1sh7"]
|
[ext_resource type="Script" uid="uid://d0dgljwwl463n" path="res://level/scripts/level.gd" id="1_e1sh7"]
|
||||||
[ext_resource type="PackedScene" uid="uid://db06e8q8f8bdq" path="res://level/scenes/Player_Lilguy.tscn" id="1_uvcbi"]
|
[ext_resource type="PackedScene" uid="uid://db06e8q8f8bdq" path="res://level/scenes/Player_Lilguy.tscn" id="1_uvcbi"]
|
||||||
|
[ext_resource type="PackedScene" path="res://level/scenes/enemies/practice_dummy.tscn" id="3_i7s07"]
|
||||||
[ext_resource type="FontFile" uid="uid://wipqjhfqeuwd" path="res://assets/fonts/Kurland.ttf" id="3_icc4p"]
|
[ext_resource type="FontFile" uid="uid://wipqjhfqeuwd" path="res://assets/fonts/Kurland.ttf" id="3_icc4p"]
|
||||||
[ext_resource type="PackedScene" uid="uid://chkrcwlprbn88" path="res://assets/Objects/Colosseum_10.fbx" id="4_u750a"]
|
[ext_resource type="PackedScene" uid="uid://chkrcwlprbn88" path="res://assets/Objects/Colosseum_10.fbx" id="4_u750a"]
|
||||||
[ext_resource type="PackedScene" uid="uid://hd6pq287rgye" path="res://level/scenes/weapons/world_weapon_testsword.tscn" id="5_cwx4m"]
|
[ext_resource type="PackedScene" uid="uid://hd6pq287rgye" path="res://level/scenes/weapons/world_weapon_testsword.tscn" id="5_cwx4m"]
|
||||||
@@ -47,6 +48,7 @@ color = Color(0, 0, 0, 0)
|
|||||||
[node name="Level" type="Node3D"]
|
[node name="Level" type="Node3D"]
|
||||||
script = ExtResource("1_e1sh7")
|
script = ExtResource("1_e1sh7")
|
||||||
player_scene = ExtResource("1_uvcbi")
|
player_scene = ExtResource("1_uvcbi")
|
||||||
|
practice_dummy_scene = ExtResource("3_i7s07")
|
||||||
|
|
||||||
[node name="Environment" type="Node3D" parent="."]
|
[node name="Environment" type="Node3D" parent="."]
|
||||||
|
|
||||||
@@ -356,6 +358,8 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.0268106, 2.6057472, 8.8369
|
|||||||
[node name="WorldWeaponSword2" parent="WeaponsContainer" instance=ExtResource("6_xerh7")]
|
[node name="WorldWeaponSword2" parent="WeaponsContainer" instance=ExtResource("6_xerh7")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.32151043, 5.2709904)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.32151043, 5.2709904)
|
||||||
|
|
||||||
|
[node name="EnemiesContainer" type="Node3D" parent="."]
|
||||||
|
|
||||||
[connection signal="pressed" from="Menu/MainContainer/MainMenu/Buttons/Host" to="." method="_on_host_pressed"]
|
[connection signal="pressed" from="Menu/MainContainer/MainMenu/Buttons/Host" to="." method="_on_host_pressed"]
|
||||||
[connection signal="pressed" from="Menu/MainContainer/MainMenu/Buttons/Join" to="." method="_on_join_pressed"]
|
[connection signal="pressed" from="Menu/MainContainer/MainMenu/Buttons/Join" to="." method="_on_join_pressed"]
|
||||||
[connection signal="pressed" from="Menu/MainContainer/MainMenu/Option4/Quit" to="." method="_on_quit_pressed"]
|
[connection signal="pressed" from="Menu/MainContainer/MainMenu/Option4/Quit" to="." method="_on_quit_pressed"]
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
extends BaseUnit
|
||||||
|
class_name BaseEnemy
|
||||||
|
|
||||||
|
## Base class for all enemies in the game
|
||||||
|
## Provides common enemy functionality like AI, pathfinding, and targeting
|
||||||
|
|
||||||
|
signal target_changed(new_target: Node)
|
||||||
|
|
||||||
|
## Current target (usually a player)
|
||||||
|
var current_target: Node = null
|
||||||
|
## Enemy detection/aggro range
|
||||||
|
@export var detection_range: float = 10.0
|
||||||
|
## Whether this enemy is aggressive (will attack players)
|
||||||
|
@export var is_aggressive: bool = true
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
super._ready()
|
||||||
|
|
||||||
|
# Enemies should respawn by default
|
||||||
|
can_respawn = true
|
||||||
|
|
||||||
|
# Connect to health signals for AI reactions
|
||||||
|
health_changed.connect(_on_enemy_health_changed)
|
||||||
|
died.connect(_on_enemy_died)
|
||||||
|
respawned.connect(_on_enemy_respawned)
|
||||||
|
|
||||||
|
func _physics_process(delta):
|
||||||
|
# Only server handles enemy AI
|
||||||
|
if not multiplayer.is_server():
|
||||||
|
return
|
||||||
|
|
||||||
|
if is_dead:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update target if needed
|
||||||
|
_update_target()
|
||||||
|
|
||||||
|
## Find and update current target
|
||||||
|
func _update_target():
|
||||||
|
# Subclasses can override this to implement custom targeting logic
|
||||||
|
pass
|
||||||
|
|
||||||
|
## Get all players in range
|
||||||
|
func get_players_in_range(range_dist: float) -> Array[Node]:
|
||||||
|
var players_in_range: Array[Node] = []
|
||||||
|
|
||||||
|
# Find the players container
|
||||||
|
var level = get_tree().get_current_scene()
|
||||||
|
if not level or not level.has_node("PlayersContainer"):
|
||||||
|
return players_in_range
|
||||||
|
|
||||||
|
var players_container = level.get_node("PlayersContainer")
|
||||||
|
|
||||||
|
for player in players_container.get_children():
|
||||||
|
if player is Character and not player.is_dead:
|
||||||
|
var distance = global_position.distance_to(player.global_position)
|
||||||
|
if distance <= range_dist:
|
||||||
|
players_in_range.append(player)
|
||||||
|
|
||||||
|
return players_in_range
|
||||||
|
|
||||||
|
## Get nearest player
|
||||||
|
func get_nearest_player() -> Node:
|
||||||
|
var players = get_players_in_range(detection_range)
|
||||||
|
|
||||||
|
if players.is_empty():
|
||||||
|
return null
|
||||||
|
|
||||||
|
var nearest_player = null
|
||||||
|
var nearest_distance = INF
|
||||||
|
|
||||||
|
for player in players:
|
||||||
|
var distance = global_position.distance_to(player.global_position)
|
||||||
|
if distance < nearest_distance:
|
||||||
|
nearest_distance = distance
|
||||||
|
nearest_player = player
|
||||||
|
|
||||||
|
return nearest_player
|
||||||
|
|
||||||
|
## Health changed callback
|
||||||
|
func _on_enemy_health_changed(old_health: float, new_health: float):
|
||||||
|
# Subclasses can override to react to damage
|
||||||
|
pass
|
||||||
|
|
||||||
|
## Death callback
|
||||||
|
func _on_enemy_died(killer_id: int):
|
||||||
|
print("[Enemy ", name, "] died. Killer ID: ", killer_id)
|
||||||
|
|
||||||
|
# Subclasses can override for death effects
|
||||||
|
pass
|
||||||
|
|
||||||
|
## Respawn callback
|
||||||
|
func _on_enemy_respawned():
|
||||||
|
print("[Enemy ", name, "] respawned at ", global_position)
|
||||||
|
|
||||||
|
# Clear target on respawn
|
||||||
|
current_target = null
|
||||||
|
target_changed.emit(null)
|
||||||
|
|
||||||
|
# Subclasses can override for respawn effects
|
||||||
|
pass
|
||||||
|
|
||||||
|
## Override hurt animation
|
||||||
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
|
func _play_hurt_animation():
|
||||||
|
# Flash red or play hurt animation
|
||||||
|
# Subclasses should implement this with their specific animations
|
||||||
|
pass
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://base_enemy_script
|
||||||
+84
-1
@@ -4,10 +4,12 @@ extends Node3D
|
|||||||
@onready var nick_input: LineEdit = $Menu/MainContainer/MainMenu/Option1/NickInput
|
@onready var nick_input: LineEdit = $Menu/MainContainer/MainMenu/Option1/NickInput
|
||||||
@onready var address_input: LineEdit = $Menu/MainContainer/MainMenu/Option3/AddressInput
|
@onready var address_input: LineEdit = $Menu/MainContainer/MainMenu/Option3/AddressInput
|
||||||
@onready var players_container: Node3D = $PlayersContainer
|
@onready var players_container: Node3D = $PlayersContainer
|
||||||
@onready var weapons_container: Node3D = null # Will be created if doesn't exist
|
@onready var weapons_container: Node3D = $WeaponsContainer
|
||||||
|
@onready var enemies_container: Node3D = $EnemiesContainer
|
||||||
@onready var menu: Control = $Menu
|
@onready var menu: Control = $Menu
|
||||||
@onready var main_menu: VBoxContainer = $Menu/MainContainer/MainMenu
|
@onready var main_menu: VBoxContainer = $Menu/MainContainer/MainMenu
|
||||||
@export var player_scene: PackedScene
|
@export var player_scene: PackedScene
|
||||||
|
@export var practice_dummy_scene: PackedScene
|
||||||
|
|
||||||
# Weapon spawning counter (server-side only)
|
# Weapon spawning counter (server-side only)
|
||||||
var _weapon_spawn_counter: int = 0
|
var _weapon_spawn_counter: int = 0
|
||||||
@@ -43,6 +45,15 @@ func _ready():
|
|||||||
add_child(weapons_container)
|
add_child(weapons_container)
|
||||||
print("Created WeaponsContainer")
|
print("Created WeaponsContainer")
|
||||||
|
|
||||||
|
# Create or find enemies container
|
||||||
|
if has_node("EnemiesContainer"):
|
||||||
|
enemies_container = get_node("EnemiesContainer")
|
||||||
|
else:
|
||||||
|
enemies_container = Node3D.new()
|
||||||
|
enemies_container.name = "EnemiesContainer"
|
||||||
|
add_child(enemies_container)
|
||||||
|
print("Created EnemiesContainer")
|
||||||
|
|
||||||
# Don't initialize weapons in _ready() - wait for Host/Join to be pressed
|
# Don't initialize weapons in _ready() - wait for Host/Join to be pressed
|
||||||
# This is handled in initialize_multiplayer() which is called after the
|
# This is handled in initialize_multiplayer() which is called after the
|
||||||
# multiplayer peer is properly set up
|
# multiplayer peer is properly set up
|
||||||
@@ -74,6 +85,9 @@ func initialize_multiplayer():
|
|||||||
# Spawn initial weapons when server starts
|
# Spawn initial weapons when server starts
|
||||||
_spawn_initial_weapons()
|
_spawn_initial_weapons()
|
||||||
|
|
||||||
|
# Spawn practice dummies
|
||||||
|
_spawn_practice_dummies()
|
||||||
|
|
||||||
# Spawn the host player (peer ID 1)
|
# Spawn the host player (peer ID 1)
|
||||||
print("[Level] Spawning host player")
|
print("[Level] Spawning host player")
|
||||||
var host_info = Network.players.get(1, {"nick": "Host", "skin": "blue"})
|
var host_info = Network.players.get(1, {"nick": "Host", "skin": "blue"})
|
||||||
@@ -219,6 +233,18 @@ func _on_player_connected(peer_id, player_info):
|
|||||||
else:
|
else:
|
||||||
print("[Server] Skipping invalid weapon ", weapon_id)
|
print("[Server] Skipping invalid weapon ", weapon_id)
|
||||||
|
|
||||||
|
# Sync existing enemies to the newly joined player
|
||||||
|
print("[Server] Syncing enemies to newly connected peer: ", peer_id)
|
||||||
|
if enemies_container:
|
||||||
|
for enemy in enemies_container.get_children():
|
||||||
|
if enemy is BaseEnemy:
|
||||||
|
# Extract ID from name (e.g., "PracticeDummy_1" -> 1)
|
||||||
|
var enemy_name_parts = enemy.name.split("_")
|
||||||
|
if enemy_name_parts.size() >= 2:
|
||||||
|
var enemy_id = enemy_name_parts[-1].to_int()
|
||||||
|
print("[Server] Syncing enemy ", enemy.name, " at position ", enemy.global_position, " to peer ", peer_id)
|
||||||
|
rpc_id(peer_id, "_spawn_dummy_local", enemy_id, enemy.global_position)
|
||||||
|
|
||||||
# Sync equipped weapons for all existing players to the newly joined player
|
# Sync equipped weapons for all existing players to the newly joined player
|
||||||
print("[Server] Syncing equipped weapons to newly connected peer: ", peer_id)
|
print("[Server] Syncing equipped weapons to newly connected peer: ", peer_id)
|
||||||
for player_node in players_container.get_children():
|
for player_node in players_container.get_children():
|
||||||
@@ -414,6 +440,63 @@ func _on_jemz_preset():
|
|||||||
skin_input.text = "Red"
|
skin_input.text = "Red"
|
||||||
address_input.text = "127.0.0.1"
|
address_input.text = "127.0.0.1"
|
||||||
|
|
||||||
|
# ---------- ENEMY SPAWNING ----------
|
||||||
|
func _spawn_practice_dummies():
|
||||||
|
if not multiplayer.is_server():
|
||||||
|
return
|
||||||
|
|
||||||
|
if not practice_dummy_scene:
|
||||||
|
push_warning("Practice dummy scene not assigned!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Wait a frame for everything to be ready
|
||||||
|
await get_tree().process_frame
|
||||||
|
|
||||||
|
print("[Server] Spawning practice dummies")
|
||||||
|
|
||||||
|
# Spawn dummies at different positions
|
||||||
|
var dummy_positions = [
|
||||||
|
Vector3(10, 0, 0),
|
||||||
|
Vector3(-10, 0, 0),
|
||||||
|
Vector3(0, 0, 10),
|
||||||
|
Vector3(0, 0, -10),
|
||||||
|
]
|
||||||
|
|
||||||
|
var dummy_counter = 0
|
||||||
|
for pos in dummy_positions:
|
||||||
|
dummy_counter += 1
|
||||||
|
rpc("_spawn_dummy_local", dummy_counter, pos)
|
||||||
|
|
||||||
|
## Spawn a practice dummy on all clients
|
||||||
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
|
func _spawn_dummy_local(dummy_id: int, spawn_pos: Vector3):
|
||||||
|
if not practice_dummy_scene:
|
||||||
|
push_error("Practice dummy scene not loaded!")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not enemies_container:
|
||||||
|
push_error("EnemiesContainer not found!")
|
||||||
|
return
|
||||||
|
|
||||||
|
var dummy_name = "PracticeDummy_" + str(dummy_id)
|
||||||
|
|
||||||
|
# Don't spawn duplicates
|
||||||
|
if enemies_container.has_node(dummy_name):
|
||||||
|
print("[Peer ", multiplayer.get_unique_id(), "] Dummy ", dummy_name, " already exists")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("[Peer ", multiplayer.get_unique_id(), "] Spawning dummy ", dummy_name, " at ", spawn_pos)
|
||||||
|
|
||||||
|
var dummy = practice_dummy_scene.instantiate()
|
||||||
|
dummy.name = dummy_name
|
||||||
|
dummy.position = spawn_pos
|
||||||
|
|
||||||
|
# Set multiplayer authority to server
|
||||||
|
dummy.set_multiplayer_authority(1)
|
||||||
|
|
||||||
|
enemies_container.add_child(dummy, true)
|
||||||
|
print("[Peer ", multiplayer.get_unique_id(), "] Dummy ", dummy_name, " spawned successfully")
|
||||||
|
|
||||||
# ---------- WEAPON SPAWNING ----------
|
# ---------- WEAPON SPAWNING ----------
|
||||||
## Spawn a weapon in the world (called from server, syncs to all clients)
|
## Spawn a weapon in the world (called from server, syncs to all clients)
|
||||||
@rpc("any_peer", "call_local", "reliable")
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
|
|||||||
@@ -65,3 +65,46 @@ func play_attack(anim_name: String = "Attack_OneHand") -> void:
|
|||||||
#push_warning("Animation '%s' not found, using Attack_OneHand" % anim_name)
|
#push_warning("Animation '%s' not found, using Attack_OneHand" % anim_name)
|
||||||
animation_player.play("Attack_OneHand", -1, 1.0)
|
animation_player.play("Attack_OneHand", -1, 1.0)
|
||||||
animation_player.animation_set_next("Attack_OneHand", "")
|
animation_player.animation_set_next("Attack_OneHand", "")
|
||||||
|
|
||||||
|
## Apply hue shift to all mesh instances in the character
|
||||||
|
func set_character_color(hue_shift: float) -> void:
|
||||||
|
# Find all MeshInstance3D children recursively
|
||||||
|
var mesh_instances = _find_mesh_instances(self)
|
||||||
|
|
||||||
|
for mesh in mesh_instances:
|
||||||
|
_apply_hue_to_mesh(mesh, hue_shift)
|
||||||
|
|
||||||
|
## Recursively find all MeshInstance3D nodes
|
||||||
|
func _find_mesh_instances(node: Node) -> Array[MeshInstance3D]:
|
||||||
|
var meshes: Array[MeshInstance3D] = []
|
||||||
|
|
||||||
|
if node is MeshInstance3D:
|
||||||
|
meshes.append(node)
|
||||||
|
|
||||||
|
for child in node.get_children():
|
||||||
|
meshes.append_array(_find_mesh_instances(child))
|
||||||
|
|
||||||
|
return meshes
|
||||||
|
|
||||||
|
## Apply hue shift to a mesh instance
|
||||||
|
func _apply_hue_to_mesh(mesh_instance: MeshInstance3D, hue_shift: float) -> void:
|
||||||
|
if not mesh_instance:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get or create material for each surface
|
||||||
|
for i in range(mesh_instance.get_surface_override_material_count()):
|
||||||
|
var material = mesh_instance.get_surface_override_material(i)
|
||||||
|
|
||||||
|
# If no override material, get the base material and duplicate it
|
||||||
|
if not material:
|
||||||
|
material = mesh_instance.mesh.surface_get_material(i)
|
||||||
|
if material:
|
||||||
|
material = material.duplicate()
|
||||||
|
mesh_instance.set_surface_override_material(i, material)
|
||||||
|
|
||||||
|
# Apply hue shift if it's a StandardMaterial3D
|
||||||
|
if material and material is StandardMaterial3D:
|
||||||
|
var std_mat = material as StandardMaterial3D
|
||||||
|
# Create a modulate color from hue
|
||||||
|
var color = Color.from_hsv(hue_shift, 0.6, 1.0)
|
||||||
|
std_mat.albedo_color = color
|
||||||
|
|||||||
+35
-11
@@ -348,12 +348,27 @@ func get_texture_from_name(skin_color: SkinColor) -> CompressedTexture2D:
|
|||||||
|
|
||||||
@rpc("any_peer", "reliable")
|
@rpc("any_peer", "reliable")
|
||||||
func set_player_skin(skin_name: SkinColor) -> void:
|
func set_player_skin(skin_name: SkinColor) -> void:
|
||||||
var texture = get_texture_from_name(skin_name)
|
# Check if we're using the LilguyRigged model
|
||||||
|
if _body is LilguyBody:
|
||||||
|
# Use hue-based color system for Lilguy
|
||||||
|
var hue = _get_hue_from_skin_color(skin_name)
|
||||||
|
_body.set_character_color(hue)
|
||||||
|
else:
|
||||||
|
# Use texture-based system for 3DGodotRobot
|
||||||
|
var texture = get_texture_from_name(skin_name)
|
||||||
|
set_mesh_texture(_bottom_mesh, texture)
|
||||||
|
set_mesh_texture(_chest_mesh, texture)
|
||||||
|
set_mesh_texture(_face_mesh, texture)
|
||||||
|
set_mesh_texture(_limbs_head_mesh, texture)
|
||||||
|
|
||||||
set_mesh_texture(_bottom_mesh, texture)
|
## Convert SkinColor enum to hue value (0.0 to 1.0)
|
||||||
set_mesh_texture(_chest_mesh, texture)
|
func _get_hue_from_skin_color(skin_color: SkinColor) -> float:
|
||||||
set_mesh_texture(_face_mesh, texture)
|
match skin_color:
|
||||||
set_mesh_texture(_limbs_head_mesh, texture)
|
SkinColor.BLUE: return 0.6 # Blue hue
|
||||||
|
SkinColor.GREEN: return 0.33 # Green hue
|
||||||
|
SkinColor.RED: return 0.0 # Red hue
|
||||||
|
SkinColor.YELLOW: return 0.16 # Yellow hue
|
||||||
|
_: return 0.6 # Default to blue
|
||||||
|
|
||||||
func set_mesh_texture(mesh_instance: MeshInstance3D, texture: CompressedTexture2D) -> void:
|
func set_mesh_texture(mesh_instance: MeshInstance3D, texture: CompressedTexture2D) -> void:
|
||||||
if mesh_instance:
|
if mesh_instance:
|
||||||
@@ -435,16 +450,25 @@ func _server_apply_damage(target_name: String, damage: float, attacker_id: int,
|
|||||||
if not multiplayer.is_server():
|
if not multiplayer.is_server():
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get the target from the players container
|
|
||||||
var level = get_tree().get_current_scene()
|
var level = get_tree().get_current_scene()
|
||||||
if not level or not level.has_node("PlayersContainer"):
|
if not level:
|
||||||
return
|
return
|
||||||
|
|
||||||
var players_container = level.get_node("PlayersContainer")
|
var target = null
|
||||||
if not players_container.has_node(target_name):
|
|
||||||
return
|
|
||||||
|
|
||||||
var target = players_container.get_node(target_name)
|
# Check players container first
|
||||||
|
if level.has_node("PlayersContainer"):
|
||||||
|
var players_container = level.get_node("PlayersContainer")
|
||||||
|
if players_container.has_node(target_name):
|
||||||
|
target = players_container.get_node(target_name)
|
||||||
|
|
||||||
|
# If not found in players, check enemies container
|
||||||
|
if not target and level.has_node("EnemiesContainer"):
|
||||||
|
var enemies_container = level.get_node("EnemiesContainer")
|
||||||
|
if enemies_container.has_node(target_name):
|
||||||
|
target = enemies_container.get_node(target_name)
|
||||||
|
|
||||||
|
# Apply damage if target found
|
||||||
if target and target is BaseUnit:
|
if target and target is BaseUnit:
|
||||||
target.take_damage(damage, attacker_id, knockback, attacker_pos)
|
target.take_damage(damage, attacker_id, knockback, attacker_pos)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
extends BaseEnemy
|
||||||
|
class_name PracticeDummy
|
||||||
|
|
||||||
|
## A stationary practice dummy for testing combat
|
||||||
|
## Cannot move or attack - just takes damage and shows health
|
||||||
|
|
||||||
|
## Visual mesh reference
|
||||||
|
@onready var _mesh: MeshInstance3D = null
|
||||||
|
## Health label above dummy
|
||||||
|
@onready var _health_label: Label3D = null
|
||||||
|
## Original material for hit flash effect
|
||||||
|
var _original_material: Material = null
|
||||||
|
## Hit flash effect
|
||||||
|
var _hit_flash_timer: float = 0.0
|
||||||
|
const HIT_FLASH_DURATION: float = 0.2
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
super._ready()
|
||||||
|
|
||||||
|
# Practice dummy should not be aggressive
|
||||||
|
is_aggressive = false
|
||||||
|
|
||||||
|
# Auto-find mesh and health label
|
||||||
|
_mesh = get_node_or_null("Mesh")
|
||||||
|
_health_label = get_node_or_null("HealthLabel")
|
||||||
|
|
||||||
|
# Store original material for flash effect
|
||||||
|
if _mesh:
|
||||||
|
_original_material = _mesh.get_surface_override_material(0)
|
||||||
|
if not _original_material and _mesh.mesh:
|
||||||
|
_original_material = _mesh.mesh.surface_get_material(0)
|
||||||
|
|
||||||
|
# Update initial health display
|
||||||
|
_update_health_display()
|
||||||
|
|
||||||
|
# Connect health change to update display
|
||||||
|
health_changed.connect(_on_health_display_changed)
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
# Handle hit flash timer
|
||||||
|
if _hit_flash_timer > 0:
|
||||||
|
_hit_flash_timer -= delta
|
||||||
|
if _hit_flash_timer <= 0:
|
||||||
|
_reset_material()
|
||||||
|
|
||||||
|
func _physics_process(delta):
|
||||||
|
# Don't call super._physics_process since we don't move
|
||||||
|
# Just apply gravity
|
||||||
|
if not is_on_floor():
|
||||||
|
velocity.y -= ProjectSettings.get_setting("physics/3d/default_gravity") * delta
|
||||||
|
move_and_slide()
|
||||||
|
|
||||||
|
## Update health display
|
||||||
|
func _update_health_display():
|
||||||
|
if _health_label:
|
||||||
|
_health_label.text = "HP: %d/%d" % [int(current_health), int(max_health)]
|
||||||
|
|
||||||
|
func _on_health_display_changed(_old_health: float, _new_health: float):
|
||||||
|
_update_health_display()
|
||||||
|
|
||||||
|
## Override hurt animation to flash red
|
||||||
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
|
func _play_hurt_animation():
|
||||||
|
if _mesh:
|
||||||
|
_flash_red()
|
||||||
|
|
||||||
|
## Flash the dummy red when hit
|
||||||
|
func _flash_red():
|
||||||
|
if not _mesh:
|
||||||
|
return
|
||||||
|
|
||||||
|
_hit_flash_timer = HIT_FLASH_DURATION
|
||||||
|
|
||||||
|
# Create red material
|
||||||
|
var red_material = StandardMaterial3D.new()
|
||||||
|
red_material.albedo_color = Color(1.5, 0.3, 0.3) # Bright red
|
||||||
|
|
||||||
|
# Copy properties from original if it exists
|
||||||
|
if _original_material and _original_material is StandardMaterial3D:
|
||||||
|
var orig = _original_material as StandardMaterial3D
|
||||||
|
red_material.metallic = orig.metallic
|
||||||
|
red_material.roughness = orig.roughness
|
||||||
|
red_material.albedo_texture = orig.albedo_texture
|
||||||
|
|
||||||
|
_mesh.set_surface_override_material(0, red_material)
|
||||||
|
|
||||||
|
## Reset to original material
|
||||||
|
func _reset_material():
|
||||||
|
if _mesh and _original_material:
|
||||||
|
_mesh.set_surface_override_material(0, _original_material.duplicate())
|
||||||
|
|
||||||
|
## Override death to just hide the mesh
|
||||||
|
func _on_enemy_died(killer_id: int):
|
||||||
|
super._on_enemy_died(killer_id)
|
||||||
|
|
||||||
|
# Hide mesh when dead
|
||||||
|
if _mesh:
|
||||||
|
_mesh.visible = false
|
||||||
|
|
||||||
|
print("[PracticeDummy] Killed by player ", killer_id, ". Respawning in ", respawn_delay, " seconds...")
|
||||||
|
|
||||||
|
## Override respawn to show mesh again
|
||||||
|
func _on_enemy_respawned():
|
||||||
|
super._on_enemy_respawned()
|
||||||
|
|
||||||
|
# Show mesh when respawned
|
||||||
|
if _mesh:
|
||||||
|
_mesh.visible = true
|
||||||
|
_reset_material()
|
||||||
|
|
||||||
|
_update_health_display()
|
||||||
|
print("[PracticeDummy] Respawned!")
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://practice_dummy_script
|
||||||
Reference in New Issue
Block a user