forked from phenom/healer-man
Release Healer Man 0.1.15
This commit is contained in:
1 parent
7b41b102fa
commit
f49e54f35f
43 files changed
+1478
-112
No files matched your search
@@ -22,6 +22,11 @@ import {
|
||||
normalizeManastormProgress,
|
||||
type ManastormProgress,
|
||||
} from "../game/manastormProgress";
|
||||
import { abilitiesForClass } from "../game/abilityCatalog";
|
||||
import {
|
||||
normalizeSpecializationId,
|
||||
specializationById,
|
||||
} from "../game/specializationCatalog";
|
||||
|
||||
const DATABASE_KEY = "healer-man.profile-database.v1";
|
||||
const SESSION_KEY = "healer-man.session.v1";
|
||||
@@ -51,7 +56,7 @@ interface LocalAccount {
|
||||
}
|
||||
|
||||
interface ProfileDatabase {
|
||||
version: 3;
|
||||
version: 4;
|
||||
accounts: LocalAccount[];
|
||||
rosters: Record<string, CharacterProfile[]>;
|
||||
presentationSettingsByOwner: Record<string, CombatPresentationSettings>;
|
||||
@@ -93,7 +98,7 @@ function browserSessionStorage(): StorageLike | null {
|
||||
}
|
||||
|
||||
function emptyDatabase(): ProfileDatabase {
|
||||
return { version: 3, accounts: [], rosters: {}, presentationSettingsByOwner: {} };
|
||||
return { version: 4, accounts: [], rosters: {}, presentationSettingsByOwner: {} };
|
||||
}
|
||||
|
||||
function isCharacterProfile(value: unknown): value is CharacterProfile {
|
||||
@@ -120,6 +125,7 @@ function normalizeCharacterProfile(value: CharacterProfile): CharacterProfile {
|
||||
settings?: unknown;
|
||||
secondaryClassId?: unknown;
|
||||
actionLoadouts?: unknown;
|
||||
specializationId?: unknown;
|
||||
};
|
||||
const level = typeof legacy.level === "number" && Number.isFinite(legacy.level)
|
||||
? Math.max(1, Math.min(80, Math.floor(legacy.level)))
|
||||
@@ -138,6 +144,9 @@ function normalizeCharacterProfile(value: CharacterProfile): CharacterProfile {
|
||||
.map(([bindingId, abilityId]) => [bindingId, abilityId as string | null]))
|
||||
: {};
|
||||
const categoryId = contentCategoryForClass(value.classId);
|
||||
const specializationId = categoryId === "rom"
|
||||
? null
|
||||
: normalizeSpecializationId(value.classId, legacy.specializationId);
|
||||
const secondaryClassId = categoryId === "rom"
|
||||
&& isRomClassId(value.classId)
|
||||
&& typeof legacy.secondaryClassId === "string"
|
||||
@@ -168,6 +177,7 @@ function normalizeCharacterProfile(value: CharacterProfile): CharacterProfile {
|
||||
talentRanks,
|
||||
actionBindings,
|
||||
categoryId,
|
||||
specializationId,
|
||||
secondaryClassId,
|
||||
actionLoadouts,
|
||||
inventory,
|
||||
@@ -186,7 +196,7 @@ function readDatabase(storage: StorageLike | null = browserLocalStorage()): Prof
|
||||
rosters?: unknown;
|
||||
presentationSettingsByOwner?: unknown;
|
||||
}) | null;
|
||||
if (!parsed || ![1, 2, 3].includes(parsed.version ?? 0) || !Array.isArray(parsed.accounts) || !parsed.rosters || typeof parsed.rosters !== "object") {
|
||||
if (!parsed || ![1, 2, 3, 4].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(
|
||||
@@ -226,8 +236,8 @@ function readDatabase(storage: StorageLike | null = browserLocalStorage()): Prof
|
||||
return [ownerId, normalized];
|
||||
}),
|
||||
);
|
||||
const database: ProfileDatabase = { version: 3, accounts, rosters, presentationSettingsByOwner };
|
||||
if (parsed.version !== 3 || presentationSettingsMigrated) writeDatabase(database, storage);
|
||||
const database: ProfileDatabase = { version: 4, accounts, rosters, presentationSettingsByOwner };
|
||||
if (parsed.version !== 4 || presentationSettingsMigrated) writeDatabase(database, storage);
|
||||
return database;
|
||||
} catch {
|
||||
return emptyDatabase();
|
||||
@@ -421,7 +431,9 @@ export function replaceCharactersForOwner(
|
||||
}
|
||||
|
||||
export function createCharacter(
|
||||
profile: Omit<CharacterProfile, "id" | "createdAt" | "lastPlayedAt" | "level" | "experience" | "talentRanks" | "actionBindings" | "inventory" | "equipment" | "manastormProgress" | "location">,
|
||||
profile: Omit<CharacterProfile, "id" | "createdAt" | "lastPlayedAt" | "level" | "experience" | "talentRanks" | "actionBindings" | "inventory" | "equipment" | "manastormProgress" | "location" | "specializationId"> & {
|
||||
specializationId?: string | null;
|
||||
},
|
||||
storage: StorageLike | null = browserLocalStorage(),
|
||||
): CharacterResult {
|
||||
if (!storage) return { ok: false, error: "Local storage is unavailable on this device." };
|
||||
@@ -491,6 +503,54 @@ export function updateCharacterProgression(
|
||||
return updated;
|
||||
}
|
||||
|
||||
export function updateCharacterSpecialization(
|
||||
ownerId: string,
|
||||
characterId: string,
|
||||
specializationId: string,
|
||||
resetTalents: boolean,
|
||||
storage: StorageLike | null = browserLocalStorage(),
|
||||
): CharacterResult {
|
||||
if (!storage) return { ok: false, error: "Local storage is unavailable on this device." };
|
||||
const database = readDatabase(storage);
|
||||
let result: CharacterResult = { ok: false, error: "That character is no longer available." };
|
||||
database.rosters[ownerId] = (database.rosters[ownerId] ?? []).map((character) => {
|
||||
if (character.id !== characterId) return character;
|
||||
if (character.categoryId === "rom") {
|
||||
result = { ok: false, error: "RoM gear follows the active primary class and activity role." };
|
||||
return character;
|
||||
}
|
||||
const specialization = specializationById(specializationId);
|
||||
if (!specialization || specialization.classId !== character.classId) {
|
||||
result = { ok: false, error: "That specialization is not available to this class." };
|
||||
return character;
|
||||
}
|
||||
const talentAbilityIds = resetTalents
|
||||
? new Set(abilitiesForClass(character.classId)
|
||||
.filter((ability) => ability.talentEntryId !== undefined || ability.source === "coa-tree")
|
||||
.map((ability) => ability.id))
|
||||
: new Set<string>();
|
||||
const actionBindings = resetTalents
|
||||
? Object.fromEntries(Object.entries(character.actionBindings)
|
||||
.map(([bindingId, abilityId]) => [bindingId, abilityId && talentAbilityIds.has(abilityId) ? null : abilityId]))
|
||||
: character.actionBindings;
|
||||
const loadoutKey = orderedClassLoadoutKey(character.classId, character.secondaryClassId);
|
||||
const updated = normalizeCharacterProfile({
|
||||
...character,
|
||||
specializationId: specialization.id,
|
||||
talentRanks: resetTalents ? {} : character.talentRanks,
|
||||
actionBindings,
|
||||
actionLoadouts: {
|
||||
...(character.actionLoadouts ?? {}),
|
||||
[loadoutKey]: { ...actionBindings },
|
||||
},
|
||||
});
|
||||
result = { ok: true, character: updated };
|
||||
return updated;
|
||||
});
|
||||
if (result.ok) writeDatabase(database, storage);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function updateCharacterActionBindings(
|
||||
ownerId: string,
|
||||
characterId: string,
|
||||
|
||||
Reference in new issue
Block a user