Files
gplan-2/game/core/battle_fx_host.gd
2026-11-04 05:08:31 +08:00

245 lines
14 KiB
GDScript
Raw Permalink 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.
## 效果引擎宿主件(自 battle_orchestrator 收编 fx 族):事件发射面(fx_emit——18 发射点统一入口)、
## 补充事实判定(biz_fact 闭面四键)、主角 AC 三值累积与同步(本轮/本战累积器+
## 基线快照)、bonus_delta 四轴累积(战末宿主 drain 汇入账本)、揭示待兑队列与兑付(含再入闸——
## 同步信号回环防二次缓冲二次兑付)、round/battle 桶计数器持有(桶随编排器生灭语义不变)、
## round_start 发射与施加、订阅重建、桶挂引用。生命周期=随编排器(编排器 _init 同建同灭,每战新建)。
## 跨件单向接线(🔴 契约红线):行发射与五事实经构造注入的 emit_cb/facts_cb 回调宿主——协议面单点
## 留宿主,禁回调宿主私有族;揭示奖励兑付走 bind_grant 注入的兑付回调(RewardGrant 授予面——
## snapshot 查询与 grant_routed 同面);对本件零反向强引用(禁 RefCounted 双向环——两件均随每战新建)。
## 随机只经注入 rng 的 fx_reveal 封闭流(除该流外零随机;零 await——效果引擎全同步解释器)。
## 类变量封闭面(15≤15):bundle 7 项(db/rng/minesweeper/action_economy/damage/emit_cb/facts_cb,
## 缺一键 assert——引用束注入防 _init max-parameters 新键)+grant_cb+engine+counters_ctx+
## ac 束(键集封闭 base/round/battle)+_fx_bonus_delta+_fx_new_resource_types+reveal 束
## (键集封闭 queue/busy)+facts_inject(键集封闭 dig_streak/rider_tier_bonus/rider_tier_crit——
## 业务发射事实束运行态写面,combat_segment 每轮写 dig_streak/begin_battle 每战复位 rider 两键)。
## 禁实现期增删键;禁为机检文本保留无效成员(原编排器 fx_counters_round/
## battle 两零读者成员迁入即删——同物异名同字典,桶引用经 counters_ctx 承载)。
## 效果端口件组建与转注(轴戊):_init 尾段建 EffectPort(六 Callable 绑本件对应方法)后对两业务类
## bind_port——端口不落本件成员(单向接线,本件零回持);业务类发射面全经端口路由,
## 计数器账面(counters_ctx)唯一宿主侧。
class_name BattleFxHost
extends RefCounted
var db: ConfigDb
var rng: RngServiceCore
var minesweeper: MinesweeperRules
var action_economy: ActionEconomy
var damage: DamageController
var emit_cb: Callable # 宿主 _emit_row——行协议面单点(5 参 head 束形:head/ri/display/value/extra)
var facts_cb: Callable # 宿主 _fx_host_fact——round_index/battle_index/seals_count/nest_count/hp_ratio 五事实宿主侧单点
var grant_cb: Callable = Callable() # 揭示兑付回调=RewardGrant 授予面(bind_grant 注入;构造序解法——本件对授予面零强引用)
var engine: EffectEngine # 效果引擎(原编排器 _fx——宿主 _init 经此建效果端口件转注两业务类 bind_port;reward_economy 经 luck_bias_for 查询口取回偏运合计)
var counters_ctx: EffectContext # 四桶引用束(round/battle 两桶本件自有;adventure/level 桶 begin_battle mount_buckets 挂引用)
var ac := {"base": -1, "round": 0, "battle": 0} # ac 束(base=主角 AC 基线快照 enc.hero_armor_class,-1=表值兜底未解析;round=ac_bonus 本轮累积器〔封印桩,round_start 清零〕;battle=本战累积器〔石肤,cap 封顶,begin_battle 清零〕)
var _fx_bonus_delta := {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0} # 本战 bonus_delta 累积(战末宿主 bonus_delta_drain 读后清零)
var _fx_new_resource_types := {} # 本战已见资源类型集(地质学家 fact new_resource_type 判定——battle 域)
var reveal := {"queue": [], "busy": false} # reveal 束(queue=揭示效果待兑队列〔见血开窍/斩首学者/地质学家/跳格采掘共用——授予段末统一兑付,防轮 facts 迭代中追加重入〕;busy=兑付中标记——宿主 _on_cell_revealed 缓冲旁路判定,防二次缓冲二次兑付)
var facts_inject := {"dig_streak": 0, "rider_tier_bonus": 0, "rider_tier_crit": false} # 业务发射事实束(键集封闭——原 damage.inject 运行态束迁宿主:dig_streak=combat_segment 每轮写;rider 两键=begin_battle 每战复位)
func _init(bundle: Dictionary) -> void:
for k in ["db", "rng", "minesweeper", "action_economy", "damage", "emit_cb", "facts_cb"]:
assert(bundle.has(k), "[效果引擎宿主] 引用束缺键: %s" % k)
db = bundle.db
rng = bundle.rng
minesweeper = bundle.minesweeper
action_economy = bundle.action_economy
damage = bundle.damage
emit_cb = bundle.emit_cb
facts_cb = bundle.facts_cb
engine = EffectEngine.new(db)
counters_ctx = EffectContext.new() # round/battle 两桶即 ctx 自带字典——编排器旧 fx_counters 两成员同物异名零读者,不再另立
# 效果端口件组建与转注(轴戊——组装期单点:先于任何发射,六 Callable 绑本件方法后对两业务类转注;
# 端口不落本件成员——单向接线,本件零回持;方法直引用形态同 bundle emit_cb/facts_cb 既有接线)
var port := EffectPort.new({
"emit_cb": fx_emit_biz,
"counter_read_cb": counter_read,
"counter_add_cb": counter_add,
"fact_cb": biz_fact,
"reveal_cb": enqueue_reveal,
"ac_cb": apply_ac_bonus,
})
bundle.damage.bind_port(port)
bundle.action_economy.bind_port(port)
# 揭示兑付回调挂接(宿主 _init 建两件后接线——构造序禁环)
func bind_grant(cb: Callable) -> void:
grant_cb = cb
# 事件发射薄面(18 发射点统一入口——订阅空=零循环零扰动;确定性同步分发零 RNG)
func fx_emit(event: String, payload: Dictionary) -> Array:
return engine.dispatch(counters_ctx, event, payload, _fx_fact)
# 业务类发射路由(效果端口件 emit_cb——damage/action_economy 发射面统一出口;
# 事实回调=biz_fact 闭面五键,battle_index/round_index/seals_count/nest_count 禁进业务发射面)
func fx_emit_biz(event: String, payload: Dictionary) -> Array:
return engine.dispatch(counters_ctx, event, payload, biz_fact)
# 计数器账面路由(效果端口件 counter_read_cb——value_counter 层值读;账面唯一宿主侧)
func counter_read(counter_id: String) -> int:
return EffectEngine.counter(counters_ctx, counter_id)
# 计数器账面路由(效果端口件 counter_add_cb——施加单点;账面唯一宿主侧)
func counter_add(counter_id: String, value: int, set_form: bool) -> void:
EffectEngine.counter_add(counters_ctx, counter_id, value, set_form)
# 新资源类型首见登记(授予段 dig_reveal 地质学家判定面——返回 true=首见并置位;空串短路零登记)
func mark_new_resource_type(ctype: String) -> bool:
if ctype == "":
return false
if _fx_new_resource_types.has(ctype):
return false
_fx_new_resource_types[ctype] = true
return true
# bonus_delta 轴累积(授予段血税双向施加面——axis 在册四轴才累积,轴外静默)
func add_bonus_delta(axis: String, delta: int) -> void:
if _fx_bonus_delta.has(axis):
_fx_bonus_delta[axis] += delta
# 业务发射事实面(闭面四键——三 when 事实 {dig_streak 轮内槽/hp_ratio 主角
# 血量比例算式逐字承 damage_controller 原式}∪ 两 rider 运行态读数 {facts_inject 现值};
# 其余事实一律 null——battle_index/round_index/seals_count/nest_count 禁进,扩面=行为变化)
func biz_fact(name: String) -> Variant:
match name:
"dig_streak":
return int(facts_inject["dig_streak"])
"hp_ratio":
var hu: Dictionary[Variant, Variant] = damage.units.hero_unit()
return int(round(100.0 * float(int(hu.get("hp", 0))) / float(maxi(1, int(hu.get("max_hp", 1)))))) # 百分比 int(when lte int 截断口径)
"rider_tier_bonus":
return int(facts_inject["rider_tier_bonus"])
"rider_tier_crit":
return bool(facts_inject["rider_tier_crit"])
return null
# 偏运权重查询(reward_roll 发射面——bias_rewards 词汇在本层收口,掷格域只见整数)。
# 三参形 dispatch(不带 facts:本事件订阅行 when 事实面不参与,命中集与缝拆前逐字同);
# 发射时点由掷格件在开流之后、权重装配之前调用;零命中=0=原行集零扰动。
func luck_bias_for(cell_count: int) -> int:
var total := 0
for h in engine.dispatch(counters_ctx, "reward_roll", {"polarity": "positive", "cell_count": cell_count}):
if String(h.get("kind", "")) == "bias_rewards":
total += int(h.get("value", 0))
return total
# round_start 发射+施加(grant_ap mode:queue=当轮入队,行动经济轮切换即兑现;计数器 round_start
# 重置点在 dispatch 内生效。调用点两处:begin_battle 首轮 / 轮始段续轮。payload 携 round〔宿主侧
# 事实单点〕+ap_exhausted_last〔一滴不剩 when 面〕;发射前 ac_bonus 本轮累积器清零并同步主角 AC)
func emit_round_start() -> void:
ac.round = 0
_sync_hero_ac()
for h in fx_emit("round_start", {"round": int(facts_cb.call("round_index")),
"ap_exhausted_last": action_economy.ap_exhausted_last_round()}):
if String(h.get("kind", "")) == "grant_ap" and String(h.get("mode", "")) == "queue":
action_economy.queue_ap_delta(int(h.get("value", 0)))
# ac_bonus 施加(mode=round 本轮累积器〔封印桩,round_start 清零〕/
# mode=battle 本战累积器〔石肤,cap 封顶;缺省 battle〕;cap=0/缺省=无上限)
func apply_ac_bonus(h: Dictionary) -> void:
var mode := String(h.get("mode", "battle"))
var cap := int(h.get("cap", 0))
if mode == "round":
ac.round = int(ac.round) + int(h.get("value", 0))
else:
ac.battle = int(ac.battle) + int(h.get("value", 0)) if cap <= 0 else mini(cap, int(ac.battle) + int(h.get("value", 0)))
_sync_hero_ac()
# 揭示效果入队(fx_reveal 掷格兑付延后到授予段末——防轮 facts 迭代中追加重入;见血开窍/
# 斩首学者〔经伤害控制器端口 reveal_effect 路由〕/地质学家/跳格采掘共用)
func enqueue_reveal() -> void:
reveal.queue.append({"round": int(facts_cb.call("round_index"))})
# 揭示效果兑付(授予段末统一——fx_reveal 封闭流从未揭示安全格掷 1 格全揭示;
# 信息红线=只揭真格〔危险格不入候选〕;空候选=零兑付留归因行且不取号;
# 揭示奖励经兑付回调正向路由直接入账——reveal.busy 旁路宿主 _on_cell_revealed 缓冲防二次兑付)
func flush_pending_reveals(ri: int) -> void:
if (reveal.queue as Array).is_empty():
return
var board: BoardData = minesweeper.current_board()
while not (reveal.queue as Array).is_empty():
(reveal.queue as Array).pop_back()
var cands: Array[Variant] = []
for c in board.all_cells():
if not minesweeper.is_revealed(c) and not board.is_mine(c):
cands.append(c)
if cands.is_empty():
emit_cb.call({"type": "extended", "source_class": "fx_reveal", "origin_cell": Vector2i(-1, -1)}, ri, "揭示落空(无未揭示安全格)", 0, {"effect_id": "fx_reveal"})
continue
var rg: RandomNumberGenerator = rng.open_stream("fx_reveal")
var cell: Vector2i = cands[rg.randi_range(0, cands.size() - 1)]
reveal.busy = true
var ok: bool = minesweeper.fx_reveal(cell)
reveal.busy = false
if not ok:
emit_cb.call({"type": "extended", "source_class": "fx_reveal", "origin_cell": Vector2i(-1, -1)}, ri, "揭示落空(格子不可揭)", 0, {"effect_id": "fx_reveal"})
continue
grant_cb.call(cell, ri)
emit_cb.call({"type": "extended", "source_class": "fx_reveal", "origin_cell": cell, "highlight": "fx_reveal"}, ri, "专长揭示", 0, {"effect_id": "fx_reveal"})
# 效果引擎订阅重建(每战从持有集/装备集重建,中途获得下一战生效;树规则合成订阅同面装载。
# 装载序=feats 表序→treasure 表序→树规则,写死保种子复放。计数器四桶经 counters_ctx 引用宿主容器,无回灌时点)
func rebuild_subscriptions(feats: Array, attunement: Array, tree_rules: Dictionary[Variant, Variant]) -> void:
engine.rebuild(feats, attunement, tree_rules)
# begin_battle 桶挂引用(adventure 桶=账本共享引用跨战持久=同一字典引用,非副本搬运;
# level 非 Dictionary 即不挂——LevelRunner 每关 new 下传)
func mount_buckets(adv_bucket: Dictionary, level_bucket: Variant) -> void:
counters_ctx.adventure_bucket = adv_bucket
if level_bucket is Dictionary:
counters_ctx.level_bucket = level_bucket
# begin_battle 运行态清零(ac 三值/bonus_delta 四轴/new_resource_types/揭示队列+再入闸——
# 原编排器 begin_battle 扩签运行态就位段原序)
func reset_battle_state(ac_base: int) -> void:
ac.base = ac_base
ac.round = 0
ac.battle = 0
_fx_new_resource_types = {}
reveal.queue = []
reveal.busy = false
_fx_bonus_delta = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
# 战末 drain(读后清零;宿主 _battle_over 单消费——读改清三拍合一,时序等价)
func bonus_delta_drain() -> Dictionary:
var out := _fx_bonus_delta
_fx_bonus_delta = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
return out
# 揭示兑付中读投影(宿主 _on_cell_revealed 旁路判定——fx 揭示的奖励由兑付点直接入账,
# 此处缓冲会导致二次兑付)
func reveal_busy() -> bool:
return bool(reveal.busy)
# 补充事实面(fact 取值第二优先源——payload 未携带的运行态读数;
# 全部转宿主 facts_cb——宿主侧单点,未知事实回落 null)
func _fx_fact(fact: String) -> Variant:
return facts_cb.call(fact)
# ac_bonus 累积后同步主角 AC(石肤/封印桩消费面——defender_ac 经 set_hero_ac_override 单点;
# 基线 = enc 口径,-1(表值兜底态)时解析为表值后叠加,守恒口径与 begin_battle 注入一致)
func _sync_hero_ac() -> void:
if int(ac.base) < 0:
ac.base = int(db.combat.hero.armor_class)
damage.set_hero_ac_override(int(ac.base) + int(ac.round) + int(ac.battle))