Files
Station-0/scripts/player/player_stats.gd
T
2026-03-11 15:47:12 -04:00

187 lines
6.9 KiB
GDScript

extends Node
# ---------------------------------------------------------------------------
# Base stats — no upgrades equipped
# ---------------------------------------------------------------------------
const BASE_SPEED := 200.0
const BASE_MAX_HEALTH := 6.0 # 6 HP = 3 full hearts
const BASE_DAMAGE := 3.5
const BASE_FIRE_RATE := 3.5 # shots per second
const BASE_RANGE := 400.0
const BASE_PROJ_SPEED := 520.0
# ---------------------------------------------------------------------------
# Stat caching — only recalculate when items/parts change
# ---------------------------------------------------------------------------
var _stats_dirty: bool = true
var _cached_item_count: int = -1 # Track item count to detect external changes
# ---------------------------------------------------------------------------
# Live stats (recalculated from base + equipped parts + run items)
# ---------------------------------------------------------------------------
var speed: float = BASE_SPEED
var max_health: float = BASE_MAX_HEALTH
var current_health: float = BASE_MAX_HEALTH
var damage: float = BASE_DAMAGE
var fire_rate: float = BASE_FIRE_RATE
var range_: float = BASE_RANGE
var proj_speed: float = BASE_PROJ_SPEED
# ---------------------------------------------------------------------------
# Run-item behavior flags (recomputed each recalculate — never saved)
# ---------------------------------------------------------------------------
var ricochet: bool = false
var scatter_count: int = 1
var scatter_spread_deg: float = 0.0
var explosive: bool = false
var explosion_radius: float = 0.0
var explosion_damage: float = 0.0
# New item flags
var coolant_leak: bool = false
var scrap_magnet: bool = false
var memory_spike: bool = false
var rust_coat: bool = false
var static_discharge: bool = false
var fragmented_map: bool = false
var overclock: bool = false
var damage_reduction: float = 0.0
var discharge_radius: float = 64.0
var discharge_damage: float = 1.0
var magnet_radius: float = 0.0
# ---------------------------------------------------------------------------
# Body-part behavior flags (recomputed each recalculate)
# ---------------------------------------------------------------------------
var shield_projector: bool = false
func _ready() -> void:
EventBus.body_part_equipped.connect(_on_part_equipped)
EventBus.save_loaded.connect(_on_save_loaded)
EventBus.item_collected.connect(_on_item_collected)
recalculate()
if RunManager.player_health_carry > 0.0:
current_health = minf(RunManager.player_health_carry, max_health)
RunManager.player_health_carry = -1.0
else:
current_health = max_health # fresh run — start at full HP
func _on_save_loaded() -> void:
var old_max := max_health
_stats_dirty = true
recalculate()
# Grant bonus HP from body parts on fresh start
if RunManager.player_health_carry < 0.0 and max_health > old_max:
current_health += (max_health - old_max)
current_health = minf(current_health, max_health)
EventBus.player_health_changed.emit(current_health, max_health)
func _on_item_collected(_item: Resource) -> void:
_stats_dirty = true
recalculate()
EventBus.player_health_changed.emit(current_health, max_health)
func mark_dirty() -> void:
_stats_dirty = true
func recalculate() -> void:
# Skip if stats haven't changed (cached)
var current_item_count := RunManager.run_items.size()
if not _stats_dirty and _cached_item_count == current_item_count:
return
_cached_item_count = current_item_count
_stats_dirty = false
speed = BASE_SPEED + UpgradeManager.get_stat_modifier("speed")
max_health = BASE_MAX_HEALTH + UpgradeManager.get_stat_modifier("max_health")
damage = BASE_DAMAGE + UpgradeManager.get_stat_modifier("damage")
fire_rate = BASE_FIRE_RATE + UpgradeManager.get_stat_modifier("fire_rate")
range_ = BASE_RANGE + UpgradeManager.get_stat_modifier("range")
proj_speed = BASE_PROJ_SPEED + UpgradeManager.get_stat_modifier("proj_speed")
_apply_run_items()
_apply_part_flags()
current_health = minf(current_health, max_health)
func _apply_run_items() -> void:
ricochet = false
scatter_count = 1
scatter_spread_deg = 0.0
explosive = false
explosion_radius = 0.0
explosion_damage = 0.0
coolant_leak = false
scrap_magnet = false
memory_spike = false
rust_coat = false
static_discharge = false
fragmented_map = false
overclock = false
damage_reduction = 0.0
discharge_radius = 64.0
discharge_damage = 1.0
magnet_radius = 0.0
for item: RunItem in RunManager.run_items:
match item.effect:
RunItem.Effect.STAT_MOD:
match item.stat_key:
"speed": speed += item.stat_delta
"max_health": max_health += item.stat_delta
"damage": damage += item.stat_delta
"fire_rate": fire_rate += item.stat_delta
"range": range_ += item.stat_delta
RunItem.Effect.RICOCHET:
ricochet = true
RunItem.Effect.SCATTER:
scatter_count = maxi(scatter_count, item.scatter_count)
scatter_spread_deg = maxf(scatter_spread_deg, item.scatter_spread_deg)
RunItem.Effect.EXPLOSIVE:
explosive = true
explosion_radius = maxf(explosion_radius, item.explosion_radius)
explosion_damage = maxf(explosion_damage, item.explosion_damage)
RunItem.Effect.COOLANT_LEAK:
coolant_leak = true
RunItem.Effect.SCRAP_MAGNET:
scrap_magnet = true
magnet_radius = maxf(magnet_radius, item.magnet_radius)
RunItem.Effect.MEMORY_SPIKE:
memory_spike = true
RunItem.Effect.RUST_COAT:
rust_coat = true
damage_reduction = maxf(damage_reduction, item.damage_reduction)
RunItem.Effect.STATIC_DISCHARGE:
static_discharge = true
discharge_radius = maxf(discharge_radius, item.discharge_radius)
discharge_damage = maxf(discharge_damage, item.discharge_damage)
RunItem.Effect.FRAGMENTED_MAP:
fragmented_map = true
RunItem.Effect.OVERCLOCK:
overclock = true
fire_rate *= 1.4
func _apply_part_flags() -> void:
shield_projector = false
# Scatter from body parts (accumulate with run item scatter)
for slot in UpgradeManager.SLOTS:
var part: BodyPartData = UpgradeManager.get_equipped_part(slot) as BodyPartData
if part == null:
continue
if part.shield_projector:
shield_projector = true
if part.mod_scatter_count > 0:
scatter_count = maxi(scatter_count, part.mod_scatter_count)
scatter_spread_deg = maxf(scatter_spread_deg, part.mod_scatter_spread_deg)
func heal(amount: float) -> void:
current_health = minf(current_health + amount, max_health)
func _on_part_equipped(_part: Resource, _slot: String) -> void:
var old_max := max_health
_stats_dirty = true
recalculate()
# If max_health increased, grant the extra HP immediately
if max_health > old_max:
current_health += (max_health - old_max)
# Update UI
EventBus.player_health_changed.emit(current_health, max_health)