mirror of
https://github.com/Kornstalx/5etools-mirror-2.github.io.git
synced 2025-10-28 20:45:35 -05:00
436 lines
12 KiB
JavaScript
436 lines
12 KiB
JavaScript
// region Based on `Charactermancer_AdditionalSpellsUtil`
|
|
|
|
class _SpellSourceUtil {
|
|
static _getCleanUid (uid) {
|
|
return DataUtil.proxy.getUid(
|
|
"spell",
|
|
DataUtil.proxy.unpackUid("spell", uid.split("#")[0], "spell"),
|
|
);
|
|
}
|
|
|
|
// region Data flattening
|
|
static getSpellUids (additionalSpellBlock, modalFilterSpells) {
|
|
additionalSpellBlock = MiscUtil.copyFast(additionalSpellBlock);
|
|
|
|
const outSpells = [];
|
|
|
|
Object.entries(additionalSpellBlock)
|
|
.forEach(([additionType, additionMeta]) => {
|
|
switch (additionType) {
|
|
case "innate":
|
|
case "known":
|
|
case "prepared":
|
|
case "expanded": {
|
|
this._getSpellUids_doProcessAdditionMeta({additionType, additionMeta, outSpells, modalFilterSpells});
|
|
break;
|
|
}
|
|
|
|
// Ignored
|
|
case "name":
|
|
case "ability":
|
|
case "resourceName": break;
|
|
|
|
default: throw new Error(`Unhandled spell addition type "${additionType}"`);
|
|
}
|
|
});
|
|
|
|
return outSpells.unique();
|
|
}
|
|
|
|
static _getSpellUids_doProcessAdditionMeta (opts) {
|
|
const {additionMeta, modalFilterSpells} = opts;
|
|
|
|
Object.values(additionMeta).forEach((levelMeta) => {
|
|
if (levelMeta instanceof Array) {
|
|
return levelMeta.forEach(spellItem => this._getSpellUids_doProcessSpellItem({...opts, spellItem, modalFilterSpells}));
|
|
}
|
|
|
|
Object.entries(levelMeta).forEach(([rechargeType, levelMetaInner]) => {
|
|
this._getSpellUids_doProcessSpellRechargeBlock({...opts, rechargeType, levelMetaInner, modalFilterSpells});
|
|
});
|
|
});
|
|
}
|
|
|
|
static _getSpellUids_doProcessSpellItem (opts) {
|
|
const {outSpells, spellItem, modalFilterSpells} = opts;
|
|
|
|
if (typeof spellItem === "string") {
|
|
return outSpells.push(this._getCleanUid(spellItem));
|
|
}
|
|
|
|
if (spellItem.all != null) { // A filter expression
|
|
return modalFilterSpells.getEntitiesMatchingFilterExpression({filterExpression: spellItem.all})
|
|
.forEach(ent => outSpells.push(DataUtil.generic.getUid({name: ent.name, source: ent.source})));
|
|
}
|
|
|
|
if (spellItem.choose != null) {
|
|
if (typeof spellItem.choose === "string") { // A filter expression
|
|
return modalFilterSpells.getEntitiesMatchingFilterExpression({filterExpression: spellItem.choose})
|
|
.forEach(ent => outSpells.push(DataUtil.generic.getUid({name: ent.name, source: ent.source})));
|
|
}
|
|
|
|
if (spellItem.choose.from) { // An array of choices
|
|
return spellItem.choose.from
|
|
.forEach(uid => outSpells.push(this._getCleanUid(uid)));
|
|
}
|
|
|
|
throw new Error(`Unhandled additional spell format: "${JSON.stringify(spellItem)}"`);
|
|
}
|
|
|
|
throw new Error(`Unhandled additional spell format: "${JSON.stringify(spellItem)}"`);
|
|
}
|
|
|
|
static _getSpellUids_doProcessSpellRechargeBlock (opts) {
|
|
const {rechargeType, levelMetaInner} = opts;
|
|
|
|
switch (rechargeType) {
|
|
case "rest":
|
|
case "daily": {
|
|
Object.values(levelMetaInner)
|
|
.forEach(spellList => {
|
|
spellList.forEach(spellItem => this._getSpellUids_doProcessSpellItem({...opts, spellItem}));
|
|
});
|
|
|
|
break;
|
|
}
|
|
|
|
case "resource": {
|
|
Object.values(levelMetaInner)
|
|
.forEach(spellList => {
|
|
spellList.forEach(spellItem => this._getSpellUids_doProcessSpellItem({...opts, spellItem}));
|
|
});
|
|
|
|
break;
|
|
}
|
|
|
|
case "will":
|
|
case "ritual":
|
|
case "_": {
|
|
levelMetaInner.forEach(spellItem => this._getSpellUids_doProcessSpellItem({...opts, spellItem}));
|
|
break;
|
|
}
|
|
|
|
default: throw new Error(`Unhandled spell recharge type "${rechargeType}"`);
|
|
}
|
|
}
|
|
// endregion
|
|
}
|
|
|
|
// endregion
|
|
|
|
class _SpellSource {
|
|
constructor () {
|
|
this._lookup = {};
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
mutLookup (otherLookup) {
|
|
this._mutLookup_recurse(otherLookup, this._lookup, []);
|
|
}
|
|
|
|
_mutLookup_recurse (otherLookup, obj, path) {
|
|
Object.entries(obj)
|
|
.forEach(([k, v]) => {
|
|
if (typeof v !== "object" || v instanceof Array) {
|
|
return MiscUtil.set(otherLookup, ...path, k, v);
|
|
}
|
|
|
|
this._mutLookup_recurse(otherLookup, v, [...path, k]);
|
|
});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
async pInit () { throw new Error("Unimplemented!"); }
|
|
}
|
|
|
|
class _SpellSourceClasses extends _SpellSource {
|
|
constructor ({spellSourceLookupAdditional = null}) {
|
|
super();
|
|
this._spellSourceLookupAdditional = spellSourceLookupAdditional;
|
|
}
|
|
|
|
async pInit () {
|
|
this._mutAddSpellSourceLookup({spellSourceLookup: await DataUtil.loadJSON(`${Renderer.get().baseUrl}data/spells/sources.json`)});
|
|
this._mutAddSpellSourceLookup({spellSourceLookup: this._spellSourceLookupAdditional});
|
|
}
|
|
|
|
_mutAddSpellSourceLookup ({spellSourceLookup}) {
|
|
if (!spellSourceLookup) return;
|
|
|
|
Object.entries(spellSourceLookup)
|
|
.forEach(([spellSource, spellNameTo]) => {
|
|
Object.entries(spellNameTo)
|
|
.forEach(([spellName, propTo]) => {
|
|
Object.entries(propTo)
|
|
.forEach(([prop, arr]) => {
|
|
const grouped = {};
|
|
|
|
arr
|
|
.forEach(({name: className, source: classSource, definedInSource}) => {
|
|
const k = definedInSource || null;
|
|
|
|
const tgt = MiscUtil.getOrSet(grouped, classSource, className, {definedInSources: []});
|
|
|
|
if (!tgt.definedInSources.includes(k)) tgt.definedInSources.push(k);
|
|
});
|
|
|
|
Object.entries(grouped)
|
|
.forEach(([classSource, byClassSource]) => {
|
|
Object.entries(byClassSource)
|
|
.forEach(([className, byClassName]) => {
|
|
MiscUtil.set(
|
|
this._lookup,
|
|
spellSource.toLowerCase(),
|
|
spellName.toLowerCase(),
|
|
prop,
|
|
classSource,
|
|
className,
|
|
byClassName.definedInSources.some(it => it != null)
|
|
? {definedInSources: byClassName.definedInSources}
|
|
: true,
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
class _AdditionalSpellSource extends _SpellSource {
|
|
constructor ({props, modalFilterSpells} = {}) {
|
|
super();
|
|
this._props = props;
|
|
this._modalFilterSpells = modalFilterSpells;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
async pInit () {
|
|
const data = await this._pLoadData();
|
|
this._props
|
|
.forEach(prop => {
|
|
data[prop]
|
|
.filter(ent => ent.additionalSpells)
|
|
.filter(ent => !this._isSkipEntity(ent))
|
|
.forEach(ent => {
|
|
const propPath = this._getPropPath(ent);
|
|
|
|
const uidToSummary = {};
|
|
|
|
ent.additionalSpells
|
|
.forEach(additionalSpellBlock => {
|
|
_SpellSourceUtil.getSpellUids(additionalSpellBlock, this._modalFilterSpells)
|
|
.forEach(uid => {
|
|
uidToSummary[uid] = uidToSummary[uid] || {
|
|
cntAdditionalSpellBlocks: ent.additionalSpells.length,
|
|
};
|
|
|
|
if (additionalSpellBlock.name) {
|
|
(uidToSummary[uid].names = uidToSummary[uid].names || []).push(additionalSpellBlock.name);
|
|
}
|
|
});
|
|
});
|
|
|
|
Object.entries(uidToSummary)
|
|
.forEach(([uid, additionalSpellsSummary]) => {
|
|
const val = this._getLookupValue(ent, additionalSpellsSummary);
|
|
const {name, source} = DataUtil.proxy.unpackUid("spell", uid, "spell", {isLower: true});
|
|
MiscUtil.set(this._lookup, source, name, ...propPath, val);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
async _pLoadData () { throw new Error("Unimplemented!"); }
|
|
|
|
_isSkipEntity (ent) { return false; }
|
|
|
|
_getPropPath (ent) { throw new Error("Unimplemented!"); }
|
|
|
|
_getPropPath_nameSource (ent) { return [ent.source, ent.name]; }
|
|
|
|
_getLookupValue (ent, additionalSpellsSummary) { return true; }
|
|
}
|
|
|
|
class _AdditionalSpellSourceClassesSubclasses extends _AdditionalSpellSource {
|
|
static _HASHES_SKIPPED = new Set([
|
|
UrlUtil.URL_TO_HASH_BUILDER["subclass"]({
|
|
name: "College of Lore",
|
|
shortName: "Lore",
|
|
source: "PHB",
|
|
className: "Bard",
|
|
classSource: "PHB",
|
|
}),
|
|
]);
|
|
|
|
constructor (opts) {
|
|
super({
|
|
...opts,
|
|
props: [
|
|
// Only include subclass additionalSpells, otherwise we add e.g. Bard Magical Secrets, which isn't helpful
|
|
// "class",
|
|
"subclass",
|
|
],
|
|
});
|
|
}
|
|
|
|
async _pLoadData () {
|
|
return DataUtil.class.loadJSON();
|
|
}
|
|
|
|
_isSkipEntity (ent) {
|
|
if (ent.className === VeCt.STR_GENERIC || ent.classSource === VeCt.STR_GENERIC) return true;
|
|
const hash = UrlUtil.URL_TO_HASH_BUILDER["subclass"](ent);
|
|
return this.constructor._HASHES_SKIPPED.has(hash);
|
|
}
|
|
|
|
_getPropPath (ent) {
|
|
switch (ent.__prop) {
|
|
case "class": return [ent.__prop, ...this._getPropPath_nameSource(ent)];
|
|
case "subclass": return [ent.__prop, ent.classSource, ent.className, ent.source, ent.shortName];
|
|
default: throw new Error(`Unhandled __prop "${ent.__prop}"`);
|
|
}
|
|
}
|
|
|
|
_getLookupValue (ent, additionalSpellsSummary) {
|
|
switch (ent.__prop) {
|
|
case "subclass": {
|
|
const out = {name: ent.name};
|
|
|
|
// Only add `subSubclasses` if a spell is not shared between every sub-subclass
|
|
if (additionalSpellsSummary.names && additionalSpellsSummary.cntAdditionalSpellBlocks !== additionalSpellsSummary.names.length) out.subSubclasses = additionalSpellsSummary.names;
|
|
|
|
return out;
|
|
}
|
|
default: return super._getLookupValue(ent);
|
|
}
|
|
}
|
|
}
|
|
|
|
class _AdditionalSpellSourceRaces extends _AdditionalSpellSource {
|
|
constructor (opts) {
|
|
super({
|
|
...opts,
|
|
props: ["race"],
|
|
});
|
|
}
|
|
|
|
async _pLoadData () {
|
|
return DataUtil.race.loadJSON();
|
|
}
|
|
|
|
_getPropPath (ent) { return ["race", ...this._getPropPath_nameSource(ent)]; }
|
|
|
|
_getLookupValue (ent) {
|
|
if (!ent._isSubRace) return super._getLookupValue(ent);
|
|
return {baseName: ent._baseName, baseSource: ent._baseSource};
|
|
}
|
|
}
|
|
|
|
class _AdditionalSpellSourceFile extends _AdditionalSpellSource {
|
|
constructor ({file, ...rest} = {}) {
|
|
super({...rest});
|
|
this._file = file;
|
|
}
|
|
|
|
async _pLoadData () {
|
|
return DataUtil.loadJSON(`./data/${this._file}`);
|
|
}
|
|
|
|
_getPropPath (ent) { return [ent.__prop, ...this._getPropPath_nameSource(ent)]; }
|
|
}
|
|
|
|
class _AdditionalSpellSourceBackgrounds extends _AdditionalSpellSourceFile {
|
|
constructor ({...rest}) {
|
|
super({
|
|
...rest,
|
|
props: ["background"],
|
|
file: "backgrounds.json",
|
|
});
|
|
}
|
|
}
|
|
|
|
class _AdditionalSpellSourceCharCreationOptions extends _AdditionalSpellSourceFile {
|
|
constructor ({...rest}) {
|
|
super({
|
|
...rest,
|
|
props: ["charoption"],
|
|
file: "charcreationoptions.json",
|
|
});
|
|
}
|
|
}
|
|
|
|
class _AdditionalSpellSourceFeats extends _AdditionalSpellSourceFile {
|
|
constructor ({...rest}) {
|
|
super({
|
|
...rest,
|
|
props: ["feat"],
|
|
file: "feats.json",
|
|
});
|
|
}
|
|
}
|
|
|
|
class _AdditionalSpellSourceOptionalFeatures extends _AdditionalSpellSourceFile {
|
|
constructor ({...rest}) {
|
|
super({
|
|
...rest,
|
|
props: ["optionalfeature"],
|
|
file: "optionalfeatures.json",
|
|
});
|
|
}
|
|
|
|
_getLookupValue (ent) {
|
|
return {featureType: [...ent.featureType]};
|
|
}
|
|
}
|
|
|
|
class _AdditionalSpellSourceRewards extends _AdditionalSpellSourceFile {
|
|
constructor ({...rest}) {
|
|
super({
|
|
...rest,
|
|
props: ["reward"],
|
|
file: "rewards.json",
|
|
});
|
|
}
|
|
}
|
|
|
|
export class SpellSourceLookupBuilder {
|
|
static async pGetLookup ({spells, spellSourceLookupAdditional = null}) {
|
|
const cpySpells = MiscUtil.copyFast(spells);
|
|
|
|
const lookup = {};
|
|
|
|
for (
|
|
const Clazz of [
|
|
_SpellSourceClasses,
|
|
_AdditionalSpellSourceClassesSubclasses,
|
|
_AdditionalSpellSourceBackgrounds,
|
|
_AdditionalSpellSourceCharCreationOptions,
|
|
_AdditionalSpellSourceFeats,
|
|
_AdditionalSpellSourceOptionalFeatures,
|
|
_AdditionalSpellSourceRaces,
|
|
_AdditionalSpellSourceRewards,
|
|
]
|
|
) {
|
|
cpySpells.forEach(sp => PageFilterSpells.unmutateForFilters(sp));
|
|
const modalFilterSpells = new ModalFilterSpells({allData: cpySpells});
|
|
await modalFilterSpells.pPopulateHiddenWrapper();
|
|
|
|
const adder = new Clazz({modalFilterSpells, spellSourceLookupAdditional});
|
|
await adder.pInit();
|
|
adder.mutLookup(lookup);
|
|
|
|
DataUtil.spell.setSpellSourceLookup(lookup, {isExternalApplication: true});
|
|
cpySpells.forEach(sp => {
|
|
DataUtil.spell.unmutEntity(sp, {isExternalApplication: true});
|
|
DataUtil.spell.mutEntity(sp, {isExternalApplication: true});
|
|
});
|
|
}
|
|
|
|
return lookup;
|
|
}
|
|
}
|