Skip to content

Commit

Permalink
Merge pull request #1 from DanielBoettner/multitable
Browse files Browse the repository at this point in the history
* internal changes (class name, notifications prefix)
  • Loading branch information
DanielBoettner authored May 22, 2021
2 parents dc0efb1 + 4b34e8a commit 444583f
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 27 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ As LootSheetNPC5e adds the capability and permission handling for players to act

Also recommended is the use of [better rolltables](https://github.com/ultrakorne/better-rolltables), it will greatly improve you experience when working with rolltables.

Also recommended is the use of [better rolltables](https://github.com/ultrakorne/better-rolltables), it will greatly improve you experience when working with rolltables.

### Settings
![image](https://github.com/DanielBoettner/fvtt-loot-populator-npc-5e/blob/master/image.png)

Expand All @@ -21,6 +23,7 @@ The right sheet is directly from the actor.
### Features

Allows you to have automated random loot on NPCs when dropping them on the scene.
If installed, it will make use of better rolltables.

### Compatibility:
- Tested with FVTT v0.7.9 and the DND5E system only.
Expand Down
7 changes: 4 additions & 3 deletions hooks/onCreateToken.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import {populateLoot} from '../scripts/populateLoot.js';
import {LootPopulator} from '../scripts/populateLoot.js';

export let initHooks = () => {
Hooks.on('createToken', (scene, data, options, userId) => {

if(! game.settings.get("lootpopulatornpc5e","autoPopulateTokens"))
if(! game.settings.get("lootpopulatornpc5e","autoPopulateTokens"))
return;

const actor = game.actors.get(data.actorId);

if (!actor || (data.actorLink)) // Don't for linked token
return data;

populateLoot.generateLoot(scene, data);
let lootPopulator = new LootPopulator();
lootPopulator.generateLoot(scene, data);
});
}
20 changes: 20 additions & 0 deletions lootpopulatornpc5e.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ Hooks.once('ready', () => {
type: Boolean
});

if (typeof game.betterTables.generateLoot === "function") {
game.settings.register("lootpopulatornpc5e", "useBetterRolltables", {
name: "Use better rolltables",
hint: "If installed make use of better rolltables",
scope: "world",
config: true,
default: false,
type: Boolean
});
}

let MyRolltables = Object.assign(...game.tables.entities.map(table => ({[table.name]: table.name})));
game.settings.register("lootpopulatornpc5e", "fallbackRolltable", {
name: "fallbackRolltable",
Expand Down Expand Up @@ -60,4 +71,13 @@ Hooks.once('ready', () => {
default: true,
type: Boolean
});

game.settings.register("lootpopulatornpc5e", "adjustCurrencyWithCR", {
name: "Adjust added currency with CR",
hint: "If enabled added currency (via better rolltables) will be slightly adjusted by the CR (rollFormula + rounden up CR).",
scope: "world",
config: true,
default: false,
type: Boolean
});
});
2 changes: 1 addition & 1 deletion module.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "lootpopulatornpc5e",
"title": "LootPopulator NPC 5e",
"description": "A module to auto populate loot on NPCs",
"version": "0.0.1",
"version": "0.0.2",
"minimumCoreVersion": "0.7.9",
"compatibleCoreVersion": "0.7.9",
"author": "Daniel Böttner",
Expand Down
111 changes: 88 additions & 23 deletions scripts/populateLoot.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import Item5e from "../../../systems/dnd5e/module/item/entity.js";

export class populateLoot {
export class LootPopulator {
constructor() {
return this;
}
this.moduleNamespace = "lootpopulatornpc5e";
return this;
}

static async generateLoot(scene, data) {
async generateLoot(scene, data) {
//instead of the main actor we want/need the actor of the token.
const tokenId = data._id;
const token = canvas.tokens.get(tokenId);
const actor = token.actor;

const ls5e_moduleNamespace = "lootsheetnpc5e";
const moduleNamespace = "lootpopulatornpc5e";
const rolltableName = actor.getFlag(ls5e_moduleNamespace, "rolltable") || game.settings.get(moduleNamespace,"fallbackRolltable");
const shopQtyFormula = actor.getFlag(ls5e_moduleNamespace, "shopQty") || game.settings.get(moduleNamespace,"fallbackShopQty") || "1";
const itemQtyFormula = actor.getFlag(ls5e_moduleNamespace, "itemQty") || game.settings.get(moduleNamespace,"fallbackItemQty") || "1";
const itemQtyLimit = actor.getFlag(ls5e_moduleNamespace, "itemQtyLimit") || game.settings.get(moduleNamespace,"fallbackItemQtyLimit") || "0";
const rolltableName = actor.getFlag(ls5e_moduleNamespace, "rolltable") || this._getSetting("fallbackRolltable");
const shopQtyFormula = actor.getFlag(ls5e_moduleNamespace, "shopQty") || this._getSetting("fallbackShopQty") || "1";
const itemQtyFormula = actor.getFlag(ls5e_moduleNamespace, "itemQty") || this._getSetting("fallbackItemQty") || "1";
const itemQtyLimit = actor.getFlag(ls5e_moduleNamespace, "itemQtyLimit") || this._getSetting("fallbackItemQtyLimit") || "0";
const itemOnlyOnce = actor.getFlag(ls5e_moduleNamespace, "itemOnlyOnce") || false;
const reducedVerbosity = game.settings.get(moduleNamespace, "reduceUpdateVerbosity") || true;
const reducedVerbosity = this._getSetting("reduceUpdateVerbosity") || true;
let shopQtyRoll = new Roll(shopQtyFormula);

shopQtyRoll.roll();
Expand All @@ -30,12 +30,14 @@ export class populateLoot {
let rolltable = game.tables.getName(rolltableName);

if (!rolltable) {
return ui.notifications.error(`No Rollable Table found with name "${rolltableName}".`);
return ui.notifications.error(this.moduleNamespace + `: No Rollable Table found with name "${rolltableName}".`);
}



if (itemOnlyOnce) {
if (rolltable.results.length < shopQtyRoll.total) {
return ui.notifications.error(`Cannot create a loot with ${shopQtyRoll.total} unqiue entries if the rolltable only contains ${rolltable.results.length} items`);
return ui.notifications.error(this.moduleNamespace + `: Cannot create a loot with ${shopQtyRoll.total} unqiue entries if the rolltable only contains ${rolltable.results.length} items`);
}
}

Expand All @@ -62,38 +64,38 @@ export class populateLoot {
let itemQtyRoll = new Roll(itemQtyFormula);
itemQtyRoll.roll();

console.log(`Loot Populator 5e| Adding ${itemQtyRoll.total} x ${newItem.name}`)
console.log(this.moduleNamespace + `: Adding ${itemQtyRoll.total} x ${newItem.name}`)

let existingItem = actor.items.find(item => item.data.name == newItem.name);

if (existingItem === null) {
await actor.createEmbeddedEntity("OwnedItem", newItem);
console.log(`Loot Populator 5e | ${newItem.name} does not exist.`);
console.log(this.moduleNamespace + `: ${newItem.name} does not exist.`);
existingItem = await actor.items.find(item => item.data.name == newItem.name);

if (itemQtyLimit > 0 && Number(itemQtyLimit) < Number(itemQtyRoll.total)) {
await existingItem.update({ "data.quantity": itemQtyLimit });
if (!reducedVerbosity) ui.notifications.info(`Added new ${itemQtyLimit} x ${newItem.name}.`);
if (!reducedVerbosity) ui.notifications.info(this.moduleNamespace + `: Added new ${itemQtyLimit} x ${newItem.name}.`);
} else {
await existingItem.update({ "data.quantity": itemQtyRoll.total });
if (!reducedVerbosity) ui.notifications.info(`Added new ${itemQtyRoll.total} x ${newItem.name}.`);
if (!reducedVerbosity) ui.notifications.info(this.moduleNamespace + `: Added new ${itemQtyRoll.total} x ${newItem.name}.`);
}
} else {
console.log(`Loot Populator 5e | Item ${newItem.name} exists.`);
console.log(this.moduleNamespace + `: Item ${newItem.name} exists.`);

let newQty = Number(existingItem.data.data.quantity) + Number(itemQtyRoll.total);

if (itemQtyLimit > 0 && Number(itemQtyLimit) === Number(existingItem.data.data.quantity)) {
if (!reducedVerbosity) ui.notifications.info(`${newItem.name} already at maximum quantity (${itemQtyLimit}).`);
if (!reducedVerbosity) ui.notifications.info(this.moduleNamespace + `: ${newItem.name} already at maximum quantity (${itemQtyLimit}).`);
}
else if (itemQtyLimit > 0 && Number(itemQtyLimit) < Number(newQty)) {
//console.log("Exceeds existing quantity, limiting");
await existingItem.update({ "data.quantity": itemQtyLimit });

if (!reducedVerbosity) ui.notifications.info(`Added additional quantity to ${newItem.name} to the specified maximum of ${itemQtyLimit}.`);
if (!reducedVerbosity) ui.notifications.info(this.moduleNamespace + `: Added additional quantity to ${newItem.name} to the specified maximum of ${itemQtyLimit}.`);
} else {
await existingItem.update({ "data.quantity": newQty });
if (!reducedVerbosity) ui.notifications.info(`Added additional ${itemQtyRoll.total} quantity to ${newItem.name}.`);
if (!reducedVerbosity) ui.notifications.info(this.moduleNamespace + `: Added additional ${itemQtyRoll.total} quantity to ${newItem.name}.`);
}
}
}
Expand Down Expand Up @@ -165,7 +167,7 @@ export class populateLoot {
newItem = await items.getEntity(rolltable.results[index].resultId);
}
if (!newItem || newItem === null) {
return ui.notifications.error(`No item found "${rolltable.results[index].resultId}".`);
return ui.notifications.error(this.moduleNamespace + `: No item found "${rolltable.results[index].resultId}".`);
}

if (newItem.type === "spell") {
Expand All @@ -177,12 +179,75 @@ export class populateLoot {

if (itemQtyLimit > 0 && Number(itemQtyLimit) < Number(itemQtyRoll.total)) {
await existingItem.update({ "data.quantity": itemQtyLimit });
if (!reducedVerbosity) ui.notifications.info(`Added new ${itemQtyLimit} x ${newItem.name}.`);
if (!reducedVerbosity) ui.notifications.info(this.moduleNamespace + `: Added new ${itemQtyLimit} x ${newItem.name}.`);
} else {
await existingItem.update({ "data.quantity": itemQtyRoll.total });
if (!reducedVerbosity) ui.notifications.info(`Added new ${itemQtyRoll.total} x ${newItem.name}.`);
if (!reducedVerbosity) ui.notifications.info(this.moduleNamespace + `: Added new ${itemQtyRoll.total} x ${newItem.name}.`);
}
}
}

if (this._getSetting('useBetterRolltables')){
if (!reducedVerbosity) ui.notifications.info(this.moduleNamespace + `: using BetterRolltables`);

let lootCurrencyString = rolltable.getFlag('better-rolltables','table-currency-string');
if(lootCurrencyString){
await this.addCurrenciesToActor(actor, this._generateCurrency(lootCurrencyString));
}
}

}

async addCurrenciesToActor(actor, lootCurrency) {
let currencyData = duplicate(actor.data.data.currency),
cr = actor.data.data.details.cr || 0,
amount = 0;

for (var key in lootCurrency) {
if (currencyData.hasOwnProperty(key)) {
if(this._getSetting('adjustCurrencyWithCR')){
amount = Number(currencyData[key].value || 0) + Math.ceil(cr) + Number(lootCurrency[key]);
} else {
amount = Number(currencyData[key].value || 0) + Number(lootCurrency[key]);
}

currencyData[key] = {"value": amount.toString()};
}
}

await actor.update({"data.currency": currencyData});
}

_generateCurrency(currencyString) {
const currenciesToAdd = {};
if (currencyString) {
const currenciesPieces = currencyString.split(",");
for (const currency of currenciesPieces) {
const match = /(.*)\[(.*?)\]/g.exec(currency); //capturing 2 groups, the formula and then the currency symbol in brakets []
if (!match || match.length < 3) {
ui.notifications.warn(this.moduleNamespace + `: Currency loot field contain wrong formatting, currencies need to be define as "diceFormula[currencyType]" => "1d100[gp]" but was ${currency}`);
continue;
}
const rollFormula = match[1];
const currencyString = match[2];
const amount = this._tryRoll(rollFormula);

if (!this._getSetting("reduceUpdateVerbosity")) ui.notifications.info(this.moduleNamespace + `: Adding `+amount+currencyString+` to the actor.`);
currenciesToAdd[currencyString] = (currenciesToAdd[currencyString] || 0) + amount;
}
}
return currenciesToAdd;
}

_tryRoll(rollFormula){
try {
return new Roll(rollFormula).roll().total || 1;
} catch (error) {
return 1;
}
}

_getSetting(setting){
return game.settings.get(this.moduleNamespace,setting);
}
}

0 comments on commit 444583f

Please sign in to comment.