Foundry V10 compatibility (#40)

* Foundry V10 compatibility

* bumped number for download zip

* Fixes for v9 and set minimum compat to v9.266

Co-authored-by: Joe Vaughan <Joe.Vaughan@xoserve.com>
features_update
joevaughan 2022-09-14 20:50:08 +01:00 committed by GitHub
parent 2941513636
commit fd0c903be3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 333 additions and 131 deletions

View File

@ -53,17 +53,23 @@
0.7.2c 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"
5-Jan-2022 0.7.2d decorateNpc(): NPCs without all details or weirdly formed ones should default damageDealt to [] not 0
5-Jan-2022 0.7.2d decorateNpc(): NPCs without all details or weirdly formed ones should default damageDealt to [] not 0
13-Sep-2022 0.8.0 Compatibility with Foundry V10
Added check for Compendium Folders 'phantom' actors (#[tempEntity]) to filter out of NPC list
Fix to handle un-migrated compendiums (they get auto-excluded from the browser even if selected)
*/
const CMPBrowser = {
MODULE_NAME : "compendium-browser",
MODULE_VERSION : "0.7.2",
MODULE_VERSION : "0.8.0",
MAXLOAD : 500, //Default for the maximum number to load before displaying a message that you need to filter to see more
}
const STOP_SEARCH = 'StopSearchException';
// JV-080 - Adding a 'not-migrated' exception for v10 if the compendiums are not migrated to the new format (breaks e.g. npc compendium browser)
const NOT_MIGRATED = 'NotMigratedException';
class CompendiumBrowser extends Application {
static get defaultOptions() {
@ -101,6 +107,7 @@ class CompendiumBrowser extends Application {
"modules/compendium-browser/template/loading.html"
]);
this.hookCompendiumList();
//Reset the filters used in the dialog
@ -529,8 +536,8 @@ class CompendiumBrowser extends Application {
name : decoratedItem.name,
img: decoratedItem.img,
data : {
level : decoratedItem.data?.level,
components : decoratedItem.data?.components
level : decoratedItem.level,
components : decoratedItem.components
},
id: item5e.id
};
@ -639,53 +646,70 @@ class CompendiumBrowser extends Application {
let numNpcsLoaded = 0;
this.npcsLoaded = false;
// fields required for displaying and decorating NPCs
const requiredIndexFields = [
let requiredIndexFields = [
'name',
'img',
'data.details.cr',
'data.traits.size',
'data.details.type',
'items.type',
'items.data.damage.parts',
]
'items.system.damage.parts',
];
if (CompendiumBrowser.isFoundryV10Plus)
{
requiredIndexFields = [
'name',
'img',
'system.details.cr',
'system.traits.size',
'system.details.type',
'items.type',
'items.system.damage.parts',
]
}
// add any fields required for currently active filters
const indexFields = requiredIndexFields.concat(
Object.values(this.npcFilters.activeFilters).map(f => f.path)
);
let collectionName = "unknown";
try{
for (let pack of game.packs) {
if (pack.documentName == "Actor" && this.settings.loadedNpcCompendium[pack.collection].load) {
await pack.getIndex({fields: indexFields}).then(async content => {
content.reduce(function(actorsList, npc5e){
if (this.CurrentSeachNumber != seachNumber) {throw STOP_SEARCH;}
// JV-080: We're in a v10 foundry but the data doesn't have Actor#system - this means index fields won't have populated. Can't 'browse' like this.
if (CompendiumBrowser.isFoundryV10Plus && npc5e.system == undefined) {collectionName = pack.collection; throw NOT_MIGRATED;}
numNpcsLoaded = Object.keys(npcs).length;
if (maxLoad <= numNpcsLoaded) {
if (updateLoading) {updateLoading(numNpcsLoaded, true);}
throw STOP_SEARCH;
}
// JV-080: Special case. Compendium Folders creates Actors called #[CF_tempEntity] as placeholders for it's functions. Avoid them
if (npc5e.name != "#[CF_tempEntity]") {
const decoratedNpc = this.decorateNpc(npc5e);
const decoratedNpc = this.decorateNpc(npc5e);
if (decoratedNpc && this.passesFilter(decoratedNpc, this.npcFilters.activeFilters)){
if (decoratedNpc && this.passesFilter(decoratedNpc, this.npcFilters.activeFilters)){
actorsList[npc5e._id] = {
compendium : pack.collection,
name : decoratedNpc.name,
img: decoratedNpc.img,
displayCR : decoratedNpc.displayCR,
displaySize : decoratedNpc.displaySize,
displayType: this.getNPCType(decoratedNpc.data?.details?.type),
orderCR : decoratedNpc.data?.details?.cr,
orderSize : decoratedNpc.filterSize
};
actorsList[npc5e._id] = {
compendium : pack.collection,
name : decoratedNpc.name,
img: decoratedNpc.img,
displayCR : decoratedNpc.displayCR,
displaySize : decoratedNpc.displaySize,
displayType: decoratedNpc.displayType,
orderCR : decoratedNpc.orderCR,
orderSize : decoratedNpc.filterSize
};
}
}
return actorsList;
}.bind(this), npcs);
@ -701,6 +725,9 @@ class CompendiumBrowser extends Application {
if (e == STOP_SEARCH){
//breaking out
}
else if (e == NOT_MIGRATED){
console.log("Cannot browse compendium %s as it is not migrated to v10 format",collectionName);
}
else{
console.timeEnd("loadAndFilterNpcs");
throw e;
@ -753,6 +780,8 @@ class CompendiumBrowser extends Application {
}
}
/* Hook to load the first data */
static afterRender(cb, html) {
@ -980,12 +1009,31 @@ class CompendiumBrowser extends Application {
decorateItem(item5e) {
if (!item5e) return null;
//Decorate and then filter a compendium entry - returns null or the item
const item = item5e.data;
//JV-080 - v10 does away with item.data and everything is under #system but we want to decorate the first level of the item for return
let item = item5e;
//JV-080: Folding these down to base item.x level so we can have v10 Item#system coexist with v9- Item
if (CompendiumBrowser.isFoundryV10Plus) {
item.level = item5e.system?.level;
item.components = item5e.system?.components;
item.damage = item5e.system?.damage;
item.classes = item5e.system?.classes;
item.requirements = item5e.system?.requirements;
}
else {
item = item5e.data;
item.level = item5e.data?.level;
item.components = item5e.data?.level;
item.damage = item.data?.damage; // equivalent to: item5e.data.data.xxx - Ugh. The 'fold down' in v10 makes sense now.
item.classes = item.data?.classes;
item.requirements = item.data?.requirements;
}
// 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) {
if (item.damage && item.damage.parts.length > 0) {
for (let part of item.damage.parts) {
let type = part[1];
if (item.damageTypes.indexOf(type) === -1) {
item.damageTypes.push(type);
@ -999,13 +1047,13 @@ class CompendiumBrowser extends Application {
//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(',');
item.classes = classes.split(',');
} else {
//FIXME: unfoundSpells += cleanSpellName + ',';
//FIXME: unfoundSpells += cleanSpellName + ',';
}
} else if (item.type === 'feat' || item.type === 'class') {
// getting class
let reqString = item.data.requirements?.replace(/[0-9]/g, '').trim();
let reqString = item.requirements?.replace(/[0-9]/g, '').trim();
let matchedClass = [];
for (let c in this.subClasses) {
if (reqString && reqString.toLowerCase().indexOf(c) !== -1) {
@ -1024,8 +1072,11 @@ class CompendiumBrowser extends Application {
// getting uses/ressources status
item.usesRessources = item5e.hasLimitedUses;
item.hasSave = item5e.hasSave;
//JV-080: In v10 this is only a getter (and will already exist since item = item5e.system)
if (!CompendiumBrowser.isFoundryV10Plus) {
item.hasSave = item5e.hasSave;
}
} else {
// getting pack
let matchedPacks = [];
@ -1047,40 +1098,79 @@ class CompendiumBrowser extends Application {
}
decorateNpc(npc) {
//console.log('%c '+npc.name, 'background: white; color: red')
const decoratedNpc = npc;
try {
const decoratedNpc = npc;
//0.8.0: update for V10 to use actor.system instead of actor.data
let npcData = decoratedNpc.data;
// cr display
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;
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];
if (CompendiumBrowser.isFoundryV10Plus)
{
npcData = decoratedNpc.system;
}
// cr display
let cr = npcData.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;
else cr = Number(cr);
// JV-080: moved here because we want the OG number for orderCR but can't depend on .details.cr being present
decoratedNpc.orderCR = cr;
if (cr > 0 && cr < 1) cr = "1/" + (1 / cr);
decoratedNpc.displayCR = cr;
decoratedNpc.displaySize = 'unset';
decoratedNpc.filterSize = 2;
if (npcData.details) {
decoratedNpc.displayType = this.getNPCType(npcData.details.type);
}
else
{
decoratedNpc.displayType = 'unknown';
}
if (CONFIG.DND5E.actorSizes[npcData.traits.size] !== undefined) {
decoratedNpc.displaySize = CONFIG.DND5E.actorSizes[npcData.traits.size];
}
let npcSize;
if (CompendiumBrowser.isFoundryV10Plus) {
npcSize = decoratedNpc.system.traits.size;
} else {
npcSize = decoratedNpc.data.traits.size;
}
switch (npcSize) {
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
decoratedNpc.hasSpells = decoratedNpc.items?.type?.reduce((hasSpells, itemType) => hasSpells || itemType === 'spell', false);
let npcDamagePart;
if (CompendiumBrowser.isFoundryV10Plus) {
npcDamagePart = decoratedNpc.items?.system?.damage?.parts;
} else {
npcDamagePart = decoratedNpc.items?.data?.damage?.parts;
}
decoratedNpc.damageDealt = npcDamagePart ? npcDamagePart.filter(p => p?.length >= 2).map(p => p[1]) : [];
// JV-080: Think we have covered this off above now. We're making no assumptions and assuring that all decoratedNpc fields are now not 'undef'
//handle poorly constructed npc
//if (npcData.details?.type && !(npcData.details?.type instanceof Object)){
// npcData.details.type = {value: npcData.details?.type};
//}
return decoratedNpc;
}
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;
catch(e){
console.log('%c Error loading NPC:'+npc.name, 'background: white; color: red')
throw e;
}
// getting value for HasSpells and damage types
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]) : [];
//handle poorly constructed npc
if (decoratedNpc.data?.details?.type && !(decoratedNpc.data?.details?.type instanceof Object)){
decoratedNpc.data.details.type = {value: decoratedNpc.data?.details?.type};
}
return decoratedNpc;
}
getNPCType(type){
@ -1263,7 +1353,10 @@ class CompendiumBrowser extends Application {
//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"));
CompendiumBrowser.isFoundryV8Plus = (game.release?.generation >= 10) || (game.data.release?.generation >= 9) || (game.data.version?.startsWith("0.8"));
// If V10, we need to know this because in v10(+) Item5e#data and Actor#data have changed to Item5e#system and Actor#system
CompendiumBrowser.isFoundryV10Plus = (game.release?.generation >= 10);
}
saveSettings() {
@ -1297,45 +1390,93 @@ class CompendiumBrowser extends Application {
async addSpellFilters() {
// Spellfilters
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")
}
);
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"),
}, true
);
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');
//Foundry v10+ Item#data is now Item#system
if (CompendiumBrowser.isFoundryV10Plus) {
this.addSpellFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("DND5E.Source"), 'system.source', 'text');
this.addSpellFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.lvl"), 'system.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"), 'system.school', 'select', CONFIG.DND5E.spellSchools);
this.addSpellFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.castingTime"), 'system.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")
}
);
this.addSpellFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.spellType"), 'system.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"), 'system.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"),
}, true
);
this.addSpellFilter(game.i18n.localize("CMPBrowser.components"), game.i18n.localize("CMPBrowser.ritual"), 'system.components.ritual', 'bool');
this.addSpellFilter(game.i18n.localize("CMPBrowser.components"), game.i18n.localize("CMPBrowser.concentration"), 'system.components.concentration', 'bool');
this.addSpellFilter(game.i18n.localize("CMPBrowser.components"), game.i18n.localize("CMPBrowser.verbal"), 'system.components.vocal', 'bool');
this.addSpellFilter(game.i18n.localize("CMPBrowser.components"), game.i18n.localize("CMPBrowser.somatic"), 'system.components.somatic', 'bool');
this.addSpellFilter(game.i18n.localize("CMPBrowser.components"), game.i18n.localize("CMPBrowser.material"), 'system.components.material', 'bool');
}
else {
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")
}
);
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"),
}, true
);
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');
// Feature Filters
//Foundry v10+ Item#data is now Item#system
if (CompendiumBrowser.isFoundryV10Plus) {
this.addItemFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("DND5E.Source"), 'system.source', 'text');
}
else
{
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"),
@ -1356,13 +1497,27 @@ class CompendiumBrowser extends Application {
scholar: "Scholar's Pack",
}, true
);
this.addItemFilter("Game Mechanics", game.i18n.localize("DND5E.ItemActivationCost"), 'data.activation.type', 'select', CONFIG.DND5E.abilityActivationTypes);
if (CompendiumBrowser.isFoundryV10Plus) {
this.addItemFilter("Game Mechanics", game.i18n.localize("DND5E.ItemActivationCost"), 'system.activation.type', 'select', CONFIG.DND5E.abilityActivationTypes);
}
else {
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');
if (CompendiumBrowser.isFoundryV10Plus) {
this.addItemFilter("Item Subtype", "Weapon", 'system.weaponType', 'text', CONFIG.DND5E.weaponTypes);
this.addItemFilter("Item Subtype", "Equipment", 'system.armor.type', 'text', CONFIG.DND5E.equipmentTypes);
this.addItemFilter("Item Subtype", "Consumable", 'system.consumableType', 'text', CONFIG.DND5E.consumableTypes);
}
else {
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);
}
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);
//0.7.2c: Fix rarity encoding (uses camelcase names)
this.addItemFilter("Magic Items", "Rarity", 'data.rarity', 'select',
@ -1378,8 +1533,14 @@ class CompendiumBrowser extends Application {
async addFeatFilters() {
// Feature Filters
this.addFeatFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("DND5E.Source"), 'data.source', 'text');
//Foundry v10+ Item#data is now Item#system
if (CompendiumBrowser.isFoundryV10Plus) {
this.addFeatFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("DND5E.Source"), 'system.source', 'text');
}
else
{
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"),
@ -1397,7 +1558,12 @@ class CompendiumBrowser extends Application {
wizard: game.i18n.localize("CMPBrowser.wizard")
}, true);
this.addFeatFilter("Game Mechanics", game.i18n.localize("DND5E.ItemActivationCost"), 'data.activation.type', 'select', CONFIG.DND5E.abilityActivationTypes);
if (CompendiumBrowser.isFoundryV10Plus) {
this.addFeatFilter("Game Mechanics", game.i18n.localize("DND5E.ItemActivationCost"), 'system.activation.type', 'select', CONFIG.DND5E.abilityActivationTypes);
}
else {
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');
@ -1407,18 +1573,33 @@ class CompendiumBrowser extends Application {
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');
//Foundry 0.8.x: Creature type (data.details.type) is now a structure, so we check data.details.types.value instead
//Foundry v10+ Actor#data is now Actor#system
if (CompendiumBrowser.isFoundryV10Plus) {
this.addNpcFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("DND5E.Source"), 'system.details.source', 'text');
this.addNpcFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.size"), 'system.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"), 'system.resources.legact.max', 'bool');
this.addNpcFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.hasLegRes"), 'system.resources.legres.max', 'bool');
this.addNpcFilter(game.i18n.localize("CMPBrowser.general"), game.i18n.localize("CMPBrowser.cr"), 'system.details.cr', 'numberCompare');
}
else {
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');
}
let npcDetailsPath;
if (CompendiumBrowser.isFoundryV8Plus) {
//Foundry v10+ Actor#data is now Actor#system
if (CompendiumBrowser.isFoundryV10Plus) {
npcDetailsPath = "system.details.type.value";
}
//Foundry 0.8.x: Creature type (data.details.type) is now a structure, so we check data.details.types.value instead
else if (CompendiumBrowser.isFoundryV8Plus) {
npcDetailsPath = "data.details.type.value";
} else {//0.7.x
}
else {//0.7.x
npcDetailsPath = "data.details.type";
}
@ -1438,20 +1619,37 @@ class CompendiumBrowser extends Application {
plant: game.i18n.localize("CMPBrowser.plant"),
undead: game.i18n.localize("CMPBrowser.undead")
});
//Foundry v10+ Actor#data is now Actor#system
if (CompendiumBrowser.isFoundryV10Plus) {
this.addNpcFilter(game.i18n.localize("CMPBrowser.abilities"), game.i18n.localize("DND5E.AbilityStr"), 'system.abilities.str.value', 'numberCompare');
this.addNpcFilter(game.i18n.localize("CMPBrowser.abilities"), game.i18n.localize("DND5E.AbilityDex"), 'system.abilities.dex.value', 'numberCompare');
this.addNpcFilter(game.i18n.localize("CMPBrowser.abilities"), game.i18n.localize("DND5E.AbilityCon"), 'system.abilities.con.value', 'numberCompare');
this.addNpcFilter(game.i18n.localize("CMPBrowser.abilities"), game.i18n.localize("DND5E.AbilityInt"), 'system.abilities.int.value', 'numberCompare');
this.addNpcFilter(game.i18n.localize("CMPBrowser.abilities"), game.i18n.localize("DND5E.AbilityWis"), 'system.abilities.wis.value', 'numberCompare');
this.addNpcFilter(game.i18n.localize("CMPBrowser.abilities"), game.i18n.localize("DND5E.AbilityCha"), 'system.abilities.cha.value', 'numberCompare');
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);
this.addNpcFilter(game.i18n.localize("CMPBrowser.dmgInteraction"), game.i18n.localize("DND5E.DamImm"), 'system.traits.di.value', 'multiSelect', CONFIG.DND5E.damageTypes, true);
this.addNpcFilter(game.i18n.localize("CMPBrowser.dmgInteraction"), game.i18n.localize("DND5E.DamRes"), 'system.traits.dr.value', 'multiSelect', CONFIG.DND5E.damageTypes, true);
this.addNpcFilter(game.i18n.localize("CMPBrowser.dmgInteraction"), game.i18n.localize("DND5E.DamVuln"), 'system.traits.dv.value', 'multiSelect', CONFIG.DND5E.damageTypes, true);
this.addNpcFilter(game.i18n.localize("CMPBrowser.dmgInteraction"), game.i18n.localize("DND5E.ConImm"), 'system.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);
}
else
{
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);
}
}
/**
@ -1517,4 +1715,4 @@ Hooks.on('ready', async () => {
});
Hooks.on("renderCompendiumBrowser", CompendiumBrowser.afterRender);
Hooks.on("renderCompendiumBrowser", CompendiumBrowser.afterRender);

View File

@ -2,7 +2,7 @@
"name": "compendium-browser",
"title": "Compendium Browser",
"description": "<p>Easily browse and filter spells, feats, items, and npcs loaded from compendia!</p><p>Now works with Foundry 0.8 and 9 only</p><strong>NEW! Compendium Browser is faster and better-behaved;</strong> it no longer loads all the compendia into memory on start-up (which sometimes hung servers because of memory or CPU requirements). Instead, it filters and loads on-demand, as well as giving you a Module Setting to control how many rows are loaded at a time.<br>Changes in 0.7.0: Supports Foundry 0.8+ only; faster searches using Foundry 0.8 queries!",
"version": "0.7.2",
"version": "0.8.0",
"author": "Spetzel#0103",
"authors": [
{
@ -15,6 +15,10 @@
},
{
"name": "Felix#6196"
},
{
"name": "DrVeoj#4863",
"url": "https://github.com/joevaughan"
}
],
"systems": ["dnd5e"],
@ -55,9 +59,9 @@
],
"url": "https://github.com/League-of-Foundry-Developers/compendium-browser",
"manifest": "https://github.com/League-of-Foundry-Developers/compendium-browser/releases/latest/download/module.json",
"download": "https://github.com/League-of-Foundry-Developers/compendium-browser/releases/download/v0.7.2/compendium-browser.zip",
"minimumCoreVersion": "0.8.6",
"compatibleCoreVersion": "9",
"download": "https://github.com/League-of-Foundry-Developers/compendium-browser/releases/download/v0.8.0/compendium-browser.zip",
"minimumCoreVersion": "0.9.266",
"compatibleCoreVersion": "10",
"allowBugReporter": true,
"bugs": "https://github.com/League-of-Foundry-Developers/compendium-browser/issues",
"readme": "https://github.com/League-of-Foundry-Developers/compendium-browser/blob/master/README.md",