feat: expand ISAC content modeling
This commit is contained in:
@@ -22,7 +22,7 @@ const snapshotPath = resolve(snapshotArgument);
|
||||
const snapshot = JSON.parse(await readFile(snapshotPath, 'utf8'));
|
||||
if (
|
||||
snapshot?.format !== 'shd-build-isac-data' ||
|
||||
snapshot?.version !== 2 ||
|
||||
snapshot?.version !== 4 ||
|
||||
!Array.isArray(snapshot.tables) ||
|
||||
!Array.isArray(snapshot.icons)
|
||||
) {
|
||||
@@ -39,6 +39,152 @@ if (
|
||||
'The ISAC snapshot table manifest is incomplete or out of order.',
|
||||
);
|
||||
}
|
||||
for (const kind of ['weapon', 'gear']) {
|
||||
const options = snapshot.tables.find(
|
||||
(table) => table.name === `${kind}_mod_secondary_options`,
|
||||
).rows;
|
||||
const values = snapshot.tables.find(
|
||||
(table) => table.name === `${kind}_mod_secondary_quality_values`,
|
||||
).rows;
|
||||
const optionIds = options.map((row) => String(row.stat_attribute_id ?? ''));
|
||||
if (
|
||||
optionIds.some((id) => !id) ||
|
||||
new Set(optionIds).size !== optionIds.length ||
|
||||
(optionIds.length !== 0 &&
|
||||
(optionIds.length < 2 || optionIds.length > 50)) ||
|
||||
values.length !== optionIds.length * 3
|
||||
) {
|
||||
throw new Error(
|
||||
`The ISAC snapshot contains an incomplete shared ${kind} Mod secondary pool.`,
|
||||
);
|
||||
}
|
||||
for (const statId of optionIds) {
|
||||
const profile = values.filter(
|
||||
(row) => String(row.stat_attribute_id) === statId,
|
||||
);
|
||||
if (
|
||||
profile.length !== 3 ||
|
||||
['Specialized', 'Superior', 'High-end'].some(
|
||||
(quality) => !profile.some((row) => row.quality === quality),
|
||||
) ||
|
||||
profile.some((row) => {
|
||||
const endpoints = [
|
||||
row.level_1_min,
|
||||
row.level_1_max,
|
||||
row.level_20_min,
|
||||
row.level_20_max,
|
||||
];
|
||||
return (
|
||||
endpoints.some(
|
||||
(value) => typeof value !== 'number' || !Number.isFinite(value),
|
||||
) ||
|
||||
endpoints[1] < endpoints[0] ||
|
||||
endpoints[2] < endpoints[0] ||
|
||||
endpoints[3] < endpoints[1] ||
|
||||
endpoints[3] < endpoints[2]
|
||||
);
|
||||
})
|
||||
) {
|
||||
throw new Error(
|
||||
`The ISAC snapshot contains an invalid shared ${kind} Mod secondary profile for ${statId}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (
|
||||
values.some(
|
||||
(row) => !optionIds.includes(String(row.stat_attribute_id ?? '')),
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
`The ISAC snapshot contains an orphaned shared ${kind} Mod secondary range.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const [profileName, qualities, requiredOptions] of [
|
||||
['primary', ['Standard', 'Specialized', 'Superior', 'High-end'], 4],
|
||||
['secondary', ['Specialized', 'Superior', 'High-end'], null],
|
||||
]) {
|
||||
const options = snapshot.tables.find(
|
||||
(table) => table.name === `skill_chip_${profileName}_options`,
|
||||
).rows;
|
||||
const values = snapshot.tables.find(
|
||||
(table) => table.name === `skill_chip_${profileName}_quality_values`,
|
||||
).rows;
|
||||
const optionIds = options.map((row) => String(row.stat_attribute_id ?? ''));
|
||||
const validCardinality = requiredOptions
|
||||
? optionIds.length === requiredOptions
|
||||
: optionIds.length <= 50;
|
||||
if (
|
||||
!validCardinality ||
|
||||
optionIds.some((id) => !id) ||
|
||||
new Set(optionIds).size !== optionIds.length
|
||||
) {
|
||||
throw new Error(
|
||||
`The ISAC snapshot contains an incomplete shared Skill Chip ${profileName} profile.`,
|
||||
);
|
||||
}
|
||||
if (profileName === 'primary' && optionIds.length) {
|
||||
const attributes = snapshot.tables.find(
|
||||
(table) => table.name === 'stat_attributes',
|
||||
).rows;
|
||||
const expectedIds = attributes
|
||||
.filter(
|
||||
(row) =>
|
||||
Number(row.active) === 1 &&
|
||||
[
|
||||
'Skill Duration',
|
||||
'Skill Radius',
|
||||
'Skill Cooldown Recovery',
|
||||
'Skill Intensity',
|
||||
].includes(String(row.name)),
|
||||
)
|
||||
.map((row) => String(row.id))
|
||||
.sort();
|
||||
if (
|
||||
expectedIds.length !== 4 ||
|
||||
optionIds
|
||||
.slice()
|
||||
.sort()
|
||||
.some((id, index) => id !== expectedIds[index])
|
||||
)
|
||||
throw new Error(
|
||||
'The ISAC snapshot does not use the four fixed Skill Chip primary Attributes.',
|
||||
);
|
||||
}
|
||||
for (const statId of optionIds) {
|
||||
const rows = values.filter(
|
||||
(row) => String(row.stat_attribute_id ?? '') === statId,
|
||||
);
|
||||
if (!rows.length) continue;
|
||||
if (
|
||||
rows.length !== qualities.length ||
|
||||
qualities.some(
|
||||
(quality) => !rows.some((row) => row.quality === quality),
|
||||
) ||
|
||||
rows.some(
|
||||
(row) =>
|
||||
typeof row.min_value !== 'number' ||
|
||||
!Number.isFinite(row.min_value) ||
|
||||
typeof row.max_value !== 'number' ||
|
||||
!Number.isFinite(row.max_value) ||
|
||||
row.min_value < 0 ||
|
||||
row.max_value < row.min_value,
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
`The ISAC snapshot contains an invalid shared Skill Chip ${profileName} profile for ${statId}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (
|
||||
values.some(
|
||||
(row) => !optionIds.includes(String(row.stat_attribute_id ?? '')),
|
||||
)
|
||||
)
|
||||
throw new Error(
|
||||
`The ISAC snapshot contains an orphaned shared Skill Chip ${profileName} range.`,
|
||||
);
|
||||
}
|
||||
for (const icon of snapshot.icons) {
|
||||
if (
|
||||
typeof icon.storageKey !== 'string' ||
|
||||
|
||||
Reference in New Issue
Block a user