This commit is contained in:
TheGiddyLimit
2024-01-01 19:34:49 +00:00
parent 332769043f
commit 8117ebddc5
1748 changed files with 2544409 additions and 1 deletions

View File

@@ -0,0 +1,435 @@
// 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;
}
}