6.6 KiB
6.6 KiB
RPG 战斗败北结果修复(2026-04-27)
更新时间:2026-04-27
背景
线上出现了一个确定性战斗兼容链 bug:
- 玩家在 RPG 战斗中已经被打死。
- 画面却突然跳到“对方已经败下阵来”的胜利收束。
- 最终走了战斗胜利固定流程,而不是死亡动画与三秒后复活。
这与既有战斗结束规则冲突:
- 玩家血量小于等于
0时必须优先进入败北/死亡流程。 - 战斗胜利只允许在玩家仍然存活、且敌方被击败时成立。
根因
问题出在 server-rs/crates/module-runtime-story-compat/src/battle.rs 的 compat 战斗结算:
apply_player_damage(...)旧实现把玩家生命最低钳在1,不会真正写出0血。resolve_battle_action(...)旧实现只有ongoing / victory / spar_complete三种结果,没有正式defeat分支。- 当同一回合里敌方也被打到
0附近时,结算会只看敌方血量并直接归类到victory。
这会把“玩家应败北”的回合错误地写成“敌方已败下阵来”。
继续复测后又确认,作品测试 里还有第二层根因:
- 作品测试并不总是走服务端 compat 战斗结算;
- 一部分战斗点击仍会走前端本地
src/hooks/combat/battlePlan.ts; - 这条本地链原先会在玩家或同伴刚打掉最后一只敌人时,立刻:
- 把敌方从
sceneHostileNpcs移除; - 把
currentNpcBattleOutcome直接写成fight_victory;
- 把敌方从
- 这样会导致同一轮中原本还应该出手的敌方单位失去反击机会;
- 体感上就会变成“我明明该死了,但作品测试先把对方判成败下阵来”。
本次修复
1. 后端 compat 战斗结算补齐败北态
在 module-runtime-story-compat::battle 中补齐:
- 显式
BattleResolutionOutcome:ongoing / victory / spar_complete / defeat apply_player_damage(...)允许把playerHp写到0- 新增统一胜负决议:
- 玩家
hp <= 0时优先判定defeat - 只有玩家仍存活时,敌方
hp <= 0才能进入victory / spar_complete
- 玩家
2. 服务端状态写回补齐 fight_defeat
当 compat 战斗判定为失败时:
presentation.battle.outcome = "defeat"currentNpcBattleOutcome = "fight_defeat"- 关闭战斗态,清理当前遭遇与敌对列表
- 不发放胜利经验、不累计
hostileNpcsDefeated
3. 前端服务端战斗回包分支补齐失败态
前端 storyChoiceRuntime 同步修正:
- 服务端回包若
playerHp <= 0,无论 battle outcome 是否同时包含敌方掉血,都优先走死亡动画与复活链 - 敌方是否播放死亡态时,
defeat不再被当成“目标已被击败”
4. 本地 battlePlan 改为整轮后统一收束
在 src/hooks/combat/battlePlan.ts 中同步修正:
- 普通战斗不再在玩家/同伴动作阶段即时写入
fight_victory - 敌方被打到
0血时,先只保留为“本轮已死亡但尚未清场”的状态 - 整轮 turn order 跑完后,再统一按以下优先级判定:
- 玩家
playerHp <= 0:fight_defeat - 否则若敌方全部
hp <= 0:fight_victory - 否则继续战斗
- 玩家
spar_complete仍维持切磋的即时收束规则,不和正式战斗混用
类型同步
本轮同步扩展:
NpcBattleOutcome:新增fight_defeatStoryNpcChatState.combatContext.battleOutcome:新增defeataiService的战斗上下文类型:新增defeat
回归测试
新增以下回归:
module-runtime-story-compat:- 同回合双方都归零时,必须优先判定为
defeat
- 同回合双方都归零时,必须优先判定为
- 前端
storyChoiceRuntime:- 服务端返回
defeat且playerHp = 0时,必须进入死亡复活流程,不能进入胜利收束
- 服务端返回
- 前端
battlePlan:- 作品测试 / 本地战斗链里,同一轮玩家先手打空敌方但随后自己被打死时,最终必须判定为
fight_defeat
- 作品测试 / 本地战斗链里,同一轮玩家先手打空敌方但随后自己被打死时,最终必须判定为
- 前端
storyChoiceContinuation/useRpgRuntimeNpcInteraction:fight_defeat不能再被当成“本地 NPC 战斗胜利”进入战后收束- 玩家死亡后必须直接走死亡复活链,且复活时重置到开局场景第一幕,不能先推进下一幕
- 前端
postBattleFlow:- 复活回到开局场景时,必须重新走首幕 encounter preview 恢复链
- 第一幕主交互 NPC 与同幕陪衬 NPC 要继续沿用既有场景槽位,不能退化成全部站成一排
继续收口(2026-04-28)
继续复测后又确认,作品测试里还残留一层“败北被当成胜利”的本地剧情续写问题:
storyChoiceContinuation旧判断把currentNpcBattleOutcome只要是 truthy 就当成本地 NPC 战斗可收束;- 这会把
fight_defeat也误送进finalizeNpcBattleResult(...); - 后续又因为
!nextState.inBattle条件过宽,继续走到buildPostBattleVictoryStory(...),表现为:- 死亡后仍显示“对方已经败下阵来”
- 并且还会把场景幕推进到下一幕
本轮补充修正如下:
- 本地 NPC 战斗收束只接受
fight_victory / spar_complete fight_defeat一律交回死亡复活主链- NPC 战后结算 helper 显式拒绝
fight_defeat - 本地
battle的战后推进只允许:- 正式 NPC 战斗明确
fight_victory - 切磋明确
spar_complete - 非 NPC 通用敌对战斗
!inBattle
- 正式 NPC 战斗明确
- 这样可以保证作品测试、幕预览与正式游戏在“死亡后回开局第一幕”这一口径上继续对齐
继续收口:复活后首幕 NPC 与站位恢复(2026-04-28)
在继续复测后,又确认死亡复活链还有一层表现问题:
- 角色虽然已经回到开局场景第一幕;
- 但复活态旧实现只是重置
currentSceneActState,没有重新恢复第一幕 encounter preview; - 于是画布只能把第一幕 NPC 都按普通 ambient 角色绘制;
- 视觉上就会表现为:
- 主交互 NPC 没有按首幕重新成为前景目标
- 同幕 NPC 失去原本的前后排关系
- 最终看起来像“所有人站成一排”
本轮补充修正如下:
buildRevivedFirstSceneState(...)在重置到首幕之后,立即复用ensureSceneEncounterPreview(...)- 这样复活链与“开局进入世界 / 场景正常进场”继续共用同一套首幕恢复逻辑
- 第一幕主交互 NPC、同幕陪衬 NPC 与既有槽位会一起恢复,不再额外发明一套复活专用站位规则
结论
本次修复后,RPG 战斗 compat 主链的胜负判定口径变为:
- 玩家死亡优先于敌方倒地胜利
- 胜利与败北都只走确定性固定流程
- 不再出现“玩家已死却结算成战斗胜利”的串线结果