From dcdde6ad99241235afe508295ac7c6e836e816df Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Tue, 27 Oct 2026 07:31:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=A1=E2=91=A0T4a(=E6=8B=86=E4=BB=B6?= =?UTF-8?q?=E6=89=B9wr5/V10):=20grant=5Fsegment=20run=20=E6=8B=86=E6=89=B9?= =?UTF-8?q?=E7=A7=81=E6=9C=89=E6=97=8F=EF=BC=8B=5Fjudge=5Fseal=20=E4=B8=A4?= =?UTF-8?q?=E5=88=86=EF=BC=8B=E6=96=BD=E5=8A=A0=E9=9D=A2=E5=88=86=E9=9D=A2?= =?UTF-8?q?=E2=80=94=E2=80=94run=20=E7=BC=96=E6=8E=92=E9=AA=A8=E6=9E=B6?= =?UTF-8?q?=E4=BF=9D=E7=95=99(=E6=A1=88C=E9=97=B8=E2=86=92=E5=BC=80?= =?UTF-8?q?=E6=A0=BC=E6=89=B9=E2=86=92=E5=9C=B0=E5=BD=A2=E6=89=B9=E2=86=92?= =?UTF-8?q?=E5=B0=81=E5=8D=B0=E6=89=B9=E2=86=92=E8=BD=AE=E6=9C=AB=E6=94=B6?= =?UTF-8?q?=E5=8F=A3),=E6=89=B9=E6=97=8F=3D=5Freveal=5Fbatch/=5Freveal=5Fs?= =?UTF-8?q?afe(=E6=96=BD=E5=8A=A0=E9=9D=A2=5Freveal=5Fsafe=5Fapply)/=5Frev?= =?UTF-8?q?eal=5Fmine(=E6=96=BD=E5=8A=A0=E9=9D=A2=5Freveal=5Fmine=5Fapply?= =?UTF-8?q?=E8=BF=94=E5=9B=9E{halve=5Fpct,mine=5Freduce})/=5Fterrain=5Fbat?= =?UTF-8?q?ch(damage=5Ftaken=E6=96=BD=E5=8A=A0=E9=9D=A2=5Fterrain=5Fdamage?= =?UTF-8?q?=5Freduce=E8=BF=94=E5=9B=9E=E5=87=8F=E5=90=8E=E5=80=BC)/=5Fseal?= =?UTF-8?q?=5Fbatch(=5Fseal=5Fstrike=5Fbatch+=5Fseal=5Fstrike=5Fvalue)/=5F?= =?UTF-8?q?round=5Fend=5Fapply(=5Fround=5Fend=5Fblast=5Ftickets);=5Fjudge?= =?UTF-8?q?=5Fseal=E4=B8=A4=E5=88=86=5Fjudge=5Fseal=5Fhit(=5Fseal=5Fburst?= =?UTF-8?q?=5Ftickets/=5Fseal=5Fhit=5Feffects/=5Fseal=5Fpreview=5Farea)/?= =?UTF-8?q?=5Fjudge=5Fseal=5Fmiss;=E6=AE=B5=E5=BA=8F/=E5=8F=91=E5=B0=84?= =?UTF-8?q?=E5=BA=8F/=E5=8F=96=E5=8F=B7=E5=BA=8F=E9=80=90=E5=AD=97(?= =?UTF-8?q?=E5=87=8F=E4=BC=A4=E8=90=BD=E5=BF=AB=E7=85=A7=E4=BB=8D=E5=9C=A8?= =?UTF-8?q?blas=5Fscaled=E4=B9=8B=E5=90=8E=E3=80=81counter=E5=9C=A8?= =?UTF-8?q?=E5=85=A5=E9=98=9F=E5=89=8D=E3=80=81grant=5Fmaterial=E5=9C=A8?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E5=90=8E=E3=80=81escrow=E8=AF=BB=E6=94=B9?= =?UTF-8?q?=E5=86=99=E5=BA=8F=E9=80=90=E4=BD=8D=E4=B8=80=E8=87=B4),?= =?UTF-8?q?=E8=BD=BD=E8=8D=B7=E4=B8=8E=E8=A1=8C=E5=AD=97=E5=85=B8=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E9=9B=B6=E5=8F=98=E5=8C=96=E3=80=82=E9=AA=8C=E6=94=B6?= =?UTF-8?q?:gdstyle=E5=9F=BA=E7=BA=BF=E5=A4=960=20NEW+=E5=8D=81=E5=A5=97?= =?UTF-8?q?=E4=BB=B6742=20PASS+=E9=9B=B6SCRIPT=20ERROR=E3=80=94=E6=8B=86?= =?UTF-8?q?=E4=BB=B6=E6=89=B9=20V10=E3=80=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/settlement/grant_segment.gd | 545 +++++++++++++++----------- 1 file changed, 311 insertions(+), 234 deletions(-) diff --git a/game/core/settlement/grant_segment.gd b/game/core/settlement/grant_segment.gd index 3f1d2ab9..2eb25351 100644 --- a/game/core/settlement/grant_segment.gd +++ b/game/core/settlement/grant_segment.gd @@ -5,9 +5,10 @@ extends RefCounted # 转存收口 + 揭示兑付 + 段界结账。行发射/开单单点归宿主(oc)——兑付管线归 RewardGrant、 # 效果施加归 BattleFxHost(段持三引用直调,零转发壳)—— # 本段只搬运时序,零算法改动(🔴 零行为变更契约:同种子完整局输出逐位一致)。 +# run 编排骨架+批私有族(_reveal_batch/_terrain_batch/_seal_batch/_round_end_apply,段内施加面 +# 再拆 _reveal_safe_apply/_reveal_mine_apply/_terrain_damage_reduce/_seal_hit_effects 等单面函数); # kind 分派单点化(P1-1 同批):counter_add 五位三行组 / grant_material 三位兑付块 -# 收 _apply_counter_hit / _apply_material_hit 两助手(原 :683/:742/:777/:839/:1193 与 -# :690/:782/:1217 八处——分支本体逐字保留在助手内)。 +# 收 _apply_counter_hit / _apply_material_hit 两助手(分支本体逐字保留在助手内)。 var oc: BattleOrchestrator # 宿主(类型标注=段内 := 推断前提;循环类名引用 Godot 4 全局类安全) var fx_host: BattleFxHost # 效果引擎宿主件(fx 发射/施加/揭示队列/计数器桶) @@ -25,6 +26,14 @@ func run(ri: int) -> void: # 判定基线 = 轮末在场封印(发射面已过滤——被挖开的封印已随挖掘擦除零兑付; # 开格事实不可撤)。 reward_grant.defer["on"] = true # 案C:授予段伤害单据只暂存不结账(兑付时点=战斗段末) + _reveal_batch(ri) + _terrain_batch(ri) + _seal_batch(ri) + _round_end_apply(ri) + + +# 开格/踩雷兑付批(轮 facts_reveals 资源事件序:safe 正向乘区兑付 / mine_hit 减半负向兑付) +func _reveal_batch(ri: int) -> void: for evt in oc._facts_reveals: if not bool(evt.get("resource", false)): continue # 封锁格(initial 通道)恒无资源——不入账(战斗结算 义务②) @@ -32,108 +41,123 @@ func run(ri: int) -> void: if not (snap is Dictionary): continue if evt.kind == "safe": - if bool(evt.get("dug_under_seal", false)): - # 封印不可逆四情况④:插安全格再挖开 = 零惩罚零奖励——正向兑付 - # 作废(归因行在案);标空判定已随封印擦除自然缺席(被挖的封印不在轮末在场基线)。 - oc._emit_row({"type": "reward_ineffective", "source_class": "safe_cell", "origin_cell": evt.cell}, ri, "奖励作废(封印格直接挖开——零惩罚零奖励)", 0, {}) - continue - # dig_reveal 发射+施加(内容数据驱动扩展批 T3——桶序固定承载等价: - # 乘区收集 → 正向兑付 → 计数器 → 追加产出(深挖)→ 奥术矢。 - # payload 携三事实:is_edge_cell〔边陲矿工〕/is_blank_zero〔跳格采掘〕/ - # new_resource_type〔地质学家——快照 cell_type 首见判定,见后集登记〕) - var board_b: BoardData = oc.minesweeper.current_board() - var snap_pre = evt.get("reward_snapshot") - var ctype := String((snap_pre as Dictionary).get("cell_type", "")) if snap_pre is Dictionary else "" - var is_new_type := ctype != "" and not fx_host._fx_new_resource_types.has(ctype) - if is_new_type: - fx_host._fx_new_resource_types[ctype] = true - var dr_hits := fx_host.fx_emit("dig_reveal", {"cell": evt.cell, "round": ri, - "is_first_dig_of_battle": not oc._feat_first_dig_used, - "terrain_kind": oc._harmful_variants(evt.get("variant_ids", []) as Array), - "is_edge_cell": evt.cell.x == 0 or evt.cell.y == 0 - or evt.cell.x == board_b.width - 1 or evt.cell.y == board_b.height - 1, - "is_blank_zero": board_b.digit_of(evt.cell) == 0, - "new_resource_type": is_new_type}) - var pos_m := 0 - var neg_m := 0 - for h in dr_hits: - if String(h.get("kind", "")) != "yield_mult": - continue - pos_m += int(h.get("mult", 0)) - neg_m += int(h.get("cost_mult", 0)) - reward_grant.grant_routed(oc._feat_yield_adjust(snap, true, pos_m, neg_m), "positive", {"source_class": "safe_cell", "cell": evt.cell, "ri": ri}) - # 计数器执行(连击计数——加法族,乘区收集后执行=本格兑付用旧值,与迁移前逐位一致) - for h in dr_hits: - if String(h.get("kind", "")) == "counter_add": - _apply_counter_hit(h) - # 揭示效果入队(地质学家/跳格采掘 reveal_tiles 订阅面;授予段末 - # _flush_pending_reveals 统一掷格兑付) - if not oc._hits_of(dr_hits, "reveal_tiles").is_empty(): - fx_host.enqueue_reveal() - # 追加产出(深挖钻头 v2——本格双份制:grant_material 命中=配额内追加第二份;标签随行) - for h in dr_hits: - if String(h.get("kind", "")) == "grant_material": - _apply_material_hit(h, evt.cell, ri) - # 奥术矢(魔能共鸣——每战首次开安全格追加魔法矢;任意真族单次生效) - var spell_hits: Array[Variant] = oc._hits_of(dr_hits, "spawn_spell") - if not spell_hits.is_empty() and not oc._feat_first_dig_used: - oc._feat_first_dig_used = true - var spell_t := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_first_dig_spell", - "奥术矢", "board", int((spell_hits[0] as Dictionary).get("value", 0)), - DamageController.TARGET_FRONT_ALIVE, ri, evt.cell, "magic") - reward_grant.submit(spell_t, false) + _reveal_safe(evt, snap, ri) elif evt.kind == "mine": - # 封印不可逆四情况②:插危险格再挖开 = 只受挖雷一层惩罚(mine_hit 照常兑付——惊动巢穴), - # 标中不发(封印已随挖掘擦除、不入轮末判定基线)。 - # mine_hit 发射+施加(内容数据驱动扩展批 T3——桶序固定承载等价: - # 减半收集 → 负向兑付〔带减半系数〕→ 反打开单 → 连击清零计数器。 - # 发射时点前移并扩 payload dug_under_seal〔破封一击〕;施加面: - # damage_reduce〔安全帽耗层减伤——value_counter 层值,一次清零〕/ heal〔血蜜〕/ - # blast_scaled〔陷阱爆发——雷伤×倍率全体敌〕/ bonus_delta〔血税〕/ reveal_tiles〔见血开窍〕) - var mine_power := int((snap as Dictionary).get("power", 0)) - var mh_hits := fx_host.fx_emit("mine_hit", {"cell": evt.cell, "round": ri, - "damage": mine_power, "dug_under_seal": bool(evt.get("dug_under_seal", false)), - "hp_ratio": int(round(100.0 * float(int(oc._enemy_system.hero_unit().get("hp", 0))) / float(maxi(1, int(oc._enemy_system.hero_unit().get("max_hp", 1))))))}) - var halve_pct := 0 - var mine_reduce := 0 - for h in mh_hits: - match String(h.get("kind", "")): - "damage_mult": - halve_pct += int(h.get("value", 0)) - "damage_reduce": - if h.has("value_counter") and String(h.get("value_counter", "")) != "": - mine_reduce += EffectEngine.counter(fx_host.counters_ctx, String(h.get("value_counter", ""))) # 安全帽——层值减免 - else: - mine_reduce += int(h.get("value", 0)) # 背水——定值减免 - "heal": - var hu_heal: Dictionary[Variant, Variant] = oc._enemy_system.hero_unit() - hu_heal["hp"] = mini(int(hu_heal.get("hp", 0)) + int(h.get("value", 0)), maxi(1, int(hu_heal.get("max_hp", 1)))) - "bonus_delta": - var axis := String(h.get("axis", "")) - if fx_host._fx_bonus_delta.has(axis): - fx_host._fx_bonus_delta[axis] += int(h.get("delta", 0)) - "blast_scaled": - var blast_v := maxi(int(oc.db.settlement.damage_floor), int(float(mine_power) * float(h.get("mult", 1)))) - var bs_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_blast_scaled", "陷阱爆发", "board", - blast_v, DamageController.TARGET_ALL_ALIVE, ri, evt.cell, "", 0, "", {}) - reward_grant.submit(bs_t, false) - if mine_reduce > 0: - snap["power"] = maxi(0, mine_power - mine_reduce) # 减伤落快照(负向兑付按减后值——安全帽每层减 1) - for h in mh_hits: - if String(h.get("kind", "")) == "counter_add": - _apply_counter_hit(h) - if not oc._hits_of(mh_hits, "reveal_tiles").is_empty(): - fx_host.enqueue_reveal() # 见血开窍——每颗雷一次揭示(授予段末统一兑付) - reward_grant.grant_routed(oc._feat_yield_adjust(snap, false, 0, 0), "negative", {"source_class": "mine_hit", "cell": evt.cell, "ri": ri, "halve_pct": halve_pct}) - for h in mh_hits: - if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "reflect": - var nct := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_nest_counter", "惊巢反制·反弹", "board", - int(h.get("value", 0)), DamageController.TARGET_ALL_ALIVE, ri, evt.cell, "", 0, "", {}) - reward_grant.submit(nct, false) - # 地形变异 兑付段(授予段尾——触发枚举 on_open:挖开即伤、chain 通道同口径;伤害单据案C 暂存到 - # 战斗段末兑付〔案C〕;信息型 hide_number 不进结算——呈现层读取,见 board_view 数字遮蔽; - # 危险格恒无变异——布置段排除,双层叠罚不存在;封印格直挖照吃变异伤害——地形后果同 mine_hit 口径, - # 非奖励不作废;吞噬流资源兑付〔荆棘编织/地脉补偿〕中伤害形态行同样经案C闸暂存,非伤害形态照常当场) + _reveal_mine(evt, snap, ri) + + +# safe 事件兑付(封印不可逆四情况④:插安全格再挖开 = 零惩罚零奖励——正向兑付作废〔归因行在案〕; +# 标空判定已随封印擦除自然缺席——被挖的封印不在轮末在场基线。dig_reveal 发射+施加序: +# 乘区收集 → 正向兑付 → 施加面〔计数器/揭示入队/追加产出/奥术矢〕。 +# payload 携 is_edge_cell〔边陲矿工〕/is_blank_zero〔跳格采掘〕/ +# new_resource_type〔地质学家——快照 cell_type 首见判定,见后集登记〕) +func _reveal_safe(evt: Dictionary, snap: Dictionary, ri: int) -> void: + if bool(evt.get("dug_under_seal", false)): + oc._emit_row({"type": "reward_ineffective", "source_class": "safe_cell", "origin_cell": evt.cell}, ri, "奖励作废(封印格直接挖开——零惩罚零奖励)", 0, {}) + return + var board_b: BoardData = oc.minesweeper.current_board() + var ctype := String(snap.get("cell_type", "")) + var is_new_type := ctype != "" and not fx_host._fx_new_resource_types.has(ctype) + if is_new_type: + fx_host._fx_new_resource_types[ctype] = true + var dr_hits := fx_host.fx_emit("dig_reveal", {"cell": evt.cell, "round": ri, + "is_first_dig_of_battle": not oc._feat_first_dig_used, + "terrain_kind": oc._harmful_variants(evt.get("variant_ids", []) as Array), + "is_edge_cell": evt.cell.x == 0 or evt.cell.y == 0 + or evt.cell.x == board_b.width - 1 or evt.cell.y == board_b.height - 1, + "is_blank_zero": board_b.digit_of(evt.cell) == 0, + "new_resource_type": is_new_type}) + var pos_m := 0 + var neg_m := 0 + for h in dr_hits: + if String(h.get("kind", "")) != "yield_mult": + continue + pos_m += int(h.get("mult", 0)) + neg_m += int(h.get("cost_mult", 0)) + reward_grant.grant_routed(oc._feat_yield_adjust(snap, true, pos_m, neg_m), "positive", {"source_class": "safe_cell", "cell": evt.cell, "ri": ri}) + _reveal_safe_apply(dr_hits, evt, ri) + + +# dig_reveal 施加面(序=计数器〔连击计数——乘区收集后执行=本格兑付用旧值〕→ 揭示入队〔地质学家/ +# 跳格采掘 reveal_tiles 订阅面,授予段末统一掷格兑付〕→ 追加产出〔深挖钻头 v2 双份制:grant_material +# 命中=配额内追加第二份〕→ 奥术矢〔魔能共鸣——每战首次开安全格追加魔法矢;任意真族单次生效〕) +func _reveal_safe_apply(dr_hits: Array, evt: Dictionary, ri: int) -> void: + for h in dr_hits: + if String(h.get("kind", "")) == "counter_add": + _apply_counter_hit(h) + if not oc._hits_of(dr_hits, "reveal_tiles").is_empty(): + fx_host.enqueue_reveal() + for h in dr_hits: + if String(h.get("kind", "")) == "grant_material": + _apply_material_hit(h, evt.cell, ri) + var spell_hits: Array[Variant] = oc._hits_of(dr_hits, "spawn_spell") + if not spell_hits.is_empty() and not oc._feat_first_dig_used: + oc._feat_first_dig_used = true + var spell_t := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_first_dig_spell", + "奥术矢", "board", int((spell_hits[0] as Dictionary).get("value", 0)), + DamageController.TARGET_FRONT_ALIVE, ri, evt.cell, "magic") + reward_grant.submit(spell_t, false) + + +# mine 事件兑付(封印不可逆四情况②:插危险格再挖开 = 只受挖雷一层惩罚——mine_hit 照常兑付惊动巢穴, +# 标中不发〔封印已随挖掘擦除、不入轮末判定基线〕。mine_hit 发射+施加序:施加面收集 → 减伤落快照 → +# 计数器 → 揭示入队 → 负向兑付〔带减半系数〕→ 反打开单) +func _reveal_mine(evt: Dictionary, snap: Dictionary, ri: int) -> void: + var mine_power := int(snap.get("power", 0)) + var mh_hits := fx_host.fx_emit("mine_hit", {"cell": evt.cell, "round": ri, + "damage": mine_power, "dug_under_seal": bool(evt.get("dug_under_seal", false)), + "hp_ratio": int(round(100.0 * float(int(oc._enemy_system.hero_unit().get("hp", 0))) / float(maxi(1, int(oc._enemy_system.hero_unit().get("max_hp", 1))))))}) + var applied := _reveal_mine_apply(mh_hits, snap, evt.cell, ri) + if int(applied["mine_reduce"]) > 0: + snap["power"] = maxi(0, mine_power - int(applied["mine_reduce"])) # 减伤落快照(负向兑付按减后值——安全帽每层减 1) + for h in mh_hits: + if String(h.get("kind", "")) == "counter_add": + _apply_counter_hit(h) + if not oc._hits_of(mh_hits, "reveal_tiles").is_empty(): + fx_host.enqueue_reveal() # 见血开窍——每颗雷一次揭示(授予段末统一兑付) + reward_grant.grant_routed(oc._feat_yield_adjust(snap, false, 0, 0), "negative", {"source_class": "mine_hit", "cell": evt.cell, "ri": ri, "halve_pct": int(applied["halve_pct"])}) + for h in mh_hits: + if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "reflect": + var nct := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_nest_counter", "惊巢反制·反弹", "board", + int(h.get("value", 0)), DamageController.TARGET_ALL_ALIVE, ri, evt.cell, "", 0, "", {}) + reward_grant.submit(nct, false) + + +# mine_hit 施加面(damage_reduce〔安全帽耗层减伤——value_counter 层值一次清零/背水——定值减免〕/ +# heal〔血蜜〕/ bonus_delta〔血税——fx 件四轴累积〕/ blast_scaled〔陷阱爆发——雷伤×倍率全体敌,案C 暂存〕/ +# damage_mult 减半系数收集;返回 {halve_pct, mine_reduce} 交兑付与快照减伤消费) +func _reveal_mine_apply(mh_hits: Array, snap: Dictionary, cell: Vector2i, ri: int) -> Dictionary: + var halve_pct := 0 + var mine_reduce := 0 + for h in mh_hits: + match String(h.get("kind", "")): + "damage_mult": + halve_pct += int(h.get("value", 0)) + "damage_reduce": + if h.has("value_counter") and String(h.get("value_counter", "")) != "": + mine_reduce += EffectEngine.counter(fx_host.counters_ctx, String(h.get("value_counter", ""))) # 安全帽——层值减免 + else: + mine_reduce += int(h.get("value", 0)) # 背水——定值减免 + "heal": + var hu_heal: Dictionary[Variant, Variant] = oc._enemy_system.hero_unit() + hu_heal["hp"] = mini(int(hu_heal.get("hp", 0)) + int(h.get("value", 0)), maxi(1, int(hu_heal.get("max_hp", 1)))) + "bonus_delta": + var axis := String(h.get("axis", "")) + if fx_host._fx_bonus_delta.has(axis): + fx_host._fx_bonus_delta[axis] += int(h.get("delta", 0)) + "blast_scaled": + var blast_v := maxi(int(oc.db.settlement.damage_floor), int(float(int(snap.get("power", 0))) * float(h.get("mult", 1)))) + var bs_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_blast_scaled", "陷阱爆发", "board", + blast_v, DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, "", {}) + reward_grant.submit(bs_t, false) + return {"halve_pct": halve_pct, "mine_reduce": mine_reduce} + + +# 地形变异 兑付批(授予段尾——触发枚举 on_open:挖开即伤、chain 通道同口径;伤害单据案C 暂存到 +# 战斗段末兑付〔案C〕;信息型 hide_number 不进结算——呈现层读取,见 board_view 数字遮蔽; +# 危险格恒无变异——布置段排除,双层叠罚不存在;封印格直挖照吃变异伤害——地形后果同 mine_hit 口径, +# 非奖励不作废;吞噬流资源兑付〔荆棘编织/地脉补偿〕中伤害形态行同样经案C闸暂存,非伤害形态照常当场) +func _terrain_batch(ri: int) -> void: for evt in oc._facts_reveals: var vids: Array[Variant] = evt.get("variant_ids", []) for vid in vids: @@ -143,33 +167,47 @@ func run(ri: int) -> void: # 地形变异伤害单据——无类型必中,下限保底在控制器管线内 # damage_taken 发射在单据开立前:damage_reduce〔背水——hp_ratio when # 面〕先收集再按减后值开单;counter_add〔保险丝/石肤〕+ ac_bonus〔石肤〕照常施加; - # grant_material〔荆棘/地脉〕移到单据提交后(与迁移前兑付值序一致——收益不因减伤变化) + # grant_material〔荆棘/地脉〕移到单据提交后(兑付值序一致——收益不因减伤变化) var dt_amount := int(vrow.get("effect_value", 0)) var dt_hits := fx_host.fx_emit("damage_taken", {"kind": "terrain", "amount": dt_amount, "source": String(vid), "cell": evt.cell, "round": ri, "terrain_kind": String(vid), "hp_ratio": int(round(100.0 * float(int(oc._enemy_system.hero_unit().get("hp", 0))) / float(maxi(1, int(oc._enemy_system.hero_unit().get("max_hp", 1))))))}) - for h in dt_hits: - match String(h.get("kind", "")): - "damage_reduce": - dt_amount = maxi(0, dt_amount - int(h.get("value", 0))) - "ac_bonus": - fx_host.apply_ac_bonus(h) - "counter_add": - _apply_counter_hit(h) + dt_amount = _terrain_damage_reduce(dt_hits, dt_amount) var vt := oc._damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, "地形变异", String(vrow.get("display_name", vid)), "board", \ dt_amount, DamageController.TARGET_HERO, ri, evt.cell, "", 0, "", {"variant_id": String(vid)}) reward_grant.submit(vt, false) for h in dt_hits: if String(h.get("kind", "")) == "grant_material": _apply_material_hit(h, evt.cell, ri) + + +# damage_taken 施加面(damage_reduce 减量收集〔背水——定值〕/ac_bonus〔石肤〕/counter_add〔保险丝/石肤〕; +# 返回减后 amount 交开单消费) +func _terrain_damage_reduce(dt_hits: Array, dt_amount: int) -> int: + for h in dt_hits: + match String(h.get("kind", "")): + "damage_reduce": + dt_amount = maxi(0, dt_amount - int(h.get("value", 0))) + "ac_bonus": + fx_host.apply_ac_bonus(h) + "counter_add": + _apply_counter_hit(h) + return dt_amount + + +# 封印判定批+战封印军规开单段(封印流:seal_judged 批后统一开单:轮末每面封印命中对最前存活敌 +# 结算 board_typed 必中伤害〔复用零新配方;「封印所在巢穴敌」简化为 default_target 最前存活敌—— +# 台账披露待追认〕;完美布防=本轮封印批全中(hit>0 且 miss==0)时封印伤 ×mult——全对推理奖励)。 +# 伤害单据案C 暂存战斗段末兑付。 +func _seal_batch(ri: int) -> void: for evt in oc._facts_seals: _judge_seal(evt, ri) - # 战封印军规开单段(封印流:seal_judged 批后统一开单:轮末每面封印命中对最前存活敌 - # 结算 board_typed 必中伤害〔复用零新配方;「封印所在巢穴敌」简化为 default_target 最前存活敌—— - # 台账披露待追认〕;完美布防=本轮封印批全中(hit>0 且 miss==0)时封印伤 ×mult——全对推理奖励)。 - # 伤害单据案C 暂存战斗段末兑付。 - # seal_settle 发射+施加(内容数据驱动扩展批 T3——战封印 open_ticket + 完美布防 modify_ticket - # 改写窗口:封印伤基值×完美倍率后逐封印开单,与迁移前算序一致) + _seal_strike_batch(ri) + + +# seal_settle 发射+施加(战封印 open_ticket + 完美布防 modify_ticket 改写窗口:封印伤基值×完美倍率后 +# 逐封印开单) +func _seal_strike_batch(ri: int) -> void: var fs_hits := 0 var fs_misses := 0 for fevt in oc._facts_seals: @@ -179,52 +217,55 @@ func run(ri: int) -> void: fs_misses += 1 var fs_fx := fx_host.fx_emit("seal_settle", {"seals_hit_all": fs_hits > 0 and fs_misses == 0, "seals_count": fs_hits, "round": ri}) + var strike := _seal_strike_value(fs_fx, fs_hits, fs_misses) + if strike.is_empty(): + return + for fevt in oc._facts_seals: + if not bool(fevt.get("hit", false)): + continue + var st_t := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_seal_strike", + "战封印军规", "board", int(strike["value"]), DamageController.TARGET_FRONT_ALIVE, ri, + fevt.cell, "normal") + reward_grant.submit(st_t, false) + + +# 战封印军规基值决议(attack 通道 open_ticket 首命中取基值;seal_mult modify_ticket 连乘; +# 完美布防=miss==0 且 hit>0 时基值×倍率。空旗集=空字典〔不开单合法态〕) +func _seal_strike_value(fs_fx: Array, fs_hits: int, fs_misses: int) -> Dictionary: var banner_hits: Array[Variant] = [] for h in fs_fx: if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "attack": banner_hits.append(h) - if not banner_hits.is_empty(): - var strike_value := int((banner_hits[0] as Dictionary).get("value", 0)) - var seal_mult := 1 - for h in fs_fx: - if String(h.get("kind", "")) == "modify_ticket" and String(h.get("mode", "")) == "seal_mult": - seal_mult *= maxi(1, int(h.get("value", 1))) - if fs_misses == 0 and fs_hits > 0 and seal_mult > 1: - strike_value *= seal_mult # 完美布防:同轮全中封印伤倍增(全对推理奖励) - for fevt in oc._facts_seals: - if not bool(fevt.get("hit", false)): - continue - var st_t := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_seal_strike", - "战封印军规", "board", strike_value, DamageController.TARGET_FRONT_ALIVE, ri, - fevt.cell, "normal") - reward_grant.submit(st_t, false) - # round_end 发射+施加(内容数据驱动扩展批 T3——每轮结算收口:熔火之躯反打单开立〔open_ticket - # 即时执行〕+积层清零〔counter_add set〕;整备转蓄力由 finish_round_stop 承载——停手侧 AP 已在 - # request_stop 记账,转存决策=grant_ap(mode:transfer) 订阅定夺,无订阅走 RAP 转存缺省) + if banner_hits.is_empty(): + return {} + var strike_value := int((banner_hits[0] as Dictionary).get("value", 0)) + var seal_mult := 1 + for h in fs_fx: + if String(h.get("kind", "")) == "modify_ticket" and String(h.get("mode", "")) == "seal_mult": + seal_mult *= maxi(1, int(h.get("value", 1))) + if fs_misses == 0 and fs_hits > 0 and seal_mult > 1: + strike_value *= seal_mult # 完美布防:同轮全中封印伤倍增(全对推理奖励) + return {"value": strike_value} + + +# round_end 发射+施加(每轮结算收口:熔火之躯反打单开立〔open_ticket 即时执行〕+积层清零〔counter_add +# set〕;整备转蓄力由 finish_round_stop 承载——停手侧 AP 已在 request_stop 记账,转存决策=grant_ap +# (mode:transfer) 订阅定夺,无订阅走 RAP 转存缺省)+施加面:grant_gp〔爆破会计/收盘——escrow 战斗域 +# 缓冲,随 gp_dropped 上抛〕/grant_ap mode:queue〔封印回响——返 1 AP 下轮,queue_ap_delta 通道与轮始 +# 加成同面〕+段界收口(转存率入参、清图奖励、揭示兑付、段界结账) +func _round_end_apply(ri: int) -> void: var re_fx := fx_host.fx_emit("round_end", {"round": ri}) - for h in re_fx: - if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "blast": - var mg_layers := EffectEngine.counter(fx_host.counters_ctx, String(h.get("per", ""))) - var mg_total := int(h.get("value", 0)) * mg_layers - if mg_total > 0: - # source_class 用中性名;per-counter 反打通道保留泛用面 - var mg_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "fx_blast_counter", - "积层反噬 ×%d" % mg_layers, "board", mg_total, - DamageController.TARGET_ALL_ALIVE, ri, oc.minesweeper.hero_pos(), "", 0, "", {}) - reward_grant.submit(mg_t, false) + _round_end_blast_tickets(re_fx, ri) for h in re_fx: if String(h.get("kind", "")) == "counter_add": _apply_counter_hit(h) - # round_end 施加面:grant_gp〔爆破会计/收盘——escrow 战斗域缓冲,随 gp_dropped 上抛〕/ - # grant_ap mode:queue〔封印回响——返 1 AP 下轮,queue_ap_delta 通道与轮始加成同面〕 for h in re_fx: if String(h.get("kind", "")) == "grant_gp": reward_grant.escrow += int(h.get("value", 0)) oc._emit_row({"type": "effect", "source_class": "fx_grant_gp", "origin_cell": Vector2i(-1, -1)}, ri, "专长·GP 入账 +%d" % int(h.get("value", 0)), int(h.get("value", 0)), {"effect_id": "fx_grant_gp", "escrow": reward_grant.escrow}) elif String(h.get("kind", "")) == "grant_ap" and String(h.get("mode", "")) == "queue": oc.action_economy.queue_ap_delta(int(h.get("value", 0))) - # 转存率入参(迭代 22 秘宝批 T3——储备罐 rap_transfer_mult magnitude=目标转存率百分比 125=1.25:1; - # 未装配=100 与旧 1:1 逐位一致) + # 转存率入参(储备罐 rap_transfer_mult magnitude=目标转存率百分比 125=1.25:1;未装配=100 与 1:1 逐位一致) var rap_pct: int = oc.attune_param_sum("rap_transfer_mult") oc.action_economy.finish_round_stop(fx_host.engine.has_effect_on("round_end", "grant_ap", "transfer"), rap_pct if rap_pct > 0 else 100) @@ -234,100 +275,136 @@ func run(ri: int) -> void: oc._damage.flush() # 授予段界结账(案C 后本段伤害单据全数暂存,此处对空队 no-op——保留作防御性段界) -# 封印判定(封印流本体——自编排器 _judge_seal 迁入,宿主引用加前缀; -# 唯一调用面=本段 run 的 facts_seals 循环)。 +# round_end blast 通道反打单(per-counter 积层读数×value=反打总量;source_class 用中性名, +# per-counter 反打通道保留泛用面;案C 暂存战斗段末兑付) +func _round_end_blast_tickets(re_fx: Array, ri: int) -> void: + for h in re_fx: + if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "blast": + var mg_layers := EffectEngine.counter(fx_host.counters_ctx, String(h.get("per", ""))) + var mg_total := int(h.get("value", 0)) * mg_layers + if mg_total > 0: + var mg_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "fx_blast_counter", + "积层反噬 ×%d" % mg_layers, "board", mg_total, + DamageController.TARGET_ALL_ALIVE, ri, oc.minesweeper.hero_pos(), "", 0, "", {}) + reward_grant.submit(mg_t, false) + + +# 封印判定(封印流本体;唯一调用面=本段 _seal_batch 的 facts_seals 循环——命中/未中两分) func _judge_seal(evt: Dictionary, ri: int) -> void: - var cell: Vector2i = evt.cell if evt.hit: - if oc._seal_consumed.has(cell): - return # 去重跳过(资格以危险格实例为键——排雷玩法 诚实重复上报的对侧);零明细行 - oc._seal_consumed[cell] = true - oc._perfect_hit_true += 1 - # seal_hit 发射+施加(内容数据驱动扩展批 T3——桶序固定承载等价:殉爆〔T4 秘宝面〕→ - # 净化改写 → 图谱额外资源 → 基线兑付) - var fh_hits := fx_host.fx_emit("seal_hit", {"cell": cell, "round": ri, - "terrain_kind": oc._harmful_adjacent(cell)}) - # 标中殉爆(迭代 11 同调批 T5 → 内容数据驱动扩展批 T4:订阅施加——行 triggers open_ticket - # channel blast value_from magnitude;每标中一面封印全体存活敌吃 N 群伤,无类型走 blast 通道。 - # 封印爆裂〔专长〕同面消费:行 triggers open_ticket channel blast) - for h in oc._hits_of(fh_hits, "open_ticket"): - if String(h.get("channel", "")) != "blast": - continue - var fbt := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_seal_burst", "封印爆裂", "board", - int(h.get("value", 0)), DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, "highlight", {}) - reward_grant.submit(fbt, false) - # 标中施加面:counter_add〔血税 标中+1 攻——bonus_delta atk 轴〕/ - # ac_bonus〔封印桩 本轮 AC+N〕/ preview_area〔示警烟火 3×3 资源类型预告〕/ - # remove_mines〔封印匠 每第 3 次该危险格中立移除——mod_eq 闸在行 when〕。 - # (净化工兵 rewrite_board 分支随老 27 件退役摘除——效果类型同批除名,T1 落法 commit 留痕) - for h in fh_hits: - match String(h.get("kind", "")): - "counter_add": - _apply_counter_hit(h) - "ac_bonus": - fx_host.apply_ac_bonus(h) - "bonus_delta": - var axis_f := String(h.get("axis", "")) - if fx_host._fx_bonus_delta.has(axis_f): - fx_host._fx_bonus_delta[axis_f] += int(h.get("delta", 0)) - "remove_mines": - var removed: Array[Variant] = oc.minesweeper.remove_mines_in([cell]) - if not removed.is_empty(): - oc._emit_row({"type": "extended", "source_class": "fx_seal_smith", "origin_cell": cell}, ri, "封印匠·危险格中立移除", 0, {"effect_id": "mine_neutralized"}) - "preview_area": - var board_p: BoardData = oc.minesweeper.current_board() - var types: Array[Variant] = [] - for n in board_p.neighbors8(cell): - var n_snap = oc._reward_economy.query_full_snapshot(n) - if n_snap is Dictionary and not types.has(String((n_snap as Dictionary).get("cell_type", ""))): - types.append(String((n_snap as Dictionary).get("cell_type", ""))) - if oc.treasure_inventory != null and not types.is_empty(): - oc.treasure_inventory.apply_overlay("reward_preview", board_p.neighbors8(cell)) - oc._emit_row({"type": "extended", "source_class": "fx_warning_beacon", "origin_cell": cell}, ri, "示警烟火·周围资源类型", 0, {"effect_id": "fx_preview_area", "types": types}) - # 雷区图谱(封印流——标中雷额外获得一份格面资源:封印开始产出对标开格; - # 复用 grant 单点,额外兑付走同一 _grant_routed 正向路由) - for h in fh_hits: - if String(h.get("kind", "")) == "grant_material": - _apply_material_hit(h, cell, ri, "seal_hit") - var snap = oc._reward_economy.grant(cell) - if not (snap is Dictionary): - return - reward_grant.grant_routed(snap, "positive", {"source_class": "seal_hit", "cell": cell, "ri": ri, "highlight": "seal_hit"}) + _judge_seal_hit(evt, ri) else: - oc._perfect_hit_false += 1 - if oc._seal_miss_seen.has(cell): - return # 每格实例一次去重(归因行与负向兑付同键——防逐轮惩罚轰炸) - oc._seal_miss_seen[cell] = true - # 危险直觉·守挂点(seal_missed 发射:契约 12 触发主语=「当回合发生的标错 - # 动作」——去重闸=新旧标错判别器〔后续轮重判旧封印被闸挡回零触发〕;双条件②「结算时封印仍在 - # 场」由发射面既有过滤承载〔被挖开的封印已随挖掘擦除零兑付〕;grant_ap mode:queue 下一轮 - # 开轮兑现——末轮判出无下一轮=合法作废自动成立〔编排器每战新建,队列不跨战〕;限次=合成行 - # adventure 域计数器闸) - for h in fx_host.fx_emit("seal_missed", {"cell": cell, "round": ri}): - if String(h.get("kind", "")) == "grant_ap" and String(h.get("mode", "")) == "queue": - oc.action_economy.queue_ap_delta(int(h.get("value", 0))) - elif String(h.get("kind", "")) == "forgive_seal_miss": - # 完美保险(迭代 31 卡② T4——订阅式豁免:本战首次标错计一次豁免,只累加自有账 - # _perfect_forgiven,绝不回写 _perfect_hit_false=契约 2 计数纯净;发射时机与消费点零改动=契约 3) - oc._perfect_forgiven += 1 - _apply_counter_hit(h) # 豁免同时记账(insurance_used battle 域——下次标错 when lt 1 不再命中) - oc._emit_row({"type": "extended", "source_class": "seal_miss", "origin_cell": cell}, ri, "完美保险·标错豁免", 0, - {"effect_id": "insurance_used", "forgiven": oc._perfect_forgiven}) - var hl := "perfect_broken" if oc._perfect_hit_false - oc._perfect_forgiven == 1 else "" - oc._emit_row({"type": "seal_miss", "source_class": "seal_miss", "origin_cell": cell, "highlight": hl}, ri, "封印未中", 0, {}) - var snap = oc._reward_economy.grant(cell) - if not (snap is Dictionary): - return - reward_grant.grant_routed(snap, "negative", {"source_class": "seal_miss", "cell": cell, "ri": ri}) + _judge_seal_miss(evt, ri) -# counter_add 三行组单点(原五位分支本体逐字收拢——kind 分派随段归位 P1-1) +# 封印命中(资格以危险格实例为键去重——排雷玩法 诚实重复上报的对侧;seal_hit 发射序: +# 殉爆票据 → 施加面 → 雷区图谱额外兑付 → 基线正向兑付) +func _judge_seal_hit(evt: Dictionary, ri: int) -> void: + var cell: Vector2i = evt.cell + if oc._seal_consumed.has(cell): + return # 去重跳过;零明细行 + oc._seal_consumed[cell] = true + oc._perfect_hit_true += 1 + var fh_hits := fx_host.fx_emit("seal_hit", {"cell": cell, "round": ri, + "terrain_kind": oc._harmful_adjacent(cell)}) + _seal_burst_tickets(fh_hits, cell, ri) + _seal_hit_effects(fh_hits, cell, ri) + # 雷区图谱(封印流——标中雷额外获得一份格面资源:封印开始产出对标开格; + # 复用 grant 单点,额外兑付走同一 grant_routed 正向路由) + for h in fh_hits: + if String(h.get("kind", "")) == "grant_material": + _apply_material_hit(h, cell, ri, "seal_hit") + var snap = oc._reward_economy.grant(cell) + if not (snap is Dictionary): + return + reward_grant.grant_routed(snap, "positive", {"source_class": "seal_hit", "cell": cell, "ri": ri, "highlight": "seal_hit"}) + + +# 标中殉爆票据(行 triggers open_ticket channel blast value_from magnitude——每标中一面封印全体存活敌 +# 吃 N 群伤,无类型走 blast 通道;封印爆裂〔专长〕同面消费) +func _seal_burst_tickets(fh_hits: Array, cell: Vector2i, ri: int) -> void: + for h in oc._hits_of(fh_hits, "open_ticket"): + if String(h.get("channel", "")) != "blast": + continue + var fbt := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_seal_burst", "封印爆裂", "board", + int(h.get("value", 0)), DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, "highlight", {}) + reward_grant.submit(fbt, false) + + +# 标中施加面:counter_add〔血税 标中+1 攻——bonus_delta atk 轴〕/ +# ac_bonus〔封印桩 本轮 AC+N〕/ bonus_delta〔血税——fx 件四轴累积〕/ +# remove_mines〔封印匠 每第 3 次该危险格中立移除——mod_eq 闸在行 when〕/ +# preview_area〔示警烟火 3×3 资源类型预告〕 +func _seal_hit_effects(fh_hits: Array, cell: Vector2i, ri: int) -> void: + for h in fh_hits: + match String(h.get("kind", "")): + "counter_add": + _apply_counter_hit(h) + "ac_bonus": + fx_host.apply_ac_bonus(h) + "bonus_delta": + var axis_f := String(h.get("axis", "")) + if fx_host._fx_bonus_delta.has(axis_f): + fx_host._fx_bonus_delta[axis_f] += int(h.get("delta", 0)) + "remove_mines": + var removed: Array[Variant] = oc.minesweeper.remove_mines_in([cell]) + if not removed.is_empty(): + oc._emit_row({"type": "extended", "source_class": "fx_seal_smith", "origin_cell": cell}, ri, "封印匠·危险格中立移除", 0, {"effect_id": "mine_neutralized"}) + "preview_area": + _seal_preview_area(cell, ri) + + +# 示警烟火施加面(3×3 邻格资源类型预告——类型去重集入 overlay;信息呈现零回写) +func _seal_preview_area(cell: Vector2i, ri: int) -> void: + var board_p: BoardData = oc.minesweeper.current_board() + var types: Array[Variant] = [] + for n in board_p.neighbors8(cell): + var n_snap = oc._reward_economy.query_full_snapshot(n) + if n_snap is Dictionary and not types.has(String((n_snap as Dictionary).get("cell_type", ""))): + types.append(String((n_snap as Dictionary).get("cell_type", ""))) + if oc.treasure_inventory != null and not types.is_empty(): + oc.treasure_inventory.apply_overlay("reward_preview", board_p.neighbors8(cell)) + oc._emit_row({"type": "extended", "source_class": "fx_warning_beacon", "origin_cell": cell}, ri, "示警烟火·周围资源类型", 0, {"effect_id": "fx_preview_area", "types": types}) + + +# 封印未中(每格实例一次去重——归因行与负向兑付同键,防逐轮惩罚轰炸;seal_missed 发射序: +# grant_ap mode:queue 下一轮兑现 / forgive_seal_miss 完美保险豁免 → 归因行 → 负向兑付) +func _judge_seal_miss(evt: Dictionary, ri: int) -> void: + var cell: Vector2i = evt.cell + oc._perfect_hit_false += 1 + if oc._seal_miss_seen.has(cell): + return # 每格实例一次去重 + oc._seal_miss_seen[cell] = true + # 危险直觉·守挂点(seal_missed 发射:契约 12 触发主语=「当回合发生的标错 + # 动作」——去重闸=新旧标错判别器〔后续轮重判旧封印被闸挡回零触发〕;双条件②「结算时封印仍在 + # 场」由发射面既有过滤承载〔被挖开的封印已随挖掘擦除零兑付〕;grant_ap mode:queue 下一轮 + # 开轮兑现——末轮判出无下一轮=合法作废自动成立〔编排器每战新建,队列不跨战〕;限次=合成行 + # adventure 域计数器闸) + for h in fx_host.fx_emit("seal_missed", {"cell": cell, "round": ri}): + if String(h.get("kind", "")) == "grant_ap" and String(h.get("mode", "")) == "queue": + oc.action_economy.queue_ap_delta(int(h.get("value", 0))) + elif String(h.get("kind", "")) == "forgive_seal_miss": + # 完美保险(订阅式豁免:本战首次标错计一次豁免,只累加自有账 + # _perfect_forgiven,绝不回写 _perfect_hit_false=契约 2 计数纯净;发射时机与消费点零改动=契约 3) + oc._perfect_forgiven += 1 + _apply_counter_hit(h) # 豁免同时记账(insurance_used battle 域——下次标错 when lt 1 不再命中) + oc._emit_row({"type": "extended", "source_class": "seal_miss", "origin_cell": cell}, ri, "完美保险·标错豁免", 0, + {"effect_id": "insurance_used", "forgiven": oc._perfect_forgiven}) + var hl := "perfect_broken" if oc._perfect_hit_false - oc._perfect_forgiven == 1 else "" + oc._emit_row({"type": "seal_miss", "source_class": "seal_miss", "origin_cell": cell, "highlight": hl}, ri, "封印未中", 0, {}) + var snap = oc._reward_economy.grant(cell) + if not (snap is Dictionary): + return + reward_grant.grant_routed(snap, "negative", {"source_class": "seal_miss", "cell": cell, "ri": ri}) + + +# counter_add 三行组单点(五位分支本体逐字收拢——kind 分派随段归位 P1-1) func _apply_counter_hit(h: Dictionary) -> void: var set_v = h.get("set", null) EffectEngine.counter_add(fx_host.counters_ctx, String(h.get("counter_id", "")), int(h.get("value", 0)) if set_v == null else int(set_v), set_v != null) -# grant_material 兑付块单点(原三位分支本体逐字收拢——source_class 两态 safe_cell/seal_hit 由参承载) +# grant_material 兑付块单点(三位分支本体逐字收拢——source_class 两态 safe_cell/seal_hit 由参承载) func _apply_material_hit(h: Dictionary, cell: Vector2i, ri: int, source_class: String = "safe_cell") -> void: var g_snap = oc._reward_economy.grant(cell) if g_snap is Dictionary: