forked from phenom/healer-man
88 lines
4.1 KiB
JavaScript
88 lines
4.1 KiB
JavaScript
export const DEFAULT_COMPANION_COOLDOWN_POLICY = Object.freeze({
|
|
schemaVersion: 1,
|
|
generic: Object.freeze({
|
|
defensiveHealthPercent: 55,
|
|
emergencyHealthPercent: 35,
|
|
healerMitigationHealthPercent: 80,
|
|
offensiveMajorCooldownSeconds: 60,
|
|
conserveOffensiveCooldownsForDurableTargets: true,
|
|
}),
|
|
mechanics: Object.freeze([]),
|
|
});
|
|
|
|
function invalid(message) {
|
|
const error = new Error(message);
|
|
error.code = "invalid_companion_cooldown_policy";
|
|
return error;
|
|
}
|
|
|
|
function number(value, label, minimum, maximum) {
|
|
if (typeof value !== "number" || !Number.isFinite(value) || value < minimum || value > maximum) {
|
|
throw invalid(`${label} must be a number from ${minimum} through ${maximum}.`);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function string(value, label, allowEmpty = false) {
|
|
if (typeof value !== "string" || (!allowEmpty && !value.trim()) || value.length > 256) {
|
|
throw invalid(`${label} must be ${allowEmpty ? "a" : "a non-empty"} string no longer than 256 characters.`);
|
|
}
|
|
return value.trim();
|
|
}
|
|
|
|
export function normalizeCompanionCooldownPolicy(value) {
|
|
if (!value || typeof value !== "object" || Array.isArray(value) || value.schemaVersion !== 1) {
|
|
throw invalid("Companion cooldown policy schemaVersion must be 1.");
|
|
}
|
|
const generic = value.generic;
|
|
if (!generic || typeof generic !== "object" || Array.isArray(generic)) throw invalid("generic rules are required.");
|
|
if (typeof generic.conserveOffensiveCooldownsForDurableTargets !== "boolean") {
|
|
throw invalid("generic.conserveOffensiveCooldownsForDurableTargets must be boolean.");
|
|
}
|
|
if (!Array.isArray(value.mechanics) || value.mechanics.length > 1_000) throw invalid("mechanics must be an array of at most 1,000 rules.");
|
|
const seen = new Set();
|
|
const mechanics = value.mechanics.map((rule, index) => {
|
|
if (!rule || typeof rule !== "object" || Array.isArray(rule)) throw invalid(`mechanics[${index}] must be an object.`);
|
|
const id = string(rule.id, `mechanics[${index}].id`);
|
|
if (seen.has(id)) throw invalid(`mechanic rule id ${id} is duplicated.`);
|
|
seen.add(id);
|
|
if (typeof rule.enabled !== "boolean") throw invalid(`mechanics[${index}].enabled must be boolean.`);
|
|
if (!new Set(["tank", "party"]).has(rule.scope)) throw invalid(`mechanics[${index}].scope is invalid.`);
|
|
const serverEntry = rule.bossServerEntry;
|
|
if (serverEntry !== null && (!Number.isInteger(serverEntry) || serverEntry <= 0)) {
|
|
throw invalid(`mechanics[${index}].bossServerEntry must be null or a positive integer.`);
|
|
}
|
|
const bossName = string(rule.bossName, `mechanics[${index}].bossName`, true);
|
|
if (serverEntry === null && !bossName) {
|
|
throw invalid(`mechanics[${index}].bossName is required when bossServerEntry is null.`);
|
|
}
|
|
return {
|
|
id,
|
|
enabled: rule.enabled,
|
|
bossKey: string(rule.bossKey, `mechanics[${index}].bossKey`, true),
|
|
bossName,
|
|
bossServerEntry: serverEntry,
|
|
attackId: string(rule.attackId, `mechanics[${index}].attackId`),
|
|
attackName: string(rule.attackName, `mechanics[${index}].attackName`),
|
|
scope: rule.scope,
|
|
leadTimeMs: number(rule.leadTimeMs, `mechanics[${index}].leadTimeMs`, 0, 15_000),
|
|
minimumCooldownSeconds: number(rule.minimumCooldownSeconds, `mechanics[${index}].minimumCooldownSeconds`, 0, 600),
|
|
};
|
|
});
|
|
return {
|
|
schemaVersion: 1,
|
|
generic: {
|
|
defensiveHealthPercent: number(generic.defensiveHealthPercent, "generic.defensiveHealthPercent", 1, 100),
|
|
emergencyHealthPercent: number(generic.emergencyHealthPercent, "generic.emergencyHealthPercent", 1, 100),
|
|
healerMitigationHealthPercent: number(generic.healerMitigationHealthPercent, "generic.healerMitigationHealthPercent", 1, 100),
|
|
offensiveMajorCooldownSeconds: number(generic.offensiveMajorCooldownSeconds, "generic.offensiveMajorCooldownSeconds", 0, 600),
|
|
conserveOffensiveCooldownsForDurableTargets: generic.conserveOffensiveCooldownsForDurableTargets,
|
|
},
|
|
mechanics,
|
|
};
|
|
}
|
|
|
|
export function cloneDefaultCompanionCooldownPolicy() {
|
|
return JSON.parse(JSON.stringify(DEFAULT_COMPANION_COOLDOWN_POLICY));
|
|
}
|