feat(mana): _charge_mana 统一门控(热值乘算+infinite扣血)+heavy_cost+HP结算

This commit is contained in:
2026-07-21 15:15:07 +08:00
parent c9f560c757
commit 6b60f6e073
+36 -14
View File
@@ -323,6 +323,10 @@ func execute_compiled(compiled: CompiledDeck, caster_id: int, spawn_pos: Vector2
return return
var persistent: bool = compiled.feature_tags == CoreFeatureTag.PERSISTENT_MEMORY var persistent: bool = compiled.feature_tags == CoreFeatureTag.PERSISTENT_MEMORY
var ctx: SpellContext = _acquire_ctx(caster_id, persistent) var ctx: SpellContext = _acquire_ctx(caster_id, persistent)
ctx.hp_cost_accum = 0.0
var infinite: bool = compiled.feature_tags == CoreFeatureTag.INFINITE_SPELLS
var heat_mult: float = 1.0 + PlayerStats.mana_heat
var cast_fired: bool = false
var deck: SpellDeck = compiled.make_runtime_deck() var deck: SpellDeck = compiled.make_runtime_deck()
var ops_count: int = 0 var ops_count: int = 0
var max_ops: int = MAX_OPS_PER_CPU * 5 var max_ops: int = MAX_OPS_PER_CPU * 5
@@ -331,8 +335,10 @@ func execute_compiled(compiled: CompiledDeck, caster_id: int, spawn_pos: Vector2
ops_count += 1 ops_count += 1
var node: SpellNode = deck.pop() var node: SpellNode = deck.pop()
var mana_cost: float = float(node.meta.get("mana_cost", 0.0)) var mana_cost: float = float(node.meta.get("mana_cost", 0.0))
if mana_cost > 0.0 and not PlayerStats.spend_mana(mana_cost): if not _charge_mana(mana_cost, ctx, infinite, heat_mult):
break # 蓝不足 → 停止本次施法(此节点不执行、剩余跳过) break # 蓝不足且非 infinite → 停施
if mana_cost > 0.0:
cast_fired = true
match node.type: match node.type:
SpellNode.SpellType.ACTION: SpellNode.SpellType.ACTION:
_push_projectile(node, ctx, spawn_pos, -1) _push_projectile(node, ctx, spawn_pos, -1)
@@ -341,7 +347,6 @@ func execute_compiled(compiled: CompiledDeck, caster_id: int, spawn_pos: Vector2
break break
SpellNode.SpellType.MODIFIER: SpellNode.SpellType.MODIFIER:
_apply_modifier(node, ctx) _apply_modifier(node, ctx)
# multicast_count 已在 _apply_modifier 内更新
actions_remaining = 1 + ctx.stats.multicast_count actions_remaining = 1 + ctx.stats.multicast_count
SpellNode.SpellType.TRIGGER: SpellNode.SpellType.TRIGGER:
_push_trigger(node, ctx, spawn_pos) _push_trigger(node, ctx, spawn_pos)
@@ -350,14 +355,17 @@ func execute_compiled(compiled: CompiledDeck, caster_id: int, spawn_pos: Vector2
break break
SpellNode.SpellType.LOGIC: SpellNode.SpellType.LOGIC:
if node.meta.has("fork_branch_ids"): if node.meta.has("fork_branch_ids"):
# CIRCUIT 分叉:各分支在施法点立即并行执行
for bid in node.meta["fork_branch_ids"]: for bid in node.meta["fork_branch_ids"]:
_run_branch_payload(int(bid), ctx, spawn_pos) _run_branch_payload(int(bid), ctx, spawn_pos, infinite, heat_mult)
elif String(node.meta.get("logic_op", "")) == "loop": elif String(node.meta.get("logic_op", "")) == "loop":
ops_count = _run_logic_loop(node, ctx, spawn_pos, deck, ops_count, max_ops) ops_count = _run_logic_loop(node, ctx, spawn_pos, deck, ops_count, max_ops, infinite, heat_mult)
break # LOOP 已消耗剩余节点 break # LOOP 已消耗剩余节点
elif _eval_logic(node, ctx, spawn_pos) == _LOGIC_SKIP_REST: elif _eval_logic(node, ctx, spawn_pos) == _LOGIC_SKIP_REST:
break # 条件不成立 → 跳过后续法术 break
if ctx.hp_cost_accum > 0.0:
PlayerStats.spend_hp_cost(minf(ctx.hp_cost_accum, PlayerStats.hp_max * SettingsManager.hp_cost_cap_ratio()))
if cast_fired:
PlayerStats.mana_heat = minf(SettingsManager.mana_heat_max(), PlayerStats.mana_heat + SettingsManager.mana_heat_per_cast())
if ops_count >= max_ops: if ops_count >= max_ops:
push_warning("SpellEvaluator: MAX_OPS reached (caster=%d)" % caster_id) push_warning("SpellEvaluator: MAX_OPS reached (caster=%d)" % caster_id)
_release_ctx(ctx, persistent) _release_ctx(ctx, persistent)
@@ -487,6 +495,18 @@ func _push_trigger(node: SpellNode, ctx: SpellContext, spawn_pos: Vector2, trigg
0, 0.0, cold 0, 0.0, cold
) )
## 统一扣蓝:base_cost>0 时按热值乘算扣蓝;infinite Core 蓝空转累加 HP 代价并继续
## 返回 true=可执行该节点;false=蓝不足且非 infinite → 停施
func _charge_mana(base_cost: float, ctx: SpellContext, infinite: bool, heat_mult: float) -> bool:
if base_cost <= 0.0:
return true
if PlayerStats.spend_mana(base_cost * heat_mult):
return true
if infinite:
ctx.hp_cost_accum += SettingsManager.infinite_hp_per_shot()
return true
return false
## MODIFIER 分支 ## MODIFIER 分支
func _apply_modifier(node: SpellNode, ctx: SpellContext) -> void: func _apply_modifier(node: SpellNode, ctx: SpellContext) -> void:
var meta: Dictionary = node.meta var meta: Dictionary = node.meta
@@ -504,6 +524,8 @@ func _apply_modifier(node: SpellNode, ctx: SpellContext) -> void:
ctx.stats.multicast_count += int(meta["multicast"]) ctx.stats.multicast_count += int(meta["multicast"])
if meta.has("pierce"): if meta.has("pierce"):
ctx.stats.pierce_add += int(meta["pierce"]) ctx.stats.pierce_add += int(meta["pierce"])
if meta.has("heavy_cost"):
ctx.hp_cost_accum += float(meta["heavy_cost"])
## ── 上下文获取(区分跨帧持久 / 池化)───────────────────────── ## ── 上下文获取(区分跨帧持久 / 池化)─────────────────────────
func _acquire_ctx(caster_id: int, persistent: bool) -> SpellContext: func _acquire_ctx(caster_id: int, persistent: bool) -> SpellContext:
@@ -553,7 +575,7 @@ func _eval_logic(node: SpellNode, ctx: SpellContext, spawn_pos: Vector2) -> int:
## 简化实现:LOOP 体内不再处理嵌套 LOGIC(顺延后续迭代);尊重 ops 预算 ## 简化实现:LOOP 体内不再处理嵌套 LOGIC(顺延后续迭代);尊重 ops 预算
## 返回更新后的 ops_count ## 返回更新后的 ops_count
func _run_logic_loop(node: SpellNode, ctx: SpellContext, spawn_pos: Vector2, func _run_logic_loop(node: SpellNode, ctx: SpellContext, spawn_pos: Vector2,
deck: SpellDeck, ops_count: int, max_ops: int) -> int: deck: SpellDeck, ops_count: int, max_ops: int, infinite: bool, heat_mult: float) -> int:
var count: int = clampi(int(node.meta.get("count", 2)), 1, 16) var count: int = clampi(int(node.meta.get("count", 2)), 1, 16)
var body: Array = [] var body: Array = []
while deck.has_next(): while deck.has_next():
@@ -564,8 +586,8 @@ func _run_logic_loop(node: SpellNode, ctx: SpellContext, spawn_pos: Vector2,
if ops_count >= max_ops: if ops_count >= max_ops:
return ops_count return ops_count
var n_cost: float = float(n.meta.get("mana_cost", 0.0)) var n_cost: float = float(n.meta.get("mana_cost", 0.0))
if n_cost > 0.0 and not PlayerStats.spend_mana(n_cost): if not _charge_mana(n_cost, ctx, infinite, heat_mult):
return ops_count # 蓝不足 → 停止整个 LOOP return ops_count
match n.type: match n.type:
SpellNode.SpellType.ACTION: SpellNode.SpellType.ACTION:
_push_projectile(n, ctx, spawn_pos, -1) _push_projectile(n, ctx, spawn_pos, -1)
@@ -579,7 +601,7 @@ func _run_logic_loop(node: SpellNode, ctx: SpellContext, spawn_pos: Vector2,
## ── CIRCUIT 分支执行(LOGIC_FORK 触发)────────────────────── ## ── CIRCUIT 分支执行(LOGIC_FORK 触发)──────────────────────
## 在施法点立即执行某分支 SubPayload 的节点;分支内 MODIFIER 作用域隔离(不泄漏给兄弟分支/主链) ## 在施法点立即执行某分支 SubPayload 的节点;分支内 MODIFIER 作用域隔离(不泄漏给兄弟分支/主链)
## 嵌套 LOGIC_FORK 递归展开(P6-N66 ## 嵌套 LOGIC_FORK 递归展开(P6-N66
func _run_branch_payload(payload_id: int, ctx: SpellContext, spawn_pos: Vector2) -> void: func _run_branch_payload(payload_id: int, ctx: SpellContext, spawn_pos: Vector2, infinite: bool, heat_mult: float) -> void:
var nodes: Array = SubPayloadRegistry.get_nodes(payload_id) var nodes: Array = SubPayloadRegistry.get_nodes(payload_id)
if nodes.is_empty(): if nodes.is_empty():
return return
@@ -595,8 +617,8 @@ func _run_branch_payload(payload_id: int, ctx: SpellContext, spawn_pos: Vector2)
var b_crit: float = s.crit_chance var b_crit: float = s.crit_chance
for node in nodes: for node in nodes:
var node_cost: float = float(node.meta.get("mana_cost", 0.0)) var node_cost: float = float(node.meta.get("mana_cost", 0.0))
if node_cost > 0.0 and not PlayerStats.spend_mana(node_cost): if not _charge_mana(node_cost, ctx, infinite, heat_mult):
break # 蓝不足 → 停止本分支 break
match node.type: match node.type:
SpellNode.SpellType.ACTION: SpellNode.SpellType.ACTION:
_push_projectile(node, ctx, spawn_pos, -1) _push_projectile(node, ctx, spawn_pos, -1)
@@ -607,7 +629,7 @@ func _run_branch_payload(payload_id: int, ctx: SpellContext, spawn_pos: Vector2)
SpellNode.SpellType.LOGIC: SpellNode.SpellType.LOGIC:
if node.meta.has("fork_branch_ids"): if node.meta.has("fork_branch_ids"):
for bid in node.meta["fork_branch_ids"]: for bid in node.meta["fork_branch_ids"]:
_run_branch_payload(int(bid), ctx, spawn_pos) _run_branch_payload(int(bid), ctx, spawn_pos, infinite, heat_mult)
elif _eval_logic(node, ctx, spawn_pos) == _LOGIC_SKIP_REST: elif _eval_logic(node, ctx, spawn_pos) == _LOGIC_SKIP_REST:
break break
# 还原 stats # 还原 stats