Health and attacks!
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
extends CharacterBody3D
|
||||
class_name BaseUnit
|
||||
|
||||
## Base class for all units (players, enemies, NPCs) in the game
|
||||
## Provides common functionality like health management and taking damage
|
||||
|
||||
signal health_changed(old_health: float, new_health: float)
|
||||
signal died(killer_id: int)
|
||||
signal respawned()
|
||||
|
||||
@export var max_health: float = 100.0
|
||||
@export var can_respawn: bool = true
|
||||
@export var respawn_delay: float = 3.0
|
||||
|
||||
var current_health: float = 100.0
|
||||
var is_dead: bool = false
|
||||
var _respawn_point: Vector3 = Vector3.ZERO
|
||||
|
||||
func _ready():
|
||||
current_health = max_health
|
||||
_respawn_point = global_position
|
||||
|
||||
func _enter_tree():
|
||||
set_multiplayer_authority(str(name).to_int())
|
||||
|
||||
## Take damage from an attacker
|
||||
## Should only be called on the server for authority
|
||||
@rpc("any_peer", "reliable")
|
||||
func take_damage(amount: float, attacker_id: int = -1):
|
||||
# Only server can process damage
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
|
||||
if is_dead:
|
||||
return
|
||||
|
||||
var old_health = current_health
|
||||
current_health = max(0, current_health - amount)
|
||||
|
||||
# Broadcast health change to all clients
|
||||
rpc("sync_health", current_health)
|
||||
health_changed.emit(old_health, current_health)
|
||||
|
||||
if current_health <= 0:
|
||||
_die(attacker_id)
|
||||
|
||||
## Heal the unit
|
||||
@rpc("any_peer", "reliable")
|
||||
func heal(amount: float):
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
|
||||
if is_dead:
|
||||
return
|
||||
|
||||
var old_health = current_health
|
||||
current_health = min(max_health, current_health + amount)
|
||||
|
||||
rpc("sync_health", current_health)
|
||||
health_changed.emit(old_health, current_health)
|
||||
|
||||
## Sync health across all clients
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func sync_health(new_health: float):
|
||||
var old_health = current_health
|
||||
current_health = new_health
|
||||
health_changed.emit(old_health, current_health)
|
||||
|
||||
## Handle death
|
||||
func _die(killer_id: int):
|
||||
if is_dead:
|
||||
return
|
||||
|
||||
is_dead = true
|
||||
died.emit(killer_id)
|
||||
rpc("sync_death", killer_id)
|
||||
|
||||
if can_respawn and multiplayer.is_server():
|
||||
await get_tree().create_timer(respawn_delay).timeout
|
||||
_respawn()
|
||||
|
||||
## Sync death state to all clients
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func sync_death(killer_id: int):
|
||||
is_dead = true
|
||||
died.emit(killer_id)
|
||||
# Subclasses should override to add visual effects, disable collision, etc.
|
||||
|
||||
## Respawn the unit
|
||||
func _respawn():
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
|
||||
is_dead = false
|
||||
current_health = max_health
|
||||
global_position = _respawn_point
|
||||
velocity = Vector3.ZERO
|
||||
|
||||
rpc("sync_respawn", _respawn_point)
|
||||
respawned.emit()
|
||||
|
||||
## Sync respawn to all clients
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func sync_respawn(spawn_pos: Vector3):
|
||||
is_dead = false
|
||||
current_health = max_health
|
||||
global_position = spawn_pos
|
||||
velocity = Vector3.ZERO
|
||||
respawned.emit()
|
||||
|
||||
## Set the respawn point
|
||||
func set_respawn_point(point: Vector3):
|
||||
_respawn_point = point
|
||||
|
||||
## Get health percentage (0.0 to 1.0)
|
||||
func get_health_percent() -> float:
|
||||
return current_health / max_health if max_health > 0 else 0.0
|
||||
Reference in New Issue
Block a user