Files
spellforge/scripts/autoloads/status_manager.gd
T
2026-07-20 10:56:52 +08:00

123 lines
4.2 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.
## StatusManager — 状态效果管理(Autoload: StatusManager
## 权威来源:combat_mechanics_depth.md §3.2.5、implementation_plan.md §2.4
extends Node
const STATUS_BATCH_LIMIT: int = 500
var _active_statuses: Array = [] # Array[StatusInstance]
var _physics_frame: int = 0
func _physics_process(delta: float) -> void:
_physics_frame += 1
var use_batch: bool = _active_statuses.size() > STATUS_BATCH_LIMIT
var batch_parity: int = _physics_frame % 2
var i: int = _active_statuses.size() - 1
while i >= 0:
var inst: StatusInstance = _active_statuses[i]
if use_batch and (inst.entity_id % 2) != batch_parity:
i -= 1
continue
inst.remaining_duration -= delta
inst.tick_accumulator += delta
if inst.type_def and not inst.type_def.is_combo_tracker and inst.type_def.dot_damage_per_tick > 0.0:
var interval: float = max(0.001, inst.type_def.tick_interval)
while inst.tick_accumulator >= interval:
inst.tick_accumulator -= interval
_apply_dot_tick(inst)
if inst.remaining_duration <= 0.0:
_active_statuses[i] = _active_statuses[_active_statuses.size() - 1]
_active_statuses.pop_back()
i -= 1
## 施加状态;duration < 0 时使用 StatusTypeDef 默认 duration
func apply(entity_id: int, status_type_id: int, stacks: int = 1, duration: float = -1.0, owner_id: int = -1) -> void:
var typedef: StatusTypeDef = StatusRegistry.get_type(status_type_id)
if typedef == null:
push_warning("StatusManager.apply: 未知 status_id=%d" % status_type_id)
return
var dur: float = typedef.duration if duration < 0.0 else duration
var existing_idx: int = _find(entity_id, status_type_id)
if existing_idx >= 0:
var inst: StatusInstance = _active_statuses[existing_idx]
match typedef.stack_mode:
StatusTypeDef.StackMode.REFRESH:
inst.remaining_duration = dur
inst.stacks = max(inst.stacks, stacks)
StatusTypeDef.StackMode.INTENSITY:
var cap: int = typedef.max_stacks if typedef.max_stacks > 0 else 9999
inst.stacks = min(inst.stacks + stacks, cap)
inst.remaining_duration = dur
StatusTypeDef.StackMode.INDEPENDENT:
pass # 独立实例:追加新条目
if typedef.stack_mode != StatusTypeDef.StackMode.INDEPENDENT:
EventBus.emit(EventID.STATUS_APPLIED, {
"target_id": entity_id, "status_type": status_type_id, "stacks": inst.stacks
})
return
var inst_new := StatusInstance.new()
inst_new.setup(entity_id, typedef, stacks, dur, owner_id)
_active_statuses.append(inst_new)
EventBus.emit(EventID.STATUS_APPLIED, {
"target_id": entity_id, "status_type": status_type_id, "stacks": inst_new.stacks
})
func get_stacks(entity_id: int, status_type_id: int) -> int:
var idx: int = _find(entity_id, status_type_id)
if idx < 0:
return 0
return _active_statuses[idx].stacks
func has_status(entity_id: int, status_type_id: int) -> bool:
return _find(entity_id, status_type_id) >= 0
func remove_all_for_entity(entity_id: int) -> void:
var i: int = _active_statuses.size() - 1
while i >= 0:
if _active_statuses[i].entity_id == entity_id:
_active_statuses[i] = _active_statuses[_active_statuses.size() - 1]
_active_statuses.pop_back()
i -= 1
func remove_source(owner_id: int) -> void:
var i: int = _active_statuses.size() - 1
while i >= 0:
if _active_statuses[i].owner_id == owner_id:
_active_statuses[i] = _active_statuses[_active_statuses.size() - 1]
_active_statuses.pop_back()
i -= 1
func get_active_count() -> int:
return _active_statuses.size()
func reset() -> void:
_active_statuses.clear()
_physics_frame = 0
func _find(entity_id: int, status_type_id: int) -> int:
for i in _active_statuses.size():
var inst: StatusInstance = _active_statuses[i]
if inst.entity_id == entity_id and inst.status_type_id == status_type_id:
return i
return -1
func _apply_dot_tick(inst: StatusInstance) -> void:
if not EnemyManager.has_entity(inst.entity_id):
return
var dmg: float = inst.type_def.dot_damage_per_tick * float(inst.stacks)
if dmg <= 0.0:
return
EnemyManager.apply_damage(inst.entity_id, dmg, inst.owner_id, false)
func _swap_and_pop(idx: int) -> void:
var last: int = _active_statuses.size() - 1
if idx != last:
_active_statuses[idx] = _active_statuses[last]
_active_statuses.pop_back()