mirror of
https://github.com/Kornstalx/5etools-mirror-2.github.io.git
synced 2026-03-09 01:04:54 -05:00
v1.198.1
This commit is contained in:
305
js/bestiary/bestiary-encounterbuilder-sublistplugin.js
Normal file
305
js/bestiary/bestiary-encounterbuilder-sublistplugin.js
Normal file
@@ -0,0 +1,305 @@
|
||||
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();
|
||||
Reference in New Issue
Block a user