52 lines
1.5 KiB
GDScript
52 lines
1.5 KiB
GDScript
extends Area3D
|
|
class_name HealthOrb
|
|
|
|
## Floating pickup that restores health when a player walks into it.
|
|
## Server-authoritative pickup; the bob/spin visuals run locally on every peer.
|
|
|
|
@export var heal_amount: float = 25.0
|
|
@export var bob_height: float = 0.3
|
|
@export var bob_speed: float = 2.0
|
|
@export var spin_speed: float = 2.0
|
|
|
|
var orb_id: int = -1 # Assigned by Level when spawned
|
|
var _base_y: float = 0.0
|
|
var _time: float = 0.0
|
|
var _collected: bool = false
|
|
@onready var _mesh: Node3D = get_node_or_null("Mesh")
|
|
|
|
func _ready():
|
|
_base_y = position.y
|
|
_time = randf() * TAU # Random phase so multiple orbs don't bob in unison
|
|
|
|
# Detect players (physics layer 1) without colliding with anything
|
|
collision_layer = 0
|
|
collision_mask = 1
|
|
|
|
# Only the server resolves pickups
|
|
if multiplayer.is_server():
|
|
body_entered.connect(_on_body_entered)
|
|
|
|
func _process(delta):
|
|
_time += delta
|
|
position.y = _base_y + sin(_time * bob_speed) * bob_height
|
|
if _mesh:
|
|
_mesh.rotate_y(spin_speed * delta)
|
|
|
|
func _on_body_entered(body: Node3D):
|
|
if _collected or not multiplayer.is_server():
|
|
return
|
|
if not (body is Character) or body.is_dead:
|
|
return
|
|
# Leave the orb in place if the player is already at full health
|
|
if body.current_health >= body.max_health:
|
|
return
|
|
|
|
_collected = true
|
|
body.heal(heal_amount)
|
|
|
|
# Remove the orb from every client via the level's centralized system
|
|
var level = get_tree().get_current_scene()
|
|
if level and level.has_method("remove_health_orb"):
|
|
level.remove_health_orb(orb_id)
|