Files
gplan-2/game/core/settlement/combat_segment.gd
T

123 lines
8.4 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.
class_name CombatSegment
extends RefCounted
# ③ 战斗段 Segment(P1-1 结算巨函数拆段;段边界=审计五段〔裁点③甲〕)。
# 职责:案C 闸复位 + 本轮扫雷事实推导(执拗 streak/连珠矢段数)+ 蓄力层注入 + 主角先手
# (含连珠矢分段开单)+ 敌方行动兑现 + 地图伤害账殿后(补提交统一结账)。
# 调用序:主角先手 → 逐敌还手 → 地图伤害账殿后〔案C〕;hero hp=0 短路。
# 行发射/开单单点归宿主(oc)——案C 暂存闸归 RewardGrant(段持三引用直调,零转发壳)——
# 本函数体为原编排器 _combat_segment 逐字搬运(🔴 零行为变更契约)。
# run 编排骨架+三私有族(_hero_strike/_enemy_actions/_board_deferred_settle);段序逐字。
var oc: BattleOrchestrator # 宿主(类型标注=段内 := 推断前提;循环类名引用 Godot 4 全局类安全)
var fx_host: BattleFxHost # 效果引擎宿主件(三引用构造——本轮无消费面,构造形与授予段同形)
var reward_grant: RewardGrant # 奖励授予件(案C 暂存闸 defer 持有方)
func _init(host, fx_host_ref: BattleFxHost, reward_grant_ref: RewardGrant) -> void:
oc = host
fx_host = fx_host_ref
reward_grant = reward_grant_ref
func run(ri: int) -> void:
reward_grant.defer["on"] = false # 案C 闸复位——战斗段起开单即入队(主角/敌方/补提交的地图单按开单序统一结账)
# 本轮扫雷事实推导(执拗 streak/连珠矢段数共用取数面):本轮 safe 事件数=
# streak;踩雷即清零(mine_hit 监听同口——执拗「踩雷清零」承载)。攻击在战斗段=授予段后
# 同一次 _settle 内,facts 已定。
var round_safe := 0
var round_mine := false
for evt in oc._facts.reveals:
if evt.kind == "safe":
round_safe += 1
elif evt.kind == "mine":
round_mine = true
oc.fx_host().facts_inject["dig_streak"] = 0 if round_mine else round_safe
# 蓄力层不再逐轮注入(效果端口件闭面——fact charge_layers 由宿主 biz_fact 活读 action_economy
# 现值:唯一写点=finish_round_stop 于上轮授予段尾,本轮授予段发射前无第二写点,活读等价)
_hero_strike(ri)
_enemy_actions(ri)
_board_deferred_settle(ri)
# ⓪ 主角先手攻击(基础攻击恒存在;先于敌方行动。案C:授予段不再产出伤害,
# 主角进入战斗段时恒存活——先手必兑,除非场上无敌)。
# 单分量伤害构成:主角攻击 = 唯一基础分量(atk,吃掷点含暴击;
# 类型 = 主角 attack_type 表值(量与类型抽象分离);查甲效矩阵对应行取克制修正)。
# 原"基础 + 穿刺/魔法充能三分量"模型随充能系统整体退役——克制策略归冒险前配置(营地换型 / 未来冒险前参数面),
# 局内不再有挖掘攒弹药的第二伤害供给。attack_up 增益经 hero_offense 池乘攻击合计(见池乘区注记)。
# 护甲等级掷点:基础分量吃掷点判定(含暴击);棋盘来源伤害不掷不折;
# 任何伤害落地 ≥ damage_floor(全局 max(1,·) 适用所有来源,下限在控制器管线单点收口)。
func _hero_strike(ri: int) -> void:
var hero_alive: bool = int(oc._enemy_system.hero_unit().get("hp", 0)) > 0
var hero_target = oc._enemy_system.default_target()
if hero_alive and hero_target != null:
var ht := oc._damage.open_ticket(DamageController.KIND_HERO_ATTACK, "hero_attack", "主角攻击",
{"channel": "combat", "base_value": oc._hero_atk(), "target_rule": DamageController.TARGET_FRONT_ALIVE,
"ri": ri, "origin_cell": Vector2i(-1, -1),
"attack_type": String(oc._enemy_system.hero_unit().get("attack_type", "normal"))})
oc._damage.submit(ht)
# 连珠矢(内容数据驱动扩展批 T3——attack_commit 订阅施加面:开格节奏变攻击段数,本轮每开
# ~3 安全格 +1 段穿刺附加(上限 cap;段值 provisional 固定值不乘 atk——台账披露)。施加位
# 紧随主角攻击单开立=迁移前开单位(先于敌方行动单入队——flush 兑现序逐位一致)
for h in oc._hits_of(ht.context.get("fx_attack_hits", []) as Array, "attack_segments"):
var segments := mini(int(h.get("cap", 2)), int(oc.fx_host().facts_inject["dig_streak"]) / maxi(1, int(h.get("divisor", 3))))
for s in segments:
# source_class 用中性名;attack_segments 兑付通道保留泛用面
var ct := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "fx_streak_segments", "连击分段",
{"channel": "board", "base_value": int(h.get("value", 0)),
"target_rule": DamageController.TARGET_FRONT_ALIVE, "ri": ri,
"origin_cell": oc.minesweeper.hero_pos(), "attack_type": "pierce"})
oc._damage.submit(ct)
# 敌方行动(敌人技能池批——本拍动作开轮已摇定,此处按技能行兑现;行序 = 生成序)。
# 死亡即不动手:摇定后、兑现前死亡的敌不在存活序中——本拍技能作废,不 commit、不进冷却。
func _enemy_actions(ri: int) -> void:
for u in oc._enemy_system.query_alive("enemy"):
if int(oc._enemy_system.hero_unit().hp) <= 0:
break # hero hp=0 立即短路(死尸挨打行是归因噪音——用户拍板)
var skill_id := oc._enemy_system.pending_action(u)
var srow: Dictionary[Variant, Variant] = oc.db.combat.skill.row_by_id(skill_id)
if srow.is_empty():
oc._fatal("敌实例待发动作悬空: %s" % skill_id)
continue
if String(srow.type) == "buff" or String(srow.type) == "debuff":
# 增益/妨害技 = 0 伤害施放行:不掷不折;状态授予即数值兑现,技能冷却与增益持续两计时器分账。
# 落点由状态行 target_side 路由(buff → 敌方侧 / debuff → 主角侧——容器零改动,proposal-status-vocabulary §1)。
var dur: int = int(srow.get("buff_duration", 1))
oc._status_effects.apply_effect(String(srow.buff_ref), {"source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "round_index": ri}, dur)
oc._emit_row({"type": "cast", "source_class": "enemy_action", "origin_cell": Vector2i(-1, -1)}, ri, "%s 施放 %s" % [u.display_name, String(srow.display_name)], 0, {"skill_id": skill_id, "effect_id": String(srow.buff_ref), "duration": dur})
oc._enemy_system.commit_action(u)
continue
# 伤害技(含普攻——倍率恒 1)= 敌方伤害技单据:基线 = round(atk×倍率)、rider 预计算归开单侧
# (表值算术零随机),掷点/克制查表(spec 束 attack_type = 敌方攻击类型——迭代 24 N3 轴开列前恒空串不查表)/
# hero_defense×enemy_output 池乘区/下限/rider 后置/commit 全归控制器管线(兑现序 = 开单序;
# 来源敌已死在兑现时作废——死亡即不动手)。
var baseline: int = int(round(float(u.atk) * float(srow.get("damage_mult", 1.0))))
var rider := 0
if srow.has("rider_mult"):
rider = maxi(int(oc.db.combat.settlement.damage_roll.damage_floor), int(floor(float(u.atk) * float(srow.rider_mult))))
var et := oc._damage.open_ticket(DamageController.KIND_ENEMY_SKILL, "enemy_action",
"%s %s" % [u.display_name, String(srow.display_name)],
{"channel": "combat", "base_value": baseline, "target_rule": DamageController.TARGET_HERO,
"ri": ri, "origin_cell": Vector2i(-1, -1),
"attack_type": String(u.get("attack_type", "normal")), "rider": rider})
et["source_unit"] = u
et["context"] = {"skill_id": skill_id, "baseline": baseline}
oc._damage.submit(et)
# 地图伤害兑付(案C:战斗段末——怪物还手后按授予段事件序补提交统一结账;
# 主角死于怪物 → 地图账不兑,逐单留「未生效」行不作废静默——没生效也要留痕口径;
# 补提交后若地图直伤反杀主角,控制器 _halted 作废余单——战斗已败,胜负归判定段)
func _board_deferred_settle(ri: int) -> void:
if int(oc._enemy_system.hero_unit().get("hp", 0)) <= 0:
for t in (reward_grant.defer["queue"] as Array):
oc._emit_row({"type": "reward_ineffective", "source_class": String(t.get("source_class", "")), "origin_cell": t.get("origin_cell", Vector2i(-1, -1))}, ri,
"%s 未生效(主角已倒下)" % String(t.get("display", "")), 0, {"forfeit": "hero_down"})
else:
for t in (reward_grant.defer["queue"] as Array):
oc._damage.submit(t)
(reward_grant.defer["queue"] as Array).clear()
oc._damage.flush() # 战斗段界结账(B 模式——主角单+敌方单+地图单按开单序统一兑现;A 模式地图单补提交时即结)