Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a989f3586 | ||
|
|
30dbf7a4cd | ||
|
|
54dfd342c2 | ||
|
|
eb9344c0bb |
@@ -11,6 +11,7 @@ var max_shield: float = 100.0
|
||||
var shield: float = 100.0
|
||||
|
||||
var is_dead: bool = false
|
||||
var time_since_last_damage: float = 0.0
|
||||
var ragdoll_instance: Node3D = null
|
||||
var pending_impulse: Vector3 = Vector3.ZERO
|
||||
var recent_attackers: Dictionary = {}
|
||||
@@ -39,6 +40,16 @@ func _ready() -> void:
|
||||
|
||||
_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:
|
||||
if is_dead:
|
||||
return
|
||||
@@ -55,6 +66,8 @@ func take_damage(amount: float, hit_position: Vector3, source: Node = null, _imp
|
||||
if attacker_id != 0:
|
||||
recent_attackers[attacker_id] = Time.get_ticks_msec() / 1000.0
|
||||
|
||||
time_since_last_damage = 0.0
|
||||
|
||||
var dmg_taken = amount
|
||||
if shield > 0:
|
||||
if shield >= amount:
|
||||
|
||||
@@ -11,6 +11,7 @@ var max_shield: float = 100.0
|
||||
var shield: float = 100.0
|
||||
|
||||
var is_dead: bool = false
|
||||
var time_since_last_damage: float = 0.0
|
||||
var recent_attackers: Dictionary = {} # attacker_id: timestamp
|
||||
var ragdoll_instance: Node3D = null
|
||||
var pending_impulse: Vector3 = Vector3.ZERO
|
||||
@@ -51,6 +52,14 @@ func _physics_process(delta: float) -> void:
|
||||
if is_dead:
|
||||
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
|
||||
if not is_on_floor():
|
||||
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:
|
||||
recent_attackers[attacker_id] = Time.get_ticks_msec() / 1000.0
|
||||
|
||||
time_since_last_damage = 0.0
|
||||
|
||||
var dmg_taken = amount
|
||||
if shield > 0:
|
||||
if shield >= amount:
|
||||
@@ -109,6 +120,8 @@ func take_damage(amount: float, hit_position: Vector3, source: Node = null, impu
|
||||
|
||||
if amount > 0:
|
||||
health -= amount
|
||||
if health <= 0:
|
||||
health = 0
|
||||
|
||||
_update_label()
|
||||
|
||||
|
||||
@@ -94,11 +94,14 @@ func _physics_process(delta: float) -> void:
|
||||
_try_start_grapple()
|
||||
|
||||
if is_grapple_shooting:
|
||||
grapple_shoot_time += delta
|
||||
if grapple_shoot_time >= grapple_travel_time:
|
||||
if not input_grapple:
|
||||
is_grapple_shooting = false
|
||||
movement_event.emit("grapple_latch", {})
|
||||
switch_to("grapple")
|
||||
else:
|
||||
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
|
||||
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
|
||||
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:
|
||||
var attacker_id = 0
|
||||
var weapon_name = "Killed"
|
||||
@@ -454,6 +461,10 @@ func rpc_play_explosion(pos: Vector3, radius: float) -> void:
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
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 impulse.length_squared() > 0.01:
|
||||
apply_impulse(impulse)
|
||||
|
||||
# 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)
|
||||
|
||||
@@ -466,7 +477,16 @@ func rpc_take_damage(amount: float, hit_pos: Vector3, attacker_id: int, weapon_n
|
||||
|
||||
# Show damage number for the attacker
|
||||
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:
|
||||
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)
|
||||
|
||||
if multiplayer.is_server():
|
||||
if get_node_or_null("/root/NetworkManager") and multiplayer.has_multiplayer_peer():
|
||||
var victim_id = multiplayer.get_unique_id()
|
||||
var nm = get_node_or_null("/root/NetworkManager")
|
||||
if nm and multiplayer.has_multiplayer_peer():
|
||||
var killer_id = attacker_id
|
||||
|
||||
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:
|
||||
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:
|
||||
if camera:
|
||||
@@ -606,9 +675,8 @@ func _physics_process(_delta: float) -> void:
|
||||
_client_grapple_just_pressed = false
|
||||
|
||||
# Update synced properties for the grapple
|
||||
if sm.current_state == "grapple" or sm.is_grapple_shooting:
|
||||
synced_grapple_point = sm.grapple_point
|
||||
synced_is_grapple_shooting = sm.is_grapple_shooting
|
||||
synced_grapple_point = sm.grapple_point
|
||||
synced_is_grapple_shooting = sm.is_grapple_shooting
|
||||
|
||||
# Update grapple rope visuals (runs on all clients for smooth interpolation)
|
||||
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_meta("current_weapon_path", synced_weapon_path)
|
||||
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:
|
||||
# Continuously follow the ragdoll torso
|
||||
if is_instance_valid(ragdoll_instance) and is_instance_valid(camera):
|
||||
@@ -695,14 +772,6 @@ func _process(delta: float) -> void:
|
||||
if 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:
|
||||
var margin = MarginContainer.new()
|
||||
margin.set_anchors_preset(Control.PRESET_BOTTOM_LEFT)
|
||||
@@ -903,8 +972,8 @@ func die(impulse: Vector3 = Vector3.ZERO) -> void:
|
||||
death_screen.visible = true
|
||||
set_process_input(true)
|
||||
|
||||
func _apply_ragdoll_velocity(ragdoll: Node3D, l_vel: Vector3, imp: Vector3) -> void:
|
||||
if is_instance_valid(ragdoll):
|
||||
func _apply_ragdoll_velocity(ragdoll, l_vel: Vector3, imp: Vector3) -> void:
|
||||
if is_instance_valid(ragdoll) and ragdoll.has_method("apply_initial_velocities"):
|
||||
ragdoll.apply_initial_velocities(l_vel, imp)
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
@@ -915,12 +984,16 @@ func _input(event: InputEvent) -> void:
|
||||
_on_respawn_pressed()
|
||||
|
||||
func _on_respawn_pressed() -> void:
|
||||
if death_screen:
|
||||
death_screen.visible = false
|
||||
if multiplayer.is_server():
|
||||
var spawn_pos = Vector3(randf_range(-5, 5), 5, randf_range(-5, 5))
|
||||
rpc_respawn.rpc(spawn_pos)
|
||||
else:
|
||||
rpc_request_respawn.rpc_id(1)
|
||||
|
||||
var _last_respawn_request_time: float = 0.0
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func rpc_request_respawn() -> void:
|
||||
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():
|
||||
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:
|
||||
var spawn_pos = Vector3(randf_range(-5, 5), 5, randf_range(-5, 5))
|
||||
rpc_respawn.rpc(spawn_pos)
|
||||
|
||||
@@ -146,12 +146,11 @@ func _apply_impulse() -> void:
|
||||
var push_dir = camera.global_transform.basis.z.normalized()
|
||||
|
||||
# Apply impulse to player velocity
|
||||
player.velocity += push_dir * impulse_strength
|
||||
|
||||
# Small hack to ensure we aren't considered on floor if we shoot down
|
||||
if push_dir.y > 0:
|
||||
# Just slightly lift the player to break floor contact immediately
|
||||
player.position.y += 0.05
|
||||
var final_force = push_dir * impulse_strength
|
||||
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer is not OfflineMultiplayerPeer:
|
||||
player.server_apply_impulse.rpc_id(1, final_force)
|
||||
else:
|
||||
player.apply_impulse(final_force)
|
||||
|
||||
func _play_muzzle_flash() -> void:
|
||||
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
|
||||
dir = (dir + Vector3(0, 0.5, 0)).normalized()
|
||||
|
||||
# Apply knockback if it has apply_impulse
|
||||
if col.has_method("apply_impulse"):
|
||||
col.apply_impulse(dir * final_knockback)
|
||||
# Apply knockback if it has server_apply_impulse (for rocket jumping)
|
||||
if col.has_method("server_apply_impulse") and col == owner_player:
|
||||
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 == 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
|
||||
elif owner_player and owner_player.multiplayer.is_server():
|
||||
elif owner_player:
|
||||
col.take_damage(final_damage, target_pos, owner_player, dir * final_knockback)
|
||||
|
||||
# Broadcast Visual Explosion Effect to other peers
|
||||
|
||||
Reference in New Issue
Block a user