feat: add second-victory relief exploration

This commit is contained in:
2026-07-27 10:53:20 +09:00
parent e9fcd27611
commit f07a45c62b
24 changed files with 4583 additions and 282 deletions

View File

@@ -16,6 +16,10 @@ try {
const { firstPursuitScoutVisitDefinition } = await server.ssrLoadModule(
'/src/game/data/firstPursuitScoutVisit.ts'
);
const { secondBattleReliefExplorationDefinition } =
await server.ssrLoadModule(
'/src/game/data/secondBattleReliefExploration.ts'
);
const scenarioData = await server.ssrLoadModule('/src/game/data/scenario.ts');
const { isCampaignStep } = await server.ssrLoadModule('/src/game/state/campaignState.ts');
const itemRewardArrays = collectItemRewardArrays(source);
@@ -26,9 +30,12 @@ try {
const knownBondsById = collectKnownBondsById(battleScenarios, scenarioData);
const dialogueEvents = collectCampEvents(source, 'campDialogues');
const visitEvents = collectCampEvents(source, 'campVisits').map((event) =>
materializeCanonicalFirstPursuitVisit(
event,
firstPursuitScoutVisitDefinition
materializeCanonicalSecondBattleReliefVisit(
materializeCanonicalFirstPursuitVisit(
event,
firstPursuitScoutVisitDefinition
),
secondBattleReliefExplorationDefinition
)
);
const campaignStepReferences =
@@ -355,6 +362,32 @@ function materializeCanonicalFirstPursuitVisit(event, definition) {
};
}
function materializeCanonicalSecondBattleReliefVisit(event, definition) {
if (!event.body.includes('secondBattleReliefExplorationDefinition')) {
return event;
}
const quote = (value) =>
`'${String(value).replaceAll('\\', '\\\\').replaceAll("'", "\\'")}'`;
const choiceSource = definition.choices
.map(
(choice) =>
`{ id: ${quote(choice.id)}, bondId: ${quote(choice.bondId)}, ` +
`bondExp: ${choice.bondExp}, ` +
`itemRewards: [${choice.itemRewards.map(quote).join(', ')}] }`
)
.join(', ');
const canonicalSource =
`{ id: ${quote(definition.id)}, ` +
'availableAfterBattleIds: [campBattleIds.second], ' +
"availableDuringSteps: ['second-camp'], " +
`choices: [${choiceSource}], `;
return {
...event,
body: canonicalSource + event.body.slice(1)
};
}
function validateCampEventCollection(events, collectionName, campBattleIdKeys, isCampaignStep) {
const seenIds = new Map();
let campaignStepReferenceCount = 0;
@@ -433,7 +466,7 @@ function validateCampBondReferences(events, collectionName, knownBondsById, opti
return;
}
bondReferenceCount += bondIdEntries.length;
if (bondIdEntries.length > 1) {
if (bondIdEntries.length > 1 && collectionName !== 'campVisits') {
errors.push(`${sourcePath}:${lineNumber(event.offset)} ${context} has multiple bondId properties`);
}
@@ -469,10 +502,20 @@ function validateRewardBondLinks(events, collectionName, rewardPropertyName) {
return;
}
rewardReferenceCount += rewardEntries.length;
if (collectStringProperties(event.body, 'bondId').length === 0) {
const bondIdCount = collectStringProperties(event.body, 'bondId').length;
if (bondIdCount === 0) {
errors.push(
`${sourcePath}:${lineNumber(rewardEntries[0].offset)} ${context} has ${rewardPropertyName} rewards but no bondId`
);
} else if (
bondIdCount > 1 &&
bondIdCount !== rewardEntries.length
) {
errors.push(
`${sourcePath}:${lineNumber(rewardEntries[0].offset)} ${context} has ` +
`${rewardEntries.length} ${rewardPropertyName} rewards but ` +
`${bondIdCount} per-choice bondIds`
);
}
});
return rewardReferenceCount;