Validate sortie deployment terrain

This commit is contained in:
2026-07-05 12:39:02 +09:00
parent 43a25f4703
commit 2031480abd
2 changed files with 33 additions and 10 deletions

View File

@@ -38,7 +38,7 @@ try {
validateMap(errors, scenario, terrainKeys, context);
validateUnits(errors, scenario, classKeys, itemKeys, equipmentSlots, context);
validateObjectives(errors, scenario, context);
validateSortie(errors, scenario, classKeys, formationRoles, context);
validateSortie(errors, scenario, classKeys, formationRoles, terrainRules, context);
validateRewards(errors, scenario, scenarioIds, itemNames, knownRewardUnitIds, context);
});
@@ -192,13 +192,15 @@ function validateObjectives(errors, scenario, context) {
});
}
function validateSortie(errors, scenario, classKeys, formationRoles, context) {
function validateSortie(errors, scenario, classKeys, formationRoles, terrainRules, context) {
const sortie = scenario.sortie;
if (!sortie) {
return;
}
const unitIds = new Set(scenario.units.map((unit) => unit.id));
const allyUnitIds = new Set(scenario.units.filter((unit) => unit.faction === 'ally').map((unit) => unit.id));
const enemyTiles = new Set(scenario.units.filter((unit) => unit.faction === 'enemy').map((unit) => `${unit.x},${unit.y}`));
if (!Number.isInteger(sortie.sortieLimit) || sortie.sortieLimit < 1) {
errors.push(`${context}: invalid sortie limit ${sortie.sortieLimit}`);
}
@@ -230,6 +232,8 @@ function validateSortie(errors, scenario, classKeys, formationRoles, context) {
[...requiredUnits, ...excludedUnits].forEach((unitId) => {
if (!unitIds.has(unitId)) {
errors.push(`${context}: sortie references missing unit "${unitId}"`);
} else if (!allyUnitIds.has(unitId)) {
errors.push(`${context}: sortie references non-allied unit "${unitId}"`);
}
});
const recommendedUnitIds = new Set();
@@ -240,6 +244,8 @@ function validateSortie(errors, scenario, classKeys, formationRoles, context) {
recommendedUnitIds.add(recommendation.unitId);
if (!unitIds.has(recommendation.unitId)) {
errors.push(`${context}: recommended unit "${recommendation.unitId}" is missing from units`);
} else if (!allyUnitIds.has(recommendation.unitId)) {
errors.push(`${context}: recommended unit "${recommendation.unitId}" is not allied`);
}
if (excludedUnitIds.has(recommendation.unitId)) {
errors.push(`${context}: excluded unit "${recommendation.unitId}" cannot also be recommended`);
@@ -276,12 +282,24 @@ function validateSortie(errors, scenario, classKeys, formationRoles, context) {
errors.push(`${context}: deployment slot ${slot.x},${slot.y} has invalid role "${slot.role}"`);
}
const tileKey = `${slot.x},${slot.y}`;
const terrain = scenario.map.terrain[slot.y][slot.x];
if (terrainRules[terrain]?.passable === false) {
errors.push(`${context}: deployment slot ${tileKey} is on impassable terrain "${terrain}"`);
}
if (enemyTiles.has(tileKey)) {
errors.push(`${context}: deployment slot ${tileKey} overlaps an enemy unit`);
}
if (deploymentTiles.has(tileKey)) {
errors.push(`${context}: duplicate deployment slot ${tileKey}`);
}
deploymentTiles.add(tileKey);
if (slot.unitId && !unitIds.has(slot.unitId)) {
errors.push(`${context}: deployment slot references missing unit "${slot.unitId}"`);
} else if (slot.unitId && !allyUnitIds.has(slot.unitId)) {
errors.push(`${context}: deployment slot references non-allied unit "${slot.unitId}"`);
}
if (slot.unitId && excludedUnitIds.has(slot.unitId)) {
errors.push(`${context}: excluded unit "${slot.unitId}" cannot have a deployment slot`);
}
if (slot.unitId) {
if (deploymentUnitIds.has(slot.unitId)) {
@@ -290,6 +308,11 @@ function validateSortie(errors, scenario, classKeys, formationRoles, context) {
deploymentUnitIds.add(slot.unitId);
}
});
requiredUnits.forEach((unitId) => {
if (!deploymentUnitIds.has(unitId)) {
errors.push(`${context}: required unit "${unitId}" must have an explicit deployment slot`);
}
});
}
function validateRewards(errors, scenario, scenarioIds, itemNames, knownRewardUnitIds, context) {