forked from phenom/healer-man
Update class mechanics, stat rulesets, and CoA reference catalogs
This commit is contained in:
1 parent
b16821e92e
commit
39458d5de6
221 files changed
+2584962
-568
No files matched your search
@@ -33,6 +33,14 @@ import {
|
||||
normalizeDungeonRunCheckpoint,
|
||||
type DungeonRunCheckpoint,
|
||||
} from "../game/dungeonRunCheckpoint";
|
||||
import {
|
||||
isWow335HunterPetFamily,
|
||||
normalizeWow335HunterPetFamily,
|
||||
} from "../game/wow335HunterPetFamilySelection";
|
||||
import {
|
||||
isCharacterStatRuleset,
|
||||
normalizeCharacterStatRuleset,
|
||||
} from "../game/characterStatRuleset";
|
||||
|
||||
const DATABASE_KEY = "healer-man.profile-database.v1";
|
||||
const SESSION_KEY = "healer-man.session.v1";
|
||||
@@ -123,6 +131,12 @@ function isCharacterProfile(value: unknown): value is CharacterProfile {
|
||||
|
||||
function normalizeCharacterProfile(value: CharacterProfile): CharacterProfile {
|
||||
const profile = { ...value };
|
||||
profile.statRuleset = normalizeCharacterStatRuleset(value.statRuleset, value.classId);
|
||||
if (value.classId === "hunter") {
|
||||
profile.hunterPetFamily = normalizeWow335HunterPetFamily(value.hunterPetFamily);
|
||||
} else {
|
||||
delete profile.hunterPetFamily;
|
||||
}
|
||||
// Cards now belong to the owner; legacy fields are consumed before normalization.
|
||||
delete profile.bossCards;
|
||||
const legacy = value as CharacterProfile & {
|
||||
@@ -235,8 +249,13 @@ function readDatabase(storage: StorageLike | null = browserLocalStorage()): Prof
|
||||
? parsed.bossCardsByOwner : {};
|
||||
const bossCardsByOwner = Object.fromEntries(Object.entries(rawCards).map(([owner, cards]) => [owner, normalizeBossCardCollection(cards)]));
|
||||
let cardsMigrated = false;
|
||||
let statRulesetsMigrated = 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) => (
|
||||
!isCharacterStatRuleset(character.statRuleset)
|
||||
|| normalizeCharacterStatRuleset(character.statRuleset, character.classId) !== character.statRuleset
|
||||
))) statRulesetsMigrated = true;
|
||||
if (characters.some((character) => character.bossCards !== undefined)) {
|
||||
const legacyCards = mergeBossCardCollections(characters.map((character) => character.bossCards), "sum");
|
||||
bossCardsByOwner[ownerId] = mergeBossCardCollections([bossCardsByOwner[ownerId], legacyCards]);
|
||||
@@ -278,7 +297,11 @@ 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: 7, accounts, rosters, bossCardsByOwner, presentationSettingsByOwner, uiPreferencesByOwner };
|
||||
if (parsed.version !== 7 || presentationSettingsMigrated || cardsMigrated) writeDatabase(database, storage);
|
||||
if (parsed.version !== 7 || presentationSettingsMigrated || cardsMigrated || statRulesetsMigrated) {
|
||||
// A full or read-only device must still expose its existing roster.
|
||||
// Retry migration on the next read when persistence is available.
|
||||
try { writeDatabase(database, storage); } catch { /* Keep the normalized in-memory profiles. */ }
|
||||
}
|
||||
return database;
|
||||
} catch {
|
||||
return emptyDatabase();
|
||||
@@ -535,6 +558,7 @@ export function createCharacter(
|
||||
const now = Date.now();
|
||||
const character = normalizeCharacterProfile({
|
||||
...profile,
|
||||
statRuleset: profile.statRuleset ?? (contentCategoryForClass(profile.classId) === "rom" ? "rom" : "wow"),
|
||||
id: makeId("character"),
|
||||
level: 1,
|
||||
experience: 0,
|
||||
@@ -553,6 +577,29 @@ export function createCharacter(
|
||||
return { ok: true, character };
|
||||
}
|
||||
|
||||
export function updateCharacterStatRuleset(
|
||||
ownerId: string,
|
||||
characterId: string,
|
||||
value: unknown,
|
||||
storage: StorageLike | null = browserLocalStorage(),
|
||||
): CharacterProfile | null {
|
||||
if (!storage || !isCharacterStatRuleset(value)) return null;
|
||||
const database = readDatabase(storage);
|
||||
let updated: CharacterProfile | null = null;
|
||||
database.rosters[ownerId] = (database.rosters[ownerId] ?? []).map((character) => {
|
||||
if (character.id !== characterId || contentCategoryForClass(character.classId) !== "rom") return character;
|
||||
updated = normalizeCharacterProfile({ ...character, statRuleset: value });
|
||||
return updated;
|
||||
});
|
||||
if (!updated) return null;
|
||||
try {
|
||||
writeDatabase(database, storage);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
export function touchCharacter(
|
||||
ownerId: string,
|
||||
characterId: string,
|
||||
@@ -788,6 +835,24 @@ export function updateCharacterEquipment(
|
||||
return updated;
|
||||
}
|
||||
|
||||
export function updateCharacterHunterPetFamily(
|
||||
ownerId: string,
|
||||
characterId: string,
|
||||
family: unknown,
|
||||
storage: StorageLike | null = browserLocalStorage(),
|
||||
): CharacterProfile | null {
|
||||
if (!storage || !isWow335HunterPetFamily(family)) return null;
|
||||
const database = readDatabase(storage);
|
||||
let updated: CharacterProfile | null = null;
|
||||
database.rosters[ownerId] = (database.rosters[ownerId] ?? []).map((character) => {
|
||||
if (character.id !== characterId || character.classId !== "hunter") return character;
|
||||
updated = normalizeCharacterProfile({ ...character, hunterPetFamily: family });
|
||||
return updated;
|
||||
});
|
||||
if (updated) writeDatabase(database, storage);
|
||||
return updated;
|
||||
}
|
||||
|
||||
export function loadAccountBossCards(
|
||||
ownerId: string,
|
||||
storage: StorageLike | null = browserLocalStorage(),
|
||||
|
||||
Reference in new issue
Block a user