SOURCE / PINNED RELEASE
Made of little things.
Black Site
- Release
- 666bc7c74ebd…
- Author-recorded commit
- 05e95af79676…
- License
- LICENSE
- Author’s source reference
- nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-d2225ddc391
Archive hash verified: 1544e072e245c4a8…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
import { armoryCatalog } from 'virtual:black-site-catalog';
export type Armory = {
unlockedThrowables: string[];
ownedArmors: string[];
weaponLevels: Record<string, number>;
armorLevels: Record<string, number>;
};
export type GameProfile = Record<string, unknown>;
export const emptyArmory = (): Armory => ({ unlockedThrowables: [], ownedArmors: [], weaponLevels: {}, armorLevels: {} });
function object(value: unknown): Record<string, unknown> {
if (!value || typeof value !== 'object' || Array.isArray(value) ||
![Object.prototype, null].includes(Object.getPrototypeOf(value))) throw new Error('armory-invalid');
return value as Record<string, unknown>;
}
function ids(value: unknown, catalog: string[]): string[] {
if (!Array.isArray(value) || value.length > catalog.length || value.some(id => typeof id !== 'string' || !catalog.includes(id)) ||
new Set(value).size !== value.length) throw new Error('armory-invalid');
return [...value].sort();
}
function levels(value: unknown, catalog: string[], maximum: number): Record<string, number> {
const result: Record<string, number> = {};
for (const [id, level] of Object.entries(object(value)).sort(([a], [b]) => a.localeCompare(b))) {
if (!catalog.includes(id) || typeof level !== 'number' || !Number.isSafeInteger(level) || level < 0 || level > maximum) {
throw new Error('armory-invalid');
}
if (level) result[id] = level;
}
return result;
}
export function validateArmory(value: unknown): Armory {
const record = object(value);
if (Object.keys(record).sort().join(',') !== 'armorLevels,ownedArmors,unlockedThrowables,weaponLevels') throw new Error('armory-invalid');
const result = {
unlockedThrowables: ids(record.unlockedThrowables, armoryCatalog.throwables),
ownedArmors: ids(record.ownedArmors, armoryCatalog.armors),
weaponLevels: levels(record.weaponLevels, armoryCatalog.weapons, armoryCatalog.maxWeaponLevel),
armorLevels: levels(record.armorLevels, armoryCatalog.armors, armoryCatalog.maxArmorLevel),
};
if (Object.keys(result.armorLevels).some(id => !result.ownedArmors.includes(id))) throw new Error('armory-invalid');
return result;
}
export function profileArmory(value: unknown): Armory {
const profile = object(value);
return validateArmory({
unlockedThrowables: profile.unlocked ?? [], ownedArmors: profile.armours ?? [],
weaponLevels: profile.levels ?? {}, armorLevels: profile.armourLevels ?? {},
});
}
export function mergeArmory(left: Armory, right: Armory): Armory {
const max = (a: Record<string, number>, b: Record<string, number>) => Object.fromEntries(
[...new Set([...Object.keys(a), ...Object.keys(b)])].sort().map(id => [id, Math.max(a[id] ?? 0, b[id] ?? 0)]),
);
return validateArmory({
unlockedThrowables: [...new Set([...left.unlockedThrowables, ...right.unlockedThrowables])],
ownedArmors: [...new Set([...left.ownedArmors, ...right.ownedArmors])],
weaponLevels: max(left.weaponLevels, right.weaponLevels), armorLevels: max(left.armorLevels, right.armorLevels),
});
}
export const armoryDigest = (value: Armory): string => JSON.stringify(validateArmory(value));
export function withArmory(profile: GameProfile, armory: Armory): GameProfile {
const valid = validateArmory(armory);
return { ...profile, unlocked: valid.unlockedThrowables, armours: valid.ownedArmors,
levels: valid.weaponLevels, armourLevels: valid.armorLevels };
}
