Add fifth Meng Huo capture battle

This commit is contained in:
2026-06-23 23:38:57 +09:00
parent b56f613e9f
commit 189766491f
11 changed files with 867 additions and 63 deletions

View File

@@ -76,15 +76,16 @@ Build a small complete tactical RPG loop that can grow into a longer Romance of
- Forty-ninth battle Meng Huo second-capture route, turning the first release into a repeated Seven Captures arc with a 98x80 marsh-jungle battlefield, renewed pacify-leader objective, second-release camp dialogues, and village/captive reassurance visits
- Fiftieth battle Meng Huo third-capture route, making the growing recruited roster matter through a Zhuge Liang-led seven-officer sortie choice, a 100x82 clan-and-marsh battlefield, named southern clan officers, and post-battle clan-persuasion camp events
- Fifty-first battle Meng Huo fourth-capture route, where prior clan-persuasion camp choices become battlefield goals through an amnesty-route objective, fire-fort suppression, a 102x84 poison-river battlefield, and Ma Chao/Ma Dai pursuit-control sortie recommendations
- Fifty-second battle Meng Huo fifth-capture route, where hardline clans split from wavering clans across a 104x86 bamboo-ravine battlefield, with hardliner-separation, village-protection, and fort-suppression objectives plus post-battle ledger/banner camp events
- Camp progress timeline tab that summarizes Liu Bei's long campaign arc, completed battles, current chapter, latest battle, and next major chapter
- Tactical sortie preparation panel with battle-specific sortie limits, recommended officers with reasons, class role, named equipment, core stats, bond partner, next-map terrain suitability, focused officer analysis, deployment preview, formation roles, active bond count, recruited-officer count, reserve roster summary, and readiness advice for each deployable officer
- Officer collection support in camp, including full roster status, selected/reserve/recommended markers, selectable reserve drill focus, and post-battle reserve training growth for benched officers
- Treasure equipment effects wired into battle previews and resolution, including named weapon damage bonuses, defensive treasure mitigation, support recovery bonuses, turn-start charm recovery, and equipment-specific growth bonuses
- Flow verification script from title through the fourth Meng Huo capture camp, King of Hanzhong council milestone, Shu-Han foundation milestone, recruit sortie selection, Liu Biao visit rewards, Zhuge Liang recruitment, Bowang/Changban/Jiangdong/Red Cliffs/Jing Province/Guiyang/Wuling/Changsha/Yi Province/Fu Pass/Luo Castle/Luofeng/Luo main gate/Mianzhu/Chengdu/Jiameng/Yangping/Dingjun/Hanzhong/Jing defense/Fan Castle/Han River/Fan siege/Jing rear/Gongan collapse/Maicheng isolation/Yiling vanguard/Yiling fire/Baidi entrustment/Nanzhong stabilization/Meng Huo pacification camp states, campaign timeline state, and camp save state
- Flow verification script from title through the fifth Meng Huo capture camp, King of Hanzhong council milestone, Shu-Han foundation milestone, recruit sortie selection, Liu Biao visit rewards, Zhuge Liang recruitment, Bowang/Changban/Jiangdong/Red Cliffs/Jing Province/Guiyang/Wuling/Changsha/Yi Province/Fu Pass/Luo Castle/Luofeng/Luo main gate/Mianzhu/Chengdu/Jiameng/Yangping/Dingjun/Hanzhong/Jing defense/Fan Castle/Han River/Fan siege/Jing rear/Gongan collapse/Maicheng isolation/Yiling vanguard/Yiling fire/Baidi entrustment/Nanzhong stabilization/Meng Huo pacification camp states, campaign timeline state, and camp save state
## Next Steps
1. Build the fifth Seven Captures Nanman battle so hardline clan separation and repeated amnesty routes begin changing enemy morale, optional objectives, and post-battle persuasion outcomes
1. Build the sixth Seven Captures Nanman battle so Meng Huo's remaining pride and the now-visible clan split create sharper capture, restraint, and village-trust tradeoffs
2. Expand post-Baidi and post-Nanzhong camp consequences with grief checks, recovery policy choices, village reassurance, captive handling, and regency decisions that affect sortie recommendations
3. Expand reserve planning into individual officer assignments, including targeted class practice, paired bond training, and camp task slots
4. Add a dedicated treasure/equipment management view so players can compare special effects and growth

View File

@@ -1,4 +1,4 @@
import { spawn } from 'node:child_process';
import { spawn } from 'node:child_process';
import { chromium } from 'playwright';
const targetUrl = process.env.VERIFY_URL ?? 'http://localhost:5173/';
@@ -1739,7 +1739,7 @@ try {
if (
progressTabState?.activeTab !== 'progress' ||
progressTabState.campaignProgress?.completedKnown !== 17 ||
progressTabState.campaignProgress?.totalKnown !== 51 ||
progressTabState.campaignProgress?.totalKnown !== 52 ||
progressTabState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
progressTabState.campaignProgress?.latestBattleTitle !== '융중 방문로' ||
progressTabState.campaignProgress?.nextBattleTitle !== '박망파 매복전'
@@ -1891,7 +1891,7 @@ try {
if (
postBowangProgressState?.activeTab !== 'progress' ||
postBowangProgressState.campaignProgress?.completedKnown !== 18 ||
postBowangProgressState.campaignProgress?.totalKnown !== 51 ||
postBowangProgressState.campaignProgress?.totalKnown !== 52 ||
postBowangProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
postBowangProgressState.campaignProgress?.latestBattleTitle !== '박망파 매복전' ||
postBowangProgressState.campaignProgress?.nextBattleTitle !== '장판파 피난로'
@@ -2029,7 +2029,7 @@ try {
if (
postChangbanProgressState?.activeTab !== 'progress' ||
postChangbanProgressState.campaignProgress?.completedKnown !== 19 ||
postChangbanProgressState.campaignProgress?.totalKnown !== 51 ||
postChangbanProgressState.campaignProgress?.totalKnown !== 52 ||
postChangbanProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
postChangbanProgressState.campaignProgress?.latestBattleTitle !== '장판파 피난로' ||
postChangbanProgressState.campaignProgress?.nextBattleTitle !== '강동 사절로'
@@ -2167,7 +2167,7 @@ try {
if (
postJiangdongProgressState?.activeTab !== 'progress' ||
postJiangdongProgressState.campaignProgress?.completedKnown !== 20 ||
postJiangdongProgressState.campaignProgress?.totalKnown !== 51 ||
postJiangdongProgressState.campaignProgress?.totalKnown !== 52 ||
postJiangdongProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
postJiangdongProgressState.campaignProgress?.latestBattleTitle !== '강동 사절로' ||
postJiangdongProgressState.campaignProgress?.nextBattleTitle !== '적벽 전초전'
@@ -2307,7 +2307,7 @@ try {
if (
postRedCliffsProgressState?.activeTab !== 'progress' ||
postRedCliffsProgressState.campaignProgress?.completedKnown !== 21 ||
postRedCliffsProgressState.campaignProgress?.totalKnown !== 51 ||
postRedCliffsProgressState.campaignProgress?.totalKnown !== 52 ||
postRedCliffsProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
postRedCliffsProgressState.campaignProgress?.latestBattleTitle !== '적벽 전초전' ||
postRedCliffsProgressState.campaignProgress?.nextBattleTitle !== '적벽 화공전'
@@ -2447,7 +2447,7 @@ try {
if (
postFireAttackProgressState?.activeTab !== 'progress' ||
postFireAttackProgressState.campaignProgress?.completedKnown !== 22 ||
postFireAttackProgressState.campaignProgress?.totalKnown !== 51 ||
postFireAttackProgressState.campaignProgress?.totalKnown !== 52 ||
postFireAttackProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postFireAttackProgressState.campaignProgress?.latestBattleTitle !== '적벽 화공전' ||
postFireAttackProgressState.campaignProgress?.nextBattleTitle !== '형주 남부 진입전'
@@ -2590,7 +2590,7 @@ try {
if (
postJingzhouProgressState?.activeTab !== 'progress' ||
postJingzhouProgressState.campaignProgress?.completedKnown !== 23 ||
postJingzhouProgressState.campaignProgress?.totalKnown !== 51 ||
postJingzhouProgressState.campaignProgress?.totalKnown !== 52 ||
postJingzhouProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postJingzhouProgressState.campaignProgress?.latestBattleTitle !== '형주 남부 진입전' ||
postJingzhouProgressState.campaignProgress?.nextBattleTitle !== '계양 설득전'
@@ -2732,7 +2732,7 @@ try {
if (
postGuiyangProgressState?.activeTab !== 'progress' ||
postGuiyangProgressState.campaignProgress?.completedKnown !== 24 ||
postGuiyangProgressState.campaignProgress?.totalKnown !== 51 ||
postGuiyangProgressState.campaignProgress?.totalKnown !== 52 ||
postGuiyangProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postGuiyangProgressState.campaignProgress?.latestBattleTitle !== '계양 설득전' ||
postGuiyangProgressState.campaignProgress?.nextBattleTitle !== '무릉 산길 확보전'
@@ -2875,7 +2875,7 @@ try {
if (
postWulingProgressState?.activeTab !== 'progress' ||
postWulingProgressState.campaignProgress?.completedKnown !== 25 ||
postWulingProgressState.campaignProgress?.totalKnown !== 51 ||
postWulingProgressState.campaignProgress?.totalKnown !== 52 ||
postWulingProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postWulingProgressState.campaignProgress?.latestBattleTitle !== '무릉 산길 확보전' ||
postWulingProgressState.campaignProgress?.nextBattleTitle !== '장사 노장 대면전'
@@ -3024,7 +3024,7 @@ try {
if (
postChangshaProgressState?.activeTab !== 'progress' ||
postChangshaProgressState.campaignProgress?.completedKnown !== 26 ||
postChangshaProgressState.campaignProgress?.totalKnown !== 51 ||
postChangshaProgressState.campaignProgress?.totalKnown !== 52 ||
postChangshaProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postChangshaProgressState.campaignProgress?.latestBattleTitle !== '장사 노장 대면전' ||
postChangshaProgressState.campaignProgress?.nextBattleTitle !== '익주 원군로'
@@ -3169,7 +3169,7 @@ try {
if (
postYizhouProgressState?.activeTab !== 'progress' ||
postYizhouProgressState.campaignProgress?.completedKnown !== 27 ||
postYizhouProgressState.campaignProgress?.totalKnown !== 51 ||
postYizhouProgressState.campaignProgress?.totalKnown !== 52 ||
postYizhouProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postYizhouProgressState.campaignProgress?.latestBattleTitle !== '익주 원군로' ||
postYizhouProgressState.campaignProgress?.nextBattleTitle !== '부수관 진입전'
@@ -3316,7 +3316,7 @@ try {
if (
postFuPassProgressState?.activeTab !== 'progress' ||
postFuPassProgressState.campaignProgress?.completedKnown !== 28 ||
postFuPassProgressState.campaignProgress?.totalKnown !== 51 ||
postFuPassProgressState.campaignProgress?.totalKnown !== 52 ||
postFuPassProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postFuPassProgressState.campaignProgress?.latestBattleTitle !== '부수관 진입전' ||
postFuPassProgressState.campaignProgress?.nextBattleTitle !== '낙성 외곽전'
@@ -3465,7 +3465,7 @@ try {
if (
postLuoProgressState?.activeTab !== 'progress' ||
postLuoProgressState.campaignProgress?.completedKnown !== 29 ||
postLuoProgressState.campaignProgress?.totalKnown !== 51 ||
postLuoProgressState.campaignProgress?.totalKnown !== 52 ||
postLuoProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postLuoProgressState.campaignProgress?.latestBattleTitle !== '낙성 외곽전' ||
postLuoProgressState.campaignProgress?.nextBattleTitle !== '낙봉파 매복전'
@@ -3611,7 +3611,7 @@ try {
if (
postLuofengProgressState?.activeTab !== 'progress' ||
postLuofengProgressState.campaignProgress?.completedKnown !== 30 ||
postLuofengProgressState.campaignProgress?.totalKnown !== 51 ||
postLuofengProgressState.campaignProgress?.totalKnown !== 52 ||
postLuofengProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postLuofengProgressState.campaignProgress?.latestBattleTitle !== '낙봉파 매복전' ||
postLuofengProgressState.campaignProgress?.nextBattleTitle !== '낙성 본성 공략'
@@ -3763,7 +3763,7 @@ try {
if (
postLuoMainProgressState?.activeTab !== 'progress' ||
postLuoMainProgressState.campaignProgress?.completedKnown !== 31 ||
postLuoMainProgressState.campaignProgress?.totalKnown !== 51 ||
postLuoMainProgressState.campaignProgress?.totalKnown !== 52 ||
postLuoMainProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postLuoMainProgressState.campaignProgress?.latestBattleTitle !== '낙성 본성 공략' ||
postLuoMainProgressState.campaignProgress?.nextBattleTitle !== '면죽관 압박전'
@@ -3916,7 +3916,7 @@ try {
if (
postMianzhuProgressState?.activeTab !== 'progress' ||
postMianzhuProgressState.campaignProgress?.completedKnown !== 32 ||
postMianzhuProgressState.campaignProgress?.totalKnown !== 51 ||
postMianzhuProgressState.campaignProgress?.totalKnown !== 52 ||
postMianzhuProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postMianzhuProgressState.campaignProgress?.latestBattleTitle !== '면죽관 압박전' ||
postMianzhuProgressState.campaignProgress?.nextBattleTitle !== '성도 항복 권고전'
@@ -4070,7 +4070,7 @@ try {
if (
postChengduProgressState?.activeTab !== 'progress' ||
postChengduProgressState.campaignProgress?.completedKnown !== 33 ||
postChengduProgressState.campaignProgress?.totalKnown !== 51 ||
postChengduProgressState.campaignProgress?.totalKnown !== 52 ||
postChengduProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' ||
postChengduProgressState.campaignProgress?.latestBattleTitle !== '성도 항복 권고전' ||
postChengduProgressState.campaignProgress?.nextBattleTitle !== '가맹관 마초 대면전'
@@ -4225,7 +4225,7 @@ try {
if (
postJiamengProgressState?.activeTab !== 'progress' ||
postJiamengProgressState.campaignProgress?.completedKnown !== 34 ||
postJiamengProgressState.campaignProgress?.totalKnown !== 51 ||
postJiamengProgressState.campaignProgress?.totalKnown !== 52 ||
postJiamengProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' ||
postJiamengProgressState.campaignProgress?.latestBattleTitle !== '가맹관 마초 대면전' ||
postJiamengProgressState.campaignProgress?.nextBattleTitle !== '양평관 정찰전'
@@ -4381,7 +4381,7 @@ try {
if (
postYangpingProgressState?.activeTab !== 'progress' ||
postYangpingProgressState.campaignProgress?.completedKnown !== 35 ||
postYangpingProgressState.campaignProgress?.totalKnown !== 51 ||
postYangpingProgressState.campaignProgress?.totalKnown !== 52 ||
postYangpingProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' ||
postYangpingProgressState.campaignProgress?.latestBattleTitle !== '양평관 정찰전' ||
postYangpingProgressState.campaignProgress?.nextBattleTitle !== '정군산 전초전'
@@ -4538,7 +4538,7 @@ try {
if (
postDingjunProgressState?.activeTab !== 'progress' ||
postDingjunProgressState.campaignProgress?.completedKnown !== 36 ||
postDingjunProgressState.campaignProgress?.totalKnown !== 51 ||
postDingjunProgressState.campaignProgress?.totalKnown !== 52 ||
postDingjunProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' ||
postDingjunProgressState.campaignProgress?.latestBattleTitle !== '정군산 전초전' ||
postDingjunProgressState.campaignProgress?.nextBattleTitle !== '한중 결전'
@@ -4691,7 +4691,7 @@ try {
if (
postHanzhongProgressState?.activeTab !== 'progress' ||
postHanzhongProgressState.campaignProgress?.completedKnown !== 37 ||
postHanzhongProgressState.campaignProgress?.totalKnown !== 51 ||
postHanzhongProgressState.campaignProgress?.totalKnown !== 52 ||
postHanzhongProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' ||
postHanzhongProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' ||
postHanzhongProgressState.campaignProgress?.nextBattleTitle !== '한중왕 즉위 준비'
@@ -4752,7 +4752,7 @@ try {
if (
postKingCouncilProgressState?.activeTab !== 'progress' ||
postKingCouncilProgressState.campaignProgress?.completedKnown !== 37 ||
postKingCouncilProgressState.campaignProgress?.totalKnown !== 51 ||
postKingCouncilProgressState.campaignProgress?.totalKnown !== 52 ||
postKingCouncilProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' ||
postKingCouncilProgressState.campaignProgress?.nextBattleTitle !== '촉한 건국 선포'
) {
@@ -4812,7 +4812,7 @@ try {
if (
postFoundationProgressState?.activeTab !== 'progress' ||
postFoundationProgressState.campaignProgress?.completedKnown !== 37 ||
postFoundationProgressState.campaignProgress?.totalKnown !== 51 ||
postFoundationProgressState.campaignProgress?.totalKnown !== 52 ||
postFoundationProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' ||
postFoundationProgressState.campaignProgress?.nextBattleTitle !== '형주 방위 전초전'
) {
@@ -4963,7 +4963,7 @@ try {
if (
postJingDefenseProgressState?.activeTab !== 'progress' ||
postJingDefenseProgressState.campaignProgress?.completedKnown !== 38 ||
postJingDefenseProgressState.campaignProgress?.totalKnown !== 51 ||
postJingDefenseProgressState.campaignProgress?.totalKnown !== 52 ||
postJingDefenseProgressState.campaignProgress?.latestBattleTitle !== '형주 방위 전초전' ||
postJingDefenseProgressState.campaignProgress?.nextBattleTitle !== '번성 외곽 압박전'
) {
@@ -5116,7 +5116,7 @@ try {
if (
postFanCastleProgressState?.activeTab !== 'progress' ||
postFanCastleProgressState.campaignProgress?.completedKnown !== 39 ||
postFanCastleProgressState.campaignProgress?.totalKnown !== 51 ||
postFanCastleProgressState.campaignProgress?.totalKnown !== 52 ||
postFanCastleProgressState.campaignProgress?.latestBattleTitle !== '번성 외곽 압박전' ||
postFanCastleProgressState.campaignProgress?.nextBattleTitle !== '한수 수공'
) {
@@ -5270,7 +5270,7 @@ try {
if (
postHanRiverProgressState?.activeTab !== 'progress' ||
postHanRiverProgressState.campaignProgress?.completedKnown !== 40 ||
postHanRiverProgressState.campaignProgress?.totalKnown !== 51 ||
postHanRiverProgressState.campaignProgress?.totalKnown !== 52 ||
postHanRiverProgressState.campaignProgress?.latestBattleTitle !== '한수 수공' ||
postHanRiverProgressState.campaignProgress?.nextBattleTitle !== '번성 공성전'
) {
@@ -5424,7 +5424,7 @@ try {
if (
postFanSiegeProgressState?.activeTab !== 'progress' ||
postFanSiegeProgressState.campaignProgress?.completedKnown !== 41 ||
postFanSiegeProgressState.campaignProgress?.totalKnown !== 51 ||
postFanSiegeProgressState.campaignProgress?.totalKnown !== 52 ||
postFanSiegeProgressState.campaignProgress?.latestBattleTitle !== '번성 공성전' ||
postFanSiegeProgressState.campaignProgress?.nextBattleTitle !== '강릉 나루 경계전'
) {
@@ -5586,7 +5586,7 @@ try {
if (
postJingRearProgressState?.activeTab !== 'progress' ||
postJingRearProgressState.campaignProgress?.completedKnown !== 42 ||
postJingRearProgressState.campaignProgress?.totalKnown !== 51 ||
postJingRearProgressState.campaignProgress?.totalKnown !== 52 ||
postJingRearProgressState.campaignProgress?.latestBattleTitle !== '강릉 나루 경계전' ||
postJingRearProgressState.campaignProgress?.nextBattleTitle !== '공안 성문 변고'
) {
@@ -5741,7 +5741,7 @@ try {
if (
postGonganProgressState?.activeTab !== 'progress' ||
postGonganProgressState.campaignProgress?.completedKnown !== 43 ||
postGonganProgressState.campaignProgress?.totalKnown !== 51 ||
postGonganProgressState.campaignProgress?.totalKnown !== 52 ||
postGonganProgressState.campaignProgress?.latestBattleTitle !== '공안 성문 변고' ||
postGonganProgressState.campaignProgress?.nextBattleTitle !== '맥성 고립전'
) {
@@ -5899,7 +5899,7 @@ try {
if (
postMaichengProgressState?.activeTab !== 'progress' ||
postMaichengProgressState.campaignProgress?.completedKnown !== 44 ||
postMaichengProgressState.campaignProgress?.totalKnown !== 51 ||
postMaichengProgressState.campaignProgress?.totalKnown !== 52 ||
postMaichengProgressState.campaignProgress?.latestBattleTitle !== '맥성 고립전' ||
postMaichengProgressState.campaignProgress?.nextBattleTitle !== '이릉 진격로'
) {
@@ -6056,7 +6056,7 @@ try {
if (
postYilingProgressState?.activeTab !== 'progress' ||
postYilingProgressState.campaignProgress?.completedKnown !== 45 ||
postYilingProgressState.campaignProgress?.totalKnown !== 51 ||
postYilingProgressState.campaignProgress?.totalKnown !== 52 ||
postYilingProgressState.campaignProgress?.latestBattleTitle !== '이릉 진격로' ||
postYilingProgressState.campaignProgress?.nextBattleTitle !== '이릉 화공전'
) {
@@ -6214,7 +6214,7 @@ try {
if (
postYilingFireProgressState?.activeTab !== 'progress' ||
postYilingFireProgressState.campaignProgress?.completedKnown !== 46 ||
postYilingFireProgressState.campaignProgress?.totalKnown !== 51 ||
postYilingFireProgressState.campaignProgress?.totalKnown !== 52 ||
postYilingFireProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' ||
postYilingFireProgressState.campaignProgress?.nextBattleTitle !== '백제성 유탁'
) {
@@ -6280,7 +6280,7 @@ try {
if (
postBaidiProgressState?.activeTab !== 'progress' ||
postBaidiProgressState.campaignProgress?.completedKnown !== 46 ||
postBaidiProgressState.campaignProgress?.totalKnown !== 51 ||
postBaidiProgressState.campaignProgress?.totalKnown !== 52 ||
postBaidiProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' ||
postBaidiProgressState.campaignProgress?.nextBattleTitle !== '남중 안정로'
) {
@@ -6441,7 +6441,7 @@ try {
if (
postNanzhongProgressState?.activeTab !== 'progress' ||
postNanzhongProgressState.campaignProgress?.completedKnown !== 47 ||
postNanzhongProgressState.campaignProgress?.totalKnown !== 51 ||
postNanzhongProgressState.campaignProgress?.totalKnown !== 52 ||
postNanzhongProgressState.campaignProgress?.latestBattleTitle !== '남중 안정로' ||
postNanzhongProgressState.campaignProgress?.nextBattleTitle !== '맹획 본대전'
) {
@@ -6603,7 +6603,7 @@ try {
if (
postMengHuoProgressState?.activeTab !== 'progress' ||
postMengHuoProgressState.campaignProgress?.completedKnown !== 48 ||
postMengHuoProgressState.campaignProgress?.totalKnown !== 51 ||
postMengHuoProgressState.campaignProgress?.totalKnown !== 52 ||
postMengHuoProgressState.campaignProgress?.latestBattleTitle !== '맹획 본대전' ||
postMengHuoProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 2차전'
) {
@@ -6765,7 +6765,7 @@ try {
if (
postSecondCaptureProgressState?.activeTab !== 'progress' ||
postSecondCaptureProgressState.campaignProgress?.completedKnown !== 49 ||
postSecondCaptureProgressState.campaignProgress?.totalKnown !== 51 ||
postSecondCaptureProgressState.campaignProgress?.totalKnown !== 52 ||
postSecondCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 2차전' ||
postSecondCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 3차전'
) {
@@ -6930,7 +6930,7 @@ try {
if (
postThirdCaptureProgressState?.activeTab !== 'progress' ||
postThirdCaptureProgressState.campaignProgress?.completedKnown !== 50 ||
postThirdCaptureProgressState.campaignProgress?.totalKnown !== 51 ||
postThirdCaptureProgressState.campaignProgress?.totalKnown !== 52 ||
postThirdCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 3차전' ||
postThirdCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 4차전'
) {
@@ -7096,14 +7096,180 @@ try {
if (
postFourthCaptureProgressState?.activeTab !== 'progress' ||
postFourthCaptureProgressState.campaignProgress?.completedKnown !== 51 ||
postFourthCaptureProgressState.campaignProgress?.totalKnown !== 51 ||
postFourthCaptureProgressState.campaignProgress?.totalKnown !== 52 ||
postFourthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 4차전' ||
postFourthCaptureProgressState.campaignProgress?.nextBattleTitle !== '준비 중'
postFourthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 5차전'
) {
throw new Error(`Expected post-fourth-capture progress tab to complete the fifty-first battle and pause before the fifth capture arc: ${JSON.stringify(postFourthCaptureProgressState?.campaignProgress)}`);
throw new Error(`Expected post-fourth-capture progress tab to complete the fifty-first battle and point to the fifth capture arc: ${JSON.stringify(postFourthCaptureProgressState?.campaignProgress)}`);
}
await page.screenshot({ path: 'dist/verification-post-fourth-capture-progress.png', fullPage: true });
await page.mouse.click(1120, 38);
await page.waitForTimeout(180);
const fifthCaptureSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
if (
!fifthCaptureSortieState?.sortieVisible ||
fifthCaptureSortieState.campaign?.step !== 'fifty-first-camp' ||
fifthCaptureSortieState.campTitle !== '칠종칠금 4차전 후 군영' ||
fifthCaptureSortieState.sortiePlan?.objectiveLine !== '칠종칠금 5차전 · 맹획 다섯 번째 생포와 강경파 분리' ||
fifthCaptureSortieState.sortiePlan?.maxCount !== 7 ||
fifthCaptureSortieState.sortiePlan?.recommendedTotal !== 7 ||
!fifthCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) ||
!fifthCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'huang-quan' && unit.recommended) ||
!fifthCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'ma-liang' && unit.recommended) ||
!fifthCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'huang-zhong' && unit.recommended) ||
!fifthCaptureSortieState.sortiePlan?.recruitedLine?.includes('합류 무장') ||
fifthCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'liu-bei')
) {
throw new Error(`Expected fifth Meng Huo sortie prep to expose hardliner-separation recommendations, Zhuge Liang lock, and the expanded recruited roster: ${JSON.stringify(fifthCaptureSortieState)}`);
}
assertSortieTacticalRoster(fifthCaptureSortieState, [
'guan-yu',
'zhang-fei',
'jian-yong',
'mi-zhu',
'sun-qian',
'zhao-yun',
'zhuge-liang',
'ma-liang',
'yi-ji',
'gong-zhi',
'huang-zhong',
'wei-yan',
'pang-tong',
'fa-zheng',
'wu-yi',
'yan-yan',
'li-yan',
'huang-quan',
'ma-chao',
'ma-dai',
'wang-ping'
]);
const fifthCapturePriorityUnits = ['huang-quan', 'ma-liang', 'wang-ping', 'zhao-yun', 'huang-zhong', 'ma-dai'];
for (const unitId of fifthCapturePriorityUnits) {
const currentSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
if (!currentSortieState.sortieRoster?.some((unit) => unit.id === unitId && unit.selected)) {
const removable = currentSortieState.sortieRoster?.find(
(unit) => unit.selected && !unit.required && !fifthCapturePriorityUnits.includes(unit.id)
);
if (removable) {
await clickSortieRosterUnit(page, removable.id);
}
await clickSortieRosterUnit(page, unitId);
}
}
const fifthCaptureSortieReadyState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
if (
!fifthCapturePriorityUnits.every((unitId) =>
fifthCaptureSortieReadyState.sortieRoster?.some((unit) => unit.id === unitId && unit.selected)
) ||
!fifthCaptureSortieReadyState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) ||
fifthCaptureSortieReadyState.sortieRoster?.some((unit) => unit.id === 'liu-bei') ||
fifthCaptureSortieReadyState.sortiePlan?.selectedCount !== 7 ||
fifthCaptureSortieReadyState.sortiePlan?.recommendedSelectedCount < 7
) {
throw new Error(`Expected fifth Meng Huo sortie to deploy Zhuge Liang, Huang Quan, Ma Liang, Wang Ping, Zhao Yun, Huang Zhong, and Ma Dai: ${JSON.stringify(fifthCaptureSortieReadyState)}`);
}
await page.screenshot({ path: 'dist/verification-fifth-capture-sortie.png', fullPage: true });
await page.mouse.click(1068, 646);
await page.waitForFunction(() => {
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
return activeScenes.includes('StoryScene');
});
await page.screenshot({ path: 'dist/verification-fifth-capture-story.png', fullPage: true });
for (let i = 0; i < 70; i += 1) {
const enteredFiftySecondBattle = await page.evaluate(() => {
const state = window.__HEROS_DEBUG__?.battle();
return state?.scene === 'BattleScene' && state?.battleId === 'fifty-second-battle-meng-huo-fifth-capture';
});
if (enteredFiftySecondBattle) {
break;
}
await page.keyboard.press('Space');
await page.waitForTimeout(320);
}
await page.waitForFunction(() => {
const state = window.__HEROS_DEBUG__?.battle();
return state?.scene === 'BattleScene' && state?.battleId === 'fifty-second-battle-meng-huo-fifth-capture' && state?.battleOutcome === null && state?.phase === 'idle';
});
await page.screenshot({ path: 'dist/verification-fifty-second-battle.png', fullPage: true });
const fiftySecondBattleState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle());
const fiftySecondEnemies = fiftySecondBattleState.units.filter((unit) => unit.faction === 'enemy');
const fiftySecondAllies = fiftySecondBattleState.units.filter((unit) => unit.faction === 'ally');
const fiftySecondEnemyBehaviors = new Set(fiftySecondEnemies.map((unit) => unit.ai));
if (
fiftySecondBattleState.camera?.mapWidth !== 104 ||
fiftySecondBattleState.camera?.mapHeight !== 86 ||
fiftySecondBattleState.victoryConditionLabel !== '맹획 다섯 번째 생포와 강경파 분리' ||
!fiftySecondBattleState.objectives?.some((objective) => objective.id === 'pacify-meng-huo-fifth' && objective.label === '맹획 다섯 번째 생포') ||
!fiftySecondBattleState.objectives?.some((objective) => objective.id === 'hardliner-separation' && objective.label === '강경파 분리로 확보') ||
fiftySecondEnemies.length < 40 ||
!fiftySecondEnemyBehaviors.has('aggressive') ||
!fiftySecondEnemyBehaviors.has('guard') ||
!fiftySecondEnemyBehaviors.has('hold') ||
!fiftySecondEnemies.some((unit) => unit.id === 'menghuo-fifth-leader') ||
!fiftySecondEnemies.some((unit) => unit.id === 'menghuo-fifth-officer-zhurong') ||
!fiftySecondEnemies.some((unit) => unit.id === 'menghuo-fifth-officer-mulu') ||
!fiftySecondEnemies.some((unit) => unit.id === 'menghuo-fifth-officer-duosi') ||
!fiftySecondEnemies.some((unit) => unit.id === 'menghuo-fifth-officer-mengyou') ||
!fiftySecondAllies.some((unit) => unit.id === 'zhuge-liang') ||
!fiftySecondAllies.some((unit) => unit.id === 'huang-quan') ||
!fiftySecondAllies.some((unit) => unit.id === 'ma-liang') ||
!fiftySecondAllies.some((unit) => unit.id === 'wang-ping') ||
!fiftySecondAllies.some((unit) => unit.id === 'zhao-yun') ||
!fiftySecondAllies.some((unit) => unit.id === 'huang-zhong') ||
!fiftySecondAllies.some((unit) => unit.id === 'ma-dai') ||
fiftySecondAllies.some((unit) => unit.id === 'liu-bei')
) {
throw new Error(`Expected fifty-second battle to use the fifth Meng Huo map, hardliner-separation objective, selected recruited officers, and mixed clan AI: ${JSON.stringify(fiftySecondBattleState)}`);
}
await page.evaluate(() => window.__HEROS_DEBUG__?.forceBattleOutcome('victory'));
await page.waitForFunction(() => {
const state = window.__HEROS_DEBUG__?.battle();
return state?.battleOutcome === 'victory' && state?.phase === 'resolved' && state?.resultVisible === true;
});
await page.mouse.click(738, 642);
await page.waitForFunction(() => {
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
return activeScenes.includes('CampScene');
});
const fiftySecondCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
if (
fiftySecondCampState?.campBattleId !== 'fifty-second-battle-meng-huo-fifth-capture' ||
fiftySecondCampState.campTitle !== '칠종칠금 5차전 후 군영' ||
fiftySecondCampState.availableDialogueIds?.length !== 3 ||
!fiftySecondCampState.availableDialogueIds.every((id) => id.includes('fifth-capture')) ||
fiftySecondCampState.availableVisitIds?.length !== 2 ||
!fiftySecondCampState.availableVisitIds.every((id) => id.includes('fifth-capture')) ||
fiftySecondCampState.rosterCollection?.total < 21
) {
throw new Error(`Expected fifty-second camp to expose fifth capture dialogue/visit sets and preserve the deployable officer roster: ${JSON.stringify(fiftySecondCampState)}`);
}
await page.screenshot({ path: 'dist/verification-fifth-capture-camp.png', fullPage: true });
await page.mouse.click(966, 38);
await page.waitForTimeout(180);
const postFifthCaptureProgressState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
if (
postFifthCaptureProgressState?.activeTab !== 'progress' ||
postFifthCaptureProgressState.campaignProgress?.completedKnown !== 52 ||
postFifthCaptureProgressState.campaignProgress?.totalKnown !== 52 ||
postFifthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 5차전' ||
postFifthCaptureProgressState.campaignProgress?.nextBattleTitle !== '준비 중'
) {
throw new Error(`Expected post-fifth-capture progress tab to complete the fifty-second battle and pause before the sixth capture arc: ${JSON.stringify(postFifthCaptureProgressState?.campaignProgress)}`);
}
await page.screenshot({ path: 'dist/verification-post-fifth-capture-progress.png', fullPage: true });
await page.evaluate(() => {
const game = window.__HEROS_GAME__;
game?.scene.stop('CampScene');
@@ -7135,11 +7301,11 @@ try {
activeScenes: window.__HEROS_DEBUG__?.activeScenes() ?? [],
camp: window.__HEROS_DEBUG__?.camp?.() ?? null
}));
if (!titleContinueState.activeScenes.includes('CampScene') || titleContinueState.camp?.campaign?.step !== 'fifty-first-camp') {
throw new Error(`Expected title continue to reopen the latest fourth Meng Huo capture camp: ${JSON.stringify(titleContinueState)}`);
if (!titleContinueState.activeScenes.includes('CampScene') || titleContinueState.camp?.campaign?.step !== 'fifty-second-camp') {
throw new Error(`Expected title continue to reopen the latest fifth Meng Huo capture camp: ${JSON.stringify(titleContinueState)}`);
}
console.log(`Verified title-to-fourth-Meng-Huo-capture flow, recruited officer sortie selection, Ma Liang, Yi Ji, Gong Zhi, Huang Zhong, Wei Yan, Pang Tong, Fa Zheng, Wu Yi, Yan Yan, Li Yan, Huang Quan, Ma Chao, Ma Dai, Wang Ping joins, Luofeng, Luo main gate, Mianzhu, Chengdu surrender, Jiameng, Yangping, Dingjun, Hanzhong decisive battle, King of Hanzhong council, Shu-Han foundation state, Jing Province defense vanguard, Fan Castle vanguard, Han River flood, Fan Castle siege, Jing rear crisis, Gongan collapse, Maicheng isolation, Yiling vanguard, Yiling fire attack, Baidi entrustment, Nanzhong stabilization, Meng Huo pacification, second capture, third capture, and fourth capture state at ${targetUrl}`);
console.log(`Verified title-to-fifth-Meng-Huo-capture flow, recruited officer sortie selection, Ma Liang, Yi Ji, Gong Zhi, Huang Zhong, Wei Yan, Pang Tong, Fa Zheng, Wu Yi, Yan Yan, Li Yan, Huang Quan, Ma Chao, Ma Dai, Wang Ping joins, Luofeng, Luo main gate, Mianzhu, Chengdu surrender, Jiameng, Yangping, Dingjun, Hanzhong decisive battle, King of Hanzhong council, Shu-Han foundation state, Jing Province defense vanguard, Fan Castle vanguard, Han River flood, Fan siege, Jing rear crisis, Gongan collapse, Maicheng isolation, Yiling vanguard, Yiling fire attack, Baidi entrustment, Nanzhong stabilization, Meng Huo pacification, second capture, third capture, fourth capture, and fifth capture state at ${targetUrl}`);
} finally {
await browser?.close();
if (serverProcess && !serverProcess.killed) {

View File

@@ -0,0 +1,77 @@
<svg xmlns="http://www.w3.org/2000/svg" width="5200" height="4300" viewBox="0 0 10400 8600" role="img" aria-label="Nanman fifth capture battlefield">
<defs>
<linearGradient id="ground" x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stop-color="#8d7f52"/>
<stop offset=".48" stop-color="#6f6843"/>
<stop offset="1" stop-color="#34482c"/>
</linearGradient>
<radialGradient id="deepJungle" cx=".52" cy=".55" r=".72">
<stop offset="0" stop-color="#476d3e"/>
<stop offset=".58" stop-color="#28472f"/>
<stop offset="1" stop-color="#16272c"/>
</radialGradient>
<filter id="softNoise">
<feTurbulence type="fractalNoise" baseFrequency=".017" numOctaves="4" seed="52"/>
<feColorMatrix type="saturate" values=".42"/>
<feBlend mode="multiply" in2="SourceGraphic"/>
</filter>
<pattern id="bamboo" width="260" height="240" patternUnits="userSpaceOnUse">
<path d="M40 240c30-100 20-154 74-234M126 240c-12-90 26-148 76-226M214 240c-40-84-18-142 10-232" stroke="#23371e" stroke-width="18" stroke-linecap="round" opacity=".38"/>
</pattern>
<pattern id="stone" width="360" height="300" patternUnits="userSpaceOnUse">
<path d="M0 230c70-112 160-176 270-190 52-6 80-22 116-48" fill="none" stroke="#261f18" stroke-width="32" opacity=".4"/>
<path d="M22 238c72-74 142-112 238-120" fill="none" stroke="#ae8950" stroke-width="16" opacity=".28"/>
</pattern>
</defs>
<rect width="10400" height="8600" fill="url(#ground)"/>
<rect width="10400" height="8600" fill="#000" opacity=".07" filter="url(#softNoise)"/>
<path d="M0 8020c1160-360 1720 160 2860-230 920-314 1130-1080 2140-980 1060 104 1290 820 2420 604 1110-210 1580-1120 2980-852v2038H0z" fill="url(#deepJungle)" opacity=".96"/>
<path d="M6680 0c-400 980-358 1920-22 2830 356 964 380 1660-26 2480-340 686-272 1360 300 3290h-760c-624-1510-724-2528-320-3360 360-744 304-1330-40-2240-326-862-430-1780-112-3000z" fill="#203f43" opacity=".92"/>
<path d="M6920 0c-270 940-132 1840 190 2690 326 864 330 1512-28 2248-292 604-188 1350 374 3662h-286c-470-1740-590-2730-224-3480 330-678 282-1254-36-2110-330-890-464-1740-206-3010z" fill="#6ea1a2" opacity=".46"/>
<path d="M0 0h3480v3380H0zM3330 0h3980v2580H3330zM7280 0h3120v3600H7280zM8700 3100h1700v2620H8700z" fill="url(#stone)" opacity=".82"/>
<path d="M260 5040c1640-920 3600-700 5320-730 1760-30 3120-460 4640-1300v730c-1520 800-2940 1190-4660 1230-1660 38-3520-88-5000 772z" fill="#3b5d2a" opacity=".74"/>
<path d="M1160 6840c1200-590 2560-590 4080-440 1330 132 2500 330 4100-360v720c-1550 588-2800 400-4110 270-1600-160-2820-126-4070 510z" fill="#496d30" opacity=".72"/>
<path d="M1040 6790c1480-590 3080-540 4620-710 1460-160 2680-500 3860-1280" fill="none" stroke="#c2a15d" stroke-width="270" opacity=".84"/>
<path d="M3020 5850c820-760 1720-1140 2660-1120h3740" fill="none" stroke="#ba9654" stroke-width="250" opacity=".82"/>
<path d="M4540 7220c150-1260 10-2500 450-3640" fill="none" stroke="#b18e50" stroke-width="230" opacity=".78"/>
<path d="M8200 4840c-570 620-620 1260-142 1920 406 560 250 1080-420 1560" fill="none" stroke="#b18d4a" stroke-width="220" opacity=".75"/>
<g fill="url(#bamboo)" opacity=".72">
<rect x="5960" y="2480" width="1080" height="5260"/>
<rect x="420" y="6860" width="2880" height="1600"/>
<rect x="7700" y="6000" width="1760" height="1600"/>
</g>
<g fill="#24492d" opacity=".8">
<ellipse cx="2100" cy="5320" rx="980" ry="570"/>
<ellipse cx="3720" cy="4960" rx="1020" ry="540"/>
<ellipse cx="5900" cy="4140" rx="1120" ry="600"/>
<ellipse cx="8340" cy="3760" rx="1080" ry="590"/>
<ellipse cx="8780" cy="6100" rx="960" ry="520"/>
</g>
<g fill="#98652c" opacity=".88">
<rect x="2600" y="5420" width="780" height="540" rx="42"/>
<rect x="4100" y="4480" width="860" height="560" rx="42"/>
<rect x="7500" y="5000" width="900" height="580" rx="42"/>
<rect x="8380" y="3500" width="900" height="540" rx="42"/>
</g>
<g fill="#d2ba73" stroke="#49331c" stroke-width="42">
<path d="M2900 5700l310-242 318 242v294h-628z"/>
<path d="M4420 4750l330-260 334 260v310h-664z"/>
<path d="M7820 5280l342-270 344 270v340h-686z"/>
<path d="M8690 3780l318-250 318 250v306h-636z"/>
</g>
<g fill="#52352a" stroke="#d38d3f" stroke-width="48">
<rect x="5480" y="3380" width="860" height="700" rx="52"/>
<rect x="6960" y="5200" width="940" height="720" rx="52"/>
<rect x="8840" y="4400" width="960" height="740" rx="52"/>
</g>
<g fill="#e49333" opacity=".75">
<circle cx="5900" cy="3730" r="132"/>
<circle cx="7380" cy="5560" r="122"/>
<circle cx="9270" cy="4760" r="144"/>
</g>
<g fill="none" stroke="#171d15" stroke-width="34" opacity=".34">
<path d="M0 5000c1860-980 4080-820 6040-870 1460-38 2860-470 4360-1280"/>
<path d="M700 6680c1420-530 2760-500 4300-350 1420 138 2740 370 4500-360"/>
<path d="M6820 0c-420 1380 92 2860 420 4040 260 940-210 1710-12 2700"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -3,6 +3,7 @@ import eighteenthBattleMapUrl from '../../assets/images/battle/eighteenth-battle
import eleventhBattleMapUrl from '../../assets/images/battle/eleventh-battle-map.svg';
import fiftiethBattleMapUrl from '../../assets/images/battle/fiftieth-battle-map.svg';
import fiftyFirstBattleMapUrl from '../../assets/images/battle/fifty-first-battle-map.svg';
import fiftySecondBattleMapUrl from '../../assets/images/battle/fifty-second-battle-map.svg';
import fifthBattleMapUrl from '../../assets/images/battle/fifth-battle-map.svg';
import fifteenthBattleMapUrl from '../../assets/images/battle/fifteenth-battle-map.svg';
import firstBattleMapUrl from '../../assets/images/battle/first-battle-map.png';
@@ -101,5 +102,6 @@ export const battleMapAssets: Record<string, string> = {
'battle-map-forty-eighth': fortyEighthBattleMapUrl,
'battle-map-forty-ninth': fortyNinthBattleMapUrl,
'battle-map-fiftieth': fiftiethBattleMapUrl,
'battle-map-fifty-first': fiftyFirstBattleMapUrl
'battle-map-fifty-first': fiftyFirstBattleMapUrl,
'battle-map-fifty-second': fiftySecondBattleMapUrl
};

View File

@@ -7,6 +7,10 @@ import {
fiftyFirstBattleMap,
fiftyFirstBattleUnits,
fiftyFirstBattleVictoryPages,
fiftySecondBattleBonds,
fiftySecondBattleMap,
fiftySecondBattleUnits,
fiftySecondBattleVictoryPages,
firstBattleBonds,
fifteenthBattleBonds,
fifteenthBattleMap,
@@ -260,7 +264,8 @@ export type BattleScenarioId =
| 'forty-eighth-battle-meng-huo-main-force'
| 'forty-ninth-battle-meng-huo-second-capture'
| 'fiftieth-battle-meng-huo-third-capture'
| 'fifty-first-battle-meng-huo-fourth-capture';
| 'fifty-first-battle-meng-huo-fourth-capture'
| 'fifty-second-battle-meng-huo-fifth-capture';
export type BattleObjectiveKind = 'defeat-leader' | 'pacify-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory';
@@ -3319,6 +3324,73 @@ export const fiftyFirstBattleScenario: BattleScenarioDefinition = {
nextCampScene: 'CampScene'
};
export const fiftySecondBattleScenario: BattleScenarioDefinition = {
id: 'fifty-second-battle-meng-huo-fifth-capture',
title: '칠종칠금 5차전',
victoryConditionLabel: '맹획 다섯 번째 생포와 강경파 분리',
defeatConditionLabel: '제갈량 퇴각',
openingObjectiveLines: [
'강경파 호족이 회유 퇴로를 배신이라 부르며 길목을 막고 있습니다. 맹획을 다시 생포하되, 물러설 뜻이 있는 호족이 본대와 갈라질 길을 지켜야 합니다.',
'마을 보증과 포로장 장부가 흔들리면 지난 네 번의 회유도 약해집니다. 강경파 요새를 누르고, 마을과 포로장을 지켜 남중 안의 의심을 끊으십시오.',
'제갈량은 반드시 생존해야 합니다. 마량과 황권으로 말을 남기고, 왕평과 마대로 퇴로를 열며, 조운과 황충으로 추격선을 억제하십시오.'
],
map: fiftySecondBattleMap,
units: fiftySecondBattleUnits,
bonds: fiftySecondBattleBonds,
mapTextureKey: 'battle-map-fifty-second',
leaderUnitId: 'menghuo-fifth-leader',
quickVictoryTurnLimit: 98,
baseVictoryGold: 12940,
objectives: [
{
id: 'pacify-meng-huo-fifth',
kind: 'pacify-leader',
label: '맹획 다섯 번째 생포',
rewardGold: 8780,
unitId: 'menghuo-fifth-leader'
},
{
id: 'zhuge-liang',
kind: 'keep-unit-alive',
label: '제갈량 생존',
rewardGold: 2220,
unitId: 'zhuge-liang'
},
{
id: 'hardliner-separation',
kind: 'secure-terrain',
label: '강경파 분리로 확보',
rewardGold: 4380,
terrain: 'road'
},
{
id: 'clan-villages',
kind: 'secure-terrain',
label: '회유 마을 보호',
rewardGold: 4040,
terrain: 'village'
},
{
id: 'hardliner-forts',
kind: 'secure-terrain',
label: '강경파 요새 제압',
rewardGold: 3860,
terrain: 'fort'
},
{
id: 'quick',
kind: 'quick-victory',
label: '98턴 이내 다섯 번째 생포',
rewardGold: 2920,
maxTurn: 98
}
],
defeatConditions: [{ kind: 'unit-defeated', unitId: 'zhuge-liang' }],
itemRewards: ['콩 70', '상처약 54', '탁주 24', '강경파 분리 장부 1', '남중 회유 깃발 1'],
victoryPages: fiftySecondBattleVictoryPages,
nextCampScene: 'CampScene'
};
export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id;
export const battleScenarios: Record<BattleScenarioId, BattleScenarioDefinition> = {
@@ -3372,7 +3444,8 @@ export const battleScenarios: Record<BattleScenarioId, BattleScenarioDefinition>
'forty-eighth-battle-meng-huo-main-force': fortyEighthBattleScenario,
'forty-ninth-battle-meng-huo-second-capture': fortyNinthBattleScenario,
'fiftieth-battle-meng-huo-third-capture': fiftiethBattleScenario,
'fifty-first-battle-meng-huo-fourth-capture': fiftyFirstBattleScenario
'fifty-first-battle-meng-huo-fourth-capture': fiftyFirstBattleScenario,
'fifty-second-battle-meng-huo-fifth-capture': fiftySecondBattleScenario
};
export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId];

View File

@@ -6,6 +6,7 @@ import {
eleventhBattleScenario,
fiftiethBattleScenario,
fiftyFirstBattleScenario,
fiftySecondBattleScenario,
fortiethBattleScenario,
fortyFirstBattleScenario,
fortySecondBattleScenario,
@@ -65,6 +66,8 @@ import {
fiftiethBattleVictoryPages,
fiftyFirstBattleIntroPages,
fiftyFirstBattleVictoryPages,
fiftySecondBattleIntroPages,
fiftySecondBattleVictoryPages,
fortiethBattleIntroPages,
fortiethBattleVictoryPages,
fortyFirstBattleIntroPages,
@@ -745,13 +748,24 @@ const sortieFlows: Record<string, SortieFlow> = {
},
[fiftyFirstBattleScenario.id]: {
afterBattleId: fiftyFirstBattleScenario.id,
eyebrow: '다음 장 준비',
title: '더 깊은 남중 회유',
eyebrow: '다음 장',
title: fiftySecondBattleScenario.title,
description:
'맹획은 네 번째로 붙잡혔고, 회유 퇴로와 보증 장부는 남중 호족들의 말 속에 남았습니다. 다음 장에서는 더 깊은 산채와 강경파 호족을 상대로 같은 약속을 지킬 수 있는지 다룹니다.',
rewardHint: '다음 장: 칠종칠금 5차전과 남중 강경파 회유 준비 중',
pages: fiftyFirstBattleVictoryPages,
unavailableNotice: '칠종칠금 5차전은 다음 작업에서 이어집니다. 지금은 네 번째 생포 뒤의 보증 장부, 퇴로 표식, 강경파 분리 흐름을 정비할 수 있습니다.'
'맹획은 네 번째로 붙잡혔고, 회유 퇴로와 보증 장부는 남중 호족들의 말 속에 남았습니다. 이제 강경파 호족이 그 길을 끊으려 하니, 다섯 번째 생포에서는 회유를 받아들일 자와 끝까지 싸울 자를 전장 안에서 분리해야 합니다.',
rewardHint: `예상 보상: ${fiftySecondBattleScenario.title} 개방 / 강경파 분리 장부`,
nextBattleId: fiftySecondBattleScenario.id,
campaignStep: 'fifty-second-battle',
pages: [...fiftyFirstBattleVictoryPages, ...fiftySecondBattleIntroPages]
},
[fiftySecondBattleScenario.id]: {
afterBattleId: fiftySecondBattleScenario.id,
eyebrow: '다음 장 준비',
title: '여섯 번째 회유의 길',
description:
'맹획은 다섯 번째로 붙잡혔고, 강경파 호족과 물러선 호족의 말이 갈라졌습니다. 다음 장에서는 남중의 자존심을 더 직접적으로 마주하면서도, 지난 회유의 약속을 무너뜨리지 않는 길을 준비합니다.',
rewardHint: '다음 장: 칠종칠금 6차전과 마지막 설득 준비 중',
pages: fiftySecondBattleVictoryPages,
unavailableNotice: '칠종칠금 6차전은 다음 작업에서 이어집니다. 지금은 다섯 번째 생포 뒤의 강경파 분리 장부와 회유 깃발을 정비할 수 있습니다.'
}
};

View File

@@ -3350,6 +3350,59 @@ export const fiftyFirstBattleVictoryPages: StoryPage[] = [
}
];
export const fiftySecondBattleIntroPages: StoryPage[] = [
{
id: 'fifty-second-hardliner-split',
bgm: 'story-dark',
chapter: '강경파의 갈라짐',
background: 'story-militia',
speaker: '마량',
text: '네 번째 회유 뒤 일부 호족은 맹획의 깃발 아래 남아 있으면서도 마을을 지키는 길을 찾고 있습니다. 반대로 강경파는 그 길을 배신이라 부르며 퇴로를 끊으려 합니다.'
},
{
id: 'fifty-second-fifth-capture-plan',
bgm: 'battle-prep',
chapter: '다섯 번째 생포',
background: 'story-three-heroes',
speaker: '제갈량',
text: '이번 전투는 맹획을 다시 잡는 것만으로는 부족합니다. 강경파 호족을 본대와 분리하고, 회유 길목과 마을 보증을 동시에 지키면 남중 안에서 싸움을 그만둘 명분이 더 커질 것입니다.'
},
{
id: 'fifty-second-sortie',
bgm: 'battle-prep',
chapter: '칠종칠금 5차전',
background: 'story-sortie',
speaker: '왕평',
text: '깊은 죽림과 협곡이 이어져 있습니다. 장수마다 맡을 길이 다릅니다. 마량과 황권은 말을 남기고, 왕평과 마대는 물러날 길을 열며, 조운과 황충은 추격을 막아야 합니다.'
}
];
export const fiftySecondBattleVictoryPages: StoryPage[] = [
{
id: 'fifty-second-victory-fifth-capture',
bgm: 'militia-theme',
chapter: '다섯 번째 생포',
background: 'story-sortie',
text: '맹획은 다섯 번째로 포위되었고, 강경파 호족은 본대와 갈라졌습니다. 촉한군은 다시 마을과 퇴로를 지켰고, 돌아간 자들은 이제 맹획의 말만 듣지 않게 되었습니다.'
},
{
id: 'fifty-second-victory-hardliner-ledger',
bgm: 'story-dark',
chapter: '갈라진 장부',
background: 'story-militia',
speaker: '황권',
text: '끝까지 싸운 자와 물러난 자의 이름이 더 분명해졌습니다. 처벌의 장부가 아니라 약속의 장부로 남겨야 합니다. 그래야 다음 석방도 군율을 잃지 않습니다.'
},
{
id: 'fifty-second-next-sixth-capture',
bgm: 'story-dark',
chapter: '여섯 번째 길',
background: 'story-three-heroes',
speaker: '제갈량',
text: '남중의 마음은 점점 흔들리고 있습니다. 그러나 맹획의 자존심은 아직 마지막 벽처럼 남아 있습니다. 다음 싸움에서는 그 자존심을 꺾되, 남중 전체를 꺾지는 말아야 합니다.'
}
];
export const firstBattleMap: BattleMap = {
width: 20,
height: 18,
@@ -3801,6 +3854,12 @@ export const fiftyFirstBattleMap: BattleMap = {
terrain: createFiftyFirstBattleTerrain()
};
export const fiftySecondBattleMap: BattleMap = {
width: 104,
height: 86,
terrain: createFiftySecondBattleTerrain()
};
export const firstBattleUnits: UnitData[] = [
{
id: 'liu-bei',
@@ -16187,6 +16246,94 @@ export const fiftyFirstBattleUnits: UnitData[] = [
createThirtyFirstEnemyUnit('menghuo-fourth-leader', '맹획', '남만왕', 'rebelLeader', 156, 1048, 258, 96, 43)
];
const fiftySecondBattleAllyPositions: Record<string, { x: number; y: number }> = {
'guan-yu': { x: 15, y: 65 },
'zhang-fei': { x: 20, y: 67 },
'jian-yong': { x: 11, y: 72 },
'mi-zhu': { x: 13, y: 70 },
'sun-qian': { x: 17, y: 74 },
'zhao-yun': { x: 25, y: 62 },
'zhuge-liang': { x: 14, y: 62 },
'ma-liang': { x: 18, y: 59 },
'yi-ji': { x: 11, y: 77 },
'gong-zhi': { x: 21, y: 75 },
'huang-zhong': { x: 28, y: 63 },
'wei-yan': { x: 33, y: 67 },
'pang-tong': { x: 16, y: 56 },
'fa-zheng': { x: 22, y: 57 },
'wu-yi': { x: 35, y: 72 },
'yan-yan': { x: 38, y: 65 },
'li-yan': { x: 27, y: 55 },
'huang-quan': { x: 11, y: 60 },
'ma-chao': { x: 40, y: 62 },
'ma-dai': { x: 43, y: 67 },
'wang-ping': { x: 30, y: 58 }
};
export const fiftySecondBattleUnits: UnitData[] = [
...[
...firstBattleUnits.filter((unit) => unit.faction === 'ally' && unit.id !== 'liu-bei'),
...xuzhouRecruitUnits,
...caoBreakRecruitUnits,
...liuBiaoRecruitUnits,
...zhugeRecruitUnits,
...jingzhouRecruitUnits,
...guiyangRecruitUnits,
...wulingRecruitUnits,
...changshaRecruitUnits,
...yizhouRecruitUnits,
...fuPassRecruitUnits,
...luoCastleRecruitUnits,
...luoCastleProperRecruitUnits,
...chengduPressureRecruitUnits,
...chengduSurrenderRecruitUnits,
...hanzhongOpeningRecruitUnits,
...hanzhongScoutRecruitUnits,
...hanzhongMainRecruitUnits
].map((unit) => placeScenarioUnit(unit, fiftySecondBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })),
createThirtyFirstEnemyUnit('menghuo-fifth-scout-a', '강경파 정찰병', '서쪽 죽림 정찰', 'bandit', 154, 684, 206, 47, 38),
createThirtyFirstEnemyUnit('menghuo-fifth-scout-b', '강경파 정찰병', '늪지 정찰', 'bandit', 154, 684, 206, 63, 36),
createThirtyFirstEnemyUnit('menghuo-fifth-scout-c', '강경파 정찰병', '마을 감시', 'bandit', 155, 692, 208, 78, 53),
createThirtyFirstEnemyUnit('menghuo-fifth-scout-d', '강경파 정찰병', '동쪽 협곡 감시', 'bandit', 155, 692, 208, 94, 68),
createThirtyFirstEnemyUnit('menghuo-fifth-scout-e', '강경파 정찰병', '북쪽 산길 감시', 'bandit', 156, 700, 210, 55, 26),
createThirtyFirstEnemyUnit('menghuo-fifth-scout-f', '강경파 정찰병', '분리로 감시', 'bandit', 156, 700, 210, 70, 74),
createThirtyFirstEnemyUnit('menghuo-fifth-vine-a', '강경파 등갑병', '죽림 등갑병', 'yellowTurban', 155, 960, 244, 52, 47),
createThirtyFirstEnemyUnit('menghuo-fifth-vine-b', '강경파 등갑병', '불씨 등갑병', 'yellowTurban', 156, 972, 246, 65, 50),
createThirtyFirstEnemyUnit('menghuo-fifth-vine-c', '강경파 등갑병', '마을 등갑병', 'yellowTurban', 156, 972, 246, 78, 60),
createThirtyFirstEnemyUnit('menghuo-fifth-vine-d', '강경파 등갑병', '본영 등갑병', 'yellowTurban', 157, 984, 248, 90, 52),
createThirtyFirstEnemyUnit('menghuo-fifth-vine-e', '강경파 등갑병', '후위 등갑병', 'yellowTurban', 157, 984, 248, 98, 44),
createThirtyFirstEnemyUnit('menghuo-fifth-vine-f', '강경파 등갑병', '협곡 등갑병', 'yellowTurban', 158, 996, 250, 86, 33),
createThirtyFirstEnemyUnit('menghuo-fifth-archer-a', '남만 죽림 궁병', '독천 궁병', 'archer', 154, 736, 218, 48, 30),
createThirtyFirstEnemyUnit('menghuo-fifth-archer-b', '남만 죽림 궁병', '절벽 궁병', 'archer', 155, 744, 220, 64, 28),
createThirtyFirstEnemyUnit('menghuo-fifth-archer-c', '남만 죽림 궁병', '마을 궁병', 'archer', 155, 744, 220, 76, 46),
createThirtyFirstEnemyUnit('menghuo-fifth-archer-d', '남만 죽림 궁병', '늪지 궁병', 'archer', 156, 752, 222, 88, 64),
createThirtyFirstEnemyUnit('menghuo-fifth-archer-e', '남만 죽림 궁병', '본영 궁병', 'archer', 156, 752, 222, 97, 57),
createThirtyFirstEnemyUnit('menghuo-fifth-beast-a', '남만 맹수 추격대', '강가 맹수대', 'cavalry', 156, 976, 248, 58, 40),
createThirtyFirstEnemyUnit('menghuo-fifth-beast-b', '남만 맹수 추격대', '늪지 맹수대', 'cavalry', 156, 976, 248, 71, 42),
createThirtyFirstEnemyUnit('menghuo-fifth-beast-c', '남만 맹수 추격대', '산채 맹수대', 'cavalry', 157, 988, 250, 83, 55),
createThirtyFirstEnemyUnit('menghuo-fifth-beast-d', '남만 맹수 추격대', '추격 맹수대', 'cavalry', 157, 988, 250, 95, 61),
createThirtyFirstEnemyUnit('menghuo-fifth-beast-e', '남만 맹수 추격대', '분리로 차단대', 'cavalry', 158, 1000, 252, 69, 70),
createThirtyFirstEnemyUnit('menghuo-fifth-ambusher-a', '강경파 매복병', '죽림 매복병', 'bandit', 155, 704, 212, 39, 53),
createThirtyFirstEnemyUnit('menghuo-fifth-ambusher-b', '강경파 매복병', '숲속 매복병', 'bandit', 156, 712, 214, 55, 56),
createThirtyFirstEnemyUnit('menghuo-fifth-ambusher-c', '강경파 매복병', '강가 매복병', 'bandit', 156, 712, 214, 68, 69),
createThirtyFirstEnemyUnit('menghuo-fifth-ambusher-d', '강경파 매복병', '후방 차단대', 'bandit', 157, 720, 216, 88, 76),
createThirtyFirstEnemyUnit('menghuo-fifth-ambusher-e', '강경파 매복병', '서쪽 차단대', 'bandit', 157, 720, 216, 34, 66),
createThirtyFirstEnemyUnit('menghuo-fifth-ambusher-f', '강경파 매복병', '분리로 감시대', 'bandit', 158, 728, 218, 75, 74),
createThirtyFirstEnemyUnit('menghuo-fifth-guard-a', '맹획 오차 친위', '포로장 친위대', 'yellowTurban', 158, 1012, 254, 72, 44),
createThirtyFirstEnemyUnit('menghuo-fifth-guard-b', '맹획 오차 친위', '산채 친위대', 'yellowTurban', 158, 1012, 254, 85, 50),
createThirtyFirstEnemyUnit('menghuo-fifth-guard-c', '맹획 오차 친위', '본영 친위대', 'yellowTurban', 159, 1024, 256, 96, 47),
createThirtyFirstEnemyUnit('menghuo-fifth-guard-d', '맹획 오차 친위', '죽림 친위대', 'yellowTurban', 159, 1024, 256, 91, 37),
createThirtyFirstEnemyUnit('menghuo-fifth-shaman-a', '독천 제사장', '늪지 제사장', 'strategist', 157, 784, 230, 64, 38),
createThirtyFirstEnemyUnit('menghuo-fifth-shaman-b', '독천 제사장', '강가 제사장', 'strategist', 158, 792, 232, 79, 51),
createThirtyFirstEnemyUnit('menghuo-fifth-shaman-c', '독천 제사장', '산채 제사장', 'strategist', 158, 792, 232, 90, 40),
createThirtyFirstEnemyUnit('menghuo-fifth-shaman-d', '독천 제사장', '죽림 제사장', 'strategist', 159, 800, 234, 84, 59),
createThirtyFirstEnemyUnit('menghuo-fifth-officer-zhurong', '축융 부인', '남중 화전장', 'rebelLeader', 160, 1058, 264, 82, 67),
createThirtyFirstEnemyUnit('menghuo-fifth-officer-mulu', '목록대왕', '맹수대왕', 'rebelLeader', 160, 1058, 264, 88, 39),
createThirtyFirstEnemyUnit('menghuo-fifth-officer-duosi', '타사대왕', '독천 호족장', 'rebelLeader', 159, 1038, 258, 72, 62),
createThirtyFirstEnemyUnit('menghuo-fifth-officer-mengyou', '맹우', '맹획의 아우', 'rebelLeader', 159, 1038, 258, 68, 54),
createThirtyFirstEnemyUnit('menghuo-fifth-leader', '맹획', '남만왕', 'rebelLeader', 162, 1100, 270, 98, 45)
];
export const firstBattleBonds: BattleBond[] = [
{
id: 'liu-bei__guan-yu',
@@ -17151,6 +17298,35 @@ export const mengHuoFourthCaptureBonds: BattleBond[] = [
export const fiftyFirstBattleBonds: BattleBond[] = [...fiftiethBattleBonds, ...mengHuoFourthCaptureBonds].map(
cloneBattleBondForScenario
);
export const mengHuoFifthCaptureBonds: BattleBond[] = [
{
id: 'zhuge-liang__huang-quan_fifth-capture',
unitIds: ['zhuge-liang', 'huang-quan'],
title: '강경파 분리 장부',
level: 53,
exp: 0,
description: '제갈량의 회유 방침과 황권의 장부는 끝까지 싸운 자와 물러난 자를 갈라, 불필요한 원한을 줄인다.'
},
{
id: 'ma-liang__wang-ping_fifth-capture',
unitIds: ['ma-liang', 'wang-ping'],
title: '말과 길의 분리',
level: 49,
exp: 0,
description: '마량이 호족에게 남길 말을 고르고 왕평이 열린 길을 지켜, 전장 안에서도 항복의 길을 보존한다.'
},
{
id: 'zhao-yun__huang-zhong_fifth-capture',
unitIds: ['zhao-yun', 'huang-zhong'],
title: '추격선 억제',
level: 45,
exp: 0,
description: '조운과 황충은 맹수대와 고지 궁병을 눌러, 강경파가 회유 길목을 짓밟지 못하게 한다.'
}
];
export const fiftySecondBattleBonds: BattleBond[] = [...fiftyFirstBattleBonds, ...mengHuoFifthCaptureBonds].map(
cloneBattleBondForScenario
);
function createEighthBattleTerrain(): TerrainType[][] {
return Array.from({ length: 22 }, (_, y) =>
@@ -19792,6 +19968,79 @@ function createFiftyFirstBattleTerrain(): TerrainType[][] {
);
}
function createFiftySecondBattleTerrain(): TerrainType[][] {
return Array.from({ length: 86 }, (_, y) =>
Array.from({ length: 104 }, (_, x): TerrainType => {
if (
(x >= 0 && x <= 103 && y >= 78) ||
(x >= 65 && x <= 71 && y >= 0 && y <= 80) ||
(x >= 5 && x <= 31 && y >= 69 && y <= 85) ||
(x >= 78 && x <= 95 && y >= 60 && y <= 78)
) {
return 'river';
}
if (
(x >= 10 && x <= 98 && y >= 65 && y <= 71) ||
(x >= 28 && x <= 76 && y >= 44 && y <= 51) ||
(x >= 72 && x <= 98 && y >= 36 && y <= 42) ||
(x >= 39 && x <= 47 && y >= 41 && y <= 74) ||
(x >= 78 && x <= 84 && y >= 50 && y <= 82) ||
(x >= 18 && x <= 52 && y >= 57 && y <= 61)
) {
return 'road';
}
if (
(x >= 18 && x <= 74 && y >= 21 && y <= 76) ||
(x >= 56 && x <= 98 && y >= 29 && y <= 72) ||
(x >= 2 && x <= 39 && y >= 51 && y <= 76) ||
(x >= 80 && x <= 103 && y >= 43 && y <= 75)
) {
return 'forest';
}
if (
(x >= 0 && x <= 32 && y <= 40) ||
(x >= 34 && x <= 70 && y <= 30) ||
(x >= 73 && x <= 103 && y <= 39) ||
(x >= 88 && x <= 103 && y >= 35 && y <= 57)
) {
return 'hill';
}
if (
(x <= 2 && y <= 76) ||
(x >= 101 && y <= 72) ||
(x >= 31 && x <= 37 && y >= 0 && y <= 42) ||
(x >= 70 && x <= 76 && y >= 0 && y <= 36) ||
(x >= 44 && x <= 50 && y >= 66 && y <= 85)
) {
return 'cliff';
}
if (
(x >= 11 && x <= 26 && y >= 61 && y <= 73) ||
(x >= 40 && x <= 56 && y >= 45 && y <= 58) ||
(x >= 77 && x <= 93 && y >= 51 && y <= 63) ||
(x >= 82 && x <= 96 && y >= 34 && y <= 46)
) {
return 'village';
}
if (
(x >= 24 && x <= 38 && y >= 53 && y <= 64) ||
(x >= 65 && x <= 80 && y >= 58 && y <= 70) ||
(x >= 83 && x <= 97 && y >= 41 && y <= 53)
) {
return 'camp';
}
if (
(x >= 48 && x <= 65 && y >= 33 && y <= 44) ||
(x >= 74 && x <= 94 && y >= 33 && y <= 47) ||
(x >= 86 && x <= 98 && y >= 45 && y <= 56)
) {
return 'fort';
}
return 'plain';
})
);
}
function placeScenarioUnit(unit: UnitData, position: { x: number; y: number }): UnitData {
return {
...cloneUnitForScenario(unit),

View File

@@ -883,7 +883,16 @@ const unitTexture: Record<string, string> = {
'menghuo-fourth-officer-mangyachang': 'unit-rebel-leader',
'menghuo-fourth-officer-dongtuna': 'unit-rebel-leader',
'menghuo-fourth-officer-ahuinan': 'unit-rebel-leader',
'menghuo-fourth-leader': 'unit-rebel-leader'
'menghuo-fourth-leader': 'unit-rebel-leader',
'menghuo-fifth-shaman-a': 'unit-rebel-archer',
'menghuo-fifth-shaman-b': 'unit-rebel-archer',
'menghuo-fifth-shaman-c': 'unit-rebel-archer',
'menghuo-fifth-shaman-d': 'unit-rebel-archer',
'menghuo-fifth-officer-zhurong': 'unit-rebel-leader',
'menghuo-fifth-officer-mulu': 'unit-rebel-leader',
'menghuo-fifth-officer-duosi': 'unit-rebel-leader',
'menghuo-fifth-officer-mengyou': 'unit-rebel-leader',
'menghuo-fifth-leader': 'unit-rebel-leader'
};
const unitTextureByClass: Partial<Record<UnitClassKey, string>> = {
@@ -2422,7 +2431,48 @@ const enemyAiByUnitId: Record<string, EnemyAiBehavior> = {
'menghuo-fourth-officer-mangyachang': 'aggressive',
'menghuo-fourth-officer-dongtuna': 'guard',
'menghuo-fourth-officer-ahuinan': 'guard',
'menghuo-fourth-leader': 'hold'
'menghuo-fourth-leader': 'hold',
'menghuo-fifth-scout-a': 'aggressive',
'menghuo-fifth-scout-b': 'aggressive',
'menghuo-fifth-scout-c': 'aggressive',
'menghuo-fifth-scout-d': 'aggressive',
'menghuo-fifth-scout-e': 'aggressive',
'menghuo-fifth-scout-f': 'aggressive',
'menghuo-fifth-vine-a': 'guard',
'menghuo-fifth-vine-b': 'guard',
'menghuo-fifth-vine-c': 'guard',
'menghuo-fifth-vine-d': 'guard',
'menghuo-fifth-vine-e': 'guard',
'menghuo-fifth-vine-f': 'guard',
'menghuo-fifth-archer-a': 'hold',
'menghuo-fifth-archer-b': 'hold',
'menghuo-fifth-archer-c': 'hold',
'menghuo-fifth-archer-d': 'hold',
'menghuo-fifth-archer-e': 'hold',
'menghuo-fifth-beast-a': 'aggressive',
'menghuo-fifth-beast-b': 'aggressive',
'menghuo-fifth-beast-c': 'aggressive',
'menghuo-fifth-beast-d': 'aggressive',
'menghuo-fifth-beast-e': 'aggressive',
'menghuo-fifth-ambusher-a': 'aggressive',
'menghuo-fifth-ambusher-b': 'aggressive',
'menghuo-fifth-ambusher-c': 'aggressive',
'menghuo-fifth-ambusher-d': 'aggressive',
'menghuo-fifth-ambusher-e': 'aggressive',
'menghuo-fifth-ambusher-f': 'aggressive',
'menghuo-fifth-guard-a': 'guard',
'menghuo-fifth-guard-b': 'guard',
'menghuo-fifth-guard-c': 'guard',
'menghuo-fifth-guard-d': 'guard',
'menghuo-fifth-shaman-a': 'hold',
'menghuo-fifth-shaman-b': 'hold',
'menghuo-fifth-shaman-c': 'hold',
'menghuo-fifth-shaman-d': 'hold',
'menghuo-fifth-officer-zhurong': 'aggressive',
'menghuo-fifth-officer-mulu': 'aggressive',
'menghuo-fifth-officer-duosi': 'guard',
'menghuo-fifth-officer-mengyou': 'guard',
'menghuo-fifth-leader': 'hold'
};
const defaultEnemyAiByClass: Partial<Record<UnitClassKey, EnemyAiBehavior>> = {

View File

@@ -27,8 +27,9 @@ import {
hanzhongMainRecruitUnits,
hanzhongScoutRecruitBonds,
hanzhongScoutRecruitUnits,
mengHuoThirdCaptureBonds,
mengHuoFourthCaptureBonds,
mengHuoFifthCaptureBonds,
mengHuoThirdCaptureBonds,
fanCastleBonds,
fanCastleSiegeBonds,
jingCollapseBonds,
@@ -415,7 +416,8 @@ const campaignTimelineChapters: CampaignTimelineChapter[] = [
'forty-eighth-battle-meng-huo-main-force',
'forty-ninth-battle-meng-huo-second-capture',
'fiftieth-battle-meng-huo-third-capture',
'fifty-first-battle-meng-huo-fourth-capture'
'fifty-first-battle-meng-huo-fourth-capture',
'fifty-second-battle-meng-huo-fifth-capture'
],
nextHints: ['왕업 선언', '번성 포위', '형주 위기', '이릉 화공', '남중 안정', '맹획 회유', '칠종칠금']
}
@@ -472,7 +474,8 @@ const campBattleIds = {
fortyEighth: 'forty-eighth-battle-meng-huo-main-force',
fortyNinth: 'forty-ninth-battle-meng-huo-second-capture',
fiftieth: 'fiftieth-battle-meng-huo-third-capture',
fiftyFirst: 'fifty-first-battle-meng-huo-fourth-capture'
fiftyFirst: 'fifty-first-battle-meng-huo-fourth-capture',
fiftySecond: 'fifty-second-battle-meng-huo-fifth-capture'
} as const;
const defaultRequiredSortieUnitIds = ['liu-bei'];
@@ -1089,6 +1092,20 @@ const sortieRulesByBattleId: Partial<Record<BattleScenarioId, SortieRuleDefiniti
{ unitId: 'ma-dai', reason: '마초와 함께 회유 퇴로 주변의 빠른 차단과 회수를 맡습니다.' }
],
note: '칠종칠금 4차전은 이전 군영의 호족 회유 선택이 전장 목표로 바뀌는 전투입니다. 생포만 노리지 말고, 회유 퇴로와 보증 장부를 지킬 편성을 고르십시오.'
},
'fifty-second-battle-meng-huo-fifth-capture': {
maxUnits: 7,
requiredUnitIds: ['zhuge-liang'],
recommended: [
{ unitId: 'zhuge-liang', reason: '다섯 번째 생포와 강경파 분리 명분을 직접 지휘하는 필수 장수입니다.' },
{ unitId: 'huang-quan', reason: '강경파와 회유파를 장부로 분리해 군율과 약속을 동시에 지킵니다.' },
{ unitId: 'ma-liang', reason: '물러설 호족에게 남길 말을 정리해 마을 회유 목표를 돕습니다.' },
{ unitId: 'wang-ping', reason: '죽림과 협곡의 분리로를 읽어 회유 퇴로를 확보합니다.' },
{ unitId: 'zhao-yun', reason: '맹수 추격대가 회유로를 짓밟지 못하게 중심을 받쳐 줍니다.' },
{ unitId: 'huang-zhong', reason: '고지 궁병과 제사장을 억제해 강경파 요새 공략을 안정시킵니다.' },
{ unitId: 'ma-dai', reason: '마초와 다른 빠른 우회로를 맡아 분리로 주변 추격선을 끊습니다.' }
],
note: '칠종칠금 5차전은 맹획 생포에 더해 강경파 호족을 본대에서 떼어 내는 전투입니다. 마을 회유, 장부 분리, 퇴로 유지, 추격 억제를 맡을 장수를 균형 있게 고르십시오.'
}
};
@@ -5388,6 +5405,87 @@ const campDialogues: CampDialogue[] = [
rewardExp: 19
}
]
},
{
id: 'fifth-capture-zhuge-huangquan-separation',
title: '갈라진 이름의 장부',
availableAfterBattleIds: [campBattleIds.fiftySecond],
unitIds: ['zhuge-liang', 'huang-quan'],
bondId: 'zhuge-liang__huang-quan_fifth-capture',
rewardExp: 66,
lines: [
'황권: 다섯 번째 생포 뒤에는 끝까지 싸운 자와 물러난 자의 이름을 더 엄격히 나누어야 합니다.',
'제갈량: 처벌을 위한 장부가 되면 다시 원한이 됩니다. 약속을 지킨 자를 보호하는 장부로 남겨야 합니다.',
'황권: 그렇다면 강경파를 누르되, 돌아선 자의 길은 먼저 지켜 두겠습니다.'
],
choices: [
{
id: 'write-hardliner-ledger',
label: '강경파 장부를 나눈다',
response: '황권은 강경파와 회유파의 이름을 따로 적었고, 제갈량은 석방의 기준을 병사들에게 다시 알렸습니다.',
rewardExp: 23
},
{
id: 'protect-returning-clans',
label: '돌아선 호족을 보호한다',
response: '제갈량은 물러난 호족의 마을을 함부로 징발하지 말라 명했고, 황권은 그 이름을 보증 장부에 옮겼습니다.',
rewardExp: 22
}
]
},
{
id: 'fifth-capture-maliang-wangping-routes',
title: '말이 닿는 길',
availableAfterBattleIds: [campBattleIds.fiftySecond],
unitIds: ['ma-liang', 'wang-ping'],
bondId: 'ma-liang__wang-ping_fifth-capture',
rewardExp: 62,
lines: [
'마량: 말만 남기면 길이 막히고, 길만 열면 뜻이 흐려집니다.',
'왕평: 그래서 이번 지도에는 말이 닿을 마을과 군대가 지나갈 길을 따로 그려야 합니다.',
'마량: 다음 싸움에서는 그 지도가 곧 회유의 증거가 되겠습니다.'
],
choices: [
{
id: 'copy-amnesty-routes',
label: '회유 길을 베낀다',
response: '왕평은 낮은 길을 다시 그렸고, 마량은 그 길마다 붙일 짧은 문장을 적었습니다.',
rewardExp: 21
},
{
id: 'separate-village-messages',
label: '마을별 문구를 나눈다',
response: '마량은 마을마다 다른 두려움을 적었고, 왕평은 그 마을로 이어지는 좁은 길을 표시했습니다.',
rewardExp: 20
}
]
},
{
id: 'fifth-capture-zhaoyun-huangzhong-containment',
title: '멀리서 막는 추격',
availableAfterBattleIds: [campBattleIds.fiftySecond],
unitIds: ['zhao-yun', 'huang-zhong'],
bondId: 'zhao-yun__huang-zhong_fifth-capture',
rewardExp: 60,
lines: [
'황충: 이번 강경파는 가까이 붙으면 오히려 회유 길을 흔들었소.',
'조운: 그래서 먼저 끊어야 했습니다. 칼끝이 아니라 거리로 막는 싸움이었습니다.',
'황충: 다음에도 고지와 중앙을 함께 잡아야 맹획을 잡되 남중을 잃지 않겠소.'
],
choices: [
{
id: 'train-distant-containment',
label: '원거리 억제선을 훈련한다',
response: '황충은 고지 사격선을 정했고, 조운은 그 앞을 지키는 간격을 병사들에게 익혔습니다.',
rewardExp: 20
},
{
id: 'hold-center-and-hill',
label: '중앙과 고지를 나눈다',
response: '조운은 중앙 방어선을 맡고, 황충은 고지 궁병을 견제할 위치를 다시 표시했습니다.',
rewardExp: 20
}
]
}
];
@@ -7635,6 +7733,65 @@ const campVisits: CampVisitDefinition[] = [
itemRewards: ['탁주 4']
}
]
},
{
id: 'fifth-capture-hardliner-ledger',
title: '강경파 분리 장부',
location: '남중 포로장',
availableAfterBattleIds: [campBattleIds.fiftySecond],
bondId: 'zhuge-liang__huang-quan_fifth-capture',
description: '제갈량과 황권이 다섯 번째 생포 뒤 강경파와 회유파의 이름을 나누어 기록합니다.',
lines: [
'황권: 같은 남중 사람이라도 오늘부터 장부의 의미가 달라집니다.',
'제갈량: 끝까지 싸운 자를 적는 일보다, 돌아선 자를 지키는 일이 먼저입니다.',
'포로장 한편에는 강경파 분리 장부와 돌아선 호족의 보증문이 나뉘어 놓였습니다.'
],
choices: [
{
id: 'seal-hardliner-list',
label: '강경파 명부를 봉한다',
response: '황권은 강경파 명부를 따로 봉했고, 제갈량은 남은 호족에게 다시 석방의 조건을 알렸습니다.',
bondExp: 52,
itemRewards: ['강경파 분리 장부 1']
},
{
id: 'protect-amnesty-witnesses',
label: '회유 증인을 보호한다',
response: '제갈량은 회유 길을 통과한 증인을 보호하게 했고, 황권은 그 이름을 보증 장부에 더했습니다.',
bondExp: 50,
gold: 1760,
itemRewards: ['남중 회유 깃발 1']
}
]
},
{
id: 'fifth-capture-amnesty-banners',
title: '회유 깃발 정비',
location: '남중 죽림 길목',
availableAfterBattleIds: [campBattleIds.fiftySecond],
bondId: 'ma-liang__wang-ping_fifth-capture',
description: '마량과 왕평이 다음 전투에서 물러날 호족이 알아볼 회유 깃발과 길목 표식을 정비합니다.',
lines: [
'마량: 글을 읽지 못하는 자도 알아볼 표식이 필요합니다.',
'왕평: 낮은 길에는 흰 깃발, 군용길에는 검은 표식을 세우겠습니다.',
'죽림 길목마다 회유 깃발과 군용 표식이 서로 다른 방향을 가리키기 시작했습니다.'
],
choices: [
{
id: 'raise-amnesty-banners',
label: '회유 깃발을 세운다',
response: '마량은 짧은 회유문을 깃발에 달았고, 왕평은 물러날 길목에 같은 표식을 반복해 세웠습니다.',
bondExp: 50,
itemRewards: ['콩 12']
},
{
id: 'separate-war-paths',
label: '군용길을 분리한다',
response: '왕평은 군대가 지나갈 길을 따로 표시했고, 마량은 그 길이 마을로 이어지지 않게 문구를 고쳤습니다.',
bondExp: 48,
itemRewards: ['상처약 12']
}
]
}
];
@@ -7734,7 +7891,8 @@ export class CampScene extends Phaser.Scene {
battleId === campBattleIds.fortyEighth ||
battleId === campBattleIds.fortyNinth ||
battleId === campBattleIds.fiftieth ||
battleId === campBattleIds.fiftyFirst
battleId === campBattleIds.fiftyFirst ||
battleId === campBattleIds.fiftySecond
) {
this.campaign = ensureCampaignRosterUnits(
[
@@ -7788,7 +7946,8 @@ export class CampScene extends Phaser.Scene {
...mengHuoPacificationBonds,
...mengHuoSecondCaptureBonds,
...mengHuoThirdCaptureBonds,
...mengHuoFourthCaptureBonds
...mengHuoFourthCaptureBonds,
...mengHuoFifthCaptureBonds
]
);
this.report = this.campaign.firstBattleReport ?? this.report;
@@ -8426,6 +8585,9 @@ export class CampScene extends Phaser.Scene {
if (this.campaign?.step === 'baidi-entrustment-camp') {
return '백제성 유탁 후 군영';
}
if (battleId === campBattleIds.fiftySecond) {
return '칠종칠금 5차전 후 군영';
}
if (battleId === campBattleIds.fiftyFirst) {
return '칠종칠금 4차전 후 군영';
}

View File

@@ -7,6 +7,7 @@ import {
eleventhBattleScenario,
fiftiethBattleScenario,
fiftyFirstBattleScenario,
fiftySecondBattleScenario,
fortiethBattleScenario,
fortyFirstBattleScenario,
fortySecondBattleScenario,
@@ -402,6 +403,7 @@ export class TitleScene extends Phaser.Scene {
campaign.step === 'forty-ninth-camp' ||
campaign.step === 'fiftieth-camp' ||
campaign.step === 'fifty-first-camp' ||
campaign.step === 'fifty-second-camp' ||
campaign.step === 'hanzhong-king-camp' ||
campaign.step === 'shu-han-foundation-camp' ||
campaign.step === 'baidi-entrustment-camp'
@@ -665,6 +667,11 @@ export class TitleScene extends Phaser.Scene {
return;
}
if (campaign.step === 'fifty-second-battle') {
this.scene.start('BattleScene', { battleId: fiftySecondBattleScenario.id });
return;
}
if (campaign.step === 'first-victory-story') {
this.scene.start('StoryScene', { pages: firstBattleVictoryPages, nextScene: 'TitleScene' });
return;

View File

@@ -144,6 +144,8 @@ export type CampaignStep =
| 'fiftieth-camp'
| 'fifty-first-battle'
| 'fifty-first-camp'
| 'fifty-second-battle'
| 'fifty-second-camp'
| 'hanzhong-king-camp'
| 'shu-han-foundation-camp'
| 'baidi-entrustment-camp';
@@ -303,7 +305,8 @@ const campaignBattleSteps: Record<string, { victory: CampaignStep; retry: Campai
'forty-eighth-battle-meng-huo-main-force': { victory: 'forty-eighth-camp', retry: 'forty-eighth-battle' },
'forty-ninth-battle-meng-huo-second-capture': { victory: 'forty-ninth-camp', retry: 'forty-ninth-battle' },
'fiftieth-battle-meng-huo-third-capture': { victory: 'fiftieth-camp', retry: 'fiftieth-battle' },
'fifty-first-battle-meng-huo-fourth-capture': { victory: 'fifty-first-camp', retry: 'fifty-first-battle' }
'fifty-first-battle-meng-huo-fourth-capture': { victory: 'fifty-first-camp', retry: 'fifty-first-battle' },
'fifty-second-battle-meng-huo-fifth-capture': { victory: 'fifty-second-camp', retry: 'fifty-second-battle' }
};
export type CampaignSaveSlotSummary = {