## DamageProcessor — Apply hit results and track kills. ## ## Receives hit results from WeaponServer (via LagCompensation), applies ## damage to the target entity, and emits events for scoreboard/UI updates. ## ## Usage (add as child of GameServer or combat manager): ## ## var dp = DamageProcessor.new() ## add_child(dp) ## dp.register_player(entity_id, 100.0) ## dp.process_hit(victim_id, shooter_id, 30.0) ## class_name DamageProcessor extends Node # --------------------------------------------------------------------------- # Signals # --------------------------------------------------------------------------- ## Emitted when a player takes damage. ## victim_id: entity_id of the damaged player ## shooter_id: entity_id of the player who dealt the damage (-1 if world/fall) ## damage: raw damage amount before any modifiers ## killed: true if this damage reduced health to 0 signal player_damaged(victim_id: int, shooter_id: int, damage: float, killed: bool) ## Emitted when a player dies from damage. ## victim_id: entity_id of the killed player ## shooter_id: entity_id of the killer (-1 if world/fall) signal player_killed(victim_id: int, shooter_id: int) # --------------------------------------------------------------------------- # State # --------------------------------------------------------------------------- ## Current health per entity_id. var _health: Dictionary = {} # entity_id (int) → current_health (float) ## Maximum health per entity_id (for respawn reset). var _max_health: Dictionary = {} # entity_id (int) → max_health (float) ## Kill count per shooter entity_id. var _kills: Dictionary = {} # shooter_id (int) → kill_count (int) # --------------------------------------------------------------------------- # Public API # --------------------------------------------------------------------------- ## Register a player for damage tracking with the given maximum health. func register_player(entity_id: int, max_health: float = 100.0) -> void: _health[entity_id] = max_health _max_health[entity_id] = max_health # Preserve existing kill count if re-registering ## Remove a player from damage tracking (on disconnect / respawn). func unregister_player(entity_id: int) -> void: _health.erase(entity_id) _max_health.erase(entity_id) _kills.erase(entity_id) ## Reset a player's health to their maximum (on respawn). ## Does NOT reset kill count. func reset_health(entity_id: int) -> void: if _max_health.has(entity_id): _health[entity_id] = _max_health[entity_id] ## Process a hit result: apply damage, check for kill, emit signals. ## ## hit_result format (from WeaponServer.fire()): ## {hit: bool, position: Vector3, target_id: int, ## damage: float, weapon_id: String} ## ## shooter_id is the entity_id of the player who fired. func process_hit(hit_result: Dictionary, shooter_id: int) -> void: if not hit_result.get("hit", false): return var victim_id: int = hit_result.get("target_id", -1) var damage: float = hit_result.get("damage", 0.0) if victim_id < 0 or damage <= 0.0: return # Apply damage var current: float = _health.get(victim_id, 100.0) current -= damage var killed: bool = false if current <= 0.0: current = 0.0 killed = true # Track kill for the shooter if shooter_id >= 0: _kills[shooter_id] = _kills.get(shooter_id, 0) + 1 player_killed.emit(victim_id, shooter_id) _health[victim_id] = max(current, 0.0) # Emit damage event (always, even on kill) player_damaged.emit(victim_id, shooter_id, damage, killed) ## Convenience: process a hit result with distance-based damage falloff. ## hit_result must contain a "position" key. damage_falloff_start is the ## distance in Godot units beyond which damage begins to decrease. ## damage_falloff_min is the minimum damage multiplier (clamped 0.0–1.0). func process_hit_with_falloff( hit_result: Dictionary, shooter_id: int, origin: Vector3, falloff_start: float = 100.0, falloff_min: float = 0.5 ) -> void: if not hit_result.get("hit", false): return var damage: float = hit_result.get("damage", 0.0) var hit_pos: Vector3 = hit_result.get("position", Vector3.ZERO) var distance: float = origin.distance_to(hit_pos) if distance > falloff_start: # Linear falloff from falloff_start to some max range (2x falloff_start) var max_range: float = falloff_start * 2.0 var t: float = clamp( (distance - falloff_start) / (max_range - falloff_start), 0.0, 1.0 ) var multiplier: float = 1.0 - (1.0 - falloff_min) * t damage *= multiplier var modified_result: Dictionary = hit_result.duplicate() modified_result["damage"] = damage process_hit(modified_result, shooter_id) # --------------------------------------------------------------------------- # Query # --------------------------------------------------------------------------- ## Get current health for an entity, or -1 if not registered. func get_health(entity_id: int) -> float: return _health.get(entity_id, -1.0) ## Get kill count for a shooter entity_id. func get_kills(player_id: int) -> int: return _kills.get(player_id, 0) ## Get all kills (shooter_id → kill_count). func get_all_kills() -> Dictionary: return _kills.duplicate() ## Reset all state (for round restart). func reset_all() -> void: _health.clear() _max_health.clear() _kills.clear()