Compare commits
4
Commits
07a15fe998
...
1a989f3586
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a989f3586 | ||
|
|
30dbf7a4cd | ||
|
|
54dfd342c2 | ||
|
|
eb9344c0bb |
@@ -11,6 +11,7 @@ var max_shield: float = 100.0
|
|||||||
var shield: float = 100.0
|
var shield: float = 100.0
|
||||||
|
|
||||||
var is_dead: bool = false
|
var is_dead: bool = false
|
||||||
|
var time_since_last_damage: float = 0.0
|
||||||
var ragdoll_instance: Node3D = null
|
var ragdoll_instance: Node3D = null
|
||||||
var pending_impulse: Vector3 = Vector3.ZERO
|
var pending_impulse: Vector3 = Vector3.ZERO
|
||||||
var recent_attackers: Dictionary = {}
|
var recent_attackers: Dictionary = {}
|
||||||
@@ -39,6 +40,16 @@ func _ready() -> void:
|
|||||||
|
|
||||||
_update_label()
|
_update_label()
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
if is_dead:
|
||||||
|
return
|
||||||
|
time_since_last_damage += delta
|
||||||
|
if time_since_last_damage >= 5.0 and shield < max_shield:
|
||||||
|
shield += 20.0 * delta
|
||||||
|
if shield > max_shield:
|
||||||
|
shield = max_shield
|
||||||
|
_update_label()
|
||||||
|
|
||||||
func take_damage(amount: float, hit_position: Vector3, source: Node = null, _impulse: Vector3 = Vector3.ZERO) -> void:
|
func take_damage(amount: float, hit_position: Vector3, source: Node = null, _impulse: Vector3 = Vector3.ZERO) -> void:
|
||||||
if is_dead:
|
if is_dead:
|
||||||
return
|
return
|
||||||
@@ -55,6 +66,8 @@ func take_damage(amount: float, hit_position: Vector3, source: Node = null, _imp
|
|||||||
if attacker_id != 0:
|
if attacker_id != 0:
|
||||||
recent_attackers[attacker_id] = Time.get_ticks_msec() / 1000.0
|
recent_attackers[attacker_id] = Time.get_ticks_msec() / 1000.0
|
||||||
|
|
||||||
|
time_since_last_damage = 0.0
|
||||||
|
|
||||||
var dmg_taken = amount
|
var dmg_taken = amount
|
||||||
if shield > 0:
|
if shield > 0:
|
||||||
if shield >= amount:
|
if shield >= amount:
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ var max_shield: float = 100.0
|
|||||||
var shield: float = 100.0
|
var shield: float = 100.0
|
||||||
|
|
||||||
var is_dead: bool = false
|
var is_dead: bool = false
|
||||||
|
var time_since_last_damage: float = 0.0
|
||||||
var recent_attackers: Dictionary = {} # attacker_id: timestamp
|
var recent_attackers: Dictionary = {} # attacker_id: timestamp
|
||||||
var ragdoll_instance: Node3D = null
|
var ragdoll_instance: Node3D = null
|
||||||
var pending_impulse: Vector3 = Vector3.ZERO
|
var pending_impulse: Vector3 = Vector3.ZERO
|
||||||
@@ -51,6 +52,14 @@ func _physics_process(delta: float) -> void:
|
|||||||
if is_dead:
|
if is_dead:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Shield recharge logic
|
||||||
|
time_since_last_damage += delta
|
||||||
|
if time_since_last_damage >= 5.0 and shield < max_shield:
|
||||||
|
shield += 20.0 * delta
|
||||||
|
if shield > max_shield:
|
||||||
|
shield = max_shield
|
||||||
|
_update_label()
|
||||||
|
|
||||||
# Basic gravity
|
# Basic gravity
|
||||||
if not is_on_floor():
|
if not is_on_floor():
|
||||||
velocity.y -= 9.8 * delta
|
velocity.y -= 9.8 * delta
|
||||||
@@ -98,6 +107,8 @@ func take_damage(amount: float, hit_position: Vector3, source: Node = null, impu
|
|||||||
if attacker_id != 0:
|
if attacker_id != 0:
|
||||||
recent_attackers[attacker_id] = Time.get_ticks_msec() / 1000.0
|
recent_attackers[attacker_id] = Time.get_ticks_msec() / 1000.0
|
||||||
|
|
||||||
|
time_since_last_damage = 0.0
|
||||||
|
|
||||||
var dmg_taken = amount
|
var dmg_taken = amount
|
||||||
if shield > 0:
|
if shield > 0:
|
||||||
if shield >= amount:
|
if shield >= amount:
|
||||||
@@ -109,6 +120,8 @@ func take_damage(amount: float, hit_position: Vector3, source: Node = null, impu
|
|||||||
|
|
||||||
if amount > 0:
|
if amount > 0:
|
||||||
health -= amount
|
health -= amount
|
||||||
|
if health <= 0:
|
||||||
|
health = 0
|
||||||
|
|
||||||
_update_label()
|
_update_label()
|
||||||
|
|
||||||
|
|||||||
@@ -94,11 +94,14 @@ func _physics_process(delta: float) -> void:
|
|||||||
_try_start_grapple()
|
_try_start_grapple()
|
||||||
|
|
||||||
if is_grapple_shooting:
|
if is_grapple_shooting:
|
||||||
grapple_shoot_time += delta
|
if not input_grapple:
|
||||||
if grapple_shoot_time >= grapple_travel_time:
|
|
||||||
is_grapple_shooting = false
|
is_grapple_shooting = false
|
||||||
movement_event.emit("grapple_latch", {})
|
else:
|
||||||
switch_to("grapple")
|
grapple_shoot_time += delta
|
||||||
|
if grapple_shoot_time >= grapple_travel_time:
|
||||||
|
is_grapple_shooting = false
|
||||||
|
movement_event.emit("grapple_latch", {})
|
||||||
|
switch_to("grapple")
|
||||||
|
|
||||||
# Manage global crouch state
|
# Manage global crouch state
|
||||||
var want_crouch = input_crouch
|
var want_crouch = input_crouch
|
||||||
|
|||||||
@@ -252,6 +252,13 @@ func apply_impulse(force: Vector3) -> void:
|
|||||||
# Tiny upward bump helps move_and_slide detach from the floor
|
# Tiny upward bump helps move_and_slide detach from the floor
|
||||||
global_position.y += 0.1
|
global_position.y += 0.1
|
||||||
|
|
||||||
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
|
func server_apply_impulse(force: Vector3) -> void:
|
||||||
|
if not multiplayer.is_server(): return
|
||||||
|
var sender = multiplayer.get_remote_sender_id()
|
||||||
|
if sender != 1 and sender != str(name).to_int(): return
|
||||||
|
apply_impulse(force)
|
||||||
|
|
||||||
func take_damage(amount: float, _hit_pos: Vector3, _source: Node3D = null, impulse: Vector3 = Vector3.ZERO) -> void:
|
func take_damage(amount: float, _hit_pos: Vector3, _source: Node3D = null, impulse: Vector3 = Vector3.ZERO) -> void:
|
||||||
var attacker_id = 0
|
var attacker_id = 0
|
||||||
var weapon_name = "Killed"
|
var weapon_name = "Killed"
|
||||||
@@ -454,6 +461,10 @@ func rpc_play_explosion(pos: Vector3, radius: float) -> void:
|
|||||||
@rpc("any_peer", "call_local", "reliable")
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
func server_take_damage(amount: float, hit_pos: Vector3, attacker_id: int, weapon_name: String, impulse: Vector3) -> void:
|
func server_take_damage(amount: float, hit_pos: Vector3, attacker_id: int, weapon_name: String, impulse: Vector3) -> void:
|
||||||
if not multiplayer.is_server(): return
|
if not multiplayer.is_server(): return
|
||||||
|
|
||||||
|
if impulse.length_squared() > 0.01:
|
||||||
|
apply_impulse(impulse)
|
||||||
|
|
||||||
# Broadcast damage event to all peers so the victim dies on all screens
|
# Broadcast damage event to all peers so the victim dies on all screens
|
||||||
rpc_take_damage.rpc(amount, hit_pos, attacker_id, weapon_name, impulse)
|
rpc_take_damage.rpc(amount, hit_pos, attacker_id, weapon_name, impulse)
|
||||||
|
|
||||||
@@ -466,7 +477,16 @@ func rpc_take_damage(amount: float, hit_pos: Vector3, attacker_id: int, weapon_n
|
|||||||
|
|
||||||
# Show damage number for the attacker
|
# Show damage number for the attacker
|
||||||
if not multiplayer.has_multiplayer_peer() or attacker_id == multiplayer.get_unique_id():
|
if not multiplayer.has_multiplayer_peer() or attacker_id == multiplayer.get_unique_id():
|
||||||
spawn_damage_number(amount, hit_pos)
|
if is_multiplayer_authority():
|
||||||
|
spawn_damage_number(amount, hit_pos)
|
||||||
|
else:
|
||||||
|
var my_node = get_parent().get_node_or_null(str(attacker_id))
|
||||||
|
if my_node and my_node.has_method("spawn_damage_number"):
|
||||||
|
my_node.spawn_damage_number(amount, hit_pos)
|
||||||
|
|
||||||
|
# Show damage indicator and screen shake for the victim
|
||||||
|
if is_multiplayer_authority() and amount > 0.0 and (not multiplayer.has_multiplayer_peer() or attacker_id != multiplayer.get_unique_id()):
|
||||||
|
_show_damage_indicator(hit_pos)
|
||||||
|
|
||||||
if attacker_id != 0:
|
if attacker_id != 0:
|
||||||
recent_attackers[attacker_id] = Time.get_ticks_msec() / 1000.0
|
recent_attackers[attacker_id] = Time.get_ticks_msec() / 1000.0
|
||||||
@@ -488,8 +508,8 @@ func rpc_take_damage(amount: float, hit_pos: Vector3, attacker_id: int, weapon_n
|
|||||||
die(impulse)
|
die(impulse)
|
||||||
|
|
||||||
if multiplayer.is_server():
|
if multiplayer.is_server():
|
||||||
if get_node_or_null("/root/NetworkManager") and multiplayer.has_multiplayer_peer():
|
var nm = get_node_or_null("/root/NetworkManager")
|
||||||
var victim_id = multiplayer.get_unique_id()
|
if nm and multiplayer.has_multiplayer_peer():
|
||||||
var killer_id = attacker_id
|
var killer_id = attacker_id
|
||||||
|
|
||||||
var assist_ids: Array = []
|
var assist_ids: Array = []
|
||||||
@@ -498,7 +518,56 @@ func rpc_take_damage(amount: float, hit_pos: Vector3, attacker_id: int, weapon_n
|
|||||||
if aid != killer_id and now - recent_attackers[aid] <= 10.0:
|
if aid != killer_id and now - recent_attackers[aid] <= 10.0:
|
||||||
assist_ids.append(aid)
|
assist_ids.append(aid)
|
||||||
|
|
||||||
get_node("/root/NetworkManager").register_kill.rpc(victim_id, killer_id, weapon_name, "", assist_ids)
|
var real_victim_name = "Player " + str(name)
|
||||||
|
if "player_name" in self and self.player_name != "":
|
||||||
|
real_victim_name = self.player_name
|
||||||
|
elif str(name).to_int() == 1:
|
||||||
|
real_victim_name = "Host"
|
||||||
|
|
||||||
|
if nm.has_method("register_kill"):
|
||||||
|
var victim_id = str(name).to_int()
|
||||||
|
if victim_id == 0:
|
||||||
|
victim_id = 1 # Fallback to host if something is weird
|
||||||
|
nm.register_kill.rpc(victim_id, killer_id, weapon_name, real_victim_name, assist_ids)
|
||||||
|
|
||||||
|
func _show_damage_indicator(hit_pos: Vector3) -> void:
|
||||||
|
if not _damage_layer: return
|
||||||
|
|
||||||
|
var indicator = Polygon2D.new()
|
||||||
|
indicator.polygon = PackedVector2Array([
|
||||||
|
Vector2(0, -100),
|
||||||
|
Vector2(-20, -75),
|
||||||
|
Vector2(20, -75)
|
||||||
|
])
|
||||||
|
indicator.color = Color(1.0, 0.0, 0.0, 0.8)
|
||||||
|
indicator.position = get_viewport().get_visible_rect().size / 2.0
|
||||||
|
|
||||||
|
var forward = -global_transform.basis.z
|
||||||
|
forward.y = 0.0
|
||||||
|
if forward.length_squared() > 0.01:
|
||||||
|
forward = forward.normalized()
|
||||||
|
var right = global_transform.basis.x
|
||||||
|
right.y = 0.0
|
||||||
|
right = right.normalized()
|
||||||
|
|
||||||
|
var dir_to_hit = (hit_pos - global_position)
|
||||||
|
dir_to_hit.y = 0.0
|
||||||
|
if dir_to_hit.length_squared() > 0.01:
|
||||||
|
dir_to_hit = dir_to_hit.normalized()
|
||||||
|
var angle = atan2(dir_to_hit.dot(right), dir_to_hit.dot(forward))
|
||||||
|
indicator.rotation = angle
|
||||||
|
|
||||||
|
_damage_layer.add_child(indicator)
|
||||||
|
var tween = create_tween()
|
||||||
|
tween.tween_property(indicator, "color:a", 0.0, 1.0)
|
||||||
|
tween.tween_callback(indicator.queue_free)
|
||||||
|
|
||||||
|
if camera:
|
||||||
|
var shake_tween = create_tween()
|
||||||
|
shake_tween.tween_property(camera, "h_offset", randf_range(-0.2, 0.2), 0.05)
|
||||||
|
shake_tween.parallel().tween_property(camera, "v_offset", randf_range(-0.2, 0.2), 0.05)
|
||||||
|
shake_tween.chain().tween_property(camera, "h_offset", 0.0, 0.05)
|
||||||
|
shake_tween.parallel().tween_property(camera, "v_offset", 0.0, 0.05)
|
||||||
|
|
||||||
func get_weapon_name() -> String:
|
func get_weapon_name() -> String:
|
||||||
if camera:
|
if camera:
|
||||||
@@ -606,9 +675,8 @@ func _physics_process(_delta: float) -> void:
|
|||||||
_client_grapple_just_pressed = false
|
_client_grapple_just_pressed = false
|
||||||
|
|
||||||
# Update synced properties for the grapple
|
# Update synced properties for the grapple
|
||||||
if sm.current_state == "grapple" or sm.is_grapple_shooting:
|
synced_grapple_point = sm.grapple_point
|
||||||
synced_grapple_point = sm.grapple_point
|
synced_is_grapple_shooting = sm.is_grapple_shooting
|
||||||
synced_is_grapple_shooting = sm.is_grapple_shooting
|
|
||||||
|
|
||||||
# Update grapple rope visuals (runs on all clients for smooth interpolation)
|
# Update grapple rope visuals (runs on all clients for smooth interpolation)
|
||||||
if (synced_movement_state == "grapple" or synced_is_grapple_shooting) and camera:
|
if (synced_movement_state == "grapple" or synced_is_grapple_shooting) and camera:
|
||||||
@@ -672,6 +740,15 @@ func _process(delta: float) -> void:
|
|||||||
humanoid.set_weapon(synced_weapon_path)
|
humanoid.set_weapon(synced_weapon_path)
|
||||||
humanoid.set_meta("current_weapon_path", synced_weapon_path)
|
humanoid.set_meta("current_weapon_path", synced_weapon_path)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Update UI
|
||||||
|
if is_instance_valid(health_bar):
|
||||||
|
health_bar.value = health
|
||||||
|
health_label.text = "%d / %d" % [ceil(health), max_health]
|
||||||
|
if is_instance_valid(shield_bar):
|
||||||
|
shield_bar.value = shield
|
||||||
|
shield_label.text = "%d / %d" % [ceil(shield), max_shield]
|
||||||
|
|
||||||
if is_dead:
|
if is_dead:
|
||||||
# Continuously follow the ragdoll torso
|
# Continuously follow the ragdoll torso
|
||||||
if is_instance_valid(ragdoll_instance) and is_instance_valid(camera):
|
if is_instance_valid(ragdoll_instance) and is_instance_valid(camera):
|
||||||
@@ -694,14 +771,6 @@ func _process(delta: float) -> void:
|
|||||||
shield += 20.0 * delta
|
shield += 20.0 * delta
|
||||||
if shield > max_shield:
|
if shield > max_shield:
|
||||||
shield = max_shield
|
shield = max_shield
|
||||||
|
|
||||||
# Update UI
|
|
||||||
if is_instance_valid(health_bar):
|
|
||||||
health_bar.value = health
|
|
||||||
health_label.text = "%d / %d" % [ceil(health), max_health]
|
|
||||||
if is_instance_valid(shield_bar):
|
|
||||||
shield_bar.value = shield
|
|
||||||
shield_label.text = "%d / %d" % [ceil(shield), max_shield]
|
|
||||||
|
|
||||||
func _setup_hud() -> void:
|
func _setup_hud() -> void:
|
||||||
var margin = MarginContainer.new()
|
var margin = MarginContainer.new()
|
||||||
@@ -903,8 +972,8 @@ func die(impulse: Vector3 = Vector3.ZERO) -> void:
|
|||||||
death_screen.visible = true
|
death_screen.visible = true
|
||||||
set_process_input(true)
|
set_process_input(true)
|
||||||
|
|
||||||
func _apply_ragdoll_velocity(ragdoll: Node3D, l_vel: Vector3, imp: Vector3) -> void:
|
func _apply_ragdoll_velocity(ragdoll, l_vel: Vector3, imp: Vector3) -> void:
|
||||||
if is_instance_valid(ragdoll):
|
if is_instance_valid(ragdoll) and ragdoll.has_method("apply_initial_velocities"):
|
||||||
ragdoll.apply_initial_velocities(l_vel, imp)
|
ragdoll.apply_initial_velocities(l_vel, imp)
|
||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
func _input(event: InputEvent) -> void:
|
||||||
@@ -915,12 +984,16 @@ func _input(event: InputEvent) -> void:
|
|||||||
_on_respawn_pressed()
|
_on_respawn_pressed()
|
||||||
|
|
||||||
func _on_respawn_pressed() -> void:
|
func _on_respawn_pressed() -> void:
|
||||||
|
if death_screen:
|
||||||
|
death_screen.visible = false
|
||||||
if multiplayer.is_server():
|
if multiplayer.is_server():
|
||||||
var spawn_pos = Vector3(randf_range(-5, 5), 5, randf_range(-5, 5))
|
var spawn_pos = Vector3(randf_range(-5, 5), 5, randf_range(-5, 5))
|
||||||
rpc_respawn.rpc(spawn_pos)
|
rpc_respawn.rpc(spawn_pos)
|
||||||
else:
|
else:
|
||||||
rpc_request_respawn.rpc_id(1)
|
rpc_request_respawn.rpc_id(1)
|
||||||
|
|
||||||
|
var _last_respawn_request_time: float = 0.0
|
||||||
|
|
||||||
@rpc("any_peer", "call_local", "reliable")
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
func rpc_request_respawn() -> void:
|
func rpc_request_respawn() -> void:
|
||||||
if not multiplayer.is_server():
|
if not multiplayer.is_server():
|
||||||
@@ -929,6 +1002,11 @@ func rpc_request_respawn() -> void:
|
|||||||
if sender_id != 1 and sender_id != str(name).to_int():
|
if sender_id != 1 and sender_id != str(name).to_int():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
var now = Time.get_ticks_msec() / 1000.0
|
||||||
|
if now - _last_respawn_request_time < 0.5:
|
||||||
|
return
|
||||||
|
_last_respawn_request_time = now
|
||||||
|
|
||||||
if is_dead:
|
if is_dead:
|
||||||
var spawn_pos = Vector3(randf_range(-5, 5), 5, randf_range(-5, 5))
|
var spawn_pos = Vector3(randf_range(-5, 5), 5, randf_range(-5, 5))
|
||||||
rpc_respawn.rpc(spawn_pos)
|
rpc_respawn.rpc(spawn_pos)
|
||||||
|
|||||||
@@ -146,12 +146,11 @@ func _apply_impulse() -> void:
|
|||||||
var push_dir = camera.global_transform.basis.z.normalized()
|
var push_dir = camera.global_transform.basis.z.normalized()
|
||||||
|
|
||||||
# Apply impulse to player velocity
|
# Apply impulse to player velocity
|
||||||
player.velocity += push_dir * impulse_strength
|
var final_force = push_dir * impulse_strength
|
||||||
|
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer is not OfflineMultiplayerPeer:
|
||||||
# Small hack to ensure we aren't considered on floor if we shoot down
|
player.server_apply_impulse.rpc_id(1, final_force)
|
||||||
if push_dir.y > 0:
|
else:
|
||||||
# Just slightly lift the player to break floor contact immediately
|
player.apply_impulse(final_force)
|
||||||
player.position.y += 0.05
|
|
||||||
|
|
||||||
func _play_muzzle_flash() -> void:
|
func _play_muzzle_flash() -> void:
|
||||||
if muzzle_flash:
|
if muzzle_flash:
|
||||||
|
|||||||
@@ -191,16 +191,19 @@ func _explode(pos: Vector3) -> void:
|
|||||||
# Add a tiny bit of upward bias to knockback so it lifts them
|
# Add a tiny bit of upward bias to knockback so it lifts them
|
||||||
dir = (dir + Vector3(0, 0.5, 0)).normalized()
|
dir = (dir + Vector3(0, 0.5, 0)).normalized()
|
||||||
|
|
||||||
# Apply knockback if it has apply_impulse
|
# Apply knockback if it has server_apply_impulse (for rocket jumping)
|
||||||
if col.has_method("apply_impulse"):
|
if col.has_method("server_apply_impulse") and col == owner_player:
|
||||||
col.apply_impulse(dir * final_knockback)
|
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer is not OfflineMultiplayerPeer:
|
||||||
|
col.server_apply_impulse.rpc_id(1, dir * final_knockback)
|
||||||
|
else:
|
||||||
|
col.apply_impulse(dir * final_knockback)
|
||||||
|
|
||||||
# Apply damage
|
# Apply damage (and knockback for other players via take_damage)
|
||||||
if col.has_method("take_damage"):
|
if col.has_method("take_damage"):
|
||||||
if col == owner_player and not can_self_damage:
|
if col == owner_player and not can_self_damage:
|
||||||
# Skip self damage, but we still applied the knockback!
|
# Skip self damage (we already applied knockback above)
|
||||||
pass
|
pass
|
||||||
elif owner_player and owner_player.multiplayer.is_server():
|
elif owner_player:
|
||||||
col.take_damage(final_damage, target_pos, owner_player, dir * final_knockback)
|
col.take_damage(final_damage, target_pos, owner_player, dir * final_knockback)
|
||||||
|
|
||||||
# Broadcast Visual Explosion Effect to other peers
|
# Broadcast Visual Explosion Effect to other peers
|
||||||
|
|||||||
Reference in New Issue
Block a user