forked from phenom/healer-man
Release Healer Man 0.1.44
This commit is contained in:
1 parent
ccc01396cd
commit
756ee4f64a
24 files changed
+575
-46
No files matched your search
+46
-1
@@ -36,6 +36,8 @@ export interface UiTemplate { name: string; layout: UiLayout }
|
||||
export interface UiPreferences {
|
||||
version: 1;
|
||||
keybinds: InputPreferences;
|
||||
/** Ability assignments belong to a character and ordered class pair; geometry and keys stay shared. */
|
||||
desktopAbilityLoadouts: Record<string, (string | null)[]>;
|
||||
current: Record<UiDevice, UiLayout>;
|
||||
slots: Record<UiDevice, (UiTemplate | null)[]>;
|
||||
}
|
||||
@@ -139,6 +141,12 @@ export function normalizeUiPreferences(value: unknown): UiPreferences {
|
||||
return {
|
||||
version: 1,
|
||||
keybinds: normalizeInputPreferences(source.keybinds),
|
||||
desktopAbilityLoadouts: Object.fromEntries(Object.entries(record(source.desktopAbilityLoadouts))
|
||||
.filter(([key, value]) => key.length <= 300 && Array.isArray(value))
|
||||
.map(([key, value]) => [key, Array.from({ length: 60 }, (_, index) => {
|
||||
const id = (value as unknown[])[index];
|
||||
return typeof id === "string" && id.length <= 200 ? id : null;
|
||||
})])),
|
||||
current: { mobile: normalizeUiLayout(current.mobile), pc: normalizeUiLayout(current.pc) },
|
||||
slots: Object.fromEntries(UI_DEVICES.map((device) => [device, Array.from({ length: 3 }, (_, index) => {
|
||||
const slot = record(Array.isArray(slots[device]) ? slots[device][index] : null);
|
||||
@@ -162,11 +170,41 @@ export function assignDesktopAbility(layout: UiLayout, barIndex: number, slotInd
|
||||
}
|
||||
|
||||
/** Restore abilities only; key chords and the arrangement belong to the UI template. */
|
||||
export function resetDesktopAbilities(layout: UiLayout, abilityIds: readonly string[], barIndex?: number): UiLayout {
|
||||
export function resetDesktopAbilities(layout: UiLayout, abilityIds: readonly (string | null)[], barIndex?: number): UiLayout {
|
||||
return { ...layout, bars: layout.bars.map((bar, b) => barIndex !== undefined && b !== barIndex ? bar : {
|
||||
...bar, slots: bar.slots.map((slot, s) => ({ ...slot, abilityId: abilityIds[b * 12 + s] ?? null })),
|
||||
}) };
|
||||
}
|
||||
|
||||
/** Use the same class defaults as mobile, while preserving an existing character's empty slots. */
|
||||
export function activateDesktopAbilityLoadout(
|
||||
preferences: UiPreferences, key: string, defaults: readonly string[], availableIds: readonly string[],
|
||||
): UiPreferences {
|
||||
const layout = preferences.current.pc;
|
||||
const available = new Set(availableIds);
|
||||
const legacy = layout.bars.flatMap((bar) => bar.slots.map((slot) => slot.abilityId));
|
||||
const saved = preferences.desktopAbilityLoadouts[key];
|
||||
// Adopt the old account-wide assignments once, only for a compatible character.
|
||||
const abilities = saved ?? (Object.keys(preferences.desktopAbilityLoadouts).length === 0
|
||||
&& legacy.some((id) => id && available.has(id))
|
||||
? legacy.map((id) => id && available.has(id) ? id : null)
|
||||
: defaults);
|
||||
const pc = resetDesktopAbilities(layout, abilities);
|
||||
return { ...preferences, current: { ...preferences.current, pc },
|
||||
desktopAbilityLoadouts: { ...preferences.desktopAbilityLoadouts,
|
||||
[key]: pc.bars.flatMap((bar) => bar.slots.map((slot) => slot.abilityId)) },
|
||||
};
|
||||
}
|
||||
|
||||
/** Modifier layers count together; inactive PC bars cannot be used until enabled. */
|
||||
export function assignedActionBarAbilityIds(
|
||||
layout: UiLayout, bindings: Readonly<Record<string, string | null>>, desktop: boolean,
|
||||
): ReadonlySet<string> {
|
||||
const ids = desktop
|
||||
? layout.bars.slice(0, layout.barCount).flatMap((bar) => bar.slots.map((slot) => slot.abilityId))
|
||||
: Object.values(bindings);
|
||||
return new Set(ids.filter((id): id is string => Boolean(id)));
|
||||
}
|
||||
export function desktopSlotForKey(layout: UiLayout, key: string | null): DesktopActionSlot | null {
|
||||
if (!layout.wowBars || !key) return null;
|
||||
// Hidden bars retain their bindings, like WoW; reducing the bar count deactivates them.
|
||||
@@ -180,6 +218,13 @@ export function importUiPreferences(text: string): UiPreferences {
|
||||
let source: Record<string, unknown>;
|
||||
try { source = record(JSON.parse(text)); } catch { throw new Error("This file is not valid JSON."); }
|
||||
if (source.format !== "healer-man-ui" || source.version !== 1) throw new Error("Unsupported UI file format or version.");
|
||||
if (source.desktopAbilityLoadouts !== undefined) {
|
||||
if (!source.desktopAbilityLoadouts || typeof source.desktopAbilityLoadouts !== "object" || Array.isArray(source.desktopAbilityLoadouts)) throw new Error("Invalid character action bars.");
|
||||
for (const [key, ids] of Object.entries(record(source.desktopAbilityLoadouts))) {
|
||||
if (key.length > 300 || !Array.isArray(ids) || ids.length !== 60
|
||||
|| ids.some((id) => id !== null && (typeof id !== "string" || id.length > 200))) throw new Error("Invalid character action bars.");
|
||||
}
|
||||
}
|
||||
const validateLayout = (value: unknown) => {
|
||||
const layout = record(value);
|
||||
if (layout.dpsMeterDisplay !== undefined && !["auto", "main", "companion"].includes(String(layout.dpsMeterDisplay))) throw new Error("Invalid DPS meter display.");
|
||||
|
||||
Reference in new issue
Block a user