import {EncounterBuilderUi} from "../encounterbuilder/encounterbuilder-ui.js"; import {EncounterBuilderCreatureMeta} from "../encounterbuilder/encounterbuilder-models.js"; import {EncounterBuilderHelpers} from "../utils-list-bestiary.js"; export class EncounterBuilderUiBestiary extends EncounterBuilderUi { static _HASH_KEY = "encounterbuilder"; _isSuspendSyncToSublist = false; constructor ({cache, comp, bestiaryPage, sublistManager}) { super({cache, comp}); this._bestiaryPage = bestiaryPage; this._sublistManager = sublistManager; this._lock = new VeLock(); this._cachedTitle = null; } initUi () { document.getElementById("stat-tabs").classList.add("best-ecgen__hidden"); document.getElementById("float-token").classList.add("best-ecgen__hidden"); document.getElementById("wrp-pagecontent").classList.add("best-ecgen__hidden"); $(`#btn-encounterbuild`).click(() => Hist.setSubhash(this.constructor._HASH_KEY, true)); } render () { super.render({ $parentRandomAndAdjust: $("#wrp-encounterbuild-random-and-adjust"), $parentGroupAndDifficulty: $("#wrp-encounterbuild-group-and-difficulty"), }); this._render_saveLoad(); } _render_saveLoad () { const $btnSave = $(``) .click(evt => this._sublistManager.pHandleClick_save(evt)); const $btnLoad = $(``) .click(evt => this._sublistManager.pHandleClick_load(evt)); $$(document.getElementById("best-ecgen__wrp-save-controls"))`
${$btnSave} ${$btnLoad}
`; } _handleClickCopyAsText (evt) { let xpTotal = 0; const ptsCreature = this._sublistManager.sublistItems .sort((a, b) => SortUtil.ascSortLower(a.name, b.name)) .map(it => { xpTotal += Parser.crToXpNumber(it.values.cr) * it.data.count; return `${it.data.count}× ${it.name}`; }); const ptXp = `${xpTotal.toLocaleString()} XP`; if (evt.shiftKey) { MiscUtil.pCopyTextToClipboard([...ptsCreature, ptXp].join("\n")).then(null); } else { MiscUtil.pCopyTextToClipboard(`${ptsCreature.join(", ")} (${ptXp})`).then(null); } JqueryUtil.showCopiedEffect(evt.currentTarget); } _handleClickBackToStatblocks () { Hist.setSubhash(this.constructor._HASH_KEY, null); } _render_groupAndDifficulty ({rdState, $parentGroupAndDifficulty}) { super._render_groupAndDifficulty({rdState, $parentGroupAndDifficulty}); const $btnSaveToUrl = $(``) .click(() => this._sublistManager.pHandleClick_download({isUrl: true, $eleCopyEffect: $btnSaveToUrl})); const $btnSaveToFile = $(``) .click(() => this._sublistManager.pHandleClick_download()); const $btnLoadFromFile = $(``) .click(evt => this._sublistManager.pHandleClick_upload({isAdditive: evt.shiftKey})); const $btnCopyAsText = $(``).click((evt) => this._handleClickCopyAsText(evt)); const $btnReset = $(``) .click((evt) => this._sublistManager.pHandleClick_new(evt)); const $btnBackToStatblocks = $(``).click((evt) => this._handleClickBackToStatblocks(evt)); $$`

${$btnSaveToUrl}
${$btnSaveToFile} ${$btnLoadFromFile}
${$btnCopyAsText} ${$btnReset}
${$btnBackToStatblocks}
` .appendTo($parentGroupAndDifficulty); } /* -------------------------------------------- */ withSublistSyncSuppressed (fn) { try { this._isSuspendSyncToSublist = true; fn(); } finally { this._isSuspendSyncToSublist = false; } } /** * On encounter builder state change, save to the sublist */ _render_hk_doUpdateExternalStates () { if (this._isSuspendSyncToSublist) return; this._render_hk_pDoUpdateExternalStates().then(null); } async _render_hk_pDoUpdateExternalStates () { try { await this._lock.pLock(); await this._render_hk_pDoUpdateExternalStates_(); } finally { this._lock.unlock(); } } async _render_hk_pDoUpdateExternalStates_ () { const nxtState = await this._sublistManager.pGetExportableSublist({isMemoryOnly: true}); Object.assign(nxtState, this._comp.getSublistPluginState()); await this._sublistManager.pDoLoadExportedSublist(nxtState, {isMemoryOnly: true}); } /* -------------------------------------------- */ onSublistChange ({$dispCrTotal}) { const encounterXpInfo = EncounterBuilderCreatureMeta.getEncounterXpInfo(this._comp.creatureMetas, this._getPartyMeta()); const monCount = this._sublistManager.sublistItems.map(it => it.data.count).sum(); $dispCrTotal.html(`${monCount} creature${monCount === 1 ? "" : "s"}; ${encounterXpInfo.baseXp.toLocaleString()} XP (Enc: ${(encounterXpInfo.adjustedXp).toLocaleString()} XP)`); } /* -------------------------------------------- */ resetCache () { this._cache.reset(); } isActive () { return Hist.getSubHash(this.constructor._HASH_KEY) === "true"; } _showBuilder () { this._cachedTitle = this._cachedTitle || document.title; document.title = "Encounter Builder - 5etools"; $(document.body).addClass("best__ecgen-active"); this._bestiaryPage.doDeselectAll(); this._sublistManager.doSublistDeselectAll(); } _hideBuilder () { if (this._cachedTitle) { document.title = this._cachedTitle; this._cachedTitle = null; } $(document.body).removeClass("best__ecgen-active"); } _handleClick ({evt, mode, entity}) { if (mode === "add") { return this._sublistManager.pDoSublistAdd({entity, doFinalize: true, addCount: evt.shiftKey ? 5 : 1}); } return this._sublistManager.pDoSublistSubtract({entity, subtractCount: evt.shiftKey ? 5 : 1}); } async _pHandleShuffleClick ({evt, sublistItem}) { const creatureMeta = EncounterBuilderHelpers.getSublistedCreatureMeta({sublistItem}); this._doShuffle({creatureMeta}); } handleSubhash () { if (Hist.getSubHash(this.constructor._HASH_KEY) === "true") this._showBuilder(); else this._hideBuilder(); } async doStatblockMouseOver ({evt, ele, source, hash, customHashId}) { return Renderer.hover.pHandleLinkMouseOver( evt, ele, { page: UrlUtil.PG_BESTIARY, source, hash, customHashId, }, ); } static getTokenHoverMeta (mon) { if (!Renderer.monster.hasToken(mon)) return null; return Renderer.hover.getMakePredefinedHover( { type: "image", href: { type: "external", url: Renderer.monster.getTokenUrl(mon), }, data: { hoverTitle: `Token \u2014 ${mon.name}`, }, }, {isBookContent: true}, ); } static _getFauxMon (name, source, scaledTo) { return {name, source, _isScaledCr: scaledTo != null, _scaledCr: scaledTo}; } async pDoCrChange ($iptCr, monScaled, scaledTo) { if (!$iptCr) return; // Should never occur, but if the creature has a non-adjustable CR, this field will not exist try { await this._lock.pLock(); await this._pDoCrChange({$iptCr, monScaled, scaledTo}); } finally { this._lock.unlock(); } } async _pDoCrChange ({$iptCr, monScaled, scaledTo}) { // Fetch original const mon = await DataLoader.pCacheAndGetHash( UrlUtil.PG_BESTIARY, UrlUtil.autoEncodeHash(monScaled), ); const baseCr = mon.cr.cr || mon.cr; if (baseCr == null) return; const baseCrNum = Parser.crToNumber(baseCr); const targetCr = $iptCr.val(); if (!Parser.isValidCr(targetCr)) { JqueryUtil.doToast({ content: `"${$iptCr.val()}" is not a valid Challenge Rating! Please enter a valid CR (0-30). For fractions, "1/X" should be used.`, type: "danger", }); $iptCr.val(Parser.numberToCr(scaledTo || baseCr)); return; } const targetCrNum = Parser.crToNumber(targetCr); if (targetCrNum === scaledTo) return; const state = await this._sublistManager.pGetExportableSublist({isForceIncludePlugins: true, isMemoryOnly: true}); const toFindHash = UrlUtil.autoEncodeHash(mon); const toFindUid = !(scaledTo == null || baseCrNum === scaledTo) ? Renderer.monster.getCustomHashId(this.constructor._getFauxMon(mon.name, mon.source, scaledTo)) : null; const ixCurrItem = state.items.findIndex(it => { if (scaledTo == null || scaledTo === baseCrNum) return !it.customHashId && it.h === toFindHash; else return it.customHashId === toFindUid; }); if (!~ixCurrItem) throw new Error(`Could not find previously sublisted item!`); const toFindNxtUid = baseCrNum !== targetCrNum ? Renderer.monster.getCustomHashId(this.constructor._getFauxMon(mon.name, mon.source, targetCrNum)) : null; const nextItem = state.items.find(it => { if (targetCrNum === baseCrNum) return !it.customHashId && it.h === toFindHash; else return it.customHashId === toFindNxtUid; }); // if there's an existing item with a matching UID (or lack of), merge into it if (nextItem) { const curr = state.items[ixCurrItem]; nextItem.c = `${Number(nextItem.c || 1) + Number(curr.c || 1)}`; state.items.splice(ixCurrItem, 1); } else { // if we're returning to the original CR, wipe the existing UID. Otherwise, adjust it if (targetCrNum === baseCrNum) delete state.items[ixCurrItem].customHashId; else state.items[ixCurrItem].customHashId = Renderer.monster.getCustomHashId(this.constructor._getFauxMon(mon.name, mon.source, targetCrNum)); } await this._sublistManager.pDoLoadExportedSublist(state, {isMemoryOnly: true}); } getButtons (monId) { return e_({ tag: "span", clazz: `best-ecgen__visible ve-col-1 no-wrap pl-0 btn-group`, click: evt => { evt.preventDefault(); evt.stopPropagation(); }, children: [ e_({ tag: "button", title: `Add (SHIFT for 5)`, clazz: `btn btn-success btn-xs best-ecgen__btn-list`, click: evt => this._handleClick({evt, entity: this._bestiaryPage.dataList_[monId], mode: "add"}), children: [ e_({ tag: "span", clazz: `glyphicon glyphicon-plus`, }), ], }), e_({ tag: "button", title: `Subtract (SHIFT for 5)`, clazz: `btn btn-danger btn-xs best-ecgen__btn-list`, click: evt => this._handleClick({evt, entity: this._bestiaryPage.dataList_[monId], mode: "subtract"}), children: [ e_({ tag: "span", clazz: `glyphicon glyphicon-minus`, }), ], }), ], }); } getSublistButtonsMeta (sublistItem) { const $btnAdd = $(``) .click(evt => this._handleClick({evt, entity: sublistItem.data.entity, mode: "add"})); const $btnSub = $(``) .click(evt => this._handleClick({evt, entity: sublistItem.data.entity, mode: "subtract"})); const $btnRandomize = $(``) .click(evt => this._pHandleShuffleClick({evt, sublistItem})); const $btnLock = $(``) .click(() => this._sublistManager.pSetDataEntry({sublistItem, key: "isLocked", value: !sublistItem.data.isLocked})) .toggleClass("active", sublistItem.data.isLocked); const $wrp = $$` ${$btnAdd} ${$btnSub} ${$btnRandomize} ${$btnLock} ` .click(evt => { evt.preventDefault(); evt.stopPropagation(); }); return { $wrp, fnUpdate: () => $btnLock.toggleClass("active", sublistItem.data.isLocked), }; } }