Colours and practise dummy
This commit is contained in:
@@ -11,7 +11,7 @@ damage = 20.0
|
||||
attack_range = 3.5
|
||||
attack_cooldown = 0.6
|
||||
knockback_force = 12.0
|
||||
startup_time = 0.12
|
||||
startup_time = 0.5
|
||||
active_time = 1.0
|
||||
mesh_scene = ExtResource("1_gdc1w")
|
||||
weight = 2.0
|
||||
|
||||
@@ -47,54 +47,54 @@ _character = NodePath("../..")
|
||||
animation_player = NodePath("../AnimationPlayer")
|
||||
|
||||
[node name="Skeleton3D" parent="LilguyRigged/Armature" index="0"]
|
||||
bones/0/position = Vector3(-0.32852697, 2.914154, -546.76843)
|
||||
bones/0/rotation = Quaternion(-0.6608288, 0.28933647, -0.19178489, 0.6654384)
|
||||
bones/0/position = Vector3(-0.32859802, 2.9141626, -546.76843)
|
||||
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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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)
|
||||
|
||||
[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_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)
|
||||
|
||||
[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_idx = 10
|
||||
|
||||
[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)
|
||||
|
||||
[node name="AnimationPlayer" parent="LilguyRigged" index="1"]
|
||||
speed_scale = 2.0
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
transform = Transform3D(2, 0, 0, 0, 2, 0, 0, 0, 2, -0.066, 1.647685, 0.01)
|
||||
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="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="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"]
|
||||
@@ -47,6 +48,7 @@ color = Color(0, 0, 0, 0)
|
||||
[node name="Level" type="Node3D"]
|
||||
script = ExtResource("1_e1sh7")
|
||||
player_scene = ExtResource("1_uvcbi")
|
||||
practice_dummy_scene = ExtResource("3_i7s07")
|
||||
|
||||
[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")]
|
||||
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/Join" to="." method="_on_join_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 address_input: LineEdit = $Menu/MainContainer/MainMenu/Option3/AddressInput
|
||||
@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 main_menu: VBoxContainer = $Menu/MainContainer/MainMenu
|
||||
@export var player_scene: PackedScene
|
||||
@export var practice_dummy_scene: PackedScene
|
||||
|
||||
# Weapon spawning counter (server-side only)
|
||||
var _weapon_spawn_counter: int = 0
|
||||
@@ -43,6 +45,15 @@ func _ready():
|
||||
add_child(weapons_container)
|
||||
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
|
||||
# This is handled in initialize_multiplayer() which is called after the
|
||||
# multiplayer peer is properly set up
|
||||
@@ -74,6 +85,9 @@ func initialize_multiplayer():
|
||||
# Spawn initial weapons when server starts
|
||||
_spawn_initial_weapons()
|
||||
|
||||
# Spawn practice dummies
|
||||
_spawn_practice_dummies()
|
||||
|
||||
# Spawn the host player (peer ID 1)
|
||||
print("[Level] Spawning host player")
|
||||
var host_info = Network.players.get(1, {"nick": "Host", "skin": "blue"})
|
||||
@@ -219,6 +233,18 @@ func _on_player_connected(peer_id, player_info):
|
||||
else:
|
||||
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
|
||||
print("[Server] Syncing equipped weapons to newly connected peer: ", peer_id)
|
||||
for player_node in players_container.get_children():
|
||||
@@ -414,6 +440,63 @@ func _on_jemz_preset():
|
||||
skin_input.text = "Red"
|
||||
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 ----------
|
||||
## Spawn a weapon in the world (called from server, syncs to all clients)
|
||||
@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)
|
||||
animation_player.play("Attack_OneHand", -1, 1.0)
|
||||
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")
|
||||
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)
|
||||
set_mesh_texture(_chest_mesh, texture)
|
||||
set_mesh_texture(_face_mesh, texture)
|
||||
set_mesh_texture(_limbs_head_mesh, texture)
|
||||
## Convert SkinColor enum to hue value (0.0 to 1.0)
|
||||
func _get_hue_from_skin_color(skin_color: SkinColor) -> float:
|
||||
match skin_color:
|
||||
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:
|
||||
if mesh_instance:
|
||||
@@ -435,16 +450,25 @@ func _server_apply_damage(target_name: String, damage: float, attacker_id: int,
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
|
||||
# Get the target from the players container
|
||||
var level = get_tree().get_current_scene()
|
||||
if not level or not level.has_node("PlayersContainer"):
|
||||
if not level:
|
||||
return
|
||||
|
||||
var players_container = level.get_node("PlayersContainer")
|
||||
if not players_container.has_node(target_name):
|
||||
return
|
||||
var target = null
|
||||
|
||||
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:
|
||||
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