Files
5etools-mirror-2.github.io/js/bestiary/bestiary-encounterbuilder-sublistplugin.js
TheGiddyLimit 8117ebddc5 v1.198.1
2024-01-01 19:34:49 +00:00

306 lines
10 KiB
JavaScript

import {EncounterBuilderHelpers} from "../utils-list-bestiary.js";
import {EncounterBuilderCreatureMeta} from "../encounterbuilder/encounterbuilder-models.js";
import {EncounterBuilderComponentBestiary} from "./bestiary-encounterbuilder-component.js";
/**
* Serialize/deserialize state from the encounter builder.
*/
export class EncounterBuilderSublistPlugin extends SublistPlugin {
constructor ({sublistManager, encounterBuilder, encounterBuilderComp}) {
super();
this._sublistManager = sublistManager;
this._encounterBuilder = encounterBuilder;
this._encounterBuilderComp = encounterBuilderComp;
}
/* -------------------------------------------- */
async pLoadData ({exportedSublist, isMemoryOnly}) {
const nxt = {};
// Allow URLified versions of keys
const keyLookup = this._encounterBuilderComp.getDefaultStateKeys()
.mergeMap(k => ({[k]: k, [k.toUrlified()]: k}));
if (exportedSublist) {
Object.entries(exportedSublist)
.filter(([, v]) => v != null)
.map(([k, v]) => {
// Only add specific keys, as we do not want to track e.g. sublist state
k = keyLookup[k];
if (!k) return null;
return [k, v];
})
.filter(Boolean)
// Always process `colsExtraAdvanced` first (if available), as used in `playersAdvanced`
.sort(([kA], [kB]) => kA === "colsExtraAdvanced" ? -1 : kB === "colsExtraAdvanced" ? 1 : 0)
.forEach(([k, v]) => {
if (isMemoryOnly) return nxt[k] = MiscUtil.copyFast(v);
// When loading from non-memory sources, expand the data
switch (k) {
case "playersSimple": return nxt[k] = v.map(it => EncounterBuilderComponentBestiary.getDefaultPlayerRow_simple(it));
case "colsExtraAdvanced": return nxt[k] = v.map(it => EncounterBuilderComponentBestiary.getDefaultColExtraAdvanced(it));
case "playersAdvanced": return nxt[k] = v.map(it => EncounterBuilderComponentBestiary.getDefaultPlayerRow_advanced({
...it,
extras: it.extras.map(x => EncounterBuilderComponentBestiary.getDefaultPlayerAdvancedExtra(x)),
colsExtraAdvanced: nxt.colsExtraAdvanced || this._encounterBuilderComp.colsExtraAdvanced,
}));
default: return nxt[k] = v;
}
});
if (nxt.playersSimple) {
nxt.playersSimple
.forEach(wrapped => {
wrapped.entity.count = wrapped.entity.count || 1;
wrapped.entity.level = wrapped.entity.level || 1;
});
}
if (nxt.playersAdvanced) {
nxt.playersAdvanced
.forEach(wrapped => {
wrapped.entity.name = wrapped.entity.name || "";
wrapped.entity.level = wrapped.entity.level || 1;
wrapped.entity.extraCols = wrapped.entity.extraCols
|| (nxt.colsExtraAdvanced || this._encounterBuilderComp.colsExtraAdvanced.map(() => ""));
});
}
}
// Note that we do not set `creatureMetas` here, as `onSublistUpdate` handles this
this._encounterBuilderComp.setStateFromLoaded(nxt);
}
async _pLoadData_getCreatureMetas ({exportedSublist}) {
if (!exportedSublist.items?.length) return [];
return exportedSublist.items
.pSerialAwaitMap(async serialItem => {
const {
entity,
entityBase,
count,
isLocked,
customHashId,
} = await SublistManager.pDeserializeExportedSublistItem(serialItem);
return new EncounterBuilderCreatureMeta({
creature: entity,
count,
isLocked,
customHashId: customHashId,
baseCreature: entityBase,
});
});
}
static async pMutLegacyData ({exportedSublist, isMemoryOnly}) {
if (!exportedSublist) return;
// region Legacy Bestiary Encounter Builder format
if (exportedSublist.p) {
exportedSublist.playersSimple = exportedSublist.p.map(it => EncounterBuilderComponentBestiary.getDefaultPlayerRow_simple(it));
if (!isMemoryOnly) this._mutExternalize({obj: exportedSublist, k: "playersSimple"});
delete exportedSublist.p;
}
if (exportedSublist.l) {
Object.assign(exportedSublist, exportedSublist.l);
delete exportedSublist.l;
}
if (exportedSublist.a != null) {
exportedSublist.isAdvanced = !!exportedSublist.a;
delete exportedSublist.a;
}
if (exportedSublist.c) {
exportedSublist.colsExtraAdvanced = exportedSublist.c.map(name => EncounterBuilderComponentBestiary.getDefaultColExtraAdvanced({name}));
if (!isMemoryOnly) this._mutExternalize({obj: exportedSublist, k: "colsExtraAdvanced"});
delete exportedSublist.c;
}
if (exportedSublist.d) {
exportedSublist.playersAdvanced = exportedSublist.d.map(({n, l, x}) => EncounterBuilderComponentBestiary.getDefaultPlayerRow_advanced({
name: n,
level: l,
extras: x.map(value => EncounterBuilderComponentBestiary.getDefaultPlayerAdvancedExtra({value})),
colsExtraAdvanced: exportedSublist.colsExtraAdvanced,
}));
if (!isMemoryOnly) this._mutExternalize({obj: exportedSublist, k: "playersAdvanced"});
delete exportedSublist.d;
}
// endregion
// region Legacy "reference" format
// These are general save manager properties, but we set them here, as encounter data was the only thing to make
// use of this system.
if (exportedSublist.bestiaryId) {
exportedSublist.saveId = exportedSublist.bestiaryId;
delete exportedSublist.bestiaryId;
}
if (exportedSublist.isRef) {
exportedSublist.managerClient_isReferencable = true;
exportedSublist.managerClient_isLoadAsCopy = false;
}
delete exportedSublist.isRef;
// endregion
}
async pMutLegacyData ({exportedSublist, isMemoryOnly}) {
await this.constructor.pMutLegacyData({exportedSublist, isMemoryOnly});
}
/* -------------------------------------------- */
async pMutSaveableData ({exportedSublist, isForce = false, isMemoryOnly = false}) {
if (!isForce && !this._encounterBuilder.isActive()) return;
[
"playersSimple",
"isAdvanced",
"colsExtraAdvanced",
"playersAdvanced",
].forEach(k => {
exportedSublist[k] = MiscUtil.copyFast(this._encounterBuilderComp[k]);
if (isMemoryOnly) return;
this.constructor._mutExternalize({obj: exportedSublist, k});
});
}
static _WALKER_EXTERNALIZE = null;
static _HANDLERS_EXTERNALIZE = {
array: (arr) => {
if (arr.some(it => !it.id || !it.entity)) return arr;
return arr.map(({entity}) => entity);
},
};
static _mutExternalize ({obj, k}) {
this._WALKER_EXTERNALIZE = this._WALKER_EXTERNALIZE || MiscUtil.getWalker();
obj[k] = this._WALKER_EXTERNALIZE.walk(
obj[k],
this._HANDLERS_EXTERNALIZE,
);
}
/* -------------------------------------------- */
async pDoInitNewState ({prevExportableSublist, evt}) {
// If SHIFT pressed, reset players
const nxt = {
playersSimple: evt.shiftKey ? [] : MiscUtil.copyFast(prevExportableSublist.playersSimple),
playersAdvanced: evt.shiftKey ? [] : MiscUtil.copyFast(prevExportableSublist.playersAdvanced),
};
this._encounterBuilderComp.setPartialStateFromLoaded(nxt);
}
/* -------------------------------------------- */
getDownloadName () {
if (!this._encounterBuilder.isActive()) return null;
return "encounter";
}
getDownloadFileType () {
if (!this._encounterBuilder.isActive()) return null;
return "encounter";
}
getUploadFileTypes ({downloadFileTypeBase}) {
if (!this._encounterBuilder.isActive()) return null;
return [this.getDownloadFileType(), downloadFileTypeBase];
}
/* -------------------------------------------- */
onSublistUpdate () {
this._encounterBuilder.withSublistSyncSuppressed(() => {
// Note that we only update `creatureMetas` here, as this only triggers on direct updates to the underlying sublist.
// For everything else, the `pLoadData` path is used.
this._encounterBuilderComp.creatureMetas = this._sublistManager.sublistItems
.map(sublistItem => EncounterBuilderHelpers.getSublistedCreatureMeta({sublistItem}));
});
}
}
class EncounterBuilderLegacyStorageMigration {
static _VERSION = 2;
static _STORAGE_KEY_LEGACY_SAVED_ENCOUNTERS = "ENCOUNTER_SAVED_STORAGE";
static _STORAGE_KEY_LEGACY_ENCOUNTER = "ENCOUNTER_STORAGE";
static _STORAGE_KEY_LEGACY_ENCOUNTER_MIGRATION_VERSION = "ENCOUNTER_STORAGE_MIGRATION_VERSION";
static _STORAGE_KEY_LEGACY_SAVED_ENCOUNTER_MIGRATION_VERSION = "ENCOUNTER_SAVED_STORAGE_MIGRATION_VERSION";
static register () {
SublistPersistor._LEGACY_MIGRATOR.registerLegacyMigration(this._pMigrateSublist.bind(this));
SaveManager._LEGACY_MIGRATOR.registerLegacyMigration(this._pMigrateSaves.bind(this));
}
static async _pMigrateSublist (stored) {
let version = await StorageUtil.pGet(this._STORAGE_KEY_LEGACY_ENCOUNTER_MIGRATION_VERSION);
if (version && version >= 2) return false;
if (!version) version = 1;
const encounter = await StorageUtil.pGet(this._STORAGE_KEY_LEGACY_ENCOUNTER);
if (!encounter) return false;
Object.entries(encounter)
.forEach(([k, v]) => {
if (stored[k] != null) return;
stored[k] = v;
});
await EncounterBuilderSublistPlugin.pMutLegacyData({exportedSublist: stored});
await StorageUtil.pSet(this._STORAGE_KEY_LEGACY_ENCOUNTER_MIGRATION_VERSION, this._VERSION);
JqueryUtil.doToast(`Migrated active Bestiary encounter from version ${version} to version ${this._VERSION}!`);
return true;
}
static async _pMigrateSaves (stored) {
let version = await StorageUtil.pGet(this._STORAGE_KEY_LEGACY_SAVED_ENCOUNTER_MIGRATION_VERSION);
if (version && version >= 2) return false;
if (!version) version = 1;
const encounters = await StorageUtil.pGet(this._STORAGE_KEY_LEGACY_SAVED_ENCOUNTERS);
if (!encounters) return false;
await Object.entries(encounters.savedEncounters || {})
.pSerialAwaitMap(async ([id, enc]) => {
const legacyData = MiscUtil.copyFast(enc.data || {});
legacyData.name = enc.name || "(Unnamed encounter)";
legacyData.saveId = id;
legacyData.manager_isSaved = true;
await EncounterBuilderSublistPlugin.pMutLegacyData({exportedSublist: legacyData});
const tgt = MiscUtil.getOrSet(stored, "state", "saves", []);
tgt.push({
id: CryptUtil.uid(),
entity: legacyData,
});
});
await StorageUtil.pSet(this._STORAGE_KEY_LEGACY_SAVED_ENCOUNTER_MIGRATION_VERSION, this._VERSION);
JqueryUtil.doToast(`Migrated saved Bestiary encounters from version ${version} to version ${this._VERSION}!`);
return true;
}
}
EncounterBuilderLegacyStorageMigration.register();