diff --git a/compendium-browser.css b/compendium-browser.css new file mode 100644 index 0000000..783741b --- /dev/null +++ b/compendium-browser.css @@ -0,0 +1,200 @@ +/* core foundry changes +#compendium.flexcolumn.active { + display: flex; + flex-direction: column; +} +#compendium.flexcolumn > .directory-list { + width: 100%; + height: auto; + flex-basis: 0; + flex-grow: 1; +} +#compendium.flexcolumn > .directory-footer { + height: auto; +} +#compendium.flexcolumn > .directory-footer > * { + margin-top: 5px; +} +*/ +#compendium .directory-footer .compendium-browser-btn { + margin-top: 5px; +} +#compendium .directory-footer { + display: block; +} +/* spellbrowser */ +/* resizing */ +.compendium-browser { + max-width: 1100px; + max-height: 90vh; +} +.compendium-browser .tabs { + max-height: 2em; + border-bottom: solid #782e22; +} +.compendium-browser .tabContainer { + height: calc(100% - 2em); +} +.compendium-browser .tabContainer .tab { + width: 100%; + height: 100%; + overflow: scroll; +} +.compendium-browser .control-area { + position: sticky; + display: block; + min-width: 250px; + width: 300px; + height: 100%; + padding-right: 5px; + overflow: scroll; +} +.compendium-browser .control-area .filtercontainer { + border: 1px solid #bbb; + border-radius: 5px; + margin-top: 5px; + padding: 2px; +} +.compendium-browser .control-area .filtercontainer h3 { + margin: 0; + cursor: pointer; +} +.compendium-browser .control-area .filtercontainer dl, +.compendium-browser .control-area .filtercontainer div { + margin: 5px 0; +} +.compendium-browser .control-area .filtercontainer dt { + display: inline-block; + width: 40%; + padding-left: 5px; +} +.compendium-browser .control-area .filtercontainer dd { + display: inline-block; + width: 58%; + margin-left: 0; +} +.compendium-browser .control-area .filtercontainer dd select { + width: 100%; +} +.compendium-browser .control-area .filtercontainer .multiselect { + border: 1px solid #bbb; + border-radius: 3px; +} +.compendium-browser .control-area .filtercontainer .multiselect label { + padding: 5px; +} +.compendium-browser .control-area .filtercontainer .small-input { + width: 144px; + height: 27px; + background: rgba(255, 255, 255, 0.8); + border: 1px solid #444; + border-radius: 3px; + padding: 0 3px; + text-overflow: ellipsis; +} +.compendium-browser .control-area .filtercontainer .small-select { + width: 40px; +} +.compendium-browser .browser .window-content { + overflow-y: hidden!important; +} +.compendium-browser .browser ul { + float: right; + display: block; + min-width: 335px; + width: 785px; + margin: 0; + height: 100%; + overflow: auto; + padding-left: 5px; +} +.compendium-browser .browser ul .filter-tags { + display: none; +} +.compendium-browser .browser .spacer { + display: inline-block; + min-width: 5px; +} +.compendium-browser .browser .spacer-large { + display: inline-block; + min-width: 15px; +} +.compendium-browser .spell-browser .spell { + cursor: default; + vertical-align: middle; + line-height: 32px; + margin: 2px 0; +} +.compendium-browser .spell-browser .spell .spell-image { + max-width: 32px; + height: 32px; +} +.compendium-browser .spell-browser .spell .spell-name { + height: 32px; + padding-left: 5px; +} +.compendium-browser .spell-browser .spell .spell-level { + text-align: center; + font-weight: 900; + max-width: 18px; + height: 32px; +} +.compendium-browser .spell-browser .spell .spell-tags { + text-align: right; + margin-right: 3px; + font-weight: 900; + max-width: 100px; + height: 32px; +} +.compendium-browser .npc-browser .npc { + cursor: default; + vertical-align: middle; + line-height: 64px; + margin: 4px 0; +} +.compendium-browser .npc-browser .npc .npc-image { + max-width: 64px; + height: 64px; +} +.compendium-browser .npc-browser .npc .npc-image img { + width: 64px; + height: 64px; + border: none; +} +.compendium-browser .npc-browser .npc .npc-line { + line-height: 25px; + padding: 9px 0 5px 5px; +} +.compendium-browser .npc-browser .npc .npc-name { + font-weight: bold; + font-size: 16px; +} +.compendium-browser .npc-browser .npc .cr { + display: inline-block; + width: 55px; +} +.compendium-browser .npc-browser .npc .size { + display: inline-block; + width: 75px; +} +.compendium-browser .npc-browser .npc .type { + display: inline-block; +} +.compendium-browser .settings .settings-group { + border: 1px solid #bbb; + border-radius: 5px; + margin-top: 5px; + padding: 2px; +} +.compendium-browser .settings .settings-group h3 { + margin: 0; + cursor: pointer; +} +.compendium-browser .settings .settings-group label { + display: block; +} +.compendium-browser .settings .settings-group h4 { + display: inline-block; + vertical-align: middle; + height: 100%; +} diff --git a/compendium-browser.js b/compendium-browser.js new file mode 100644 index 0000000..c62048c --- /dev/null +++ b/compendium-browser.js @@ -0,0 +1,1278 @@ +/** + * @author Felix Müller aka syl3r86 + * @version 0.1 + */ + +class SpellBrowser extends Application { + + constructor(app) { + super(app); + + // load settings + Hooks.on('ready', e => { + this.initSettings(); + + this.loadSpells().then(obj => { + this.spells = obj + }); + this.loadNpcs().then(obj => { + this.npcs = obj + }); + + loadTemplates([ + "modules/compendium-browser/template/spell-browser.html", + "modules/compendium-browser/template/npc-browser.html", + "modules/compendium-browser/template/filter-container.html", + "modules/compendium-browser/template/settings.html" + ]) + }); + this.hookCompendiumList(); + + this.spellFilters = { + registeredFilterCategorys: {}, + activeFilters: {} + }; + this.npcFilters = { + registeredFilterCategorys: {}, + activeFilters: {} + }; + + } + + static get defaultOptions() { + const options = super.defaultOptions; + options.classes = options.classes.concat('compendium-browser'); + options.template = "modules/compendium-browser/template/template.html"; + options.width = 700; + options.height = 700; + options.resizable = true; + options.minimizable = true; + options.title = game.i18n.localize("Compendium Browser"); + return options; + } + + hookCompendiumList() { + Hooks.on('renderCompendiumDirectory', (app, html, data) => { + if (game.user.isGM || this.settings.allowSpellBrowser || this.settings.allowNpcBrowser) { + const importButton = $(``); + html.find('.compendium-browser-btn').remove(); + + // adding to directory-list since the footer doesn't exist if the user is not gm + html.find('.directory-footer').append(importButton); + + // Handle button clicks + importButton.click(ev => { + ev.preventDefault(); + this.render(true); + }); + } + }); + } + + async getData() { + if (!this.spellsLoaded) { + // spells will be stored locally to not require full loading each time the browser is opened + this.spells = await this.loadSpells(); + this.spellsLoaded = true; + } + + let data = {}; + data.spells = this.spells; + data.spellFilters = this.spellFilters; + data.showSpellBrowser = (game.user.isGM || this.settings.allowSpellBrowser); + data.npcs = this.npcs; + data.npcFilters = this.npcFilters; + data.showNpcBrowser = (game.user.isGM || this.settings.allowNpcBrowser); + data.settings = this.settings; + data.isGM = game.user.isGM; + return data; + } + + async loadSpells() { + console.log('Spell Browser | Started loading spells'); + this.spellsLoaded = false; + this.spellsLoading = true; + + let unfoundSpells = ''; + + let spells = {}; + + for (let pack of game.packs) { + if (pack['metadata']['entity'] == "Item" && this.settings.loadedSpellCompendium[pack.collection].load) { + await pack.getContent().then(content => { + for (let spell of content) { + spell = spell.data; + if (spell.type == 'spell') { + + spell.compendium = pack.collection; + + // determining classes that can use the spell + let cleanSpellName = spell.name.toLowerCase().replace(/[^a-zA-Z0-9\s:]/g, '').replace("'", '').replace(/ /g, ''); + if (spellClassList[cleanSpellName] !== undefined) { + let classes = spellClassList[cleanSpellName]; + spell.data.classes = classes.split(','); + } else { + unfoundSpells += cleanSpellName + ','; + } + + // getting damage types + spell.damageTypes = []; + if (spell.data.damage.parts.length > 0) { + for (let part of spell.data.damage.parts) { + let type = part[1]; + if (spell.damageTypes.indexOf(type) === -1) { + spell.damageTypes.push(type); + } + } + } + + spells[(spell._id)] = spell; + } + } + }); + } + } + if (unfoundSpells !== '') { + console.log(`Spell Browser | List of Spells that don't have a class assosiated to them:`); + console.log(unfoundSpells); + } + console.log('Spell Browser | Finished loading spells'); + return spells; + } + + async loadNpcs() { + console.log('NPC Browser | Started loading spells'); + + let npcs = {}; + + for (let pack of game.packs) { + if (pack['metadata']['entity'] == "Actor" && this.settings.loadedNpcCompendium[pack.collection].load) { + await pack.getContent().then(async content => { + for (let npc of content) { + npc = npc.data; + // add needed data + npc.compendium = pack.collection; + // cr display + let cr = npc.data.details.cr; + if (cr == undefined || cr == '') cr = 0; + else cr = Number(cr); + if (cr > 0 && cr < 1) cr = "1/" + (1 / cr); + npc.displayCR = cr; + npc.displaySize = 'unset'; + npc.filterSize = 2; + if (CONFIG.DND5E.actorSizes[npc.data.traits.size] !== undefined) { + npc.displaySize = CONFIG.DND5E.actorSizes[npc.data.traits.size]; + } + switch (npc.data.traits.size) { + case 'grg': npc.filterSize = 5; break; + case 'huge': npc.filterSize = 4; break; + case 'lg': npc.filterSize = 3; break; + case 'sm': npc.filterSize = 1; break; + case 'tiny': npc.filterSize = 0; break; + case 'med': + default: npc.filterSize = 2; break; + } + + // getting value for HasSpells and damage types + npc.hasSpells = false; + npc.damageDealt = []; + for (let item of npc.items) { + if (item.type == 'spell') { + npc.hasSpells = true; + } + if (item.data.damage.parts.length > 0) { + for (let part of item.data.damage.parts) { + let type = part[1]; + if (npc.damageDealt.indexOf(type) === -1) { + npc.damageDealt.push(type); + } + } + } + } + + npcs[npc._id] = npc; + } + }); + } + } + console.log('NPC Browser | Finished loading NPCs'); + return npcs; + } + + activateListeners(html) { + // activating tabs/* + let nav = $('.tabs[data-group="toplvl"]'); + new Tabs(nav, { + initial: this["activeTab"] || 'tab1', + callback: clicked => { + this["activeTab"] = clicked.data("tab"); + } + }); + + // show entity sheet + html.find('.item-edit').click(ev => { + let itemId = $(ev.currentTarget).parents("li").attr("data-entry-id"); + let compendium = $(ev.currentTarget).parents("li").attr("data-entry-compendium"); + let pack = game.packs.find(p => p.collection === compendium); + pack.getEntity(itemId).then(entity => { + entity.sheet.render(true); + }); + }); + + // make draggable + html.find('.draggable').each((i, li) => { + li.setAttribute("draggable", true); + li.addEventListener('dragstart', event => { + let packName = li.getAttribute("data-entry-compendium"); + let pack = game.packs.find(p => p.collection === packName); + if (!pack) { + event.preventDefault(); + return false; + } + event.dataTransfer.setData("text/plain", JSON.stringify({ + type: pack.entity, + pack: pack.collection, + id: li.getAttribute("data-entry-id") + })); + }, false); + }); + + // toggle visibility of filter containers + html.find('.filtercontainer h3, .multiselect label').click(async ev => { + await $(ev.target.nextElementSibling).toggle(100); + + }); + html.find('.multiselect label').trigger('click'); + + // sort spell list + html.find('.spell-browser select[name=sortorder]').on('change', ev => { + let spellList = html.find('.spell-browser li'); + let byName = (ev.target.value == 'true'); + let sortedList = this.sortSpells(spellList, byName); + let ol = $(html.find('.spell-browser ul')); + ol[0].innerHTML = []; + for (let element of sortedList) { + ol[0].append(element); + } + }); + html.find('.spell-browser select[name=sortorder]').trigger('change'); + + // sort npc list + html.find('.npc-browser select[name=sortorder]').on('change', ev => { + let npcList = html.find('.npc-browser li'); + let orderBy = ev.target.value; + let sortedList = this.sortNpcs(npcList, orderBy); + let ol = $(html.find('.npc-browser ul')); + ol[0].innerHTML = []; + for (let element of sortedList) { + ol[0].append(element); + } + }); + html.find('.npc-browser select[name=sortorder]').trigger('change') + + // settings + html.find('.settings input').on('change', ev => { + let setting = ev.target.dataset.setting; + let value = ev.target.checked; + if (setting === 'spell-compendium-setting') { + console.log('changing spells'); + let key = ev.target.dataset.key; + this.settings.loadedSpellCompendium[key].load = value; + this.loadSpells().then((spells) => { + this.spells = spells; + this.render(); + }); + ui.notifications.info("Settings Saved. Spell Compendiums are being reloaded."); + } else if (setting === 'npc-compendium-setting') { + console.log('changing npcs'); + let key = ev.target.dataset.key; + this.settings.loadedNpcCompendium[key].load = value; + this.loadNpcs().then((npcs) => { + this.npcs = npcs; + this.render(); + }); + ui.notifications.info("Settings Saved. NPC Compendiums are being reloaded."); + } + if (setting === 'allow-spell-browser') { + this.settings.allowSpellBrowser = value; + } + if (setting === 'allow-npc-browser') { + this.settings.allowNpcBrowser = value; + } + this.saveSettings(); + }); + + + // activating or deactivating filters + + // text filters + + html.find('.filter[data-type=text] input, .filter[data-type=text] select').on('keyup change paste', ev => { + let path = $(ev.target).parents('.filter').data('path'); + let key = path.replace(/\./g, ''); + let value = ev.target.value; + let itemType = $(ev.target).parents('.tab').data('tab'); + + let filterTarget = `${itemType}Filters`; + + if (value === '' || value === undefined) { + delete this[filterTarget].activeFilters[key]; + } else { + this[filterTarget].activeFilters[key] = { + path: path, + type: 'text', + valIsArray: false, + value: ev.target.value + } + } + + let list = null; + let subjects = null; + if (itemType === 'spell') { + list = html.find('.spell-browser li'); + subjects = this.spells; + } else if (itemType === 'npc') { + list = html.find('.npc-browser li'); + subjects = this.npcs; + } + this.filterElements(list, subjects, this[filterTarget].activeFilters); + }); + + // select filters + html.find('.filter[data-type=select] select, .filter[data-type=bool] select').on('change', ev => { + let path = $(ev.target).parents('.filter').data('path'); + let key = path.replace(/\./g, ''); + let filterType = $(ev.target).parents('.filter').data('type'); + let itemType = $(ev.target).parents('.tab').data('tab'); + let valIsArray = $(ev.target).parents('.filter').data('valisarray'); + if (valIsArray === 'true') valIsArray = true; + let value = ev.target.value; + if (value === 'false') value = false; + if (value === 'true') value = true; + + let filterTarget = `${itemType}Filters`; + + if (value === "null") { + delete this[filterTarget].activeFilters[key] + } else { + this[filterTarget].activeFilters[key] = { + path: path, + type: filterType, + valIsArray: valIsArray, + value:value + } + } + + let list = null; + let subjects = null; + if (itemType === 'spell') { + list = html.find('.spell-browser li'); + subjects = this.spells; + } else if (itemType === 'npc') { + list = html.find('.npc-browser li'); + subjects = this.npcs; + } + this.filterElements(list, subjects, this[filterTarget].activeFilters); + }); + + // multiselect filters + html.find('.filter[data-type=multiSelect] input').on('change', ev => { + let path = $(ev.target).parents('.filter').data('path'); + let key = path.replace(/\./g, ''); + let filterType = 'multiSelect'; + let itemType = $(ev.target).parents('.tab').data('tab'); + let valIsArray = $(ev.target).parents('.filter').data('valisarray'); + if (valIsArray === 'true') valIsArray = true; + let value = $(ev.target).data('value'); + + let filterTarget = `${itemType}Filters`; + let filter = this[filterTarget].activeFilters[key]; + + if (ev.target.checked === true) { + if (filter === undefined) { + this[filterTarget].activeFilters[key] = { + path: path, + type: filterType, + valIsArray: valIsArray, + values: [ value ] + } + } else { + this[filterTarget].activeFilters[key].values.push(value); + } + } else { + delete this[filterTarget].activeFilters[key].values.splice(this[filterTarget].activeFilters[key].values.indexOf(value),1); + if (this[filterTarget].activeFilters[key].values.length === 0) { + delete this[filterTarget].activeFilters[key]; + } + } + + let list = null; + let subjects = null; + if (itemType === 'spell') { + list = html.find('.spell-browser li'); + subjects = this.spells; + } else if (itemType === 'npc') { + list = html.find('.npc-browser li'); + subjects = this.npcs; + } + this.filterElements(list, subjects, this[filterTarget].activeFilters); + }); + + + html.find('.filter[data-type=numberCompare] select, .filter[data-type=numberCompare] input').on('change keyup paste', ev => { + let path = $(ev.target).parents('.filter').data('path'); + let key = path.replace(/\./g, ''); + let filterType = 'numberCompare'; + let itemType = $(ev.target).parents('.tab').data('tab'); + let valIsArray = false; + + let operator = $(ev.target).parents('.filter').find('select').val(); + let value = $(ev.target).parents('.filter').find('input').val(); + + console.log(operator, value); + console.log(value === ''); + console.log(operator === 'null'); + + let filterTarget = `${itemType}Filters`; + + if (value === '' || operator === 'null') { + delete this[filterTarget].activeFilters[key] + } else { + this[filterTarget].activeFilters[key] = { + path: path, + type: filterType, + valIsArray: valIsArray, + operator: operator, + value: value + } + } + + let list = null; + let subjects = null; + if (itemType === 'spell') { + list = html.find('.spell-browser li'); + subjects = this.spells; + } else if (itemType === 'npc') { + list = html.find('.npc-browser li'); + subjects = this.npcs; + } + this.filterElements(list, subjects, this[filterTarget].activeFilters); + }); + + } + + sortSpells(list, byName) { + if(byName) { + list.sort((a, b) => { + let aName = $(a).find('.spell-name a')[0].innerHTML; + let bName = $(b).find('.spell-name a')[0].innerHTML; + //console.log(`${aName} vs ${bName}`); + if (aName < bName) return -1; + if (aName > bName) return 1; + return 0; + }); + } else { + list.sort((a, b) => { + let aVal = $(a).find('input[name=level]').val(); + let bVal = $(b).find('input[name=level]').val(); + console.log(aVal, bVal); + if (aVal < bVal) return -1; + if (aVal > bVal) return 1; + if (aVal == bVal) { + let aName = $(a).find('.spell-name a')[0].innerHTML; + let bName = $(b).find('.spell-name a')[0].innerHTML; + if (aName < bName) return -1; + if (aName > bName) return 1; + return 0; + } + }); + } + return list; + } + + sortNpcs(list, orderBy) { + switch (orderBy) { + case 'name': + list.sort((a, b) => { + let aName = $(a).find('.npc-name a')[0].innerHTML; + let bName = $(b).find('.npc-name a')[0].innerHTML; + if (aName < bName) return -1; + if (aName > bName) return 1; + return 0; + }); break; + case 'cr': + list.sort((a, b) => { + let aVal = Number($(a).find('input[name="order.cr"]').val()); + let bVal = Number($(b).find('input[name="order.cr"]').val()); + if (aVal < bVal) return -1; + if (aVal > bVal) return 1; + if (aVal == bVal) { + let aName = $(a).find('.npc-name a')[0].innerHTML; + let bName = $(b).find('.npc-name a')[0].innerHTML; + if (aName < bName) return -1; + if (aName > bName) return 1; + return 0; + } + }); break; + case 'size': + list.sort((a, b) => { + let aVal = $(a).find('input[name="order.size"]').val(); + let bVal = $(b).find('input[name="order.size"]').val(); + if (aVal < bVal) return -1; + if (aVal > bVal) return 1; + if (aVal == bVal) { + let aName = $(a).find('.npc-name a')[0].innerHTML; + let bName = $(b).find('.npc-name a')[0].innerHTML; + if (aName < bName) return -1; + if (aName > bName) return 1; + return 0; + } + }); break; + } + return list; + } + + filterElements(list, subjects, filters) { + for (let element of list) { + let subject = subjects[element.dataset.entryId]; + if (this.getFilterResult(subject, filters) == false) { + $(element).hide(); + } else { + $(element).show(); + } + } + } + + getFilterResult(subject, filters) { + for (let filterKey in filters) { + let filter = filters[filterKey]; + let prop = getProperty(subject, filter.path); + console.log(filter); + console.log(prop); + if (filter.type === 'numberCompare') { + + switch (filter.operator) { + case '=': if (prop != filter.value) { return false; } break; + case '<': if (prop >= filter.value) { return false; } break; + case '>': if (prop <= filter.value) { return false; } break; + } + + continue; + } + if (filter.valIsArray === false) { + if (filter.type === 'text') { + if (prop.toLowerCase().indexOf(filter.value.toLowerCase()) === -1) { + return false; + } + } else { + if (filter.value !== undefined && prop !== undefined && prop != filter.value && !(filter.value === true && prop)) { + return false; + } + if (filter.values && filter.values.indexOf(prop) === -1) { + return false; + } + } + } else { + if (typeof prop === 'object') { + if (filter.value && prop.indexOf(filter.value) === -1) { + return false; + } else { + console.log(prop, filter.values); + for (let val of filter.values) { + if (prop.indexOf(val) !== -1) { + continue; + } + return false; + } + } + } else { + for (let val of filter.values) { + if (prop === val) { + continue; + } + } + return false; + } + } + } + + return true; + } + + clearObject(obj) { + let newObj = {}; + for (let key in obj) { + if (obj[key] == true) { + newObj[key] = true; + } + } + return newObj; + } + + initSettings() { + let defaultSettings = { + loadedSpellCompendium: {}, + loadedNpcCompendium: {}, + }; + for (let compendium of game.packs) { + if (compendium['metadata']['entity'] == "Item") { + defaultSettings.loadedSpellCompendium[compendium.collection] = { + load: true, + name: `${compendium['metadata']['label']} (${compendium.collection})` + }; + } + if (compendium['metadata']['entity'] == "Actor") { + defaultSettings.loadedNpcCompendium[compendium.collection] = { + load: true, + name: `${compendium['metadata']['label']} (${compendium.collection})` + }; + } + } + // creating game setting container + game.settings.register("compendiumBrowser", "settings", { + name: "Compendium Browser Settings", + hint: "Settings to exclude packs from loading and visibility of the browser", + default: defaultSettings, + type: Object, + scope: 'world', + onChange: settings => { + this.settings = settings; + } + }); + + // load settings from container and apply to default settings (available compendie might have changed) + let settings = game.settings.get('compendiumBrowser', 'settings'); + for (let compKey in defaultSettings.loadedSpellCompendium) { + if (settings.loadedSpellCompendium[compKey] !== undefined) { + defaultSettings.loadedSpellCompendium[compKey].load = settings.loadedSpellCompendium[compKey].load; + } + } + for (let compKey in defaultSettings.loadedNpcCompendium) { + if (settings.loadedNpcCompendium[compKey] !== undefined) { + defaultSettings.loadedNpcCompendium[compKey].load = settings.loadedNpcCompendium[compKey].load; + } + } + defaultSettings.allowSpellBrowser = settings.allowSpellBrowser; + defaultSettings.allowNpcBrowser = settings.allowNpcBrowser; + + game.settings.set('compendiumBrowser', 'settings', defaultSettings); + this.settings = defaultSettings; + } + + saveSettings() { + game.settings.set('compendiumBrowser', 'settings', this.settings); + } + + addFilter(entityType, category, label, path, type, possibleValues = null, valIsArray = false) { + let target = `${entityType}Filters`; + let filter = {}; + filter.path = path; + filter.label = label; + filter.type = 'text'; + if (['text', 'bool', 'select', 'multiSelect', 'numberCompare'].indexOf(type) !== -1) { + filter[`is${type}`] = true; + filter.type = type; + } + if (possibleValues !== null) { + filter.possibleValues = possibleValues; + } + filter.valIsArray = valIsArray; + + let catId = category.replace(/\W/g, ''); + if (this[target].registeredFilterCategorys[catId] === undefined) { + this[target].registeredFilterCategorys[catId] = { label: category, filters: [] }; + } + this[target].registeredFilterCategorys[catId].filters.push(filter); + + } + + /** + * Used to add custom filters to the Spell-Browser + * @param {String} category - Title of the category + * @param {String} label - Title of the filter + * @param {String} path - path to the data that the filter uses. uses dotnotation. example: data.abilities.dex.value + * @param {String} type - type of filter + * possible filter: + * text: will give a textinput (or use a select if possibleValues has values) to compare with the data. will use objectData.indexOf(searchedText) to enable partial matching + * bool: will see if the data at the path exists and not false. + * select: exactly matches the data with the chosen selector from possibleValues + * multiSelect: enables selecting multiple values from possibleValues, any of witch has to match the objects data + * numberCompare: gives the option to compare numerical values, either with =, < or the > operator + * @param {Boolean} possibleValues - predetermined values to choose from. needed for select and multiSelect, can be used in text filters + * @param {Boolean} valIsArray - if the objects data is an object use this. the filter will check each property in the object (not recursive). if no match is found, the object will be hidden + */ + addSpellFilter(category, label, path, type, possibleValues = null, valIsArray = false) { + this.addFilter('spell', category, label, path, type, possibleValues, valIsArray); + } + + /** + * Used to add custom filters to the Spell-Browser + * @param {String} category - Title of the category + * @param {String} label - Title of the filter + * @param {String} path - path to the data that the filter uses. uses dotnotation. example: data.abilities.dex.value + * @param {String} type - type of filter + * possible filter: + * text: will give a textinput (or use a select if possibleValues has values) to compare with the data. will use objectData.indexOf(searchedText) to enable partial matching + * bool: will see if the data at the path exists and not false. + * select: exactly matches the data with the chosen selector from possibleValues + * multiSelect: enables selecting multiple values from possibleValues, any of witch has to match the objects data + * numberCompare: gives the option to compare numerical values, either with =, < or the > operator + * @param {Boolean} possibleValues - predetermined values to choose from. needed for select and multiSelect, can be used in text filters + * @param {Boolean} valIsArray - if the objects data is an object use this. the filter will check each property in the object (not recursive). if no match is found, the object will be hidden + */ + addNpcFilter(category, label, path, type, possibleValues = null, valIsArray = false) { + this.addFilter('npc', category, label, path, type, possibleValues, valIsArray); + } +} + +Hooks.on('init', () => { + if (game.compendiumBrowser === undefined) { + game.compendiumBrowser = new SpellBrowser(); + } +}); + +//game.i18n.localize("FRIEND"); + +Hooks.on('ready', () => { + game.compendiumBrowser.addSpellFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.lvl"), 'data.level', 'multiSelect', [game.i18n.localize("CMPBrowser.cantip"), 1, 2, 3, 4, 5, 6, 7, 8, 9]); + game.compendiumBrowser.addSpellFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.school"), 'data.school', 'select', CONFIG.DND5E.spellSchools); + game.compendiumBrowser.addSpellFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.castingTime"), 'data.activation.type', 'select', + { + action: game.i18n.localize("DND5E.Action"), + bonus: game.i18n.localize("CMPBrowser.bonusAction"), + reaction: game.i18n.localize("CMPBrowser.reaction"), + minute: game.i18n.localize("DND5E.TimeMinute"), + hour: game.i18n.localize("DND5E.TimeHour"), + day: game.i18n.localize("DND5E.TimeDay") + }); + game.compendiumBrowser.addSpellFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.spellType"), 'data.actionType', 'select', CONFIG.DND5E.itemActionTypes); + game.compendiumBrowser.addSpellFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.damageType"), 'damageTypes', 'select', CONFIG.DND5E.damageTypes); + game.compendiumBrowser.addSpellFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.class"), 'data.classes', 'select', + { + bard: game.i18n.localize("CMPBrowser.bard"), + cleric: game.i18n.localize("CMPBrowser.cleric"), + druid: game.i18n.localize("CMPBrowser.druid"), + paladin: game.i18n.localize("CMPBrowser.paladin"), + ranger: game.i18n.localize("CMPBrowser.ranger"), + sorcerer: game.i18n.localize("CMPBrowser.sorcerer"), + warlock: game.i18n.localize("CMPBrowser.warlock"), + wizard: game.i18n.localize("CMPBrowser.wizard"), + }, true); + + game.compendiumBrowser.addSpellFilter(game.i18n.localize("CMPBrowser.components"), game.i18n.localize("CMPBrowser.ritual"), 'data.components.ritual', 'bool'); + game.compendiumBrowser.addSpellFilter(game.i18n.localize("CMPBrowser.components"), game.i18n.localize("CMPBrowser.concentration"), 'data.components.concentration', 'bool'); + game.compendiumBrowser.addSpellFilter(game.i18n.localize("CMPBrowser.components"), game.i18n.localize("CMPBrowser.verbal"), 'data.components.vocal', 'bool'); + game.compendiumBrowser.addSpellFilter(game.i18n.localize("CMPBrowser.components"), game.i18n.localize("CMPBrowser.somatic"), 'data.components.somatic', 'bool'); + game.compendiumBrowser.addSpellFilter(game.i18n.localize("CMPBrowser.components"), game.i18n.localize("CMPBrowser.material"), 'data.components.material', 'bool'); + + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.hasSpells"), 'data.traits.size', 'select', CONFIG.DND5E.actorSizes); + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.ritual"), 'hasSpells', 'bool'); + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.hasLegAct"), 'data.resources.legact.max', 'bool'); + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.hasLegRes"), 'data.resources.legres.max', 'bool'); + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.cr"), 'data.details.cr', 'numberCompare'); + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.creatureType"), 'data.details.type', 'text', { + aberration: game.i18n.localize("CMPBrowser.aberration"), + beast: game.i18n.localize("CMPBrowser.beast"), + celestial: game.i18n.localize("CMPBrowser.celestial"), + construct: game.i18n.localize("CMPBrowser.construct"), + dragon: game.i18n.localize("CMPBrowser.dragon"), + elemental: game.i18n.localize("CMPBrowser.elemental"), + fey: game.i18n.localize("CMPBrowser.fey"), + fiend: game.i18n.localize("CMPBrowser.fiend"), + giant: game.i18n.localize("CMPBrowser.giant"), + humanoid: game.i18n.localize("CMPBrowser.humanoid"), + monstrosity: game.i18n.localize("CMPBrowser.monstrosity"), + ooze: game.i18n.localize("CMPBrowser.ooze"), + plant: game.i18n.localize("CMPBrowser.plant"), + undead: game.i18n.localize("CMPBrowser.undead") + }); + + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.abilities"), game.i18n.localize("DND5E.AbilityStr"), 'data.abilities.str.value', 'numberCompare'); + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.abilities"), game.i18n.localize("DND5E.AbilityDex"), 'data.abilities.dex.value', 'numberCompare'); + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.abilities"), game.i18n.localize("DND5E.AbilityCon"), 'data.abilities.con.value', 'numberCompare'); + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.abilities"), game.i18n.localize("DND5E.AbilityInt"), 'data.abilities.int.value', 'numberCompare'); + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.abilities"), game.i18n.localize("DND5E.AbilityWis"), 'data.abilities.wis.value', 'numberCompare'); + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.abilities"), game.i18n.localize("DND5E.AbilityCha"), 'data.abilities.cha.value', 'numberCompare'); + + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.dmgInteraction"), game.i18n.localize("DND5E.DamImm"), 'data.traits.di.value', 'multiSelect', CONFIG.DND5E.damageTypes, true); + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.dmgInteraction"), game.i18n.localize("DND5E.DamRes"), 'data.traits.dr.value', 'multiSelect', CONFIG.DND5E.damageTypes, true); + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.dmgInteraction"), game.i18n.localize("DND5E.DamVuln"), 'data.traits.dv.value', 'multiSelect', CONFIG.DND5E.damageTypes, true); + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.dmgInteraction"), game.i18n.localize("DND5E.ConImm"), 'data.traits.ci.value', 'multiSelect', CONFIG.DND5E.conditionTypes, true); + game.compendiumBrowser.addNpcFilter(game.i18n.localize("CMPBrowser.dmgInteraction"), game.i18n.localize("CMPBrowser.dmgDealt"), 'damageDealt', 'multiSelect', CONFIG.DND5E.damageTypes, true); +}) + + +spellClassList = { + "abidalzimshorridwilting": "sorcerer,wizard", + "absorbelements": "druid,ranger,sorcerer,wizard", + "aganazzarsscorcher": "sorcerer,wizard", + "beastbond": "druid,ranger", + "bonesoftheearth": "druid", + "catapult": "sorcerer,wizard", + "catnap": "bard,sorcerer,wizard", + "causefear": "warlock,wizard", + "ceremony": "cleric,paladin", + "chaosbolt": "sorcerer", + "charmmonster": "bard,druid,sorcerer,warlock,wizard", + "controlflames": "druid,sorcerer,wizard", + "controlwinds": "druid,sorcerer,wizard", + "createbonfire": "druid,sorcerer,warlock,wizard", + "createhomunculus": "wizard", + "crownofstars": "sorcerer,warlock,wizard", + "dansemacabre": "warlock,wizard", + "dawn": "cleric,wizard", + "dragonsbreath": "sorcerer,wizard", + "druidgrove": "druid", + "dustdevil": "druid,sorcerer,wizard", + "earthtremor": "bard,druid,sorcerer,wizard", + "earthbind": "druid,sorcerer,warlock,wizard", + "elementalbane": "druid,warlock,wizard", + "enemiesabound": "bard,sorcerer,warlock,wizard", + "enervation": "sorcerer,warlock,wizard", + "eruptingearth": "druid,sorcerer,wizard", + "farstep": "sorcerer,warlock,wizard", + "findgreatersteed": "paladin", + "flamearrows": "druid,ranger,sorcerer,wizard", + "frostbite": "druid,sorcerer,warlock,wizard", + "guardianofnature": "druid,ranger", + "gust": "druid,sorcerer,wizard", + "healingspirit": "druid,ranger", + "holyweapon": "cleric,paladin", + "iceknife": "druid,sorcerer,wizard", + "illusorydragon": "wizard", + "immolation": "sorcerer,wizard", + "infernalcalling": "warlock,wizard", + "infestation": "druid,sorcerer,warlock,wizard", + "investitureofflame": "druid,sorcerer,warlock,wizard", + "investitureofice": "druid,sorcerer,warlock,wizard", + "investitureofstone": "druid,sorcerer,warlock,wizard", + "investitureofwind": "druid,sorcerer,warlock,wizard", + "invulnerability": "wizard", + "lifetransference": "cleric,wizard", + "maddeningdarkness": "warlock,wizard", + "maelstrom": "druid", + "magicstone": "druid,warlock", + "masspolymorph": "bard,sorcerer,wizard", + "maximiliansearthengrasp": "sorcerer,wizard", + "melfsminutemeteors": "sorcerer,wizard", + "mentalprison": "sorcerer,warlock,wizard", + "mightyfortress": "wizard", + "mindspike": "sorcerer,warlock,wizard", + "moldearth": "druid,sorcerer,wizard", + "negativeenergyflood": "warlock,wizard", + "powerwordpain": "sorcerer,warlock,wizard", + "primalsavagery": "druid", + "primordialward": "druid", + "psychicscream": "bard,sorcerer,warlock,wizard", + "pyrotechnics": "bard,sorcerer,wizard", + "scatter": "sorcerer,warlock,wizard", + "shadowblade": "sorcerer,warlock,wizard", + "shadowofmoil": "warlock", + "shapewater": "druid,sorcerer,wizard", + "sickeningradiance": "sorcerer,warlock,wizard", + "skillempowerment": "bard,sorcerer,wizard", + "skywrite": "bard,druid,wizard", + "snare": "druid,ranger,wizard", + "snillocssnowballswarm": "sorcerer,wizard", + "soulcage": "warlock,wizard", + "steelwindstrike": "ranger,wizard", + "stormsphere": "sorcerer,wizard", + "summongreaterdemon": "warlock,wizard", + "summonlesserdemons": "warlock,wizard", + "synapticstatic": "bard,sorcerer,warlock,wizard", + "templeofthegods": "cleric", + "tenserstransformation": "wizard", + "thunderstep": "sorcerer,warlock,wizard", + "thunderclap": "bard,druid,sorcerer,warlock,wizard", + "tidalwave": "druid,sorcerer,wizard", + "tinyservant": "wizard", + "tollthedead": "cleric,warlock,wizard", + "transmuterock": "druid,wizard", + "vitriolicsphere": "sorcerer,wizard", + "walloflight": "sorcerer,warlock,wizard", + "wallofsand": "wizard", + "wallofwater": "druid,sorcerer,wizard", + "wardingwind": "bard,druid,sorcerer,wizard", + "waterysphere": "druid,sorcerer,wizard", + "whirlwind": "druid,sorcerer,wizard", + "wordofradiance": "cleric", + "wrathofnature": "druid,ranger", + "zephyrstrike": "ranger", + "boomingblade": "sorcerer,warlock,wizard", + "greenflameblade": "sorcerer,warlock,wizard", + "lightninglure": "sorcerer,warlock,wizard", + "swordburst": "sorcerer,warlock,wizard", + "arcaneweapon": "artificerrevisited", + "acidsplash": "sorcerer,wizard,artificerrevisited", + "aid": "cleric,paladin,artificer,artificerrevisited", + "alarm": "ranger,wizard,artificer,artificerrevisited", + "alterself": "sorcerer,wizard,artificer,artificerrevisited", + "animalfriendship": "bard,druid,ranger", + "animalmessenger": "bard,druid,ranger", + "animalshapes": "druid", + "animatedead": "cleric,wizard", + "animateobjects": "bard,sorcerer,wizard,artificerrevisited", + "antilifeshell": "druid", + "antimagicfield": "cleric,wizard", + "antipathysympathy": "druid,wizard", + "arcaneeye": "wizard,artificer,artificerrevisited", + "arcanegate": "sorcerer,warlock,wizard", + "arcanelock": "wizard,artificer,artificerrevisited", + "armorofagathys": "warlock", + "armsofhadar": "warlock", + "astralprojection": "cleric,warlock,wizard", + "augury": "cleric", + "auraoflife": "paladin", + "auraofpurity": "paladin", + "auraofvitality": "paladin", + "awaken": "bard,druid", + "bane": "bard,cleric", + "banishingsmite": "paladin", + "banishment": "cleric,paladin,sorcerer,warlock,wizard", + "barkskin": "druid,ranger", + "beaconofhope": "cleric", + "beastsense": "druid,ranger", + "bestowcurse": "bard,cleric,wizard", + "bigbyshand": "wizard,artificerrevisited", + "arcanehand": "wizard,artificerrevisited", + "bladebarrier": "cleric", + "bladeward": "bard,sorcerer,warlock,wizard", + "bless": "cleric,paladin", + "blight": "druid,sorcerer,warlock,wizard", + "blindingsmite": "paladin", + "blindnessdeafness": "bard,cleric,sorcerer,wizard", + "blink": "sorcerer,wizard,artificer,artificerrevisited", + "blur": "sorcerer,wizard,artificer,artificerrevisited", + "brandingsmite": "paladin", + "burninghands": "sorcerer,wizard", + "calllightning": "druid", + "calmemotions": "bard,cleric", + "chainlightning": "sorcerer,wizard", + "charmperson": "bard,druid,sorcerer,warlock,wizard", + "chilltouch": "sorcerer,warlock,wizard", + "chromaticorb": "sorcerer,wizard", + "circleofdeath": "sorcerer,warlock,wizard", + "circleofpower": "paladin", + "clairvoyance": "bard,cleric,sorcerer,wizard", + "clone": "wizard", + "cloudofdaggers": "bard,sorcerer,warlock,wizard", + "cloudkill": "sorcerer,wizard", + "colorspray": "sorcerer,wizard", + "command": "cleric,paladin", + "commune": "cleric", + "communewithnature": "druid,ranger", + "compelledduel": "paladin", + "comprehendlanguages": "bard,sorcerer,warlock,wizard", + "compulsion": "bard", + "coneofcold": "sorcerer,wizard", + "confusion": "bard,druid,sorcerer,wizard", + "conjureanimals": "druid,ranger", + "conjurebarrage": "ranger", + "conjurecelestial": "cleric", + "conjureelemental": "druid,wizard", + "conjurefey": "druid,warlock", + "conjureminorelementals": "druid,wizard", + "conjurevolley": "ranger", + "conjurewoodlandbeings": "druid,ranger", + "contactotherplane": "warlock,wizard", + "contagion": "cleric,druid", + "contingency": "wizard", + "continualflame": "cleric,wizard,artificer,artificerrevisited", + "controlwater": "cleric,druid,wizard", + "controlweather": "cleric,druid,wizard", + "cordonofarrows": "ranger", + "counterspell": "sorcerer,warlock,wizard", + "createfoodandwater": "cleric,paladin", + "createundead": "cleric,warlock,wizard", + "createordestroywater": "cleric,druid", + "creation": "sorcerer,wizard,artificerrevisited", + "crownofmadness": "bard,sorcerer,warlock,wizard", + "crusadersmantle": "paladin", + "curewounds": "bard,cleric,druid,paladin,ranger,artificer,artificerrevisited", + "dancinglights": "bard,sorcerer,wizard,artificerrevisited", + "darkness": "sorcerer,warlock,wizard", + "darkvision": "druid,ranger,sorcerer,wizard,artificer,artificerrevisited", + "daylight": "cleric,druid,paladin,ranger,sorcerer", + "deathward": "cleric,paladin,artificer", + "delayedblastfireball": "sorcerer,wizard", + "demiplane": "warlock,wizard", + "destructivewave": "paladin", + "detectevilandgood": "cleric,paladin", + "detectmagic": "bard,cleric,druid,paladin,ranger,sorcerer,wizard,artificerrevisited", + "detectpoisonanddisease": "cleric,druid,paladin,ranger", + "detectthoughts": "bard,sorcerer,wizard", + "dimensiondoor": "bard,sorcerer,warlock,wizard", + "disguiseself": "bard,sorcerer,wizard,artificer,artificerrevisited", + "disintegrate": "sorcerer,wizard", + "dispelevilandgood": "cleric,paladin", + "dispelmagic": "bard,cleric,druid,paladin,sorcerer,warlock,wizard,artificerrevisited", + "dissonantwhispers": "bard", + "divination": "cleric", + "divinefavor": "paladin", + "divineword": "cleric", + "dominatebeast": "druid,sorcerer", + "dominatemonster": "bard,sorcerer,warlock,wizard", + "dominateperson": "bard,sorcerer,wizard", + "drawmijsinstantsummons": "wizard", + "instantsummons": "wizard", + "dream": "bard,warlock,wizard", + "druidcraft": "druid", + "earthquake": "cleric,druid,sorcerer", + "eldritchblast": "warlock", + "elementalweapon": "paladin,artificerrevisited", + "enhanceability": "bard,cleric,druid,sorcerer,artificer,artificerrevisited", + "enlargereduce": "sorcerer,wizard,artificer,artificerrevisited", + "ensnaringstrike": "ranger", + "entangle": "druid", + "enthrall": "bard,warlock", + "etherealness": "bard,cleric,sorcerer,warlock,wizard", + "evardsblacktentacles": "wizard", + "blacktentacles": "wizard", + "expeditiousretreat": "sorcerer,warlock,wizard,artificer,artificerrevisited", + "eyebite": "bard,sorcerer,warlock,wizard", + "fabricate": "wizard,artificer,artificerrevisited", + "faeriefire": "bard,druid", + "falselife": "sorcerer,wizard,artificer,artificerrevisited", + "fear": "bard,sorcerer,warlock,wizard", + "featherfall": "bard,sorcerer,wizard", + "feeblemind": "bard,druid,warlock,wizard", + "feigndeath": "bard,cleric,druid,wizard", + "findfamiliar": "wizard", + "findsteed": "paladin", + "findtraps": "cleric,druid,ranger", + "findthepath": "bard,cleric,druid", + "fingerofdeath": "sorcerer,warlock,wizard", + "firebolt": "sorcerer,wizard,artificerrevisited", + "fireshield": "wizard", + "firestorm": "cleric,druid,sorcerer", + "fireball": "sorcerer,wizard", + "flameblade": "druid", + "flamestrike": "cleric", + "flamingsphere": "druid,wizard", + "fleshtostone": "warlock,wizard", + "fly": "sorcerer,warlock,wizard,artificer,artificerrevisited", + "fogcloud": "druid,ranger,sorcerer,wizard", + "forbiddance": "cleric", + "forcecage": "bard,warlock,wizard", + "foresight": "bard,druid,warlock,wizard", + "freedomofmovement": "bard,cleric,druid,ranger,artificer,artificerrevisited", + "friends": "bard,sorcerer,warlock,wizard", + "gaseousform": "sorcerer,warlock,wizard,artificer,artificerrevisited", + "gate": "cleric,sorcerer,wizard", + "geas": "bard,cleric,druid,paladin,wizard", + "gentlerepose": "cleric,wizard", + "giantinsect": "druid", + "glibness": "bard,warlock", + "globeofinvulnerability": "sorcerer,wizard", + "glyphofwarding": "bard,cleric,wizard,artificer,artificerrevisited", + "goodberry": "druid,ranger", + "graspingvine": "druid,ranger", + "grease": "wizard,artificerrevisited", + "greaterinvisibility": "bard,sorcerer,wizard", + "greaterrestoration": "bard,cleric,druid,artificerrevisited", + "guardianoffaith": "cleric", + "guardsandwards": "bard,wizard", + "guidance": "cleric,druid,artificerrevisited", + "guidingbolt": "cleric", + "gustofwind": "druid,sorcerer,wizard", + "hailofthorns": "ranger", + "hallow": "cleric", + "hallucinatoryterrain": "bard,druid,warlock,wizard", + "harm": "cleric", + "haste": "sorcerer,wizard,artificer,artificerrevisited", + "heal": "cleric,druid", + "healingword": "bard,cleric,druid", + "heatmetal": "bard,druid,artificerrevisited", + "hellishrebuke": "warlock", + "heroesfeast": "cleric,druid", + "heroism": "bard,paladin", + "hex": "warlock", + "holdmonster": "bard,sorcerer,warlock,wizard", + "holdperson": "bard,cleric,druid,sorcerer,warlock,wizard", + "holyaura": "cleric", + "hungerofhadar": "warlock", + "huntersmark": "ranger", + "hypnoticpattern": "bard,sorcerer,warlock,wizard", + "icestorm": "druid,sorcerer,wizard", + "identify": "bard,wizard,artificerrevisited", + "illusoryscript": "bard,warlock,wizard", + "imprisonment": "warlock,wizard", + "incendiarycloud": "sorcerer,wizard", + "inflictwounds": "cleric", + "insectplague": "cleric,druid,sorcerer", + "invisibility": "bard,sorcerer,warlock,wizard,artificer,artificerrevisited", + "jump": "druid,ranger,sorcerer,wizard,artificer,artificerrevisited", + "knock": "bard,sorcerer,wizard", + "legendlore": "bard,cleric,wizard", + "leomundssecretchest": "wizard,artificer,artificerrevisited", + "leomundstinyhut": "bard,wizard", + "lesserrestoration": "bard,cleric,druid,paladin,ranger,artificer,artificerrevisited", + "levitate": "sorcerer,wizard,artificerrevisited", + "light": "bard,cleric,sorcerer,wizard,artificerrevisited", + "lightningarrow": "ranger", + "lightningbolt": "sorcerer,wizard", + "locateanimalsorplants": "bard,druid,ranger", + "locatecreature": "bard,cleric,druid,paladin,ranger,wizard", + "locateobject": "bard,cleric,druid,paladin,ranger,wizard", + "longstrider": "bard,druid,ranger,wizard,artificer,artificerrevisited", + "magearmor": "sorcerer,wizard", + "magehand": "bard,sorcerer,warlock,wizard,artificerrevisited", + "magiccircle": "cleric,paladin,warlock,wizard", + "magicjar": "wizard", + "magicmissile": "sorcerer,wizard", + "magicmouth": "bard,wizard,artificerrevisited", + "magicweapon": "paladin,wizard,artificer,artificerrevisited", + "majorimage": "bard,sorcerer,warlock,wizard", + "masscurewounds": "bard,cleric,druid", + "massheal": "cleric", + "masshealingword": "cleric", + "masssuggestion": "bard,sorcerer,warlock,wizard", + "maze": "wizard", + "meldintostone": "cleric,druid", + "melfsacidarrow": "wizard", + "acidarrow": "wizard", + "mending": "bard,cleric,druid,sorcerer,wizard,artificerrevisited", + "message": "bard,sorcerer,wizard,artificerrevisited", + "meteorswarm": "sorcerer,wizard", + "mindblank": "bard,wizard", + "minorillusion": "bard,sorcerer,warlock,wizard", + "miragearcane": "bard,druid,wizard", + "mirrorimage": "sorcerer,warlock,wizard", + "mislead": "bard,wizard", + "mistystep": "sorcerer,warlock,wizard", + "modifymemory": "bard,wizard", + "moonbeam": "druid", + "mordenkainensfaithfulhound": "wizard,artificer,artificerrevisited", + "faithfulhound": "wizard,artificer,artificerrevisited", + "mordenkainensmagnificentmansion": "bard,wizard", + "magnificentmansion": "bard,wizard", + "mordenkainensprivatesanctum": "wizard,artificer,artificerrevisited", + "mordenkainenssword": "bard,wizard", + "arcanesword": "bard,wizard", + "moveearth": "druid,sorcerer,wizard", + "nondetection": "bard,ranger,wizard", + "nystulsmagicaura": "wizard", + "arcanistsmagicaura": "wizard", + "otilukesfreezingsphere": "wizard", + "otilukesresilientsphere": "wizard,artificer,artificerrevisited", + "ottosirresistibledance": "bard,wizard", + "passwithouttrace": "druid,ranger", + "passwall": "wizard", + "phantasmalforce": "bard,sorcerer,wizard", + "phantasmalkiller": "wizard", + "phantomsteed": "wizard", + "planarally": "cleric", + "planarbinding": "bard,cleric,druid,wizard", + "planeshift": "cleric,druid,sorcerer,warlock,wizard", + "plantgrowth": "bard,druid,ranger", + "poisonspray": "druid,sorcerer,warlock,wizard,artificerrevisited", + "polymorph": "bard,druid,sorcerer,wizard", + "powerwordheal": "bard", + "powerwordkill": "bard,sorcerer,warlock,wizard", + "powerwordstun": "bard,sorcerer,warlock,wizard", + "prayerofhealing": "cleric", + "prestidigitation": "bard,sorcerer,warlock,wizard,artificerrevisited", + "prismaticspray": "sorcerer,wizard", + "prismaticwall": "wizard", + "produceflame": "druid", + "programmedillusion": "bard,wizard", + "projectimage": "bard,wizard", + "protectionfromenergy": "cleric,druid,ranger,sorcerer,wizard,artificer,artificerrevisited", + "protectionfromevilandgood": "cleric,paladin,warlock,wizard", + "protectionfrompoison": "cleric,druid,paladin,ranger,artificer,artificerrevisited", + "purifyfoodanddrink": "cleric,druid,paladin", + "raisedead": "bard,cleric,paladin", + "rarystelepathicbond": "wizard", + "rayofenfeeblement": "warlock,wizard", + "rayoffrost": "sorcerer,wizard,artificerrevisited", + "rayofsickness": "sorcerer,wizard", + "regenerate": "bard,cleric,druid", + "reincarnate": "druid", + "removecurse": "cleric,paladin,warlock,wizard", + "resistance": "cleric,druid,artificerrevisited", + "resurrection": "bard,cleric", + "reversegravity": "druid,sorcerer,wizard", + "revivify": "cleric,paladin,artificer,artificerrevisited", + "ropetrick": "wizard,artificer,artificerrevisited", + "sacredflame": "cleric", + "sanctuary": "cleric,artificer,artificerrevisited", + "scorchingray": "sorcerer,wizard", + "scrying": "bard,cleric,druid,warlock,wizard", + "searingsmite": "paladin", + "seeinvisibility": "bard,sorcerer,wizard,artificerrevisited", + "seeming": "bard,sorcerer,wizard", + "sending": "bard,cleric,wizard", + "sequester": "wizard", + "shapechange": "druid,wizard", + "shatter": "bard,sorcerer,warlock,wizard", + "shield": "sorcerer,wizard", + "shieldoffaith": "cleric,paladin,artificer,artificerrevisited", + "shillelagh": "druid", + "shockinggrasp": "sorcerer,wizard,artificerrevisited", + "silence": "bard,cleric,ranger", + "silentimage": "bard,sorcerer,wizard", + "simulacrum": "wizard", + "sleep": "bard,sorcerer,wizard", + "sleetstorm": "druid,sorcerer,wizard", + "slow": "sorcerer,wizard", + "sparethedying": "cleric,artificerrevisited", + "speakwithanimals": "bard,druid,ranger", + "speakwithdead": "bard,cleric", + "speakwithplants": "bard,druid,ranger", + "spiderclimb": "sorcerer,warlock,wizard,artificer,artificerrevisited", + "spikegrowth": "druid,ranger", + "spiritguardians": "cleric", + "spiritualweapon": "cleric", + "staggeringsmite": "paladin", + "stinkingcloud": "bard,sorcerer,wizard", + "stoneshape": "cleric,druid,wizard,artificer,artificerrevisited", + "stoneskin": "druid,ranger,sorcerer,wizard,artificer,artificerrevisited", + "stormofvengeance": "druid", + "suggestion": "bard,sorcerer,warlock,wizard", + "sunbeam": "druid,sorcerer,wizard", + "sunburst": "druid,sorcerer,wizard", + "swiftquiver": "ranger", + "symbol": "bard,cleric,wizard", + "tashashideouslaughter": "bard,wizard", + "hideouslaughter": "bard,wizard", + "telekinesis": "sorcerer,wizard", + "telepathy": "wizard", + "teleport": "bard,sorcerer,wizard", + "teleportationcircle": "bard,sorcerer,wizard", + "tensersfloatingdisk": "wizard", + "tensersfloatingdisc": "wizard", + "floatingdisc": "wizard", + "thaumaturgy": "cleric", + "thornwhip": "druid,artificerrevisited", + "thunderoussmite": "paladin", + "thunderwave": "bard,druid,sorcerer,wizard", + "timestop": "sorcerer,wizard", + "tongues": "bard,cleric,sorcerer,warlock,wizard", + "transportviaplants": "druid", + "treestride": "druid,ranger", + "truepolymorph": "bard,warlock,wizard", + "trueresurrection": "cleric,druid", + "trueseeing": "bard,cleric,sorcerer,warlock,wizard", + "truestrike": "bard,sorcerer,warlock,wizard", + "tsunami": "druid", + "unseenservant": "bard,warlock,wizard", + "vampirictouch": "warlock,wizard", + "viciousmockery": "bard", + "walloffire": "druid,sorcerer,wizard", + "wallofforce": "wizard", + "wallofice": "wizard", + "wallofstone": "druid,sorcerer,wizard,artificerrevisited", + "wallofthorns": "druid", + "wardingbond": "cleric", + "waterbreathing": "druid,ranger,sorcerer,wizard,artificer,artificerrevisited", + "waterwalk": "cleric,druid,ranger,sorcerer,artificer,artificerrevisited", + "web": "sorcerer,wizard", + "weird": "wizard", + "windwalk": "druid", + "windwall": "druid,ranger", + "wish": "sorcerer,wizard", + "witchbolt": "sorcerer,warlock,wizard", + "wordofrecall": "cleric", + "wrathfulsmite": "paladin", + "zoneoftruth": "bard,cleric,paladin" +} \ No newline at end of file diff --git a/compendium-browser.less b/compendium-browser.less new file mode 100644 index 0000000..1d9a9b5 --- /dev/null +++ b/compendium-browser.less @@ -0,0 +1,236 @@ +/* core foundry changes +#compendium.flexcolumn.active { + display: flex; + flex-direction: column; +} +#compendium.flexcolumn > .directory-list { + width: 100%; + height: auto; + flex-basis: 0; + flex-grow: 1; +} +#compendium.flexcolumn > .directory-footer { + height: auto; +} +#compendium.flexcolumn > .directory-footer > * { + margin-top: 5px; +} +*/ +#compendium .directory-footer .compendium-browser-btn { + margin-top:5px; +} + +#compendium .directory-footer { + display:block; +} + +/* spellbrowser */ +/* resizing */ +.compendium-browser { + max-width:1100px; + max-height:90vh; + + .tabs { + max-height:2em; + border-bottom: solid #782e22; + a { + } + } + .tabContainer { + height:calc(100% - 2em); + .tab { + width: 100%; + height: 100%; + overflow:scroll; + } + } + + .control-area { + position:sticky; + display: block; + min-width: 250px; + width: 300px; + height:100%; + padding-right:5px; + overflow:scroll; + + .filtercontainer { + border: 1px solid #bbb; + border-radius: 5px; + margin-top: 5px; + padding:2px; + + h3 { + margin:0; + cursor:pointer; + } + + dl, div { + margin: 5px 0; + } + + dt { + display:inline-block; + width:40%; + padding-left:5px; + } + + dd { + display:inline-block; + width:58%; + margin-left:0; + + select { + width:100%; + } + } + .multiselect { + border: 1px solid #bbb; + border-radius: 3px; + + label { + padding:5px; + } + } + .small-input { + width: 144px; + height: 27px; + background: rgba(255, 255, 255, 0.8); + border: 1px solid #444; + border-radius: 3px; + padding: 0 3px; + text-overflow: ellipsis; + } + .small-select { + width: 40px; + } + } + } + + .browser { + + .window-content { + overflow-y: hidden!important; + } + + ul { + float:right; + display: block; + min-width: 335px; + width: 785px; + margin:0; + height: 100%; + overflow:auto; + padding-left: 5px; + + .filter-tags { + display:none; + } + + } + + .spacer { + display:inline-block; + min-width:5px; + } + .spacer-large { + display:inline-block; + min-width:15px; + } + } + + .spell-browser { + .spell { + cursor:default; + vertical-align: middle; + line-height:32px; + margin:2px 0; + + .spell-image { + max-width:32px; + height:32px; + } + .spell-name { + height:32px; + padding-left:5px; + } + .spell-level { + text-align:center; + font-weight:900; + max-width:18px; + height:32px; + } + .spell-tags { + text-align:right; + margin-right:3px; + font-weight:900; + max-width:100px; + height:32px; + } + } + } + + .npc-browser { + .npc { + cursor:default; + vertical-align: middle; + line-height:64px; + margin:4px 0; + .npc-image { + max-width: 64px; + height: 64px; + } + .npc-image img { + width: 64px; + height: 64px; + border: none; + } + + .npc-line { + line-height: 25px; + padding: 9px 0 5px 5px; + } + .npc-name { + font-weight:bold; + font-size:16px; + } + .cr { + display: inline-block; + width: 55px; + } + .size { + display: inline-block; + width: 75px; + } + .type { + display: inline-block; + } + } + } + + .settings { + .settings-group { + border: 1px solid #bbb; + border-radius: 5px; + margin-top: 5px; + padding:2px; + + h3 { + margin:0; + cursor:pointer; + } + + label { + display:block; + } + input { + + } + h4 { + display:inline-block; + vertical-align: middle; + height:100%; + } + } + } +} diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 0000000..29cb339 --- /dev/null +++ b/lang/en.json @@ -0,0 +1,55 @@ +{ + "CMPBrowser.sortBy":"Sort by", + "CMPBrowser.cr":"Challenge Rating", + "CMPBrowser.generalSettings":"General Settings", + "CMPBrowser.allowSpellAcc":"Allow Players Access to the spell browser", + "CMPBrowser.allowNpcAcc":"Allow Players Access to the npc browser", + "CMPBrowser.compSettings":"Spell Compendium Settings", + "CMPBrowser.load":"Load", + "CMPBrowser.lvl":"Level", + "CMPBrowser.ritual":"Ritual", + "CMPBrowser.concentration":"Concentration", + "CMPBrowser.verbal":"Verbal", + "CMPBrowser.somatic":"Somatic", + "CMPBrowser.material":"Material", + "CMPBrowser.cantip":"Cantip", + "CMPBrowser.school":"School", + "CMPBrowser.castingTime":"Casting Time", + "CMPBrowser.bonusAction":"Bonus Action", + "CMPBrowser.reaction":"Reaction", + "CMPBrowser.spellType":"Spell Type", + "CMPBrowser.damageType":"Damage Type", + "CMPBrowser.class":"Class", + "CMPBrowser.bard":"Bard", + "CMPBrowser.cleric":"Cleric", + "CMPBrowser.druid":"Druid", + "CMPBrowser.paladin":"Paladin", + "CMPBrowser.ranger":"Ranger", + "CMPBrowser.sorcerer":"Sorcerer", + "CMPBrowser.warlock":"Warlock", + "CMPBrowser.wizard":"Wizard", + "CMPBrowser.general":"General", + "CMPBrowser.components":"Components", + "CMPBrowser.hasSpells":"Has Spells", + "CMPBrowser.hasLegAct":"Has Legendary Actions", + "CMPBrowser.hasLegRes":"Has Legendary Resistance", + "CMPBrowser.creatureType":"Creature Type", + "CMPBrowser.aberration": "Aberration", + "CMPBrowser.beast": "Beast", + "CMPBrowser.celestial": "Celestial", + "CMPBrowser.construct": "construct", + "CMPBrowser.dragon": "Dragon", + "CMPBrowser.elemental": "Elemental", + "CMPBrowser.fey": "Fey", + "CMPBrowser.fiend": "Fiend", + "CMPBrowser.giant": "Giant", + "CMPBrowser.humanoid": "Humanoid", + "CMPBrowser.monstrosity": "Monstrosity", + "CMPBrowser.ooze": "Ooze", + "CMPBrowser.plant": "Plant", + "CMPBrowser.undead": "Undead", + "CMPBrowser.abilities": "Abilities", + "CMPBrowser.dmgInteraction": "Damage Interaction", + "CMPBrowser.dmgDealt": "Damage Dealt", + "CMPBrowser.cmpBrowser": "Compendium Browser" +} \ No newline at end of file diff --git a/module.json b/module.json new file mode 100644 index 0000000..89a3bd3 --- /dev/null +++ b/module.json @@ -0,0 +1,22 @@ +{ + "name": "compendium-browser", + "title": "Compendium Browser", + "description": "A module to easily browse and filter spells as well as npcs loaded from compendie.", + "version": "0.1", + "author": "Felix#6196", + "systems": ["dnd5e"], + "scripts": ["./compendium-browser.js"], + "styles": ["./compendium-browser.css"], + "packs": [], + "minimumCoreVersion": "0.4.3", + "languages": [ + { + "lang": "en", + "name": "English", + "path": "lang/en.json" + } + ], + "url": "https://github.com/syl3r86/compendium-browser", + "manifest": "https://raw.githubusercontent.com/syl3r86/compendium-browser/master/module.json", + "download": "https://github.com/syl3r86/compendium-browser/archive/master.zip" +} diff --git a/preview.jpg b/preview.jpg new file mode 100644 index 0000000..b745e2b Binary files /dev/null and b/preview.jpg differ diff --git a/template/filter-container.html b/template/filter-container.html new file mode 100644 index 0000000..4fc70d9 --- /dev/null +++ b/template/filter-container.html @@ -0,0 +1,78 @@ +{{#each filters.registeredFilterCategorys as |cat key|}} +
+

{{cat.label}}

+
+ {{#each cat.filters as |filter key|}} +
+ {{#if filter.istext}} +
{{filter.label}}
+
+ {{#if filter.possibleValues}} + + {{else}} + + {{/if}} +
+ {{/if}} + {{#if filter.isbool}} +
+
{{filter.label}}
+
+ +
+
+ {{/if}} + {{#if filter.isselect}} +
+
{{filter.label}}
+
+ +
+
+ {{/if}} + {{#if filter.ismultiSelect}} +
+ +
+ {{#each filter.possibleValues as |label val|}} + +
+
+ {{/each}} +
+
+ {{/if}} + {{#if filter.isnumberCompare}} +
+
{{filter.label}}
+
+ + +
+
+ {{/if}} +
+ {{/each}} +
+ +
+{{/each}} \ No newline at end of file diff --git a/template/npc-browser.html b/template/npc-browser.html new file mode 100644 index 0000000..8d19dd3 --- /dev/null +++ b/template/npc-browser.html @@ -0,0 +1,42 @@ +
+
+
+
+ +
+
+
{{localize "CMPBrowser.sortBy"}}:
+
+
+
+ {{> "modules/compendium-browser/template/filter-container.html" filters=npcFilters}} +
+ + +
\ No newline at end of file diff --git a/template/settings.html b/template/settings.html new file mode 100644 index 0000000..d2777fa --- /dev/null +++ b/template/settings.html @@ -0,0 +1,32 @@ +
+
+

{{localize "CMPBrowser.generalSettings"}}

+ + +
+ +
+

{{localize "CMPBrowser.compSettings"}}

+ {{#each settings.loadedSpellCompendium as |spellComp key|}} + + {{/each}} +
+
+

Npc Compendium Settings

+ {{#each settings.loadedNpcCompendium as |npcComp key|}} + + {{/each}} +
+
\ No newline at end of file diff --git a/template/spell-browser.html b/template/spell-browser.html new file mode 100644 index 0000000..3708598 --- /dev/null +++ b/template/spell-browser.html @@ -0,0 +1,43 @@ +
+
+
+
+ +
+
+
{{localize "CMPBrowser.sortBy"}}:
+
+
+
+ {{> "modules/compendium-browser/template/filter-container.html" filters=spellFilters}} +
+ + +
\ No newline at end of file diff --git a/template/template.html b/template/template.html new file mode 100644 index 0000000..6d56fe0 --- /dev/null +++ b/template/template.html @@ -0,0 +1,11 @@ +
+ {{#if showSpellBrowser}}Spell Browser{{/if}} + {{#if showNpcBrowser}}NPC Browser{{/if}} + {{#if isGM}}Settings{{/if}} +
+ +
+ {{#if showSpellBrowser}}{{> "modules/compendium-browser/template/spell-browser.html"}}{{/if}} + {{#if showNpcBrowser}} {{> "modules/compendium-browser/template/npc-browser.html"}}{{/if}} + {{#if isGM}} {{> "modules/compendium-browser/template/settings.html"}}{{/if}} +
\ No newline at end of file