Files
spellforge/scripts/domain/player_manager.gd
T
joywayerandClaude Opus 5 6f11f4c402 fix(attr): 修复调试场景因 cpu_limit 基准改为 0 而退化;记录 cast_delay_mod 快照时序
combat_test 绕过 CombatManager 直接 equip_wand,拿不到 _rebuild_wand 注入的
"core" 份额,导致 cpu_limit=0 触发守卫报错、MAX_OPS 由 200 退化为 40。
镜像 _rebuild_wand 的做法在该场景自行注入(先 remove 再 add),仅修本次弄坏
的部分;该场景整体绕过 CombatManager(如从不设 mana_leech)属既有分歧,不在
本次范围内。

_cast_interval 仅在 equip_wand 时快照,install_spell 只调 _rebuild_wand 不调
equip_wand,实际再同步点是每波开战的 _transition_to_battle。行为不改,加注释
记下时序,免得后续属性商店的实现者误以为购买即时生效。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 11:38:08 +08:00

127 lines
3.8 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## PlayerManager — 玩家管理 Autoload
## S0WASD 移动 + 位置查询
## S1:自动施法
## S4:停步蓄力 / 冲刺边沿检测(P6-N69、P6-N36
extends Node
const MOVE_THRESHOLD_NORM: float = 0.1
const CHARGE_TRIGGER_TIME: float = 1.5
var _position: Vector2 = Vector2.ZERO
var _velocity: Vector2 = Vector2.ZERO
var _player_node: Node2D = null
var _equipped_core: CoreDefinition = null
var _equipped_compiled: CompiledDeck = null
var _cast_timer: float = 0.0
var _cast_interval: float = 0.5 # equip_wand 时快照 core.cast_interval × cast_delay_mod
# 中途改 cast_delay_mod 要到下次 equip_wand(每波开战 _transition_to_battle)才生效
var _is_moving: bool = false
var _stationary_time: float = 0.0
var _charge_triggered: bool = false
var _last_dash_time: float = -999.0
var kill_count: int = 0
func _ready() -> void:
EventBus.subscribe(EventID.ENEMY_KILLED, _on_enemy_killed)
func _physics_process(delta: float) -> void:
PlayerStats.regen_mana(delta)
PlayerStats.mana_heat = maxf(0.0, PlayerStats.mana_heat - SettingsManager.mana_heat_decay() * delta)
_handle_movement(delta)
_handle_charge_state(delta)
_handle_auto_cast(delta)
func _handle_movement(delta: float) -> void:
var dir := Vector2(
Input.get_axis("ui_left", "ui_right"),
Input.get_axis("ui_up", "ui_down")
)
_is_moving = dir.length() > MOVE_THRESHOLD_NORM
if _is_moving:
dir = dir.normalized()
_velocity = dir * PlayerStats.move_speed
else:
_velocity = Vector2.ZERO
_position += _velocity * delta
if _player_node:
_player_node.global_position = _position
func _handle_charge_state(delta: float) -> void:
if Input.is_action_just_pressed("ui_accept"):
_last_dash_time = Time.get_ticks_msec() / 1000.0
EventBus.emit(EventID.DASH_TRIGGERED, {
"caster_id": 0,
"stationary_time": _stationary_time,
})
_stationary_time = 0.0
_charge_triggered = false
return
if _is_moving:
if _stationary_time > 0.0:
_stationary_time = 0.0
_charge_triggered = false
EventBus.emit(EventID.CHARGE_STATE_CHANGED, {"caster_id": 0, "progress": 0.0})
else:
_stationary_time += delta
if _stationary_time >= 0.3:
var prog: float = clampf(_stationary_time / CHARGE_TRIGGER_TIME, 0.0, 1.0)
EventBus.emit(EventID.CHARGE_STATE_CHANGED, {"caster_id": 0, "progress": prog})
if _stationary_time >= CHARGE_TRIGGER_TIME and not _charge_triggered:
_charge_triggered = true
EventBus.emit(EventID.CHARGE_FIRED, {"caster_id": 0})
func _handle_auto_cast(delta: float) -> void:
if _equipped_compiled == null or _equipped_compiled.is_empty():
return
_cast_timer -= delta
if _cast_timer <= 0.0:
_cast_timer = _cast_interval
SpellEvaluator.execute_compiled(_equipped_compiled, 0, _position)
func equip_wand(core: CoreDefinition, compiled: CompiledDeck) -> void:
_equipped_core = core
_equipped_compiled = compiled
_cast_interval = (core.cast_interval if core else 0.5) * PlayerStats.cast_delay_mod
_cast_timer = 0.0
if core:
PlayerStats.set_mana_pool(core.base_mana_max, core.base_mana_regen)
else:
PlayerStats.set_mana_pool(100.0, 5.0)
PlayerStats.mana_heat = 0.0
func get_position() -> Vector2:
return _position
func get_velocity() -> Vector2:
return _velocity
func get_stationary_time() -> float:
return _stationary_time
func is_charge_triggered() -> bool:
return _charge_triggered
func set_player_node(node: Node2D) -> void:
_player_node = node
if node:
_position = node.global_position
func _on_enemy_killed(_payload: Dictionary) -> void:
kill_count += 1
func reset() -> void:
_position = Vector2.ZERO
_velocity = Vector2.ZERO
_player_node = null
_equipped_compiled = null
_equipped_core = null
_cast_timer = 0.0
_is_moving = false
_stationary_time = 0.0
_charge_triggered = false
kill_count = 0