187 lines
6.9 KiB
GDScript
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) |