diff --git a/CHANGELOG.md b/CHANGELOG.md index 15793ea..74eeb2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +## Version 10.6 + +Added a check to make sure adding XP only adds to tokens that are linked to an Actor. + +Fixed issues calculating XP values from Actors that didn't have a max or value for xp. + +Removed 'melee', 'condition' and 'spellcastEntry' from items lootable will collect. + +Fixed issues collecting currency from Actors in PF2E. + +Fixed issue changing if you want the loot entry to be automatically opened. + +Fixed issue with Lootable name + +Fixed issue with getting the default Loot Entry name. + +Fixed issue with the loot being added to loot entries not correct, and not able to be assigned to players. + +Fixed issues with saving throws when a token with tools is addded or removed from the Dialog. + +Added option to use handlebars in flavor field when requesting rolls in MATT. + +Added the option to select lore rolls in PF2E. + +Fixed resizing issues with the saving throw dialog. + ## Version 10.5 Fixed issue with Contested Rolls not responding to the ctrl or alt keys. diff --git a/apps/assignxp.js b/apps/assignxp.js index 75991cf..13a5940 100644 --- a/apps/assignxp.js +++ b/apps/assignxp.js @@ -20,7 +20,7 @@ export class AssignXPApp extends Application { //get the actors let monsters = []; for (let combatant of entity.combatants) { - if (combatant.token?.disposition == 1 && combatant.actor && combatant.actor.hasPlayerOwner) { + if (combatant.token?.disposition == 1 && combatant.token?.actorLink && combatant.actor && combatant.actor.hasPlayerOwner) { let actor = (combatant.actor.isPolymorphed ? game.actors.find(a => a.id == combatant.actor.getFlag(game.system.id, 'originalActor')) : combatant.actor); this.actors.push({ actor: actor, @@ -110,7 +110,7 @@ export class AssignXPApp extends Application { const aXP = MonksTokenBar.system.getXP(a.actor); const bXP = MonksTokenBar.system.getXP(b.actor); - let value = (MonksTokenBar.system.getLevel(a.actor) + (aXP.value / aXP.max)) - (MonksTokenBar.system.getLevel(b.actor) + (bXP.value / bXP.max)); + let value = (MonksTokenBar.system.getLevel(a.actor) + ((aXP?.value ?? 0) / (aXP?.max ?? 1))) - (MonksTokenBar.system.getLevel(b.actor) + ((bXP?.value ?? 0) / (bXP?.max ?? 1))); log(a.actor.name, b.actor.name, value); return value; }); diff --git a/apps/lootables.js b/apps/lootables.js index 015b21b..d65bc31 100644 --- a/apps/lootables.js +++ b/apps/lootables.js @@ -69,7 +69,7 @@ export class LootablesApp extends Application { else result = item.system.armor.type != 'natural'; } else - result = !(['class', 'spell', 'feat', 'action', 'lore'].includes(item.type)); + result = !(['class', 'spell', 'feat', 'action', 'lore', 'melee', 'condition', 'spellcastingEntry'].includes(item.type)); return result; }).map(i => { @@ -103,8 +103,12 @@ export class LootablesApp extends Application { entry.items = entry.items.sort((a, b) => { return a.name.localeCompare(b.name); }); let actorCurrency = (document.actorData.currency || document.actor.system.currency); - for (let [key, value] of Object.entries(actorCurrency)) { - this.currency[key] = (this.currency[key] ?? 0) + value; + if (actorCurrency) { + for (let [key, value] of Object.entries(actorCurrency)) { + let val = (value.value ?? value); + if (isNaN(val)) val = 0; + this.currency[key] = (this.currency[key] ?? 0) + val; + } } }; this.entries = this.entries.sort((a, b) => { return a.name.localeCompare(b.name); }); @@ -207,7 +211,7 @@ export class LootablesApp extends Application { $('.delete-entry', html).click(this.deleteEntry.bind(this)); $('[name="create-canvas-object"]', html).click(() => { this.createCanvasObject = $('[name="create-canvas-object"]', html).prop("checked"); }); - $('[name="open-loot"]', html).click(() => { this.openLoot = $('[name="open-loot"]', html).prop("checked"); }); + $('[name="open-loot"]', html).change(() => { this.openLoot = $('[name="open-loot"]', html).val(); }); $('[name="clear-items"]', html).click(() => { this.clearItems = $('[name="clear-items"]', html).prop("checked"); }); $('[name="entity-name"]', html).blur(() => { this.entityName = $('[name="entity-name"]', html).val(); }); $('[name="loot-entity"]', html).on("change", this.changeEntity.bind(this)); @@ -475,7 +479,7 @@ export class LootablesApp extends Application { else if (entity == "convert") return "Convert tokens"; else if (entity) - return `Creating ${entity.documentClass.documentName == "JournalEntry" ? "Journal Entry" : "Actor"} in the root folder`; + return `Creating ${(entity?.documentClass?.documentName || entity?.parent?.documentClass?.documentName) == "JournalEntry" ? "Journal Entry" : "Actor"} in the root folder`; else return "Unknown"; } @@ -485,11 +489,15 @@ export class LootablesApp extends Application { let lootSheet = setting('loot-sheet'); let collection = (this.isLootActor(lootSheet) ? game.actors : game.journal); - let documents = (entity == undefined ? collection.filter(e => e.folder == undefined) : entity.contents || entity.pages); - let previous = documents.map((e, i) => - parseInt(e.name.replace('Loot Entry ', '').replace('(', '').replace(')', '')) || (i + 1) - ).sort((a, b) => { return b - a; }); - let num = (previous.length ? previous[0] + 1 : 1); + let num = 0; + + let documents = (entity == undefined ? collection.filter(e => e.folder == undefined) : entity.contents || entity.pages || entity.parent.contents || entity.parent.pages); + if (documents && documents.length) { + let previous = documents.map((e, i) => + parseInt(e.name.replace('Loot Entry ', '').replace('(', '').replace(')', '')) || (i + 1) + ).sort((a, b) => { return b - a; }); + num = (previous.length ? previous[0] + 1 : 1); + } name = `${i18n("MonksTokenBar.LootEntry")}${(num > 1 ? ` (${num})` : '')}`; return name; @@ -697,30 +705,33 @@ export class LootablesApp extends Application { ptAvg.y += entry.token.y; ptAvg.count++; - let loot = entry.items.filter(i => i.quantity != "0"); - for (let item of loot) { + let loots = entry.items.filter(i => i.quantity != "0"); + for (let loot of loots) { + let item = loot.data; let sysPrice = game.MonksEnhancedJournal.getSystemPrice(item); let price = game.MonksEnhancedJournal.getPrice(sysPrice); - if (typeof item.quantity == "string" && item.quantity.indexOf("d") != -1) { - let r = new Roll(item.quantity); + if (typeof loot.quantity == "string" && loot.quantity.indexOf("d") != -1) { + let r = new Roll(loot.quantity); await r.evaluate({ async: true }); - item.quantity = r.total; + loot.quantity = r.total; } else - item.quantity = parseInt(item.quantity); + loot.quantity = parseInt(loot.quantity); - if (isNaN(item.quantity)) - item.quantity = 1; + if (isNaN(loot.quantity)) + loot.quantity = 1; item._id = randomID(); - setProperty(item, "flags.monks-enhanced-journal.quantity", item.quantity); + setProperty(item, "flags.monks-enhanced-journal.quantity", loot.quantity); setProperty(item, "flags.monks-enhanced-journal.price", price.value + " " + price.currency); - setProperty(item, "flags.monks-enhanced-journal.from", item.from); + setProperty(item, "flags.monks-enhanced-journal.from", loot.from); if (lootSheet !== 'monks-enhanced-journal') { - setProperty(item, "system.quantity", item.quantity); + //+++ Need to set to correct system quantity + setProperty(item, "system.quantity", loot.quantity); } + + items.push(item); } - items = items.concat(loot); } if (this.isLootActor(lootSheet)) { diff --git a/apps/savingthrow.js b/apps/savingthrow.js index 53dffc1..65f3a0d 100644 --- a/apps/savingthrow.js +++ b/apps/savingthrow.js @@ -104,6 +104,7 @@ export class SavingThrowApp extends Application { this.entries = this.entries.concat(MonksTokenBar.getTokenEntries(tokens)); this.render(true); + window.setTimeout(() => { this.setPosition({ height: 'auto' }); }, 100); } changeTokens(e) { let type = e.target.dataset.type; @@ -143,7 +144,8 @@ export class SavingThrowApp extends Application { this.entries.splice(idx, 1); } $(`li[data-item-id="${id}"]`, this.element).remove(); - //this.render(true); + this.render(true); // Need this in case the token has tools + window.setTimeout(() => { this.setPosition({ height: 'auto' }); }, 100); } async requestRoll(roll) { diff --git a/lang/en.json b/lang/en.json index b353477..1c0be98 100644 --- a/lang/en.json +++ b/lang/en.json @@ -59,6 +59,7 @@ "MonksTokenBar.TokenNoActorAttrs": "token has no actor to use for additional attributes", "MonksTokenBar.ActorNoRollFunction": ": Could not find function to roll", "MonksTokenBar.ActorNoTool": ": Does not have that tool", + "MonksTokenBar.ActorNoLore": ": Does not know that Lore", "MonksTokenBar.UnknownSystem": "You're not using a system that Monk's Tokenbar supports.", "MonksTokenBar.ActorNotCombatant": "Actor is not part of combat to roll initiative", @@ -156,7 +157,7 @@ "MonksTokenBar.auto-gold-cr.hint": "Automatically calculate the gold by CR whenever the loot sheet is shown", "MonksTokenBar.add-advantage-buttons.name": "Add Advantage Buttons", "MonksTokenBar.add-advantage-buttons.hint": "Add advantage and disadvantage buttons to the token roll button", - "MonksTokenBar.minimum-ownership.name": "Minimum PLayer Permission", + "MonksTokenBar.minimum-ownership.name": "Minimum Player Permission", "MonksTokenBar.minimum-ownership.hint": "The minimum permission a player needs to see a token", "MonksTokenBar.XP": "XP", diff --git a/module.json b/module.json index ff1ffc1..c9e0b2a 100644 --- a/module.json +++ b/module.json @@ -1,7 +1,7 @@ { "title": "Monk's TokenBar", "description": "Add a bar with all the current player tokens. Limit movement, roll saving throws, assign XP.", - "version": "10.5", + "version": "10.6", "authors": [ { "name": "IronMonk", @@ -64,7 +64,7 @@ "css/tokenbar.css" ], "url": "https://github.com/ironmonk88/monks-tokenbar", - "download": "https://github.com/ironmonk88/monks-tokenbar/archive/10.5.zip", + "download": "https://github.com/ironmonk88/monks-tokenbar/archive/10.6.zip", "manifest": "https://github.com/ironmonk88/monks-tokenbar/releases/latest/download/module.json", "bugs": "https://github.com/ironmonk88/monks-tokenbar/issues", "allowBugReporter": true, diff --git a/monks-tokenbar.js b/monks-tokenbar.js index b311ba6..8636431 100644 --- a/monks-tokenbar.js +++ b/monks-tokenbar.js @@ -309,8 +309,10 @@ export class MonksTokenBar { let message = game.messages.get(data.msgid); const revealDice = game.dice3d ? game.settings.get("dice-so-nice", "immediatelyDisplayChatMessages") : true; for (let response of data.response) { - let r = Roll.fromData(response.roll); - response.roll = r; + if (response.roll) { + let r = Roll.fromData(response.roll); + response.roll = r; + } } if (data.type == 'savingthrow') SavingThrow.updateMessage(data.response, message, revealDice); @@ -499,7 +501,7 @@ export class MonksTokenBar { let curPermission = entry.actor?.ownership ?? {}; let tokPermission = token.actor?.ownership ?? {}; let ownedUsers = Object.keys(curPermission).filter(k => curPermission[k] === 3); - allowNpc = ownedUsers.some(u => tokPermission[u] === 3 && !game.users.get(u).isGM) + allowNpc = ownedUsers.some(u => tokPermission[u] === 3 && !game.users?.get(u).isGM) && curCombat.turns.every(t => { return t.tokenId !== token.id; }); } @@ -604,7 +606,7 @@ export class MonksTokenBar { return o.id == (request.type || request.key); }); let req = (rt?.groups && rt?.groups[request.key]); - let flavor = i18n(req || rt?.text); + let flavor = i18n(req?.label || req || rt?.text); switch (game.i18n.lang) { case "pt-BR": case "es": @@ -1227,7 +1229,7 @@ Hooks.on("setupTileActions", (app) => { }, group: 'monks-tokenbar', fn: async (args = {}) => { - const { action, tile } = args; + const { action, tile, tokens, userid, value, method, change } = args; let entities = await game.MonksActiveTiles.getEntities(args); //if (entities.length == 0) @@ -1239,7 +1241,27 @@ Hooks.on("setupTileActions", (app) => { let type = (parts.length > 1 ? parts[0] : ''); let key = (parts.length > 1 ? parts[1] : parts[0]); - let savingthrow = new SavingThrowApp(MonksTokenBar.getTokenEntries(entities), { rollmode: action.data.rollmode, request: [{ type, key }], dc: action.data.dc, flavor: action.data.flavor }); + let flavor = action.data.flavor; + + if (flavor && flavor.includes("{{")) { + let context = { + actor: tokens[0]?.actor?.toObject(false), + token: tokens[0]?.toObject(false), + speaker: tokens[0], + tile: tile.toObject(false), + entities: entities, + user: game.users.get(userid), + value: value, + scene: canvas.scene, + method: method, + change: change + }; + + const compiled = Handlebars.compile(flavor); + flavor = compiled(context, { allowProtoMethodsByDefault: true, allowProtoPropertiesByDefault: true }).trim(); + } + + let savingthrow = new SavingThrowApp(MonksTokenBar.getTokenEntries(entities), { rollmode: action.data.rollmode, request: [{ type, key }], dc: action.data.dc, flavor:flavor }); savingthrow['active-tiles'] = { id: args._id, tile: args.tile.uuid, action: action }; if (action.data.silent === true) { let msg = await savingthrow.requestRoll(); @@ -1348,7 +1370,7 @@ Hooks.on("setupTileActions", (app) => { }, group: 'monks-tokenbar', fn: async (args = {}) => { - const { action, tile } = args; + const { action, tile, tokens, userid, value, method, change } = args; let entities1 = await game.MonksActiveTiles.getEntities(args, "tokens", action.data.entity1); let entities2 = await game.MonksActiveTiles.getEntities(args, "tokens", action.data.entity2); @@ -1366,9 +1388,29 @@ Hooks.on("setupTileActions", (app) => { let request1 = mergeObject((MonksTokenBar.getTokenEntries([entity1])[0] || {}), { request: action.data.request1 }); let request2 = mergeObject((MonksTokenBar.getTokenEntries([entity2])[0] || {}), { request: action.data.request2 }); + let flavor = action.data.flavor; + + if (flavor && flavor.includes("{{")) { + let context = { + actor: tokens[0]?.actor?.toObject(false), + token: tokens[0]?.toObject(false), + speaker: tokens[0], + tile: tile.toObject(false), + entities: [entity1, entity2], + user: game.users.get(userid), + value: value, + scene: canvas.scene, + method: method, + change: change + }; + + const compiled = Handlebars.compile(flavor); + flavor = compiled(context, { allowProtoMethodsByDefault: true, allowProtoPropertiesByDefault: true }).trim(); + } + let contested = new ContestedRollApp( [request1, request2], - { rollmode: action.data.rollmode, request: action.data.request, flavor: action.data.flavor }); + { rollmode: action.data.rollmode, request: action.data.request, flavor: flavor }); contested['active-tiles'] = { id: args._id, tile: args.tile.uuid, action: action }; if (action.data.silent === true) { let msg = await contested.requestRoll(); diff --git a/systems/pf2e-rolls.js b/systems/pf2e-rolls.js index 8a54285..4e928a4 100644 --- a/systems/pf2e-rolls.js +++ b/systems/pf2e-rolls.js @@ -45,6 +45,35 @@ export class PF2eRolls extends BaseRolls { return 'ability:str'; } + dynamicRequest(entries) { + let lore = {}; + //get the first token's tools + for (let item of entries[0].token.actor?.items) { + if (item.type == 'lore') { + let sourceID = item.id; + //let toolid = item.name.toLowerCase().replace(/[^a-z]/gi, ''); + lore[sourceID] = item.name; + } + } + //see if the other tokens have these tools + if (Object.keys(lore).length > 0) { + for (let i = 1; i < entries.length; i++) { + for (let [k, v] of Object.entries(lore)) { + let _lore = entries[i].token.actor.items.find(l => { + return l.type == 'lore' && l.id == k; + }); + if (_lore == undefined) + delete lore[k]; + } + } + } + + if (Object.keys(lore).length == 0) + return; + + return [{ id: 'lore', text: 'Lore', groups: lore }]; + } + getXP(actor) { return actor?.system.details.xp; } @@ -145,10 +174,20 @@ export class PF2eRolls extends BaseRolls { } else rollfn = actor.rollSkill; } + else if (request.type == 'lore') { + let lore = actor.items.find(i => { return i.id == request.key; }); + if (lore != undefined) { + let slug = lore.name.slugify(); + opts = actor.getRollOptions(["all", "skill-check", slug]); + rollfn = actor.skills[slug].check.roll; + actor = actor.skills[slug].check; + } else + return { id: id, error: true, msg: i18n("MonksTokenBar.ActorNoLore") }; + } if (rollfn != undefined) { try { - if (request.type != 'skill') + if (request.type != 'skill' && request.type != 'lore') return rollfn.call(actor, e, opts).then((roll) => { return callback(roll); }).catch(() => { return { id: id, error: true, msg: i18n("MonksTokenBar.UnknownError") } }); else { return new Promise(function (resolve, reject) { diff --git a/systems/sfrpg-rolls.js b/systems/sfrpg-rolls.js index ceff54e..b5b7761 100644 --- a/systems/sfrpg-rolls.js +++ b/systems/sfrpg-rolls.js @@ -109,7 +109,6 @@ export class SFRPGRolls extends BaseRolls { } else return { id: id, error: true, msg: actor.name + i18n("MonksTokenBar.ActorNoRollFunction") }; */ - let rollfn = null; let options = { rollMode: rollMode, fastForward: fastForward, chatMessage: false, event: e }; let context = actor; @@ -126,7 +125,9 @@ export class SFRPGRolls extends BaseRolls { if (rollfn != undefined) { try { return new Promise(function (resolve, reject) { - options.onClose = function (roll) { resolve(callback(roll)); }; + options.onClose = function (roll) { + resolve(callback(roll)); + }; rollfn.call(context, request.key, options); }).catch(() => { return { id: id, error: true, msg: i18n("MonksTokenBar.UnknownError") } }); } catch{ diff --git a/templates/savingthrow.html b/templates/savingthrow.html index f4eff16..cd5e4a2 100644 --- a/templates/savingthrow.html +++ b/templates/savingthrow.html @@ -1,7 +1,7 @@ -