2021-02-05 02:50:25 +00:00
/* eslint-disable valid-jsdoc */
/* eslint-disable complexity */
/ * *
2019-12-30 10:37:22 +00:00
* @ author Felix Müller aka syl3r86
2020-04-30 17:07:00 +00:00
* @ version 0.2 . 0
2019-12-29 16:54:57 +00:00
* /
2021-02-05 02:50:25 +00:00
/ * * @ a u t h o r J e f f r e y P u g h a k a @ s p e t z e l 2 0 2 0
2021-03-15 20:06:06 +00:00
* @ version 0.4
2021-02-05 02:50:25 +00:00
* /
/ *
4 - Feb - 2020 0.4 . 0 Switch to not pre - loading the indexes , and instead do that at browsing time , to reduce server load and memory usage
Refactor some of the eslint warnings
2021-02-08 17:13:31 +00:00
5 - Feb - 2021 Don ' t do memory allocation - just browse compendia in real - time
After this , next step would be incremental ( lazy ) loading
7 - Feb - 2021 0.4 . 1 Move load back to "ready" hook , but limit number loaded
8 - Feb - 2021 0.4 . 1 Bug fix : initialize ( ) was setting this . spells , not this . items so CB was using twice the memory ( once loaded incorrectly into this . spells
and once loaded on first getData ( ) into this . items )
2021-02-09 18:21:12 +00:00
0.4 . 1 b SpellBrowser - > CompendiumBrowser
2021-02-09 22:37:44 +00:00
9 - Feb - 2021 0.4 . 1 b Call loadAndFilterItems instead of loadItems ; filter as we go , limited by numToPreload
2021-02-10 05:53:05 +00:00
0.4 . 1 c Needed to pass specific spellFilters , itemFilters etc .
2021-02-12 00:40:18 +00:00
0.4 . 1 d : Fixed img observer on replaced spellData
2021-02-12 02:05:08 +00:00
11 - Feb - 2021 0.4 . 1 e : Don ' t save the filter data ( which is most of the memory ) and remove the preload limit ; instead just save the minimal amount of data
2021-02-12 04:47:43 +00:00
0.4 . 1 g : Generalize the spell list reload and confirm spells still working
2021-02-12 16:53:29 +00:00
0.4 . 1 h : Add the partials for npc , feat , item and the backing code
12 - Feb - 2021 0.4 . 1 j : Correct compactItem for feats and items required display items
Rename itemType - > browserTab to differentiate candidate item ' s type from the tab it appears on ( spell , feat / class , item , NPC )
2021-02-12 18:53:58 +00:00
Fixed : Was calling the wrong sort for feat and NPC
2021-02-12 20:14:37 +00:00
0.4 . 1 k : Don ' t call loadItems ( ) during initalize ; getData ( ) just displays static elements
0.4 . 1 l : Display progress indicator for loading - for now just a static one
2021-02-15 21:50:14 +00:00
15 - Feb - 2021 0.4 . 2 : Fix NPCs to use loadAndFilterNpcs
2021-02-15 22:14:45 +00:00
0.4 . 2 b : Add Loading ... message for NPCs
2021-02-16 00:47:48 +00:00
0.4 . 2 c : Correct Loading ... message on initial tab , but not on tab switch
2021-02-16 02:49:06 +00:00
0.4 . 2 d : Display the type of item being loaded
2021-03-12 01:32:07 +00:00
16 - Dec - 2021 0.4 . 2 f : Change preload to maxLoaded and display a message to filter if you want more
10 - Mar - 2021 0.4 . 3 : activateItemListListeners ( ) : Remove spurious li . parents ( wasn ' t being used anyway )
11 - Mar - 2021 0.4 . 3 Fixed : Reset Filters doesn ' t clear the on - screen filter fields ( because it is not completely re - rendering like it used to ) Issue # 4
Hack solution is to re - render whole dialog which unfortunately loses filter settings on other tabs as well
2021-03-15 20:06:06 +00:00
0.4 . 3 b : Clear all filters to match displayed
15 - Mar - 2021 0.4 . 5 : Fix : Spells from non - system compendium show up in items tab . Issue # 10
2021-03-15 20:45:45 +00:00
loadAndFilterItems ( ) : Changed tests to switch + more explicit tests
0.4 . 5 b Show compendium source in results issue # 11
Try showing compendium in the image mouseover
2021-06-13 02:59:08 +00:00
12 - Jun - 2021 0.5 . 0 Test for Foundry 0.8 . x in which creature type is now data . details . type . value
2021-09-07 02:34:03 +00:00
9 - Spt - 2021 CHANGES Removed functions that are disabled in Foundry 0.9 . 0
Speed up on spells by using queries
Stops already in progress searches if a new one is started
Handles monster types from older revisions
Uses some built - ins for minor performance improvement
2021-09-12 01:26:38 +00:00
12 - Sep - 2021 0.7 . 1 Issue # 25 Initialization fails because of corrupted settings
Fix : Check for settings . loadedSpellCompendium and settings . loadedNpcCompendium
2022-01-02 05:27:33 +00:00
1 - Jan - 2022 0.7 . 2 Switch to isFoundryV8Plus class variable
2022-01-05 01:39:05 +00:00
4 - Jan - 2022 0.7 . 2 Merge PR # 33 ( thanks kyleady ) to improve NPC filtering performance
0.7 . 2 c Fix rarity encoding ( uses camelcase names ) ( Issue # 28 )
Check for data . details ? . cr in case you have NPCs without details ( type = character )
Change message to "Loading..." until we ' re done , then "Loaded"
2021-02-05 02:50:25 +00:00
* /
2019-12-29 16:54:57 +00:00
2021-02-08 17:13:31 +00:00
const CMPBrowser = {
MODULE _NAME : "compendium-browser" ,
2022-01-02 05:27:33 +00:00
MODULE _VERSION : "0.7.2" ,
2021-02-17 04:39:39 +00:00
MAXLOAD : 500 , //Default for the maximum number to load before displaying a message that you need to filter to see more
2021-02-08 17:13:31 +00:00
}
2021-09-07 02:34:03 +00:00
const STOP _SEARCH = 'StopSearchException' ;
2021-02-09 18:21:12 +00:00
class CompendiumBrowser extends Application {
2021-02-12 18:53:58 +00:00
static get defaultOptions ( ) {
const options = super . defaultOptions ;
mergeObject ( options , {
title : "CMPBrowser.compendiumBrowser" ,
tabs : [ { navSelector : ".tabs" , contentSelector : ".content" , initial : "spell" } ] ,
classes : options . classes . concat ( 'compendium-browser' ) ,
template : "modules/compendium-browser/template/template.html" ,
width : 800 ,
height : 700 ,
resizable : true ,
minimizable : true
} ) ;
return options ;
}
2021-02-05 02:50:25 +00:00
async initialize ( ) {
2019-12-29 16:54:57 +00:00
// load settings
2020-04-30 17:07:00 +00:00
if ( this . settings === undefined ) {
this . initSettings ( ) ;
2021-02-08 17:13:31 +00:00
}
2021-02-17 04:39:39 +00:00
2020-04-30 17:07:00 +00:00
await loadTemplates ( [
"modules/compendium-browser/template/spell-browser.html" ,
2021-02-12 04:47:43 +00:00
"modules/compendium-browser/template/spell-browser-list.html" ,
2020-04-30 17:07:00 +00:00
"modules/compendium-browser/template/npc-browser.html" ,
2021-02-12 04:47:43 +00:00
"modules/compendium-browser/template/npc-browser-list.html" ,
2020-06-12 15:29:59 +00:00
"modules/compendium-browser/template/feat-browser.html" ,
2021-02-12 04:47:43 +00:00
"modules/compendium-browser/template/feat-browser-list.html" ,
2020-06-12 15:29:59 +00:00
"modules/compendium-browser/template/item-browser.html" ,
2021-02-12 04:47:43 +00:00
"modules/compendium-browser/template/item-browser-list.html" ,
2020-04-30 17:07:00 +00:00
"modules/compendium-browser/template/filter-container.html" ,
2021-02-16 00:47:48 +00:00
"modules/compendium-browser/template/settings.html" ,
"modules/compendium-browser/template/loading.html"
2020-04-30 17:07:00 +00:00
] ) ;
2019-12-29 16:54:57 +00:00
this . hookCompendiumList ( ) ;
2021-02-05 02:50:25 +00:00
//Reset the filters used in the dialog
2019-12-29 16:54:57 +00:00
this . spellFilters = {
registeredFilterCategorys : { } ,
activeFilters : { }
} ;
this . npcFilters = {
registeredFilterCategorys : { } ,
activeFilters : { }
} ;
2020-06-12 15:29:59 +00:00
this . featFilters = {
registeredFilterCategorys : { } ,
activeFilters : { }
} ;
this . itemFilters = {
registeredFilterCategorys : { } ,
activeFilters : { }
} ;
2019-12-29 16:54:57 +00:00
}
2020-06-12 15:29:59 +00:00
2021-02-12 18:53:58 +00:00
/** override */
_onChangeTab ( event , tabs , active ) {
super . _onChangeTab ( event , tabs , active ) ;
const html = this . element ;
2021-02-15 22:27:15 +00:00
this . replaceList ( html , active , { reload : false } )
2021-02-12 18:53:58 +00:00
}
2021-02-09 18:21:12 +00:00
2021-02-12 18:53:58 +00:00
/** override */
async getData ( ) {
2021-02-09 18:21:12 +00:00
2021-02-12 18:53:58 +00:00
//0.4.1 Filter as we load to support new way of filtering
//Previously loaded all data and filtered in place; now loads minimal (preload) amount, filtered as we go
//First time (when you press Compendium Browser button) is called with filters unset
2021-02-16 00:47:48 +00:00
2021-02-12 18:53:58 +00:00
//0.4.1k: Don't do any item/npc loading until tab is visible
let data = {
2021-02-16 00:47:48 +00:00
items : [ ] ,
npcs : [ ] ,
2021-02-12 18:53:58 +00:00
spellFilters : this . spellFilters ,
showSpellBrowser : ( game . user . isGM || this . settings . allowSpellBrowser ) ,
featFilters : this . featFilters ,
showFeatBrowser : ( game . user . isGM || this . settings . allowFeatBrowser ) ,
itemFilters : this . itemFilters ,
showItemBrowser : ( game . user . isGM || this . settings . allowItemBrowser ) ,
npcFilters : this . npcFilters ,
showNpcBrowser : ( game . user . isGM || this . settings . allowNpcBrowser ) ,
settings : this . settings ,
isGM : game . user . isGM
2020-06-12 15:29:59 +00:00
} ;
2021-02-12 18:53:58 +00:00
return data ;
}
2019-12-29 16:54:57 +00:00
2021-02-12 18:53:58 +00:00
activateItemListListeners ( html ) {
// 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 ) ;
2021-09-07 02:34:03 +00:00
pack . getDocument ( itemId ) . then ( entity => {
2021-02-12 18:53:58 +00:00
entity . sheet . render ( true ) ;
} ) ;
} ) ;
2019-12-29 16:54:57 +00:00
2021-02-12 18:53:58 +00:00
// make draggable
//0.4.1: Avoid the game.packs lookup
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 ( {
2022-01-02 05:27:33 +00:00
type : pack . documentName ,
2021-02-12 18:53:58 +00:00
pack : pack . collection ,
id : li . getAttribute ( "data-entry-id" )
} ) ) ;
} , false ) ;
} ) ;
}
2021-02-08 17:13:31 +00:00
2021-02-12 18:53:58 +00:00
/** override */
activateListeners ( html ) {
super . activateListeners ( html ) ;
2020-06-12 15:29:59 +00:00
2021-02-12 18:53:58 +00:00
this . observer = new IntersectionObserver ( ( entries , observer ) => {
for ( let e of entries ) {
if ( ! e . isIntersecting ) continue ;
const img = e . target ;
// Avatar image
//const img = li.querySelector("img");
if ( img && img . dataset . src ) {
img . src = img . dataset . src ;
delete img . dataset . src ;
}
2020-06-12 15:29:59 +00:00
2021-02-12 18:53:58 +00:00
// No longer observe the target
observer . unobserve ( e . target ) ;
}
} ) ;
2020-06-12 15:29:59 +00:00
2021-02-12 18:53:58 +00:00
this . activateItemListListeners ( html ) ;
2019-12-29 16:54:57 +00:00
// 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 ) ;
}
} ) ;
2021-02-12 02:05:08 +00:00
this . triggerSort ( html , "spell" ) ;
2019-12-29 16:54:57 +00:00
2021-02-09 18:21:12 +00:00
// sort feat list in place
2020-06-12 15:29:59 +00:00
html . find ( '.feat-browser select[name=sortorder]' ) . on ( 'change' , ev => {
let featList = html . find ( '.feat-browser li' ) ;
let byName = ( ev . target . value == 'true' ) ;
let sortedList = this . sortFeats ( featList , byName ) ;
let ol = $ ( html . find ( '.feat-browser ul' ) ) ;
ol [ 0 ] . innerHTML = [ ] ;
for ( let element of sortedList ) {
ol [ 0 ] . append ( element ) ;
}
} ) ;
2021-02-12 02:05:08 +00:00
this . triggerSort ( html , "feat" ) ;
2020-06-12 15:29:59 +00:00
2021-02-09 18:21:12 +00:00
// sort item list in place
2020-06-12 15:29:59 +00:00
html . find ( '.item-browser select[name=sortorder]' ) . on ( 'change' , ev => {
let itemList = html . find ( '.item-browser li' ) ;
let byName = ( ev . target . value == 'true' ) ;
let sortedList = this . sortItems ( itemList , byName ) ;
let ol = $ ( html . find ( '.item-browser ul' ) ) ;
ol [ 0 ] . innerHTML = [ ] ;
for ( let element of sortedList ) {
ol [ 0 ] . append ( element ) ;
}
} ) ;
2021-02-12 02:05:08 +00:00
this . triggerSort ( html , "item" ) ;
2020-06-12 15:29:59 +00:00
2021-02-09 18:21:12 +00:00
// sort npc list in place
2019-12-29 16:54:57 +00:00
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 ) ;
}
} ) ;
2021-02-12 02:05:08 +00:00
this . triggerSort ( html , "npc" ) ;
2019-12-29 16:54:57 +00:00
2021-02-09 18:21:12 +00:00
// reset filters and re-render
2021-03-12 02:26:51 +00:00
//0.4.3: Reset ALL filters because when we do a re-render it affects all tabs
2020-04-30 17:07:00 +00:00
html . find ( '#reset-spell-filter' ) . click ( ev => {
2021-03-12 02:26:51 +00:00
this . resetFilters ( ) ;
2021-03-12 01:32:07 +00:00
//v0.4.3: Re-render so that we display the filters correctly
this . refreshList = "spell" ;
this . render ( ) ;
2020-04-30 17:07:00 +00:00
} ) ;
2020-06-12 15:29:59 +00:00
html . find ( '#reset-feat-filter' ) . click ( ev => {
2021-03-12 02:26:51 +00:00
this . resetFilters ( ) ;
2021-03-12 01:32:07 +00:00
//v0.4.3: Re-render so that we display the filters correctly
this . refreshList = "feat" ;
this . render ( ) ;
2020-06-12 15:29:59 +00:00
} ) ;
html . find ( '#reset-item-filter' ) . click ( ev => {
2021-03-12 02:26:51 +00:00
this . resetFilters ( ) ;
2021-03-12 01:32:07 +00:00
//v0.4.3: Re-render so that we display the filters correctly
this . refreshList = "item" ;
this . render ( ) ;
2020-06-12 15:29:59 +00:00
} ) ;
2020-04-30 17:07:00 +00:00
html . find ( '#reset-npc-filter' ) . click ( ev => {
2021-03-12 02:26:51 +00:00
this . resetFilters ( ) ;
2021-03-12 01:32:07 +00:00
//v0.4.3: Re-render so that we display the filters correctly
this . refreshList = "npc" ;
this . render ( ) ;
2020-04-30 17:07:00 +00:00
} ) ;
2019-12-29 16:54:57 +00:00
// settings
html . find ( '.settings input' ) . on ( 'change' , ev => {
let setting = ev . target . dataset . setting ;
let value = ev . target . checked ;
if ( setting === 'spell-compendium-setting' ) {
let key = ev . target . dataset . key ;
this . settings . loadedSpellCompendium [ key ] . load = value ;
2021-02-15 21:50:14 +00:00
this . render ( ) ;
2021-02-12 18:53:58 +00:00
ui . notifications . info ( "Settings Saved. Item Compendiums are being reloaded." ) ;
2019-12-29 16:54:57 +00:00
} else if ( setting === 'npc-compendium-setting' ) {
let key = ev . target . dataset . key ;
this . settings . loadedNpcCompendium [ key ] . load = value ;
2021-02-15 21:50:14 +00:00
this . render ( ) ;
2019-12-29 16:54:57 +00:00
ui . notifications . info ( "Settings Saved. NPC Compendiums are being reloaded." ) ;
}
if ( setting === 'allow-spell-browser' ) {
this . settings . allowSpellBrowser = value ;
}
2020-06-12 15:29:59 +00:00
if ( setting === 'allow-feat-browser' ) {
this . settings . allowFeatBrowser = value ;
}
if ( setting === 'allow-item-browser' ) {
this . settings . allowItemBrowser = value ;
}
2019-12-29 16:54:57 +00:00
if ( setting === 'allow-npc-browser' ) {
this . settings . allowNpcBrowser = value ;
}
this . saveSettings ( ) ;
} ) ;
2021-02-12 18:53:58 +00:00
// activating or deactivating filters
//0.4.1: Now does a re-load and updates just the data side
// text filters
html . find ( '.filter[data-type=text] input, .filter[data-type=text] select' ) . on ( 'keyup change paste' , ev => {
const path = $ ( ev . target ) . parents ( '.filter' ) . data ( 'path' ) ;
const key = path . replace ( /\./g , '' ) ;
const value = ev . target . value ;
const browserTab = $ ( ev . target ) . parents ( '.tab' ) . data ( 'tab' ) ;
const filterTarget = ` ${ browserTab } 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
}
}
this . replaceList ( html , browserTab ) ;
} ) ;
// select filters
html . find ( '.filter[data-type=select] select, .filter[data-type=bool] select' ) . on ( 'change' , ev => {
const path = $ ( ev . target ) . parents ( '.filter' ) . data ( 'path' ) ;
const key = path . replace ( /\./g , '' ) ;
const filterType = $ ( ev . target ) . parents ( '.filter' ) . data ( 'type' ) ;
const browserTab = $ ( 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 ;
const filterTarget = ` ${ browserTab } Filters ` ;
if ( value === "null" ) {
delete this [ filterTarget ] . activeFilters [ key ]
} else {
this [ filterTarget ] . activeFilters [ key ] = {
path : path ,
type : filterType ,
valIsArray : valIsArray ,
value : value
}
}
this . replaceList ( html , browserTab ) ;
} ) ;
// multiselect filters
html . find ( '.filter[data-type=multiSelect] input' ) . on ( 'change' , ev => {
const path = $ ( ev . target ) . parents ( '.filter' ) . data ( 'path' ) ;
const key = path . replace ( /\./g , '' ) ;
const filterType = 'multiSelect' ;
const browserTab = $ ( 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' ) ;
const filterTarget = ` ${ browserTab } Filters ` ;
const 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 ] ;
}
}
2021-02-16 00:47:48 +00:00
this . replaceList ( html , browserTab ) ;
2021-02-12 18:53:58 +00:00
} ) ;
html . find ( '.filter[data-type=numberCompare] select, .filter[data-type=numberCompare] input' ) . on ( 'change keyup paste' , ev => {
const path = $ ( ev . target ) . parents ( '.filter' ) . data ( 'path' ) ;
const key = path . replace ( /\./g , '' ) ;
const filterType = 'numberCompare' ;
const browserTab = $ ( ev . target ) . parents ( '.tab' ) . data ( 'tab' ) ;
let valIsArray = false ;
const operator = $ ( ev . target ) . parents ( '.filter' ) . find ( 'select' ) . val ( ) ;
const value = $ ( ev . target ) . parents ( '.filter' ) . find ( 'input' ) . val ( ) ;
const filterTarget = ` ${ browserTab } 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
}
}
this . replaceList ( html , browserTab ) ;
} ) ;
2021-02-12 20:14:37 +00:00
//Just for the loading image
if ( this . observer ) {
html . find ( "img" ) . each ( ( i , img ) => this . observer . observe ( img ) ) ;
}
2021-02-12 18:53:58 +00:00
}
async checkListsLoaded ( ) {
//Provides extra info not in the standard SRD, like which classes can learn a spell
if ( ! this . classList ) {
this . classList = await fetch ( 'modules/compendium-browser/spell-classes.json' ) . then ( result => {
return result . json ( ) ;
} ) . then ( obj => {
return this . classList = obj ;
} ) ;
}
if ( ! this . packList ) {
this . packList = await fetch ( 'modules/compendium-browser/item-packs.json' ) . then ( result => {
return result . json ( ) ;
} ) . then ( obj => {
return this . packList = obj ;
} ) ;
}
if ( ! this . subClasses ) {
this . subClasses = await fetch ( 'modules/compendium-browser/sub-classes.json' ) . then ( result => {
return result . json ( ) ;
} ) . then ( obj => {
return this . subClasses = obj ;
} ) ;
}
}
2021-02-17 04:39:39 +00:00
async loadAndFilterItems ( browserTab = "spell" , updateLoading = null ) {
2021-02-12 18:53:58 +00:00
console . log ( ` Load and Filter Items | Started loading ${ browserTab } s ` ) ;
console . time ( "loadAndFilterItems" ) ;
await this . checkListsLoaded ( ) ;
2021-09-07 02:34:03 +00:00
const seachNumber = Date . now ( ) ;
this . CurrentSeachNumber = seachNumber ;
2021-02-17 04:39:39 +00:00
const maxLoad = game . settings . get ( CMPBrowser . MODULE _NAME , "maxload" ) ? ? CMPBrowser . MAXLOAD ;
2021-09-07 02:34:03 +00:00
2021-02-12 18:53:58 +00:00
//0.4.1: Load and filter just one of spells, feats, and items (specified by browserTab)
let unfoundSpells = '' ;
let numItemsLoaded = 0 ;
let compactItems = { } ;
2021-09-07 02:34:03 +00:00
try {
//Filter the full list, but only save the core compendium information + displayed info
for ( let pack of game . packs ) {
2022-01-02 05:27:33 +00:00
if ( pack . documentName === "Item" && this . settings . loadedSpellCompendium [ pack . collection ] . load ) {
2021-09-07 02:34:03 +00:00
//can query just for spells since there is only 1 type
let query = { } ;
2022-01-02 05:27:33 +00:00
if ( browserTab === "spell" ) {
2021-09-07 02:34:03 +00:00
query = { type : "spell" } ;
}
2021-03-15 20:06:06 +00:00
2021-09-07 02:34:03 +00:00
//FIXME: How much could we do with the loaded index rather than all content?
//OR filter the content up front for the decoratedItem.type??
await pack . getDocuments ( query ) . then ( content => {
2021-03-15 20:06:06 +00:00
2022-01-05 01:39:05 +00:00
if ( browserTab === "spell" ) {
2021-02-12 18:53:58 +00:00
2022-01-02 05:27:33 +00:00
content . reduce ( function ( itemsList , item5e ) {
2021-09-07 02:34:03 +00:00
if ( this . CurrentSeachNumber != seachNumber ) throw STOP _SEARCH ;
2021-02-12 18:53:58 +00:00
2021-09-07 02:34:03 +00:00
numItemsLoaded = Object . keys ( itemsList ) . length ;
2021-02-12 18:53:58 +00:00
2021-09-07 02:34:03 +00:00
if ( maxLoad <= numItemsLoaded ) {
2022-01-05 01:39:05 +00:00
if ( updateLoading ) { updateLoading ( numItemsLoaded , true ) ; }
2021-09-07 02:34:03 +00:00
throw STOP _SEARCH ;
}
2021-02-12 18:53:58 +00:00
2021-09-07 02:34:03 +00:00
const decoratedItem = this . decorateItem ( item5e ) ;
if ( decoratedItem && this . passesFilter ( decoratedItem , this . spellFilters . activeFilters ) ) {
itemsList [ item5e . id ] = {
compendium : pack . collection ,
name : decoratedItem . name ,
img : decoratedItem . img ,
data : {
level : decoratedItem . data ? . level ,
components : decoratedItem . data ? . components
} ,
id : item5e . id
} ;
}
2021-02-12 18:53:58 +00:00
2021-09-07 02:34:03 +00:00
return itemsList ;
} . bind ( this ) , compactItems ) ;
2021-02-12 18:53:58 +00:00
2021-09-07 02:34:03 +00:00
}
2022-01-02 05:27:33 +00:00
else if ( browserTab === "feat" ) {
2021-09-07 02:34:03 +00:00
content . reduce ( function ( itemsList , item5e ) {
if ( this . CurrentSeachNumber != seachNumber ) throw STOP _SEARCH ;
numItemsLoaded = Object . keys ( itemsList ) . length ;
if ( maxLoad <= numItemsLoaded ) {
2022-01-05 01:39:05 +00:00
if ( updateLoading ) { updateLoading ( numItemsLoaded , true ) ; }
2021-09-07 02:34:03 +00:00
throw STOP _SEARCH ;
2021-02-12 18:53:58 +00:00
}
2021-09-07 02:34:03 +00:00
const decoratedItem = this . decorateItem ( item5e ) ;
if ( decoratedItem && [ "feat" , "class" ] . includes ( decoratedItem . type ) && this . passesFilter ( decoratedItem , this . featFilters . activeFilters ) ) {
itemsList [ item5e . id ] = {
compendium : pack . collection ,
name : decoratedItem . name ,
img : decoratedItem . img ,
classRequirementString : decoratedItem . classRequirementString
} ;
2021-02-12 18:53:58 +00:00
}
2021-09-07 02:34:03 +00:00
return itemsList ;
} . bind ( this ) , compactItems ) ;
2019-12-29 16:54:57 +00:00
2021-09-07 02:34:03 +00:00
}
2022-01-02 05:27:33 +00:00
else if ( browserTab === "item" ) {
2019-12-29 16:54:57 +00:00
2021-09-07 02:34:03 +00:00
content . reduce ( function ( itemsList , item5e ) {
if ( this . CurrentSeachNumber != seachNumber ) throw STOP _SEARCH ;
2019-12-29 16:54:57 +00:00
2021-09-07 02:34:03 +00:00
numItemsLoaded = Object . keys ( itemsList ) . length ;
2019-12-29 16:54:57 +00:00
2021-09-07 02:34:03 +00:00
if ( maxLoad <= numItemsLoaded ) {
2022-01-05 01:39:05 +00:00
if ( updateLoading ) { updateLoading ( numItemsLoaded , true ) ; }
2021-09-07 02:34:03 +00:00
throw STOP _SEARCH ;
2021-02-12 18:53:58 +00:00
}
2019-12-29 16:54:57 +00:00
2021-09-07 02:34:03 +00:00
const decoratedItem = this . decorateItem ( item5e ) ;
if ( decoratedItem && ! [ "spell" , "feat" , "class" ] . includes ( decoratedItem . type ) && this . passesFilter ( decoratedItem , this . itemFilters . activeFilters ) ) {
itemsList [ item5e . id ] = {
compendium : pack . collection ,
name : decoratedItem . name ,
img : decoratedItem . img ,
type : decoratedItem . type
2021-02-12 18:53:58 +00:00
}
}
2019-12-29 16:54:57 +00:00
2021-09-07 02:34:03 +00:00
return itemsList ;
} . bind ( this ) , compactItems ) ;
2021-02-12 18:53:58 +00:00
2021-09-07 02:34:03 +00:00
}
2021-02-12 18:53:58 +00:00
2021-09-07 02:34:03 +00:00
numItemsLoaded = Object . keys ( compactItems ) . length ;
2022-01-05 01:39:05 +00:00
if ( updateLoading ) { updateLoading ( numItemsLoaded , false ) ; }
2021-09-07 02:34:03 +00:00
} ) ;
} //end if pack entity === Item
} //for packs
}
catch ( e ) {
if ( e === STOP _SEARCH ) {
//stopping search early
2019-12-29 16:54:57 +00:00
}
2021-09-07 02:34:03 +00:00
else {
throw e ;
}
}
// this.removeDuplicates(compactItems);
/ *
2021-02-12 18:53:58 +00:00
if ( unfoundSpells !== '' ) {
2021-09-07 02:34:03 +00:00
console . log ( ` Load and Fliter Items | List of Spells that don't have a class associated to them: ` ) ;
2021-02-12 18:53:58 +00:00
console . log ( unfoundSpells ) ;
}
2021-09-07 02:34:03 +00:00
* /
2021-02-12 18:53:58 +00:00
this . itemsLoaded = true ;
2021-09-07 02:34:03 +00:00
console . timeEnd ( "loadAndFilterItems" ) ;
console . log ( ` Load and Filter Items | Finished loading ${ Object . keys ( compactItems ) . length } ${ browserTab } s ` ) ;
2022-01-05 01:39:05 +00:00
updateLoading ( numItemsLoaded , true )
2021-09-07 02:34:03 +00:00
return compactItems ;
2021-02-12 18:53:58 +00:00
}
2021-09-07 02:34:03 +00:00
2021-02-17 04:39:39 +00:00
async loadAndFilterNpcs ( updateLoading = null ) {
2021-02-12 18:53:58 +00:00
console . log ( 'NPC Browser | Started loading NPCs' ) ;
2021-09-07 02:34:03 +00:00
const seachNumber = Date . now ( ) ;
this . CurrentSeachNumber = seachNumber ;
2021-02-15 21:50:14 +00:00
console . time ( "loadAndFilterNpcs" ) ;
2021-02-12 18:53:58 +00:00
let npcs = { } ;
2019-12-29 16:54:57 +00:00
2021-02-17 04:39:39 +00:00
const maxLoad = game . settings . get ( CMPBrowser . MODULE _NAME , "maxload" ) ? ? CMPBrowser . MAXLOAD ;
2021-09-07 02:34:03 +00:00
2021-02-17 04:39:39 +00:00
let numNpcsLoaded = 0 ;
2021-02-12 18:53:58 +00:00
this . npcsLoaded = false ;
2021-09-07 02:34:03 +00:00
2022-01-05 00:07:04 +00:00
// fields required for displaying and decorating NPCs
const requiredIndexFields = [
'name' ,
'img' ,
'data.details.cr' ,
'data.traits.size' ,
'data.details.type' ,
'items.type' ,
'items.data.damage.parts' ,
]
// add any fields required for currently active filters
const indexFields = requiredIndexFields . concat (
Object . values ( this . npcFilters . activeFilters ) . map ( f => f . path )
) ;
2021-09-07 02:34:03 +00:00
try {
for ( let pack of game . packs ) {
2022-01-02 05:27:33 +00:00
if ( pack . documentName == "Actor" && this . settings . loadedNpcCompendium [ pack . collection ] . load ) {
2022-01-05 00:07:04 +00:00
await pack . getIndex ( { fields : indexFields } ) . then ( async content => {
2021-09-07 02:34:03 +00:00
content . reduce ( function ( actorsList , npc5e ) {
if ( this . CurrentSeachNumber != seachNumber ) { throw STOP _SEARCH ; }
numNpcsLoaded = Object . keys ( npcs ) . length ;
if ( maxLoad <= numNpcsLoaded ) {
2022-01-05 01:39:05 +00:00
if ( updateLoading ) { updateLoading ( numNpcsLoaded , true ) ; }
2021-09-07 02:34:03 +00:00
throw STOP _SEARCH ;
2021-02-12 18:53:58 +00:00
}
2021-09-07 02:34:03 +00:00
const decoratedNpc = this . decorateNpc ( npc5e ) ;
if ( decoratedNpc && this . passesFilter ( decoratedNpc , this . npcFilters . activeFilters ) ) {
2022-01-05 00:07:04 +00:00
actorsList [ npc5e . _id ] = {
2021-09-07 02:34:03 +00:00
compendium : pack . collection ,
name : decoratedNpc . name ,
img : decoratedNpc . img ,
displayCR : decoratedNpc . displayCR ,
displaySize : decoratedNpc . displaySize ,
displayType : this . getNPCType ( decoratedNpc . data ? . details ? . type ) ,
2022-01-05 01:39:05 +00:00
orderCR : decoratedNpc . data ? . details ? . cr ,
2021-09-07 02:34:03 +00:00
orderSize : decoratedNpc . filterSize
} ;
}
return actorsList ;
} . bind ( this ) , npcs ) ;
numNpcsLoaded = Object . keys ( npcs ) . length ;
2022-01-05 01:39:05 +00:00
if ( updateLoading ) { updateLoading ( numNpcsLoaded , false ) ; }
2021-09-07 02:34:03 +00:00
} ) ;
}
//0.4.1 Only preload a limited number and fill more in as needed
}
}
catch ( e ) {
if ( e == STOP _SEARCH ) {
//breaking out
}
else {
console . timeEnd ( "loadAndFilterNpcs" ) ;
throw e ;
2019-12-29 16:54:57 +00:00
}
2021-02-12 18:53:58 +00:00
}
2019-12-29 16:54:57 +00:00
2021-02-12 18:53:58 +00:00
this . npcsLoaded = true ;
2021-02-15 21:50:14 +00:00
console . timeEnd ( "loadAndFilterNpcs" ) ;
2021-02-12 18:53:58 +00:00
console . log ( ` NPC Browser | Finished loading NPCs: ${ Object . keys ( npcs ) . length } NPCs ` ) ;
2022-01-05 01:39:05 +00:00
updateLoading ( numNpcsLoaded , true )
2021-02-12 18:53:58 +00:00
return npcs ;
}
2019-12-29 16:54:57 +00:00
2021-02-12 18:53:58 +00:00
hookCompendiumList ( ) {
Hooks . on ( 'renderCompendiumDirectory' , ( app , html , data ) => {
this . hookCompendiumList ( ) ;
} ) ;
2019-12-29 16:54:57 +00:00
2021-02-12 18:53:58 +00:00
let html = $ ( '#compendium' ) ;
if ( this . settings === undefined ) {
this . initSettings ( ) ;
}
if ( game . user . isGM || this . settings . allowSpellBrowser || this . settings . allowNpcBrowser ) {
2021-02-15 22:14:45 +00:00
const cbButton = $ ( ` <button class="compendium-browser-btn"><i class="fas fa-fire"></i> ${ game . i18n . localize ( "CMPBrowser.compendiumBrowser" ) } </button> ` ) ;
2021-02-12 18:53:58 +00:00
html . find ( '.compendium-browser-btn' ) . remove ( ) ;
2019-12-29 16:54:57 +00:00
2021-02-12 18:53:58 +00:00
// adding to directory-list since the footer doesn't exist if the user is not gm
2021-02-15 22:14:45 +00:00
html . find ( '.directory-footer' ) . append ( cbButton ) ;
2019-12-29 16:54:57 +00:00
2021-02-12 18:53:58 +00:00
// Handle button clicks
2021-02-15 22:14:45 +00:00
cbButton . click ( ev => {
2021-02-12 18:53:58 +00:00
ev . preventDefault ( ) ;
2021-03-12 01:32:07 +00:00
//0.4.1: Reset filters when you click button
2021-02-12 18:53:58 +00:00
this . resetFilters ( ) ;
2021-03-12 01:32:07 +00:00
//0.4.3: Reset everything (including data) when you press the button - calls afterRender() hook
if ( game . user . isGM || this . settings . allowSpellBrowser ) {
this . refreshList = "spell" ;
} else if ( this . settings . allowFeatBrowser ) {
this . refreshList = "feat" ;
} else if ( this . settings . allowItemBrowser ) {
this . refreshList = "item" ;
} else if ( this . settings . allowNPCBrowser ) {
this . refreshList = "npc" ;
}
2021-02-12 18:53:58 +00:00
this . render ( true ) ;
} ) ;
}
}
2019-12-29 16:54:57 +00:00
2021-03-12 01:32:07 +00:00
/* Hook to load the first data */
static afterRender ( cb , html ) {
2021-03-12 02:26:51 +00:00
//0.4.3: Because a render always resets ALL the displayed filters (on all tabs) to unselected , we have to blank all the lists as well
// (because the current HTML template doesn't set the selected filter values)
2021-03-12 01:32:07 +00:00
if ( ! cb ? . refreshList ) { return ; }
cb . replaceList ( html , cb . refreshList ) ;
cb . refreshList = null ;
}
2021-02-12 18:53:58 +00:00
resetFilters ( ) {
this . spellFilters . activeFilters = { } ;
this . featFilters . activeFilters = { } ;
this . itemFilters . activeFilters = { } ;
this . npcFilters . activeFilters = { } ;
}
2019-12-29 16:54:57 +00:00
2020-06-12 15:29:59 +00:00
2019-12-29 16:54:57 +00:00
2021-02-15 22:27:15 +00:00
async replaceList ( html , browserTab , options = { reload : true } ) {
2021-02-16 00:47:48 +00:00
//After rendering the first time or re-rendering trigger the load/reload of visible data
2021-02-16 02:49:06 +00:00
2021-02-15 22:14:45 +00:00
let elements = null ;
2021-02-17 04:39:39 +00:00
//0.4.2 Display a Loading... message while the data is being loaded and filtered
let loadingMessage = null ;
2021-02-12 16:53:29 +00:00
if ( browserTab === 'spell' ) {
2021-02-15 22:14:45 +00:00
elements = html . find ( "ul#CBSpells" ) ;
2021-02-17 04:39:39 +00:00
loadingMessage = html . find ( "#CBSpellsMessage" ) ;
2021-02-12 16:53:29 +00:00
} else if ( browserTab === 'npc' ) {
2021-02-15 22:14:45 +00:00
elements = html . find ( "ul#CBNPCs" ) ;
2021-02-17 04:39:39 +00:00
loadingMessage = html . find ( "#CBNpcsMessage" ) ;
2021-02-12 16:53:29 +00:00
} else if ( browserTab === 'feat' ) {
2021-02-15 22:14:45 +00:00
elements = html . find ( "ul#CBFeats" ) ;
2021-02-17 04:39:39 +00:00
loadingMessage = html . find ( "#CBFeatsMessage" ) ;
2021-02-12 16:53:29 +00:00
} else if ( browserTab === 'item' ) {
2021-02-15 22:14:45 +00:00
elements = html . find ( "ul#CBItems" ) ;
2021-02-17 04:39:39 +00:00
loadingMessage = html . find ( "#CBItemsMessage" ) ;
2021-02-10 05:42:50 +00:00
}
2021-02-15 22:14:45 +00:00
if ( elements ? . length ) {
2021-02-16 00:47:48 +00:00
//0.4.2b: On a tab-switch, only reload if there isn't any data already
if ( options ? . reload || ! elements [ 0 ] . children . length ) {
2021-02-17 04:39:39 +00:00
const maxLoad = game . settings . get ( CMPBrowser . MODULE _NAME , "maxload" ) ? ? CMPBrowser . MAXLOAD ;
2022-01-05 01:39:05 +00:00
const updateLoading = async ( numLoaded , doneLoading ) => {
if ( loadingMessage . length ) { this . renderLoading ( loadingMessage [ 0 ] , browserTab , numLoaded , numLoaded >= maxLoad , doneLoading ) ; }
2021-02-16 19:55:46 +00:00
}
2022-01-05 01:39:05 +00:00
updateLoading ( 0 , false ) ;
2021-02-15 22:14:45 +00:00
//Uses loadAndFilterItems to read compendia for items which pass the current filters and render on this tab
2021-02-16 19:55:46 +00:00
const newItemsHTML = await this . renderItemData ( browserTab , updateLoading ) ;
2021-02-15 22:14:45 +00:00
elements [ 0 ] . innerHTML = newItemsHTML ;
//Re-sort before setting up lazy loading
this . triggerSort ( html , browserTab ) ;
//Lazy load images
if ( this . observer ) {
$ ( elements ) . find ( "img" ) . each ( ( i , img ) => this . observer . observe ( img ) ) ;
}
2021-02-12 02:05:08 +00:00
2021-02-15 22:14:45 +00:00
//Reactivate listeners for clicking and dragging
this . activateItemListListeners ( $ ( elements ) ) ;
}
2021-02-12 02:05:08 +00:00
}
2021-02-10 05:42:50 +00:00
}
2022-01-05 01:39:05 +00:00
async renderLoading ( messageElement , itemType , numLoaded , maxLoaded = false , doneLoading = false ) {
2021-02-17 04:39:39 +00:00
if ( ! messageElement ) return ;
2021-02-16 02:49:06 +00:00
2022-01-05 01:39:05 +00:00
let loadingHTML = await renderTemplate ( "modules/compendium-browser/template/loading.html" , { numLoaded : numLoaded , itemType : itemType , maxLoaded : maxLoaded , doneLoading : doneLoading } ) ;
2021-02-17 04:39:39 +00:00
messageElement . innerHTML = loadingHTML ;
2021-02-16 02:49:06 +00:00
}
2021-02-16 19:55:46 +00:00
async renderItemData ( browserTab , updateLoading = null ) {
2021-02-17 04:39:39 +00:00
let listItems ;
if ( browserTab === "npc" ) {
listItems = await this . loadAndFilterNpcs ( updateLoading ) ;
2021-02-12 02:05:08 +00:00
} else {
2021-02-17 04:39:39 +00:00
listItems = await this . loadAndFilterItems ( browserTab , updateLoading ) ;
2021-02-12 02:05:08 +00:00
}
2021-02-17 04:39:39 +00:00
const html = await renderTemplate ( ` modules/compendium-browser/template/ ${ browserTab } -browser-list.html ` , { listItems : listItems } )
2021-02-10 05:42:50 +00:00
return html ;
}
2021-02-05 02:50:25 +00:00
//SORTING
2021-02-12 16:53:29 +00:00
triggerSort ( html , browserTab ) {
if ( browserTab === 'spell' ) {
2021-02-12 02:05:08 +00:00
html . find ( '.spell-browser select[name=sortorder]' ) . trigger ( 'change' ) ;
2021-02-12 16:53:29 +00:00
} else if ( browserTab === 'feat' ) {
2021-02-12 02:05:08 +00:00
html . find ( '.feat-browser select[name=sortorder]' ) . trigger ( 'change' ) ;
2021-02-12 16:53:29 +00:00
} else if ( browserTab === 'npc' ) {
2021-02-12 02:05:08 +00:00
html . find ( '.npc-browser select[name=sortorder]' ) . trigger ( 'change' )
2021-02-12 16:53:29 +00:00
} else if ( browserTab === 'item' ) {
2021-02-12 02:05:08 +00:00
html . find ( '.item-browser select[name=sortorder]' ) . trigger ( 'change' ) ;
}
2021-02-12 01:23:49 +00:00
}
2019-12-29 16:54:57 +00:00
sortSpells ( list , byName ) {
2021-02-09 18:21:12 +00:00
if ( byName ) {
2019-12-29 16:54:57 +00:00
list . sort ( ( a , b ) => {
2020-06-12 15:29:59 +00:00
let aName = $ ( a ) . find ( '.item-name a' ) [ 0 ] . innerHTML ;
let bName = $ ( b ) . find ( '.item-name a' ) [ 0 ] . innerHTML ;
2019-12-29 16:54:57 +00:00
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 ( ) ;
if ( aVal < bVal ) return - 1 ;
if ( aVal > bVal ) return 1 ;
if ( aVal == bVal ) {
2020-06-12 15:29:59 +00:00
let aName = $ ( a ) . find ( '.item-name a' ) [ 0 ] . innerHTML ;
let bName = $ ( b ) . find ( '.item-name a' ) [ 0 ] . innerHTML ;
if ( aName < bName ) return - 1 ;
if ( aName > bName ) return 1 ;
return 0 ;
}
} ) ;
}
return list ;
}
sortFeats ( list , byName ) {
if ( byName ) {
list . sort ( ( a , b ) => {
let aName = $ ( a ) . find ( '.item-name a' ) [ 0 ] . innerHTML ;
let bName = $ ( b ) . find ( '.item-name a' ) [ 0 ] . innerHTML ;
if ( aName < bName ) return - 1 ;
if ( aName > bName ) return 1 ;
return 0 ;
} ) ;
} else {
list . sort ( ( a , b ) => {
let aVal = $ ( a ) . find ( 'input[name=class]' ) . val ( ) ;
let bVal = $ ( b ) . find ( 'input[name=class]' ) . val ( ) ;
if ( aVal < bVal ) return - 1 ;
if ( aVal > bVal ) return 1 ;
if ( aVal == bVal ) {
let aName = $ ( a ) . find ( '.item-name a' ) [ 0 ] . innerHTML ;
let bName = $ ( b ) . find ( '.item-name a' ) [ 0 ] . innerHTML ;
if ( aName < bName ) return - 1 ;
if ( aName > bName ) return 1 ;
return 0 ;
}
} ) ;
}
return list ;
}
sortItems ( list , byName ) {
if ( byName ) {
list . sort ( ( a , b ) => {
let aName = $ ( a ) . find ( '.item-name a' ) [ 0 ] . innerHTML ;
let bName = $ ( b ) . find ( '.item-name a' ) [ 0 ] . innerHTML ;
if ( aName < bName ) return - 1 ;
if ( aName > bName ) return 1 ;
return 0 ;
} ) ;
} else {
list . sort ( ( a , b ) => {
let aVal = $ ( a ) . find ( 'input[name=type]' ) . val ( ) ;
let bVal = $ ( b ) . find ( 'input[name=type]' ) . val ( ) ;
if ( aVal < bVal ) return - 1 ;
if ( aVal > bVal ) return 1 ;
if ( aVal == bVal ) {
let aName = $ ( a ) . find ( '.item-name a' ) [ 0 ] . innerHTML ;
let bName = $ ( b ) . find ( '.item-name a' ) [ 0 ] . innerHTML ;
2019-12-29 16:54:57 +00:00
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 ;
}
2021-02-15 21:50:14 +00:00
decorateItem ( item5e ) {
2021-02-09 18:21:12 +00:00
if ( ! item5e ) return null ;
//Decorate and then filter a compendium entry - returns null or the item
2021-02-12 16:53:29 +00:00
const item = item5e . data ;
2021-02-09 18:21:12 +00:00
// getting damage types (common to all Items, although some won't have any)
item . damageTypes = [ ] ;
if ( item . data . damage && item . data . damage . parts . length > 0 ) {
for ( let part of item . data . damage . parts ) {
let type = part [ 1 ] ;
if ( item . damageTypes . indexOf ( type ) === - 1 ) {
item . damageTypes . push ( type ) ;
}
}
}
if ( item . type === 'spell' ) {
// determining classes that can use the spell
let cleanSpellName = item . name . toLowerCase ( ) . replace ( /[^一-龠ぁ-ゔァ-ヴーa-zA-Z0-9a -z A -Z 0 -9々〆〤]/g , '' ) . replace ( "'" , '' ) . replace ( / /g , '' ) ;
//let cleanSpellName = spell.name.toLowerCase().replace(/[^a-zA-Z0-9\s:]/g, '').replace("'", '').replace(/ /g, '');
if ( this . classList [ cleanSpellName ] ) {
let classes = this . classList [ cleanSpellName ] ;
item . data . classes = classes . split ( ',' ) ;
} else {
//FIXME: unfoundSpells += cleanSpellName + ',';
}
} else if ( item . type === 'feat' || item . type === 'class' ) {
// getting class
let reqString = item . data . requirements ? . replace ( /[0-9]/g , '' ) . trim ( ) ;
let matchedClass = [ ] ;
for ( let c in this . subClasses ) {
if ( reqString && reqString . toLowerCase ( ) . indexOf ( c ) !== - 1 ) {
matchedClass . push ( c ) ;
} else {
for ( let subClass of this . subClasses [ c ] ) {
if ( reqString && reqString . indexOf ( subClass ) !== - 1 ) {
matchedClass . push ( c ) ;
break ;
}
}
}
}
item . classRequirement = matchedClass ;
item . classRequirementString = matchedClass . join ( ', ' ) ;
// getting uses/ressources status
item . usesRessources = item5e . hasLimitedUses ;
item . hasSave = item5e . hasSave ;
} else {
// getting pack
let matchedPacks = [ ] ;
for ( let pack of Object . keys ( this . packList ) ) {
for ( let packItem of this . packList [ pack ] ) {
if ( item . name . toLowerCase ( ) === packItem . toLowerCase ( ) ) {
matchedPacks . push ( pack ) ;
break ;
}
}
}
item . matchedPacks = matchedPacks ;
item . matchedPacksString = matchedPacks . join ( ', ' ) ;
// getting uses/ressources status
item . usesRessources = item5e . hasLimitedUses
}
return item ;
}
2021-02-15 21:50:14 +00:00
decorateNpc ( npc ) {
//console.log('%c '+npc.name, 'background: white; color: red')
2022-01-05 00:07:04 +00:00
const decoratedNpc = npc ;
2021-02-15 21:50:14 +00:00
// cr display
2022-01-05 01:39:05 +00:00
let cr = decoratedNpc . data . details ? . cr ; //0.7.2c: Possibly because of getIndex() use we now have to check for existence of details (doesn't for Character-type NPCs)
if ( cr === undefined || cr === '' ) cr = 0 ;
2021-02-15 21:50:14 +00:00
else cr = Number ( cr ) ;
if ( cr > 0 && cr < 1 ) cr = "1/" + ( 1 / cr ) ;
decoratedNpc . displayCR = cr ;
decoratedNpc . displaySize = 'unset' ;
decoratedNpc . filterSize = 2 ;
if ( CONFIG . DND5E . actorSizes [ decoratedNpc . data . traits . size ] !== undefined ) {
decoratedNpc . displaySize = CONFIG . DND5E . actorSizes [ decoratedNpc . data . traits . size ] ;
}
switch ( decoratedNpc . data . traits . size ) {
case 'grg' : decoratedNpc . filterSize = 5 ; break ;
case 'huge' : decoratedNpc . filterSize = 4 ; break ;
case 'lg' : decoratedNpc . filterSize = 3 ; break ;
case 'sm' : decoratedNpc . filterSize = 1 ; break ;
case 'tiny' : decoratedNpc . filterSize = 0 ; break ;
case 'med' :
default : decoratedNpc . filterSize = 2 ; break ;
}
// getting value for HasSpells and damage types
2022-01-05 01:39:05 +00:00
decoratedNpc . hasSpells = decoratedNpc . items ? . type ? . reduce ( ( hasSpells , itemType ) => hasSpells || itemType === 'spell' , false ) ;
decoratedNpc . damageDealt = decoratedNpc . items ? . data ? . damage ? . parts ? decoratedNpc . items ? . data ? . damage ? . parts ? . filter ( p => p ? . length >= 2 ) . map ( p => p [ 1 ] ) : 0 ;
2021-02-15 21:50:14 +00:00
2021-09-07 02:34:03 +00:00
//handle poorly constructed npc
if ( decoratedNpc . data ? . details ? . type && ! ( decoratedNpc . data ? . details ? . type instanceof Object ) ) {
decoratedNpc . data . details . type = { value : decoratedNpc . data ? . details ? . type } ;
}
2021-02-15 21:50:14 +00:00
return decoratedNpc ;
}
2021-09-07 02:34:03 +00:00
getNPCType ( type ) {
if ( type instanceof Object ) {
return type . value ;
}
return type ;
}
2019-12-29 16:54:57 +00:00
filterElements ( list , subjects , filters ) {
for ( let element of list ) {
let subject = subjects [ element . dataset . entryId ] ;
2021-03-15 20:06:06 +00:00
if ( this . passesFilter ( subject , filters ) == false ) {
2019-12-29 16:54:57 +00:00
$ ( element ) . hide ( ) ;
} else {
$ ( element ) . show ( ) ;
}
}
}
2021-03-15 20:06:06 +00:00
passesFilter ( subject , filters ) {
2021-02-09 18:21:12 +00:00
for ( let filter of Object . values ( filters ) ) {
2019-12-29 16:54:57 +00:00
let prop = getProperty ( subject , filter . path ) ;
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' ) {
2020-06-12 15:29:59 +00:00
if ( prop === undefined ) return false ;
2019-12-29 16:54:57 +00:00
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 {
2019-12-29 18:33:53 +00:00
if ( prop === undefined ) return false ;
2019-12-29 16:54:57 +00:00
if ( typeof prop === 'object' ) {
2019-12-29 18:10:27 +00:00
if ( filter . value ) {
if ( prop . indexOf ( filter . value ) === - 1 ) {
return false ;
}
2021-02-05 02:50:25 +00:00
} else if ( filter . values ) {
2019-12-29 16:54:57 +00:00
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 ;
}
2021-09-07 02:34:03 +00:00
//incomplete removal of duplicate items
removeDuplicates ( spellList ) {
//sort at n log n
let sortedList = Object . values ( spellList ) . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
//search through sorted list for duplicates
for ( let index = 0 ; index < sortedList . length - 1 ; ) {
//all duplicates will be next to eachother
if ( sortedList [ index ] . name == sortedList [ index + 1 ] . name ) {
//duplicate something is getting removed
//TODO choose what to remove rather then the second
let remove = index + 1 ;
delete spellList [ sortedList [ remove ] . id ] ;
sortedList . splice ( remove , 1 ) ;
}
else {
index ++ ;
}
}
}
2019-12-29 16:54:57 +00:00
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 ) {
2022-01-02 05:27:33 +00:00
if ( compendium . documentName === "Item" ) {
2019-12-29 16:54:57 +00:00
defaultSettings . loadedSpellCompendium [ compendium . collection ] = {
load : true ,
name : ` ${ compendium [ 'metadata' ] [ 'label' ] } ( ${ compendium . collection } ) `
} ;
}
2022-01-02 05:27:33 +00:00
if ( compendium . documentName === "Actor" ) {
2019-12-29 16:54:57 +00:00
defaultSettings . loadedNpcCompendium [ compendium . collection ] = {
load : true ,
name : ` ${ compendium [ 'metadata' ] [ 'label' ] } ( ${ compendium . collection } ) `
} ;
}
}
// creating game setting container
2021-02-09 18:21:12 +00:00
game . settings . register ( CMPBrowser . MODULE _NAME , "settings" , {
2019-12-29 16:54:57 +00:00
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 ;
}
} ) ;
2021-02-17 04:39:39 +00:00
game . settings . register ( CMPBrowser . MODULE _NAME , "maxload" , {
name : game . i18n . localize ( "CMPBrowser.SETTING.Maxload.NAME" ) ,
hint : game . i18n . localize ( "CMPBrowser.SETTING.Maxload.HINT" ) ,
2021-02-08 17:13:31 +00:00
scope : "world" ,
config : true ,
2021-02-17 04:39:39 +00:00
default : CMPBrowser . MAXLOAD ,
2021-02-08 17:13:31 +00:00
type : Number ,
range : { // If range is specified, the resulting setting will be a range slider
2021-02-17 04:39:39 +00:00
min : 200 ,
max : 5000 ,
2021-02-12 01:23:49 +00:00
step : 100
2021-02-08 17:13:31 +00:00
}
} ) ;
2020-01-13 11:13:55 +00:00
2019-12-29 16:54:57 +00:00
// load settings from container and apply to default settings (available compendie might have changed)
2021-02-09 18:21:12 +00:00
let settings = game . settings . get ( CMPBrowser . MODULE _NAME , 'settings' ) ;
2019-12-29 16:54:57 +00:00
for ( let compKey in defaultSettings . loadedSpellCompendium ) {
2021-09-12 01:26:38 +00:00
//v0.7.1 Check for settings.loadedSpellCompendium
if ( settings . loadedSpellCompendium && ( settings . loadedSpellCompendium [ compKey ] !== undefined ) ) {
2019-12-29 16:54:57 +00:00
defaultSettings . loadedSpellCompendium [ compKey ] . load = settings . loadedSpellCompendium [ compKey ] . load ;
}
}
for ( let compKey in defaultSettings . loadedNpcCompendium ) {
2021-09-12 01:26:38 +00:00
//v0.7.1 Check for settings.loadedNpcCompendium
if ( settings . loadedNpcCompendium && ( settings . loadedNpcCompendium [ compKey ] !== undefined ) ) {
2019-12-29 16:54:57 +00:00
defaultSettings . loadedNpcCompendium [ compKey ] . load = settings . loadedNpcCompendium [ compKey ] . load ;
}
}
2020-06-12 15:29:59 +00:00
defaultSettings . allowSpellBrowser = settings . allowSpellBrowser ? true : false ;
defaultSettings . allowFeatBrowser = settings . allowFeatBrowser ? true : false ;
defaultSettings . allowItemBrowser = settings . allowItemBrowser ? true : false ;
defaultSettings . allowNpcBrowser = settings . allowNpcBrowser ? true : false ;
2020-04-20 17:33:52 +00:00
if ( game . user . isGM ) {
2021-02-09 18:21:12 +00:00
game . settings . set ( CMPBrowser . MODULE _NAME , 'settings' , defaultSettings ) ;
2020-06-12 15:29:59 +00:00
console . log ( "New default settings set" ) ;
console . log ( defaultSettings ) ;
2020-04-20 17:33:52 +00:00
}
2019-12-29 16:54:57 +00:00
this . settings = defaultSettings ;
2022-01-02 05:27:33 +00:00
//0.9.5 Set the CompendiumBrowser.isFoundryV8Plus variable for different code-paths
//If v9, then game.data.version will throw a deprecation warning so test for v9 first
CompendiumBrowser . isFoundryV8Plus = ( game . data . release ? . generation >= 9 ) || ( game . data . version ? . startsWith ( "0.8" ) ) ;
2019-12-29 16:54:57 +00:00
}
saveSettings ( ) {
2021-02-09 18:21:12 +00:00
game . settings . set ( CMPBrowser . MODULE _NAME , 'settings' , this . settings ) ;
2019-12-29 16:54:57 +00:00
}
2021-02-05 02:50:25 +00:00
//FILTERS - Added on the Ready hook
//0.4.0 Make this async so filters can be added all at once
async addFilter ( entityType , category , label , path , type , possibleValues = null , valIsArray = false ) {
2019-12-29 16:54:57 +00:00
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 ) {
2021-02-05 02:50:25 +00:00
this [ target ] . registeredFilterCategorys [ catId ] = { label : category , filters : [ ] } ;
2019-12-29 16:54:57 +00:00
}
this [ target ] . registeredFilterCategorys [ catId ] . filters . push ( filter ) ;
}
2021-02-05 02:50:25 +00:00
async addSpellFilters ( ) {
2021-02-08 17:13:31 +00:00
// Spellfilters
2021-02-05 02:50:25 +00:00
this . addSpellFilter ( game . i18n . localize ( "CMPBrowser.general" ) , game . i18n . localize ( "DND5E.Source" ) , 'data.source' , 'text' ) ;
this . 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 ] ) ;
this . addSpellFilter ( game . i18n . localize ( "CMPBrowser.general" ) , game . i18n . localize ( "CMPBrowser.school" ) , 'data.school' , 'select' , CONFIG . DND5E . spellSchools ) ;
this . 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" )
2021-02-08 17:13:31 +00:00
}
) ;
2021-02-05 02:50:25 +00:00
this . addSpellFilter ( game . i18n . localize ( "CMPBrowser.general" ) , game . i18n . localize ( "CMPBrowser.spellType" ) , 'data.actionType' , 'select' , CONFIG . DND5E . itemActionTypes ) ;
this . addSpellFilter ( game . i18n . localize ( "CMPBrowser.general" ) , game . i18n . localize ( "CMPBrowser.damageType" ) , 'damageTypes' , 'select' , CONFIG . DND5E . damageTypes ) ;
this . addSpellFilter ( game . i18n . localize ( "CMPBrowser.general" ) , game . i18n . localize ( "CMPBrowser.class" ) , 'data.classes' , 'select' ,
{
artificer : game . i18n . localize ( "CMPBrowser.artificer" ) ,
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" ) ,
2021-02-08 17:13:31 +00:00
} , true
) ;
2021-02-05 02:50:25 +00:00
this . addSpellFilter ( game . i18n . localize ( "CMPBrowser.components" ) , game . i18n . localize ( "CMPBrowser.ritual" ) , 'data.components.ritual' , 'bool' ) ;
this . addSpellFilter ( game . i18n . localize ( "CMPBrowser.components" ) , game . i18n . localize ( "CMPBrowser.concentration" ) , 'data.components.concentration' , 'bool' ) ;
this . addSpellFilter ( game . i18n . localize ( "CMPBrowser.components" ) , game . i18n . localize ( "CMPBrowser.verbal" ) , 'data.components.vocal' , 'bool' ) ;
this . addSpellFilter ( game . i18n . localize ( "CMPBrowser.components" ) , game . i18n . localize ( "CMPBrowser.somatic" ) , 'data.components.somatic' , 'bool' ) ;
this . addSpellFilter ( game . i18n . localize ( "CMPBrowser.components" ) , game . i18n . localize ( "CMPBrowser.material" ) , 'data.components.material' , 'bool' ) ;
}
async addItemFilters ( ) {
// Item Filters
this . addItemFilter ( game . i18n . localize ( "CMPBrowser.general" ) , game . i18n . localize ( "DND5E.Source" ) , 'data.source' , 'text' ) ;
this . addItemFilter ( game . i18n . localize ( "CMPBrowser.general" ) , "Item Type" , 'type' , 'select' , {
consumable : game . i18n . localize ( "DND5E.ItemTypeConsumable" ) ,
backpack : game . i18n . localize ( "DND5E.ItemTypeContainer" ) ,
equipment : game . i18n . localize ( "DND5E.ItemTypeEquipment" ) ,
loot : game . i18n . localize ( "DND5E.ItemTypeLoot" ) ,
tool : game . i18n . localize ( "DND5E.ItemTypeTool" ) ,
weapon : game . i18n . localize ( "DND5E.ItemTypeWeapon" )
} ) ;
this . addItemFilter ( game . i18n . localize ( "CMPBrowser.general" ) , "Packs" , 'matchedPacks' , 'select' ,
{
burglar : "Burglar's Pack" ,
diplomat : "Diplomat's Pack" ,
dungeoneer : "Dungeoneer's Pack" ,
entertainer : "Entertainer's Pack" ,
explorer : "Explorer's Pack" ,
monsterhunter : "Monster Hunter's Pack" ,
priest : "Priest's Pack" ,
scholar : "Scholar's Pack" ,
2021-02-08 17:13:31 +00:00
} , true
) ;
2021-02-05 02:50:25 +00:00
this . addItemFilter ( "Game Mechanics" , game . i18n . localize ( "DND5E.ItemActivationCost" ) , 'data.activation.type' , 'select' , CONFIG . DND5E . abilityActivationTypes ) ;
this . addItemFilter ( "Game Mechanics" , game . i18n . localize ( "CMPBrowser.damageType" ) , 'damageTypes' , 'select' , CONFIG . DND5E . damageTypes ) ;
this . addItemFilter ( "Game Mechanics" , "Uses Resources" , 'usesRessources' , 'bool' ) ;
this . addItemFilter ( "Item Subtype" , "Weapon" , 'data.weaponType' , 'text' , CONFIG . DND5E . weaponTypes ) ;
this . addItemFilter ( "Item Subtype" , "Equipment" , 'data.armor.type' , 'text' , CONFIG . DND5E . equipmentTypes ) ;
this . addItemFilter ( "Item Subtype" , "Consumable" , 'data.consumableType' , 'text' , CONFIG . DND5E . consumableTypes ) ;
2022-01-05 01:39:05 +00:00
//0.7.2c: Fix rarity encoding (uses camelcase names)
2021-02-08 17:13:31 +00:00
this . addItemFilter ( "Magic Items" , "Rarity" , 'data.rarity' , 'select' ,
{
2022-01-05 01:39:05 +00:00
common : "Common" ,
uncommon : "Uncommon" ,
rare : "Rare" ,
veryRare : "Very Rare" ,
legendary : "Legendary"
2021-02-05 02:50:25 +00:00
} ) ;
}
async addFeatFilters ( ) {
// Feature Filters
this . addFeatFilter ( game . i18n . localize ( "CMPBrowser.general" ) , game . i18n . localize ( "DND5E.Source" ) , 'data.source' , 'text' ) ;
this . addFeatFilter ( game . i18n . localize ( "CMPBrowser.general" ) , game . i18n . localize ( "CMPBrowser.class" ) , 'classRequirement' , 'select' ,
{
artificer : game . i18n . localize ( "CMPBrowser.artificer" ) ,
barbarian : "Barbarian" ,
bard : game . i18n . localize ( "CMPBrowser.bard" ) ,
cleric : game . i18n . localize ( "CMPBrowser.cleric" ) ,
druid : game . i18n . localize ( "CMPBrowser.druid" ) ,
fighter : "Fighter" ,
monk : "Monk" ,
paladin : game . i18n . localize ( "CMPBrowser.paladin" ) ,
ranger : game . i18n . localize ( "CMPBrowser.ranger" ) ,
rogue : "Rogue" ,
sorcerer : game . i18n . localize ( "CMPBrowser.sorcerer" ) ,
warlock : game . i18n . localize ( "CMPBrowser.warlock" ) ,
wizard : game . i18n . localize ( "CMPBrowser.wizard" )
} , true ) ;
this . addFeatFilter ( "Game Mechanics" , game . i18n . localize ( "DND5E.ItemActivationCost" ) , 'data.activation.type' , 'select' , CONFIG . DND5E . abilityActivationTypes ) ;
this . addFeatFilter ( "Game Mechanics" , game . i18n . localize ( "CMPBrowser.damageType" ) , 'damageTypes' , 'select' , CONFIG . DND5E . damageTypes ) ;
this . addFeatFilter ( "Game Mechanics" , "Uses Resources" , 'usesRessources' , 'bool' ) ;
}
async addNpcFilters ( ) {
// NPC Filters
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.general" ) , game . i18n . localize ( "DND5E.Source" ) , 'data.details.source' , 'text' ) ;
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.general" ) , game . i18n . localize ( "CMPBrowser.size" ) , 'data.traits.size' , 'select' , CONFIG . DND5E . actorSizes ) ;
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.general" ) , game . i18n . localize ( "CMPBrowser.hasSpells" ) , 'hasSpells' , 'bool' ) ;
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.general" ) , game . i18n . localize ( "CMPBrowser.hasLegAct" ) , 'data.resources.legact.max' , 'bool' ) ;
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.general" ) , game . i18n . localize ( "CMPBrowser.hasLegRes" ) , 'data.resources.legres.max' , 'bool' ) ;
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.general" ) , game . i18n . localize ( "CMPBrowser.cr" ) , 'data.details.cr' , 'numberCompare' ) ;
2021-06-13 02:59:08 +00:00
//Foundry 0.8.x: Creature type (data.details.type) is now a structure, so we check data.details.types.value instead
let npcDetailsPath ;
2022-01-02 05:27:33 +00:00
if ( CompendiumBrowser . isFoundryV8Plus ) {
2021-06-13 02:59:08 +00:00
npcDetailsPath = "data.details.type.value" ;
} else { //0.7.x
npcDetailsPath = "data.details.type" ;
}
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.general" ) , game . i18n . localize ( "CMPBrowser.creatureType" ) , npcDetailsPath , 'text' , {
2021-02-05 02:50:25 +00:00
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" )
} ) ;
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.abilities" ) , game . i18n . localize ( "DND5E.AbilityStr" ) , 'data.abilities.str.value' , 'numberCompare' ) ;
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.abilities" ) , game . i18n . localize ( "DND5E.AbilityDex" ) , 'data.abilities.dex.value' , 'numberCompare' ) ;
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.abilities" ) , game . i18n . localize ( "DND5E.AbilityCon" ) , 'data.abilities.con.value' , 'numberCompare' ) ;
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.abilities" ) , game . i18n . localize ( "DND5E.AbilityInt" ) , 'data.abilities.int.value' , 'numberCompare' ) ;
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.abilities" ) , game . i18n . localize ( "DND5E.AbilityWis" ) , 'data.abilities.wis.value' , 'numberCompare' ) ;
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.abilities" ) , game . i18n . localize ( "DND5E.AbilityCha" ) , 'data.abilities.cha.value' , 'numberCompare' ) ;
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.dmgInteraction" ) , game . i18n . localize ( "DND5E.DamImm" ) , 'data.traits.di.value' , 'multiSelect' , CONFIG . DND5E . damageTypes , true ) ;
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.dmgInteraction" ) , game . i18n . localize ( "DND5E.DamRes" ) , 'data.traits.dr.value' , 'multiSelect' , CONFIG . DND5E . damageTypes , true ) ;
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.dmgInteraction" ) , game . i18n . localize ( "DND5E.DamVuln" ) , 'data.traits.dv.value' , 'multiSelect' , CONFIG . DND5E . damageTypes , true ) ;
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.dmgInteraction" ) , game . i18n . localize ( "DND5E.ConImm" ) , 'data.traits.ci.value' , 'multiSelect' , CONFIG . DND5E . conditionTypes , true ) ;
this . addNpcFilter ( game . i18n . localize ( "CMPBrowser.dmgInteraction" ) , game . i18n . localize ( "CMPBrowser.dmgDealt" ) , 'damageDealt' , 'multiSelect' , CONFIG . DND5E . damageTypes , true ) ;
2020-06-12 15:29:59 +00:00
}
2019-12-29 16:54:57 +00:00
/ * *
* 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 ) ;
}
2020-06-12 15:29:59 +00:00
addFeatFilter ( category , label , path , type , possibleValues = null , valIsArray = false ) {
this . addFilter ( 'feat' , category , label , path , type , possibleValues , valIsArray ) ;
}
addItemFilter ( category , label , path , type , possibleValues = null , valIsArray = false ) {
this . addFilter ( 'item' , category , label , path , type , possibleValues , valIsArray ) ;
}
2019-12-29 16:54:57 +00:00
}
2021-02-05 02:50:25 +00:00
Hooks . on ( 'ready' , async ( ) => {
2020-04-30 17:07:00 +00:00
2019-12-29 16:54:57 +00:00
if ( game . compendiumBrowser === undefined ) {
2021-02-09 18:21:12 +00:00
game . compendiumBrowser = new CompendiumBrowser ( ) ;
2021-02-05 02:50:25 +00:00
//0.4.0 Defer loading content until we actually use the Compendium Browser
//A compromise approach would be better (periodic loading) except would still create the memory use problem
await game . compendiumBrowser . initialize ( ) ;
2019-12-29 16:54:57 +00:00
}
2021-02-05 02:50:25 +00:00
game . compendiumBrowser . addSpellFilters ( ) ;
game . compendiumBrowser . addFeatFilters ( ) ;
game . compendiumBrowser . addItemFilters ( ) ;
game . compendiumBrowser . addNpcFilters ( ) ;
2020-06-12 15:29:59 +00:00
2021-02-12 18:53:58 +00:00
} ) ;
Hooks . on ( "renderCompendiumBrowser" , CompendiumBrowser . afterRender ) ;