652 lines
20 KiB
JavaScript
652 lines
20 KiB
JavaScript
import { createServer } from 'vite';
|
|
|
|
const storage = new Map();
|
|
globalThis.window = {
|
|
localStorage: {
|
|
getItem(key) {
|
|
return storage.has(key) ? storage.get(key) : null;
|
|
},
|
|
setItem(key, value) {
|
|
storage.set(key, String(value));
|
|
},
|
|
removeItem(key) {
|
|
storage.delete(key);
|
|
},
|
|
clear() {
|
|
storage.clear();
|
|
}
|
|
}
|
|
};
|
|
|
|
const server = await createServer({
|
|
logLevel: 'error',
|
|
server: { middlewareMode: true, hmr: false },
|
|
appType: 'custom'
|
|
});
|
|
|
|
try {
|
|
const memoryModule = await server.ssrLoadModule(
|
|
'/src/game/data/firstBattleCamaraderieMemory.ts'
|
|
);
|
|
const campaignModule = await server.ssrLoadModule(
|
|
'/src/game/state/campaignState.ts'
|
|
);
|
|
const battleSaveModule = await server.ssrLoadModule(
|
|
'/src/game/state/battleSaveState.ts'
|
|
);
|
|
const { battleScenarios } = await server.ssrLoadModule(
|
|
'/src/game/data/battles.ts'
|
|
);
|
|
|
|
verifyDominantSelection(memoryModule);
|
|
verifyMemoryAndBonus(memoryModule);
|
|
verifyCampGuidanceAndSortieGuard(memoryModule);
|
|
verifyCampaignRoundTrip(memoryModule, campaignModule, battleScenarios);
|
|
verifyBattleSaveRoundTrip(battleSaveModule);
|
|
|
|
console.log(
|
|
'First-battle camaraderie memory verification passed (selection, camp guidance, one-warning sortie confirmation, campaign settlement, legacy battle save, and +5%p gate).'
|
|
);
|
|
} finally {
|
|
await server.close();
|
|
}
|
|
|
|
function verifyDominantSelection(memoryModule) {
|
|
const bonds = [
|
|
{ id: 'bond-c', unitIds: ['liu-bei', 'guan-yu'] },
|
|
{ id: 'bond-a', unitIds: ['liu-bei', 'zhang-fei'] },
|
|
{ id: 'bond-b', unitIds: ['guan-yu', 'zhang-fei'] }
|
|
];
|
|
const statsByBond = {
|
|
'bond-c': { attempts: 8, successes: 0, additionalDamage: 80 },
|
|
'bond-b': { attempts: 2, successes: 1, additionalDamage: 6 },
|
|
'bond-a': { attempts: 3, successes: 1, additionalDamage: 6 }
|
|
};
|
|
const snapshot = memoryModule.selectDominantSortieCooperationSnapshot({
|
|
bonds,
|
|
statsByBond
|
|
});
|
|
assert(
|
|
snapshot?.bondId === 'bond-a' &&
|
|
snapshot.attempts === 3 &&
|
|
snapshot.successes === 1 &&
|
|
snapshot.additionalDamage === 6,
|
|
`A successful pair must outrank a failure-only pair, then use success, damage, attempts, and stable id: ${JSON.stringify(snapshot)}`
|
|
);
|
|
snapshot.unitIds[0] = 'mutated';
|
|
assert(
|
|
bonds[1].unitIds[0] === 'liu-bei',
|
|
'Dominant selection must clone the canonical bond pair.'
|
|
);
|
|
|
|
const failureOnly = memoryModule.selectDominantSortieCooperationSnapshot({
|
|
bonds: [{ id: 'bond-c', unitIds: ['liu-bei', 'guan-yu'] }],
|
|
statsByBond: {
|
|
'bond-c': { attempts: 2.9, successes: -4, additionalDamage: 99 }
|
|
}
|
|
});
|
|
assert(
|
|
failureOnly?.attempts === 2 &&
|
|
failureOnly.successes === 0 &&
|
|
failureOnly.additionalDamage === 0,
|
|
`Failure-only cooperation must discard impossible damage: ${JSON.stringify(failureOnly)}`
|
|
);
|
|
}
|
|
|
|
function verifyMemoryAndBonus(memoryModule) {
|
|
const snapshot = {
|
|
version: 1,
|
|
bondId: 'liu-bei__zhang-fei',
|
|
unitIds: ['liu-bei', 'zhang-fei'],
|
|
attempts: 1,
|
|
successes: 1,
|
|
additionalDamage: 7
|
|
};
|
|
const campaign = {
|
|
firstBattleReport: {
|
|
battleId: 'second-battle-yellow-turban-pursuit',
|
|
outcome: 'defeat'
|
|
},
|
|
battleHistory: {
|
|
'first-battle-zhuo-commandery': {
|
|
battleId: 'first-battle-zhuo-commandery',
|
|
outcome: 'victory',
|
|
units: [
|
|
{ unitId: 'liu-bei', name: '유비' },
|
|
{ unitId: 'zhang-fei', name: '장비' }
|
|
],
|
|
sortieCooperation: snapshot
|
|
},
|
|
'second-battle-yellow-turban-pursuit': {
|
|
battleId: 'second-battle-yellow-turban-pursuit',
|
|
outcome: 'defeat',
|
|
units: []
|
|
}
|
|
},
|
|
completedCampDialogues: []
|
|
};
|
|
const memory = memoryModule.resolveFirstBattleCamaraderieMemory(campaign);
|
|
assert(
|
|
memory?.bondId === snapshot.bondId &&
|
|
memory.rememberedOutcome === 'success' &&
|
|
memory.completed === false &&
|
|
memory.dialogue.id === memoryModule.firstBattleCamaraderieDialogueId &&
|
|
memory.dialogue.lines.length === 3 &&
|
|
memory.dialogue.choices.every((choice) => choice.response.includes('+5%p')),
|
|
`Memory must come from the first victory settlement and explain the unlock for either choice: ${JSON.stringify(memory)}`
|
|
);
|
|
assert(
|
|
memoryModule.firstBattleCamaraderieBonusFor({
|
|
campaign,
|
|
battleId: memoryModule.firstBattleCamaraderieTargetBattleId,
|
|
coreBondId: snapshot.bondId
|
|
}) === 0,
|
|
'The remembered pair must not receive a bonus before its camp dialogue is completed.'
|
|
);
|
|
|
|
campaign.completedCampDialogues.push(
|
|
memoryModule.firstBattleCamaraderieDialogueId
|
|
);
|
|
assert(
|
|
memoryModule.firstBattleCamaraderieBonusFor({
|
|
campaign,
|
|
battleId: memoryModule.firstBattleCamaraderieTargetBattleId,
|
|
coreBondId: snapshot.bondId
|
|
}) === 5 &&
|
|
memoryModule.firstBattleCamaraderieBonusFor({
|
|
campaign,
|
|
battleId: memoryModule.firstBattleCamaraderieTargetBattleId,
|
|
coreBondId: 'liu-bei__guan-yu'
|
|
}) === 0 &&
|
|
memoryModule.firstBattleCamaraderieBonusFor({
|
|
campaign,
|
|
battleId: 'third-battle-guangzong-road',
|
|
coreBondId: snapshot.bondId
|
|
}) === 0,
|
|
'The +5%p bonus must require dialogue completion, the exact next battle, and the same manually selected core pair.'
|
|
);
|
|
assert(
|
|
memoryModule.resolveFirstBattleCamaraderieMemory({
|
|
battleHistory: {
|
|
'first-battle-zhuo-commandery': {
|
|
...campaign.battleHistory['first-battle-zhuo-commandery'],
|
|
outcome: 'defeat'
|
|
}
|
|
}
|
|
}) === undefined,
|
|
'A first-battle defeat must never create a camaraderie memory.'
|
|
);
|
|
|
|
const attemptedCampaign = structuredClone(campaign);
|
|
attemptedCampaign.completedCampDialogues = [];
|
|
attemptedCampaign.battleHistory[
|
|
memoryModule.firstBattleCamaraderieSourceBattleId
|
|
].sortieCooperation = {
|
|
...snapshot,
|
|
attempts: 2,
|
|
successes: 0,
|
|
additionalDamage: 99
|
|
};
|
|
const attemptedMemory =
|
|
memoryModule.resolveFirstBattleCamaraderieMemory(attemptedCampaign);
|
|
assert(
|
|
attemptedMemory?.rememberedOutcome === 'attempted' &&
|
|
attemptedMemory.attempts === 2 &&
|
|
attemptedMemory.successes === 0 &&
|
|
attemptedMemory.additionalDamage === 0 &&
|
|
attemptedMemory.dialogue.title.includes('엇갈린') &&
|
|
attemptedMemory.dialogue.lines.length === 3 &&
|
|
attemptedMemory.dialogue.choices.every(
|
|
(choice) => choice.response.includes('+5%p')
|
|
),
|
|
`A failed-only pursuit must become a safe learning dialogue with no phantom damage: ${JSON.stringify(attemptedMemory)}`
|
|
);
|
|
}
|
|
|
|
function verifyCampGuidanceAndSortieGuard(memoryModule) {
|
|
const snapshot = {
|
|
version: 1,
|
|
bondId: 'liu-bei__zhang-fei',
|
|
unitIds: ['liu-bei', 'zhang-fei'],
|
|
attempts: 1,
|
|
successes: 1,
|
|
additionalDamage: 7
|
|
};
|
|
const baseCampaign = {
|
|
battleHistory: {
|
|
[memoryModule.firstBattleCamaraderieSourceBattleId]: {
|
|
battleId: memoryModule.firstBattleCamaraderieSourceBattleId,
|
|
outcome: 'victory',
|
|
units: [
|
|
{ unitId: 'liu-bei', name: '유비' },
|
|
{ unitId: 'zhang-fei', name: '장비' }
|
|
],
|
|
sortieCooperation: snapshot
|
|
}
|
|
},
|
|
completedCampDialogues: []
|
|
};
|
|
const incompleteMemory =
|
|
memoryModule.resolveFirstBattleCamaraderieMemory(baseCampaign);
|
|
const completedMemory =
|
|
memoryModule.resolveFirstBattleCamaraderieMemory({
|
|
...baseCampaign,
|
|
completedCampDialogues: [
|
|
memoryModule.firstBattleCamaraderieDialogueId
|
|
]
|
|
});
|
|
const targetBattleId =
|
|
memoryModule.firstBattleCamaraderieTargetBattleId;
|
|
|
|
const guidanceCases = [
|
|
{
|
|
label: '기억 없음',
|
|
options: {
|
|
memory: undefined,
|
|
nextBattleId: targetBattleId,
|
|
selectedCoreBondId: undefined
|
|
},
|
|
expected: {
|
|
kind: 'sortie',
|
|
label: '출진 준비',
|
|
targetId: null
|
|
}
|
|
},
|
|
{
|
|
label: '기억 미완료',
|
|
options: {
|
|
memory: incompleteMemory,
|
|
nextBattleId: targetBattleId,
|
|
selectedCoreBondId: undefined
|
|
},
|
|
expected: {
|
|
kind: 'dialogue',
|
|
label: '전우 기억 되짚기',
|
|
targetId: memoryModule.firstBattleCamaraderieDialogueId
|
|
}
|
|
},
|
|
{
|
|
label: '기억 완료 + 핵심조 미지정',
|
|
options: {
|
|
memory: completedMemory,
|
|
nextBattleId: targetBattleId,
|
|
selectedCoreBondId: undefined
|
|
},
|
|
expected: {
|
|
kind: 'sortie',
|
|
label: '전우 조 편성하기',
|
|
targetId: null
|
|
}
|
|
},
|
|
{
|
|
label: '기억 완료 + 정확한 핵심조 지정',
|
|
options: {
|
|
memory: completedMemory,
|
|
nextBattleId: targetBattleId,
|
|
selectedCoreBondId: snapshot.bondId
|
|
},
|
|
expected: {
|
|
kind: 'sortie',
|
|
label: '출진 준비',
|
|
targetId: null
|
|
}
|
|
}
|
|
];
|
|
guidanceCases.forEach((testCase) => {
|
|
const actual =
|
|
memoryModule.resolveFirstCampCamaraderieGuidedAction(
|
|
testCase.options
|
|
);
|
|
assert(
|
|
JSON.stringify(actual) === JSON.stringify(testCase.expected),
|
|
`${testCase.label}: 안내 동작이 예상과 다릅니다. ${JSON.stringify(actual)}`
|
|
);
|
|
});
|
|
|
|
const commonGuardOptions = {
|
|
nextBattleId: targetBattleId,
|
|
selectedUnitIds: ['zhang-fei', 'guan-yu', 'liu-bei']
|
|
};
|
|
const noMemoryGuard =
|
|
memoryModule.resolveFirstBattleCamaraderieSortieGuard({
|
|
...commonGuardOptions,
|
|
memory: undefined
|
|
});
|
|
assert(
|
|
noMemoryGuard.applies === false &&
|
|
noMemoryGuard.shouldWarn === false &&
|
|
noMemoryGuard.confirmedByRepeat === false,
|
|
`기억 없음: 첫 출진을 막으면 안 됩니다. ${JSON.stringify(noMemoryGuard)}`
|
|
);
|
|
|
|
const incompleteFirst =
|
|
memoryModule.resolveFirstBattleCamaraderieSortieGuard({
|
|
...commonGuardOptions,
|
|
memory: incompleteMemory
|
|
});
|
|
const incompleteSecond =
|
|
memoryModule.resolveFirstBattleCamaraderieSortieGuard({
|
|
...commonGuardOptions,
|
|
memory: incompleteMemory,
|
|
selectedUnitIds: ['liu-bei', 'guan-yu', 'zhang-fei'],
|
|
previousSignature: incompleteFirst.signature ?? undefined
|
|
});
|
|
assert(
|
|
incompleteFirst.applies === true &&
|
|
incompleteFirst.shouldWarn === true &&
|
|
incompleteFirst.notice.includes('전우 기억 미완료') &&
|
|
incompleteFirst.notice.includes('유비↔장비') &&
|
|
incompleteFirst.notice.includes('+5%p') &&
|
|
incompleteFirst.notice.includes('출진을 한 번 더') &&
|
|
incompleteSecond.applies === true &&
|
|
incompleteSecond.shouldWarn === false &&
|
|
incompleteSecond.confirmedByRepeat === true &&
|
|
incompleteSecond.signature === incompleteFirst.signature,
|
|
`기억 미완료: 첫 클릭 경고 뒤 같은 조건의 둘째 클릭은 통과해야 합니다. ${JSON.stringify({
|
|
first: incompleteFirst,
|
|
second: incompleteSecond
|
|
})}`
|
|
);
|
|
|
|
const unselectedFirst =
|
|
memoryModule.resolveFirstBattleCamaraderieSortieGuard({
|
|
...commonGuardOptions,
|
|
memory: completedMemory
|
|
});
|
|
const unselectedSecond =
|
|
memoryModule.resolveFirstBattleCamaraderieSortieGuard({
|
|
...commonGuardOptions,
|
|
memory: completedMemory,
|
|
previousSignature: unselectedFirst.signature ?? undefined
|
|
});
|
|
assert(
|
|
unselectedFirst.applies === true &&
|
|
unselectedFirst.shouldWarn === true &&
|
|
unselectedFirst.notice.includes('전우 기억 미지정') &&
|
|
unselectedFirst.notice.includes('유비↔장비') &&
|
|
unselectedFirst.notice.includes('+5%p') &&
|
|
unselectedFirst.notice.includes('출진을 한 번 더') &&
|
|
unselectedSecond.applies === true &&
|
|
unselectedSecond.shouldWarn === false &&
|
|
unselectedSecond.confirmedByRepeat === true &&
|
|
unselectedSecond.signature === unselectedFirst.signature,
|
|
`기억 완료 + 핵심조 미지정: 첫 클릭 경고 뒤 같은 조건의 둘째 클릭은 통과해야 합니다. ${JSON.stringify({
|
|
first: unselectedFirst,
|
|
second: unselectedSecond
|
|
})}`
|
|
);
|
|
|
|
const exactPairGuard =
|
|
memoryModule.resolveFirstBattleCamaraderieSortieGuard({
|
|
...commonGuardOptions,
|
|
memory: completedMemory,
|
|
selectedCoreBondId: snapshot.bondId
|
|
});
|
|
assert(
|
|
exactPairGuard.applies === false &&
|
|
exactPairGuard.shouldWarn === false &&
|
|
exactPairGuard.confirmedByRepeat === false,
|
|
`기억 완료 + 정확한 핵심조 지정: 첫 출진을 막으면 안 됩니다. ${JSON.stringify(exactPairGuard)}`
|
|
);
|
|
|
|
const changedRosterGuard =
|
|
memoryModule.resolveFirstBattleCamaraderieSortieGuard({
|
|
...commonGuardOptions,
|
|
memory: completedMemory,
|
|
selectedUnitIds: ['liu-bei', 'guan-yu'],
|
|
previousSignature: unselectedFirst.signature ?? undefined
|
|
});
|
|
assert(
|
|
changedRosterGuard.shouldWarn === true &&
|
|
changedRosterGuard.signature !== unselectedFirst.signature,
|
|
`첫 경고 뒤 편성이 달라지면 오래된 확인을 재사용하면 안 됩니다. ${JSON.stringify(changedRosterGuard)}`
|
|
);
|
|
}
|
|
|
|
function verifyCampaignRoundTrip(
|
|
memoryModule,
|
|
campaignModule,
|
|
battleScenarios
|
|
) {
|
|
storage.clear();
|
|
campaignModule.resetCampaignState();
|
|
const scenario =
|
|
battleScenarios[memoryModule.firstBattleCamaraderieSourceBattleId];
|
|
const cooperation = {
|
|
version: 1,
|
|
bondId: 'liu-bei__zhang-fei',
|
|
unitIds: ['liu-bei', 'zhang-fei'],
|
|
attempts: 3,
|
|
successes: 1,
|
|
additionalDamage: 8
|
|
};
|
|
campaignModule.setFirstBattleReport({
|
|
battleId: scenario.id,
|
|
battleTitle: scenario.title,
|
|
outcome: 'victory',
|
|
turnNumber: 5,
|
|
rewardGold: 100,
|
|
defeatedEnemies: scenario.units.filter((unit) => unit.faction === 'enemy')
|
|
.length,
|
|
totalEnemies: scenario.units.filter((unit) => unit.faction === 'enemy')
|
|
.length,
|
|
objectives: [],
|
|
units: scenario.units,
|
|
bonds: scenario.bonds.map((bond) => ({ ...bond, battleExp: 0 })),
|
|
itemRewards: [],
|
|
sortieCooperation: cooperation,
|
|
completedCampDialogues: [],
|
|
completedCampVisits: [],
|
|
createdAt: '2026-07-27T00:00:00.000Z'
|
|
});
|
|
|
|
let campaign = campaignModule.getCampaignState();
|
|
const settlement =
|
|
campaign.battleHistory[memoryModule.firstBattleCamaraderieSourceBattleId];
|
|
assert(
|
|
JSON.stringify(campaign.firstBattleReport?.sortieCooperation) ===
|
|
JSON.stringify(cooperation) &&
|
|
JSON.stringify(settlement?.sortieCooperation) ===
|
|
JSON.stringify(cooperation) &&
|
|
campaign.firstBattleReport.sortieCooperation !==
|
|
settlement.sortieCooperation &&
|
|
campaign.firstBattleReport.sortieCooperation.unitIds !==
|
|
settlement.sortieCooperation.unitIds,
|
|
'Report-to-settlement creation must preserve and deep-clone the selected cooperation snapshot.'
|
|
);
|
|
|
|
campaign = campaignModule.loadCampaignState();
|
|
assert(
|
|
campaign.battleHistory[memoryModule.firstBattleCamaraderieSourceBattleId]
|
|
?.sortieCooperation?.bondId === cooperation.bondId,
|
|
'A valid cooperation snapshot must survive a persisted campaign round trip.'
|
|
);
|
|
const failedOnly = campaignModule.normalizeCampaignSortieCooperationSnapshot(
|
|
{
|
|
...cooperation,
|
|
attempts: 2.8,
|
|
successes: -1,
|
|
additionalDamage: 100
|
|
},
|
|
{
|
|
allowedUnitIds: new Set(cooperation.unitIds),
|
|
bonds: scenario.bonds
|
|
}
|
|
);
|
|
assert(
|
|
failedOnly?.attempts === 2 &&
|
|
failedOnly.successes === 0 &&
|
|
failedOnly.additionalDamage === 0,
|
|
`Campaign normalization must canonicalize failed-only totals: ${JSON.stringify(failedOnly)}`
|
|
);
|
|
|
|
const raw = JSON.parse(storage.get(campaignModule.campaignStorageKey));
|
|
raw.firstBattleReport.sortieCooperation.unitIds[1] = 'ghost-unit';
|
|
raw.battleHistory[
|
|
memoryModule.firstBattleCamaraderieSourceBattleId
|
|
].sortieCooperation.bondId = 'ghost-bond';
|
|
storage.set(campaignModule.campaignStorageKey, JSON.stringify(raw));
|
|
campaign = campaignModule.loadCampaignState();
|
|
assert(
|
|
campaign.firstBattleReport?.sortieCooperation === undefined &&
|
|
campaign.battleHistory[
|
|
memoryModule.firstBattleCamaraderieSourceBattleId
|
|
]?.sortieCooperation === undefined,
|
|
'Unknown roster and bond references must be removed without rejecting the campaign save.'
|
|
);
|
|
}
|
|
|
|
function verifyBattleSaveRoundTrip(battleSaveModule) {
|
|
const options = {
|
|
expectedBattleId: 'first-battle-zhuo-commandery',
|
|
mapWidth: 12,
|
|
mapHeight: 8,
|
|
validUnitIds: new Set(['liu-bei', 'guan-yu', 'rebel-1']),
|
|
validAllyUnitIds: new Set(['liu-bei', 'guan-yu']),
|
|
validEnemyUnitIds: new Set(['rebel-1']),
|
|
validUsableIds: new Set(['bean']),
|
|
validItemIds: new Set(['bean']),
|
|
validEquipmentItemIds: new Set([
|
|
'training-sword',
|
|
'cloth-armor',
|
|
'grain-pouch'
|
|
]),
|
|
validEquipmentSlotItems: {
|
|
weapon: new Set(['training-sword']),
|
|
armor: new Set(['cloth-armor']),
|
|
accessory: new Set(['grain-pouch'])
|
|
},
|
|
validTriggeredBattleEventIds: new Set(['opening'])
|
|
};
|
|
const base = createValidBattleSaveState();
|
|
const normalized = battleSaveModule.normalizeBattleSaveState(
|
|
{
|
|
...base,
|
|
cooperationStatsByBond: {
|
|
taoyuan: {
|
|
attempts: 2.9,
|
|
successes: -1,
|
|
additionalDamage: 100
|
|
},
|
|
'ghost-bond': {
|
|
attempts: 9,
|
|
successes: 9,
|
|
additionalDamage: 99
|
|
}
|
|
}
|
|
},
|
|
options
|
|
);
|
|
assert(
|
|
JSON.stringify(normalized?.cooperationStatsByBond) ===
|
|
JSON.stringify({
|
|
taoyuan: { attempts: 2, successes: 0, additionalDamage: 0 }
|
|
}),
|
|
`Battle saves must normalize per-bond totals and discard unknown bonds: ${JSON.stringify(normalized?.cooperationStatsByBond)}`
|
|
);
|
|
|
|
const legacy = battleSaveModule.normalizeBattleSaveState(
|
|
{
|
|
...base,
|
|
sortieResonanceBondId: 'taoyuan',
|
|
coreResonanceStats: {
|
|
attempts: 3,
|
|
successes: 1,
|
|
failures: 2,
|
|
additionalDamage: 9
|
|
}
|
|
},
|
|
options
|
|
);
|
|
assert(
|
|
JSON.stringify(legacy?.cooperationStatsByBond) ===
|
|
JSON.stringify({
|
|
taoyuan: { attempts: 3, successes: 1, additionalDamage: 9 }
|
|
}),
|
|
`Legacy core-resonance totals must backfill the generic per-bond record: ${JSON.stringify(legacy?.cooperationStatsByBond)}`
|
|
);
|
|
assert(
|
|
!battleSaveModule.isValidBattleSaveState(
|
|
{
|
|
...base,
|
|
cooperationStatsByBond: {
|
|
taoyuan: { attempts: 1, successes: 0, additionalDamage: 7 }
|
|
}
|
|
},
|
|
options
|
|
),
|
|
'Direct validation must reject impossible failure-only additional damage.'
|
|
);
|
|
}
|
|
|
|
function createValidBattleSaveState() {
|
|
return {
|
|
version: 1,
|
|
battleId: 'first-battle-zhuo-commandery',
|
|
campaignStep: 'first-battle',
|
|
sortieOrderId: 'elite',
|
|
savedAt: '2026-07-27T00:00:00.000Z',
|
|
turnNumber: 2,
|
|
activeFaction: 'ally',
|
|
rosterTab: 'ally',
|
|
actedUnitIds: [],
|
|
attackIntents: [],
|
|
battleLog: [],
|
|
units: [
|
|
savedUnit('liu-bei', 1, 2),
|
|
savedUnit('guan-yu', 2, 2),
|
|
savedUnit('rebel-1', 8, 4)
|
|
],
|
|
bonds: [
|
|
{
|
|
id: 'taoyuan',
|
|
unitIds: ['liu-bei', 'guan-yu'],
|
|
title: 'Oath',
|
|
level: 30,
|
|
exp: 0,
|
|
battleExp: 0,
|
|
description: 'Shared oath.'
|
|
}
|
|
],
|
|
itemStocks: {},
|
|
battleBuffs: [],
|
|
battleStatuses: [],
|
|
battleStats: {},
|
|
enemyUsableUseKeys: [],
|
|
triggeredBattleEvents: []
|
|
};
|
|
}
|
|
|
|
function savedUnit(id, x, y) {
|
|
return {
|
|
id,
|
|
level: 2,
|
|
exp: 0,
|
|
hp: 30,
|
|
maxHp: 30,
|
|
attack: 10,
|
|
move: 4,
|
|
stats: {
|
|
might: 70,
|
|
intelligence: 70,
|
|
leadership: 70,
|
|
agility: 70,
|
|
luck: 70
|
|
},
|
|
x,
|
|
y,
|
|
direction: 'south',
|
|
equipment: {
|
|
weapon: { itemId: 'training-sword', level: 1, exp: 0 },
|
|
armor: { itemId: 'cloth-armor', level: 1, exp: 0 },
|
|
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
|
}
|
|
};
|
|
}
|
|
|
|
function assert(condition, message) {
|
|
if (!condition) {
|
|
throw new Error(message);
|
|
}
|
|
}
|