Release Healer Man 0.1.43

This commit is contained in:
phenom committed 2026-09-14 13:20:20 -04:00
1 parent c7e8163790
commit ccc01396cd
86 files changed
+2951 -198

No files matched your search

+50 -33
View File
@@ -1,5 +1,5 @@
import type { CharacterProfile, PlayerSession } from "./types";
import { normalizeBossCardCollection, type BossCardCollection } from "../game/bossCards";
import { mergeBossCardCollections, normalizeBossCardCollection, type BossCardCollection } from "../game/bossCards";
import { normalizeUiPreferences, type UiPreferences } from "../game/uiLayout";
import {
contentCategoryForClass,
@@ -62,9 +62,10 @@ interface LocalAccount {
}
interface ProfileDatabase {
version: 6;
version: 7;
accounts: LocalAccount[];
rosters: Record<string, CharacterProfile[]>;
bossCardsByOwner: Record<string, BossCardCollection>;
presentationSettingsByOwner: Record<string, CombatPresentationSettings>;
uiPreferencesByOwner?: Record<string, UiPreferences>;
}
@@ -105,7 +106,7 @@ function browserSessionStorage(): StorageLike | null {
}
function emptyDatabase(): ProfileDatabase {
return { version: 6, accounts: [], rosters: {}, presentationSettingsByOwner: {} };
return { version: 7, accounts: [], rosters: {}, bossCardsByOwner: {}, presentationSettingsByOwner: {} };
}
function isCharacterProfile(value: unknown): value is CharacterProfile {
@@ -121,6 +122,9 @@ function isCharacterProfile(value: unknown): value is CharacterProfile {
}
function normalizeCharacterProfile(value: CharacterProfile): CharacterProfile {
const profile = { ...value };
// Cards now belong to the owner; legacy fields are consumed before normalization.
delete profile.bossCards;
const legacy = value as CharacterProfile & {
actionBindings?: unknown;
experience?: unknown;
@@ -180,7 +184,7 @@ function normalizeCharacterProfile(value: CharacterProfile): CharacterProfile {
: undefined;
const dungeonRunCheckpoint = normalizeDungeonRunCheckpoint(legacy.dungeonRunCheckpoint);
return {
...value,
...profile,
level,
experience,
talentRanks,
@@ -191,7 +195,6 @@ function normalizeCharacterProfile(value: CharacterProfile): CharacterProfile {
actionLoadouts,
inventory,
equipment,
bossCards: normalizeBossCardCollection(value.bossCards),
manastormProgress,
dungeonRunCheckpoint,
settings,
@@ -205,10 +208,11 @@ function readDatabase(storage: StorageLike | null = browserLocalStorage()): Prof
version?: number;
accounts?: unknown;
rosters?: unknown;
bossCardsByOwner?: unknown;
presentationSettingsByOwner?: unknown;
uiPreferencesByOwner?: unknown;
}) | null;
if (!parsed || ![1, 2, 3, 4, 5, 6].includes(parsed.version ?? 0) || !Array.isArray(parsed.accounts) || !parsed.rosters || typeof parsed.rosters !== "object") {
if (!parsed || ![1, 2, 3, 4, 5, 6, 7].includes(parsed.version ?? 0) || !Array.isArray(parsed.accounts) || !parsed.rosters || typeof parsed.rosters !== "object") {
return emptyDatabase();
}
const accounts = parsed.accounts.filter((account): account is LocalAccount => Boolean(
@@ -220,12 +224,19 @@ function readDatabase(storage: StorageLike | null = browserLocalStorage()): Prof
&& typeof account.verifier.salt === "string"
&& typeof account.verifier.digest === "string",
));
const rosters = Object.fromEntries(Object.entries(parsed.rosters as Record<string, unknown>).map(([ownerId, roster]) => [
ownerId,
Array.isArray(roster)
? roster.filter(isCharacterProfile).map(normalizeCharacterProfile).slice(0, MAX_CHARACTERS)
: [],
]));
const rawCards = parsed.bossCardsByOwner && typeof parsed.bossCardsByOwner === "object" && !Array.isArray(parsed.bossCardsByOwner)
? parsed.bossCardsByOwner : {};
const bossCardsByOwner = Object.fromEntries(Object.entries(rawCards).map(([owner, cards]) => [owner, normalizeBossCardCollection(cards)]));
let cardsMigrated = false;
const rosters = Object.fromEntries(Object.entries(parsed.rosters as Record<string, unknown>).map(([ownerId, roster]) => {
const characters = Array.isArray(roster) ? roster.filter(isCharacterProfile).slice(0, MAX_CHARACTERS) : [];
if (characters.some((character) => character.bossCards !== undefined)) {
const legacyCards = mergeBossCardCollections(characters.map((character) => character.bossCards), "sum");
bossCardsByOwner[ownerId] = mergeBossCardCollections([bossCardsByOwner[ownerId], legacyCards]);
cardsMigrated = true;
}
return [ownerId, characters.map(normalizeCharacterProfile)];
}));
const rawPresentationSettings = parsed.presentationSettingsByOwner
&& typeof parsed.presentationSettingsByOwner === "object"
&& !Array.isArray(parsed.presentationSettingsByOwner)
@@ -259,8 +270,8 @@ function readDatabase(storage: StorageLike | null = browserLocalStorage()): Prof
);
const rawUi = parsed.uiPreferencesByOwner && typeof parsed.uiPreferencesByOwner === "object" ? parsed.uiPreferencesByOwner : {};
const uiPreferencesByOwner = Object.fromEntries(Object.entries(rawUi).map(([owner, value]) => [owner, normalizeUiPreferences(value)]));
const database: ProfileDatabase = { version: 6, accounts, rosters, presentationSettingsByOwner, uiPreferencesByOwner };
if (parsed.version !== 6 || presentationSettingsMigrated) writeDatabase(database, storage);
const database: ProfileDatabase = { version: 7, accounts, rosters, bossCardsByOwner, presentationSettingsByOwner, uiPreferencesByOwner };
if (parsed.version !== 7 || presentationSettingsMigrated || cardsMigrated) writeDatabase(database, storage);
return database;
} catch {
return emptyDatabase();
@@ -270,7 +281,7 @@ function readDatabase(storage: StorageLike | null = browserLocalStorage()): Prof
function writeDatabase(database: ProfileDatabase, storage: StorageLike | null = browserLocalStorage()): void {
storage?.setItem(DATABASE_KEY, JSON.stringify(database));
if (!storage || !rosterChangeListener) return;
for (const ownerId of new Set([...Object.keys(database.rosters), ...Object.keys(database.uiPreferencesByOwner ?? {})])) {
for (const ownerId of new Set([...Object.keys(database.rosters), ...Object.keys(database.bossCardsByOwner), ...Object.keys(database.uiPreferencesByOwner ?? {})])) {
rosterChangeListener(ownerId, database.rosters[ownerId] ?? []);
}
}
@@ -452,15 +463,19 @@ export function replaceCharactersForOwner(
ownerId: string,
characters: unknown,
storage: StorageLike | null = browserLocalStorage(),
accountBossCards?: unknown,
): CharacterProfile[] {
if (!storage) return [];
const database = readDatabase(storage);
database.rosters[ownerId] = Array.isArray(characters)
? characters.filter(isCharacterProfile).slice(0, MAX_CHARACTERS).map((character) => normalizeCharacterProfile({
...character,
ownerId,
}))
: [];
const roster = Array.isArray(characters) ? characters.filter(isCharacterProfile).slice(0, MAX_CHARACTERS) : [];
const importedCards = accountBossCards === undefined
? mergeBossCardCollections(roster.map((character) => character.bossCards), "sum")
: normalizeBossCardCollection(accountBossCards);
database.bossCardsByOwner[ownerId] = mergeBossCardCollections([database.bossCardsByOwner[ownerId], importedCards]);
database.rosters[ownerId] = roster.map((character) => normalizeCharacterProfile({
...character,
ownerId,
}));
writeDatabase(database, storage);
return listCharacters(ownerId, storage);
}
@@ -730,22 +745,24 @@ export function updateCharacterEquipment(
return updated;
}
export function updateCharacterBossCards(
export function loadAccountBossCards(
ownerId: string,
storage: StorageLike | null = browserLocalStorage(),
): BossCardCollection {
return normalizeBossCardCollection(readDatabase(storage).bossCardsByOwner[ownerId]);
}
export function saveAccountBossCards(
ownerId: string,
characterId: string,
bossCards: BossCardCollection,
storage: StorageLike | null = browserLocalStorage(),
): CharacterProfile | null {
if (!storage) return null;
): BossCardCollection {
const normalized = normalizeBossCardCollection(bossCards);
if (!storage) return normalized;
const database = readDatabase(storage);
let updated: CharacterProfile | null = null;
database.rosters[ownerId] = (database.rosters[ownerId] ?? []).map((character) => {
if (character.id !== characterId) return character;
updated = normalizeCharacterProfile({ ...character, bossCards });
return updated;
});
if (updated) writeDatabase(database, storage);
return updated;
database.bossCardsByOwner[ownerId] = normalized;
writeDatabase(database, storage);
return normalized;
}
export function updateCharacterSettings(