305 lines
9.9 KiB
TypeScript
305 lines
9.9 KiB
TypeScript
import {
|
|
CLASSES,
|
|
type BaseClassId,
|
|
type ClassId,
|
|
type CoaClassId,
|
|
} from "../app/characterCatalog";
|
|
import liveData from "./coaLiveData.generated.json";
|
|
|
|
export type CoaEssenceType = "ability" | "talent";
|
|
export type CoaEntryType = "Ability" | "Talent";
|
|
export type CoaNodeType = "SpendCircle" | "SpendSquare" | "SpendHex";
|
|
|
|
export interface CoaLiveSource {
|
|
readonly builderUrl: string;
|
|
readonly databaseClassUrl: string;
|
|
readonly realmId: number;
|
|
readonly realmSlug: string;
|
|
readonly realmName: string;
|
|
readonly maximumLevel: number;
|
|
readonly schemaVersion: Readonly<Record<string, number>>;
|
|
readonly synchronizedAt: string;
|
|
readonly spellDbcPath: string;
|
|
readonly supportDbcDirectory: string;
|
|
}
|
|
|
|
export interface CoaLiveCounts {
|
|
readonly classes: number;
|
|
readonly tabs: number;
|
|
readonly entries: number;
|
|
readonly abilityEntries: number;
|
|
readonly talentEntries: number;
|
|
readonly rankedNodeSpells: number;
|
|
readonly classSpellRows: number;
|
|
readonly classicClassSpellRows: number;
|
|
readonly coaClassSpellRows: number;
|
|
readonly uniqueSpells: number;
|
|
readonly dbcMatchedSpells: number;
|
|
readonly progressionChains: number;
|
|
readonly classicProgressionChains: number;
|
|
readonly classicBaseProgressionChains: number;
|
|
readonly coaProgressionChains: number;
|
|
readonly progressionRanks: number;
|
|
readonly liveTooltips: number;
|
|
}
|
|
|
|
export interface CoaClassTab {
|
|
readonly tabId: number;
|
|
readonly tabName: string;
|
|
readonly sortOrder: number;
|
|
}
|
|
|
|
export interface CoaLiveClass {
|
|
readonly classId: number;
|
|
readonly className: string;
|
|
readonly maxTalentEssence: number;
|
|
readonly maxAbilityEssence: number;
|
|
readonly tabs: readonly CoaClassTab[];
|
|
}
|
|
|
|
export interface CoaRankDescription {
|
|
readonly rank: number;
|
|
readonly spellId: number;
|
|
readonly description: string;
|
|
readonly liveTooltip?: string;
|
|
}
|
|
|
|
export interface CoaTreeEntry {
|
|
readonly id: number;
|
|
readonly classId: number;
|
|
readonly tabId: number;
|
|
readonly x: number;
|
|
readonly y: number;
|
|
readonly name: string;
|
|
readonly iconBasename: string;
|
|
readonly nodeType: CoaNodeType;
|
|
readonly entryType: CoaEntryType;
|
|
readonly isPassive: boolean;
|
|
readonly maxPoints: number;
|
|
readonly aeCost: number;
|
|
readonly teCost: number;
|
|
readonly requiredClassPoints: number;
|
|
readonly requiredSpecPoints: number;
|
|
readonly requiredIds: readonly number[];
|
|
readonly connectedNodeIds: readonly number[];
|
|
readonly requiredLevel: number;
|
|
readonly isStartingNode: boolean;
|
|
readonly flags: number;
|
|
readonly group: number;
|
|
readonly sortOrder: number;
|
|
readonly description: string;
|
|
readonly rankDescriptions: readonly CoaRankDescription[];
|
|
}
|
|
|
|
export interface CoaDbcEffect {
|
|
readonly index: number;
|
|
readonly effectType: number;
|
|
readonly auraType: number;
|
|
readonly basePoints: number;
|
|
readonly dieSides: number;
|
|
readonly pointsPerLevel: number;
|
|
readonly mechanic: number;
|
|
readonly implicitTargetA: number;
|
|
readonly implicitTargetB: number;
|
|
readonly radiusIndex: number;
|
|
readonly radius: number;
|
|
readonly periodMs: number;
|
|
readonly valueMultiplier: number;
|
|
readonly chainTargets: number;
|
|
readonly itemType: number;
|
|
readonly miscValue: number;
|
|
readonly miscValueB: number;
|
|
readonly triggerSpellId: number;
|
|
readonly pointsPerCombo: number;
|
|
readonly coefficient: number;
|
|
}
|
|
|
|
export interface CoaDbcSpell {
|
|
readonly id: number;
|
|
readonly name: string;
|
|
readonly rankText: string;
|
|
readonly description: string;
|
|
readonly categoryId: number;
|
|
readonly dispelType: number;
|
|
readonly mechanic: number;
|
|
readonly attributes: readonly number[];
|
|
readonly castTimeIndex: number;
|
|
readonly castTimeMs: number;
|
|
readonly recoveryTimeMs: number;
|
|
readonly categoryRecoveryTimeMs: number;
|
|
readonly interruptFlags: number;
|
|
readonly auraInterruptFlags: number;
|
|
readonly channelInterruptFlags: number;
|
|
readonly procFlags: number;
|
|
readonly procChance: number;
|
|
readonly procCharges: number;
|
|
readonly maxLevel: number;
|
|
readonly baseLevel: number;
|
|
readonly spellLevel: number;
|
|
readonly durationIndex: number;
|
|
readonly durationMs: number;
|
|
readonly powerType: number;
|
|
readonly powerCost: number;
|
|
readonly powerCostPerLevel: number;
|
|
readonly powerCostPerSecond: number;
|
|
readonly powerCostPerSecondPerLevel: number;
|
|
readonly rangeIndex: number;
|
|
readonly rangeMin: number;
|
|
readonly rangeMax: number;
|
|
readonly projectileSpeed: number;
|
|
readonly stackAmount: number;
|
|
readonly equippedItemClass: number;
|
|
readonly equippedItemSubclassMask: number;
|
|
readonly equippedItemInventoryMask: number;
|
|
readonly iconId: number;
|
|
readonly powerCostPercentage: number;
|
|
readonly startRecoveryCategory: number;
|
|
readonly startRecoveryTimeMs: number;
|
|
readonly maximumTargets: number;
|
|
readonly damageClass: number;
|
|
readonly preventionType: number;
|
|
readonly schoolMask: number;
|
|
readonly runeCostId: number;
|
|
readonly powerDisplayId: number;
|
|
readonly effects: readonly CoaDbcEffect[];
|
|
}
|
|
|
|
export interface CoaLiveSpell {
|
|
readonly id: number;
|
|
readonly classIds: readonly number[];
|
|
readonly name: string;
|
|
readonly iconBasename: string;
|
|
readonly level: number;
|
|
readonly rank: string;
|
|
readonly description: string;
|
|
readonly liveTooltip?: string;
|
|
readonly sourceIds: readonly number[];
|
|
readonly advancementType: string;
|
|
readonly advancementTabId: number;
|
|
readonly talented: boolean;
|
|
readonly excluded: boolean;
|
|
readonly dbc?: CoaDbcSpell;
|
|
}
|
|
|
|
export interface CoaProgressionRank {
|
|
readonly spellId: number;
|
|
readonly rank: number;
|
|
readonly level: number;
|
|
}
|
|
|
|
export interface CoaProgressionChain {
|
|
readonly id: string;
|
|
readonly classId: number;
|
|
readonly name: string;
|
|
readonly iconBasename: string;
|
|
readonly talented: boolean;
|
|
readonly advancementType: string;
|
|
readonly trainer: boolean;
|
|
readonly ranks: readonly CoaProgressionRank[];
|
|
}
|
|
|
|
interface CoaLiveData {
|
|
readonly source: CoaLiveSource;
|
|
readonly counts: CoaLiveCounts;
|
|
readonly classes: readonly CoaLiveClass[];
|
|
readonly entries: readonly CoaTreeEntry[];
|
|
readonly progressionChains: readonly CoaProgressionChain[];
|
|
readonly spells: readonly CoaLiveSpell[];
|
|
}
|
|
|
|
const data = liveData as unknown as CoaLiveData;
|
|
const COA_CLASS_ID_BY_DBC_ID = Object.freeze(Object.fromEntries(
|
|
CLASSES
|
|
.filter((definition) => definition.mode === "conquest")
|
|
.map((definition) => [definition.dbcId, definition.id]),
|
|
)) as Readonly<Record<number, CoaClassId>>;
|
|
const CLASSIC_CLASS_ID_BY_DBC_ID = Object.freeze(Object.fromEntries(
|
|
CLASSES
|
|
.filter((definition) => definition.mode === "classic")
|
|
.map((definition) => [definition.dbcId, definition.id]),
|
|
)) as Readonly<Record<number, BaseClassId>>;
|
|
const SPELL_BY_ID = new Map(data.spells.map((spell) => [spell.id, spell]));
|
|
const ENTRY_BY_ID = new Map(data.entries.map((entry) => [entry.id, entry]));
|
|
const CLASS_BY_DBC_ID = new Map(data.classes.map((definition) => [definition.classId, definition]));
|
|
|
|
export const COA_LIVE_SOURCE = data.source;
|
|
export const COA_LIVE_COUNTS = data.counts;
|
|
export const COA_LIVE_CLASSES = data.classes;
|
|
export const COA_TREE_ENTRIES = data.entries;
|
|
export const COA_PROGRESSION_CHAINS = data.progressionChains.filter((chain) => (
|
|
COA_CLASS_ID_BY_DBC_ID[chain.classId] !== undefined
|
|
));
|
|
export const ASCENSION_CLASSIC_PROGRESSION_CHAINS = data.progressionChains.filter((chain) => (
|
|
CLASSIC_CLASS_ID_BY_DBC_ID[chain.classId] !== undefined
|
|
));
|
|
|
|
export function isCoaClassId(classId: ClassId): classId is CoaClassId {
|
|
return CLASSES.find((definition) => definition.id === classId)?.mode === "conquest";
|
|
}
|
|
|
|
export function coaClassIdFromDbcId(dbcId: number): CoaClassId | null {
|
|
return COA_CLASS_ID_BY_DBC_ID[dbcId] ?? null;
|
|
}
|
|
|
|
export function ascensionClassicClassIdFromDbcId(dbcId: number): BaseClassId | null {
|
|
return CLASSIC_CLASS_ID_BY_DBC_ID[dbcId] ?? null;
|
|
}
|
|
|
|
export function coaClassDefinition(classId: CoaClassId): CoaLiveClass {
|
|
const dbcId = CLASSES.find((definition) => definition.id === classId)?.dbcId ?? 0;
|
|
const definition = CLASS_BY_DBC_ID.get(dbcId);
|
|
if (!definition) throw new Error(`Missing live CoA definition for ${classId}`);
|
|
return definition;
|
|
}
|
|
|
|
export function coaTreeEntryById(entryId: number): CoaTreeEntry | null {
|
|
return ENTRY_BY_ID.get(entryId) ?? null;
|
|
}
|
|
|
|
export function coaEntriesForClass(classId: CoaClassId): readonly CoaTreeEntry[] {
|
|
const dbcId = CLASSES.find((definition) => definition.id === classId)?.dbcId ?? 0;
|
|
return data.entries.filter((entry) => entry.classId === dbcId);
|
|
}
|
|
|
|
export function coaEntriesForTab(classId: CoaClassId, tabId: number): readonly CoaTreeEntry[] {
|
|
return coaEntriesForClass(classId).filter((entry) => entry.tabId === tabId);
|
|
}
|
|
|
|
export function coaSpellById(spellId: number): CoaLiveSpell | null {
|
|
return SPELL_BY_ID.get(spellId) ?? null;
|
|
}
|
|
|
|
export function coaProgressionForClass(classId: CoaClassId): readonly CoaProgressionChain[] {
|
|
const dbcId = CLASSES.find((definition) => definition.id === classId)?.dbcId ?? 0;
|
|
return data.progressionChains.filter((chain) => chain.classId === dbcId);
|
|
}
|
|
|
|
export function ascensionProgressionForClass(classId: BaseClassId): readonly CoaProgressionChain[] {
|
|
const dbcId = CLASSES.find((definition) => definition.id === classId)?.dbcId ?? 0;
|
|
return ASCENSION_CLASSIC_PROGRESSION_CHAINS.filter((chain) => chain.classId === dbcId);
|
|
}
|
|
|
|
export function coaRankForLevel(
|
|
chain: CoaProgressionChain,
|
|
playerLevel: number,
|
|
): CoaProgressionRank {
|
|
return [...chain.ranks]
|
|
.filter((rank) => rank.level <= playerLevel)
|
|
.sort((left, right) => right.rank - left.rank)[0]
|
|
?? chain.ranks[0];
|
|
}
|
|
|
|
export function coaEntryRank(
|
|
entry: CoaTreeEntry,
|
|
rank: number,
|
|
): CoaRankDescription | null {
|
|
const normalized = Math.max(1, Math.min(entry.maxPoints, Math.trunc(rank)));
|
|
return entry.rankDescriptions.find((description) => description.rank === normalized)
|
|
?? entry.rankDescriptions[normalized - 1]
|
|
?? null;
|
|
}
|
|
|
|
export function coaIconUrl(icon: string): string {
|
|
return `/assets/ui/spells/${(icon || "inv_misc_questionmark").toLowerCase()}.png`;
|
|
}
|