'use strict';
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/* global on log playerIsGM findObjs getObj getAttrByName sendChat globalconfig state */
/*
CASHMASTER 0.10.0
A currency management script for the D&D 5E OGL sheets on roll20.net.
Please use `!cm` for inline help and examples.
arthurbauer@me.com
*/
on('ready', function () {
var _shapedItem;
var v = '0.10.0'; // version number
var usd = 110;
/*
Change this if you want to have a rough estimation of a character’s wealth in USD.
After some research I believe a reasonable exchange ratio is roughly 1 gp = 110 USD
Set it to 0 to disable it completely.
*/
var scname = 'CashMaster'; // script name
state.CashMaster.Sheet = '5E-Shaped';
var gmNotesHeaderString = '
' + ('
' + avatarTag + shop.Name + '
') + ('
Location: ' + shop.Location) + ('
Appearance: ' + shop.Appearance) + ('
Shopkeeper: ' + shop.Shopkeeper);
playerShop += shopDisplay;
gmShop += shopDisplay;
gmShop += '
[Edit](!cm -updateShop "?{Shop Field|Name|Location|Appearance|Shopkeeper}^?{New Field Value}")';
if (shop.Items) {
var waresHeader = '
Wares
';
playerShop += waresHeader;
gmShop += waresHeader;
shop.Items.forEach(function (item) {
var itemDisplay = '
' + item.Name;
itemDisplay += item.Quantity && item.Quantity > 1 ? ' x' + item.Quantity + '' : '';
itemDisplay += item.Price ? '
Price: ' + item.Price : '';
itemDisplay += item.Weight ? '
Weight: ' + item.Weight + 'lbs' : '';
itemDisplay += item.Description ? '
Description: ' + item.Description : '';
itemDisplay += item.Modifiers ? '
Modifiers: ' + item.Weight : '';
itemDisplay += item.Properties ? '
Properties: ' + item.Properties : '';
itemDisplay += '
';
playerShop += itemDisplay;
gmShop += itemDisplay;
if (item.Quantity > 0 && item.Price) {
var buyButton = '[Buy](!cm -buy -T "' + item.Name + ',' + shopkeeperName + '")';
playerShop += buyButton;
gmShop += buyButton;
if (state.CashMaster.Sheet === '5E-Shaped') gmShop += ' - ';
}
gmShop += '[Edit](!cm -updateItem "' + item.Name + '^?{Item Field|Name|Quantity|Price|Weight|Description|Modifiers|Properties}^?{New field value}")';
if (state.CashMaster.Sheet === '5E-Shaped') gmShop += ' - ';
gmShop += '[Delete](!cm -updateItem "' + item.Name + ',?{Type DELETE if you wish to delete ' + item.Name + '.},?{Are you absolutely sure|YES|NO}")';
if (state.CashMaster.Sheet === '5E-Shaped') gmShop += ' - ';
gmShop += '[Duplicate](!cm -updateItem "' + item.Name + ',DUPLICATE,?{Duplicate the item|YES|NO}")';
playerShop += '
';
gmShop += '
';
});
} else {
shop.Items = [];
}
gmShop += '
[Add Item](!cm -addItem "' + '?{Item Name}^' + '?{Item Price. Leave blank to disable Buy button.}^' + '?{Item Description}^' + '?{Quantity}^' + '?{Weight}^' + '?{Properties. It is okay to leave blank.}^' + '?{Modifiers. It is okay to leave blank.}")';
} else {
var _shopDisplay = '&{template:' + rt[0] + '} {{' + rt[1] + '=
' + ('
' + avatarTag + shop.Name + '
');
playerShop += _shopDisplay;
gmShop += _shopDisplay;
gmShop += '
[Edit](!cm -updateShop "?{Shop Field|Name|Location|Appearance|Shopkeeper}^?{New Field Value}")
';
if (shop.Items) {
shop.Items.forEach(function (item) {
var itemDisplay = '
' + item.Name + '' + ('
[View](!cm -viewItem "' + item.Name + ',' + shopkeeperName + '")');
playerShop += itemDisplay;
gmShop += itemDisplay;
if (state.CashMaster.Sheet === '5E-Shaped') gmShop += ' - ';
gmShop += '[Edit](!cm -updateItem "' + item.Name + '^?{Item Field|Name|Quantity|Price|Weight|Description|Modifiers|Properties}^?{New field value}")';
if (state.CashMaster.Sheet === '5E-Shaped') gmShop += ' - ';
gmShop += '[Delete](!cm -updateItem "' + item.Name + ',?{Type DELETE if you wish to delete ' + item.Name + '.},?{Are you absolutely sure|YES|NO}")';
if (state.CashMaster.Sheet === '5E-Shaped') gmShop += ' - ';
gmShop += '[Duplicate](!cm -updateItem "' + item.Name + ',DUPLICATE,?{Duplicate the item|YES|NO}")';
playerShop += '
';
gmShop += '
';
});
} else {
shop.Items = [];
}
gmShop += '
[Add Item](!cm -addItem "' + '?{Item Name}^' + '?{Item Price. Leave blank to disable Buy button.}^' + '?{Item Description}^' + '?{Quantity}^' + '?{Weight}^' + '?{Properties. It is okay to leave blank.}^' + '?{Modifiers. It is okay to leave blank.}")';
}
var closeDiv = '}}';
playerShop += closeDiv;
gmShop += closeDiv;
if (whisperRecipient !== '') {
sendChat(shopkeeperName, '/w ' + whisperRecipient + ' ' + playerShop);
} else {
sendChat(shopkeeperName, playerShop);
}
sendChat(shopkeeperName, '/w gm ' + gmShop);
});
};
var displayItem = function displayItem(item, shopkeeperName, showBuyButton) {
var itemDisplay = '&{template:' + rt[0] + '} {{' + rt[1] + '=
' + ('
' + item.Name + '
');
itemDisplay += item.Quantity && item.Quantity > 1 ? 'Stock: ' + item.Quantity : 'Stock: None Available';
itemDisplay += item.Price ? '
Price: ' + item.Price : '';
itemDisplay += item.Weight ? '
Weight: ' + item.Weight + 'lbs' : '';
itemDisplay += item.Description ? '
Description: ' + item.Description : '';
itemDisplay += item.Modifiers ? '
Modifiers: ' + item.Weight : '';
itemDisplay += item.Properties ? '
Properties: ' + item.Properties : '';
itemDisplay += '
';
if (showBuyButton && item.Quantity > 0 && item.Price) {
itemDisplay += '[Buy for ' + item.Price + '](!cm -buy -T "' + item.Name + ',' + shopkeeperName + '")';
}
itemDisplay += '}}';
return itemDisplay;
};
var oglItem = {
// ==============================================================================
// ITEM_ITEM_PREFIX + ROW_ID + ATTR_SUFFIX ======================================
// Prefix
ITEM_PREFIX: 'repeating_inventory_',
ATTACK_PREFIX: 'repeating_attack_',
RESOURCE_PREFIX: 'repeating_resource_',
// Suffixes
COUNT_SUFFIX: '_itemcount',
COUNT_INDEX: 0,
NAME_SUFFIX: '_itemname',
NAME_INDEX: 1,
WEIGHT_SUFFIX: '_itemweight',
WEIGHT_INDEX: 2,
EQUIPPED_SUFFIX: '_equipped',
EQUIPPED_INDEX: 3, // Actually determines whether the item is equipped in regards to other attributes (AC, modifiers, etc.)
USEASARESOURCE_SUFFIX: '_useasaresource',
USEASARESOURCE_INDEX: 4,
HASATTACK_SUFFIX: '_hasattack',
HASATTACK_INDEX: 5,
PROPERTIES_SUFFIX: '_itemproperties',
PROPERTIES_INDEX: 6,
MODIFIERS_SUFFIX: '_itemmodifiers',
MODIFIERS_INDEX: 7,
CONTENT_SUFFIX: '_itemcontent',
CONTENTS_INDEX: 8,
ATTACKID_SUFFIX: '_itemattackid',
ATTACKID_INDEX: 9,
RESOURCEID_SUFFIX: '_itemresourceid',
RESOURCEID_INDEX: 10,
INVENTORYSUBFLAG_SUFFIX: '_inventorysubflag',
INVENTORYSUBFLAG_INDEX: 11,
// These have to be the string equivalent, otherwise the sheet worker will not pick up the change
CHECKED: '1',
UNCHECKED: '0',
// Generate a random UUID. Disabling eslint as this is a The Aaron utility function.
/* eslint-disable */
generateUUID: function generateUUID() {
var a = 0;
var b = [];
return function () {
var c = new Date().getTime() + 0,
d = c === a;
a = c;
for (var e = new Array(8), f = 7; 0 <= f; f--) {
e[f] = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'.charAt(c % 64);
c = Math.floor(c / 64);
}
c = e.join('');
if (d) {
for (f = 11; 0 <= f && 63 === b[f]; f--) {
b[f] = 0;
}
b[f]++;
} else {
for (f = 0; 12 > f; f++) {
b[f] = Math.floor(64 * Math.random());
}
}
for (f = 0; 12 > f; f++) {
c += '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'.charAt(b[f]);
}
return c;
};
},
/* eslint-enable */
// Generate a random row ID
generateRowID: function generateRowID() {
return oglItem.generateUUID()().replace(/_/g, 'Z');
},
// Given a row attribute
getRowIdFromAttribute: function getRowIdFromAttribute(attrName) {
var regex = new RegExp(oglItem.ITEM_PREFIX + '(.+?)(?:' + oglItem.NAME_SUFFIX + '|' + oglItem.EQUIPPED_SUFFIX + ')');
return regex.exec(attrName) ? regex.exec(attrName)[1] : '';
},
getExistingRowId: function getExistingRowId(subject, itemName) {
var nameAttr = filterObjs(function (obj) {
// eslint-disable-line no-undef, consistent-return
if (obj.get('type') === 'attribute' && obj.get('characterid') === subject.id && obj.get('name').indexOf(oglItem.ITEM_PREFIX) !== -1 && obj.get('name').indexOf(oglItem.NAME_SUFFIX) !== -1 && obj.get('current') === itemName) {
log(JSON.stringify(obj));
return obj;
}
});
if (!nameAttr || nameAttr.length === 0) {
return null;
}
return nameAttr[0] ? oglItem.getRowIdFromAttribute(nameAttr[0].get('name')) : null;
},
getItemAttr: function getItemAttr(subject, rowId, suffix) {
var existing = findObjs({
_type: 'attribute',
characterid: subject.id,
name: oglItem.ITEM_PREFIX + rowId + suffix
})[0];
return existing;
},
// Add or subtract to the quantity of the item
alterItemCount: function alterItemCount(subject, item, rowId, modifierQuantity) {
var countAttr = oglItem.getItemAttr(subject, rowId, oglItem.COUNT_SUFFIX);
var oldCountStr = countAttr ? countAttr.get('current') : '0';
var newCount = parseInt(oldCountStr, 10) + modifierQuantity;
countAttr.setWithWorker({ current: '' + newCount });
},
// Creates an item (or increments the count if it already exists)
createOrIncrementItem: function createOrIncrementItem(subject, item) {
var existingRowId = oglItem.getExistingRowId(subject, item.Name);
if (existingRowId !== null) {
oglItem.alterItemCount(subject, item, existingRowId, 1);
} else {
oglItem.createItem(subject, item);
}
},
// Creates a brand new item in an inventory
createItem: function createItem(subject, item) {
log('Creating new item for ' + subject.id + '.');
var newRowId = oglItem.generateRowID();
oglItem.createItemAttr(subject.id, newRowId, oglItem.COUNT_SUFFIX, '1');
oglItem.createItemAttr(subject.id, newRowId, oglItem.NAME_SUFFIX, item.Name);
oglItem.createItemAttr(subject.id, newRowId, oglItem.WEIGHT_SUFFIX, item.Weight);
oglItem.createItemAttr(subject.id, newRowId, oglItem.EQUIPPED_SUFFIX, oglItem.UNCHECKED);
oglItem.createItemAttr(subject.id, newRowId, oglItem.USEASARESOURCE_SUFFIX, oglItem.UNCHECKED);
oglItem.createItemAttr(subject.id, newRowId, oglItem.HASATTACK_SUFFIX, oglItem.UNCHECKED);
oglItem.createItemAttr(subject.id, newRowId, oglItem.PROPERTIES_SUFFIX, item.Properties);
oglItem.createItemAttr(subject.id, newRowId, oglItem.MODIFIERS_SUFFIX, item.Modifiers);
oglItem.createItemAttr(subject.id, newRowId, oglItem.CONTENT_SUFFIX, item.Description);
oglItem.createItemAttr(subject.id, newRowId, oglItem.INVENTORYSUBFLAG_SUFFIX, oglItem.UNCHECKED);
log('Done creating.');
},
// Creates an item attribute (there are several per item)
createItemAttr: function createItemAttr(charId, rowId, suffix, value) {
var attrName = '' + oglItem.ITEM_PREFIX + rowId + suffix;
log(attrName);
createObj('attribute', { // eslint-disable-line no-undef
characterid: charId,
name: attrName,
current: value,
max: ''
});
}
};
var shapedItem = (_shapedItem = {
SECTION_CURRENCY: 'currency',
SECTION_INVENTORY: 'equipment',
SUFFIX_CURRENCY_ACRONYMN: 'acronymn',
SUFFIX_CURRENCY_NAME: 'name',
SUFFIX_CURRENCY_QUANTITY: 'quantity',
SUFFIX_CURRENCY_VALUE: 'value',
SUFFIX_CURRENCY_WEIGHT: 'weight',
SUFFIX_CURRENCY_BORDER: 'border'
}, _defineProperty(_shapedItem, 'SUFFIX_CURRENCY_WEIGHT', 'weight'), _defineProperty(_shapedItem, 'SUFFIX_CURRENCY_WEIGHT_SYSTEM', 'weight_system'), _defineProperty(_shapedItem, 'getRepeatingSectionAttrs', function getRepeatingSectionAttrs(characterId, sectionName) {
var prefix = 'repeating_' + sectionName;
return _.filter(findObjs({
type: "attribute",
characterid: characterId
}), function (attr) {
return 0 === attr.get("name").indexOf(prefix);
});
}), _defineProperty(_shapedItem, 'getRepeatingSectionItemIdsByName', function getRepeatingSectionItemIdsByName(characterId, sectionName) {
var re = new RegExp('repeating_' + sectionName + '_([^_]+)_name$');
return _.reduce(shapedItem.getRepeatingSectionAttrs(characterId, sectionName), function (lookup, attr) {
var match = attr.get("name").match(re);
match && (lookup[attr.get("current").toLowerCase()] = match[1]);
return lookup;
}, {});
}), _defineProperty(_shapedItem, 'getCoinCount', function getCoinCount(characterId, coinAttrName) {
coinAttrs = filterObjs(function (obj) {
// eslint-disable-line no-undef, consistent-return
if (obj.get('type') === 'attribute' && obj.get('characterid') === characterId && obj.get('name').indexOf(coinAttrName) !== -1) {
return true;
}
});
if (coinAttrs && coinAttrs.length > 0) {
var current = coinAttrs[0].get("current");
return parseFloat(current);
}
return 0;
}), _defineProperty(_shapedItem, 'loadAccount', function loadAccount(character) {
var characterId = character.id;
var playerCurrencyBlock = shapedItem.getRepeatingSectionItemIdsByName(characterId, shapedItem.SECTION_CURRENCY);
var platinumName = 'repeating_' + shapedItem.SECTION_CURRENCY + '_' + playerCurrencyBlock.platinum + '_' + shapedItem.SUFFIX_CURRENCY_QUANTITY;
var goldName = 'repeating_' + shapedItem.SECTION_CURRENCY + '_' + playerCurrencyBlock.gold + '_' + shapedItem.SUFFIX_CURRENCY_QUANTITY;
var electrumName = 'repeating_' + shapedItem.SECTION_CURRENCY + '_' + playerCurrencyBlock.electrum + '_' + shapedItem.SUFFIX_CURRENCY_QUANTITY;
var silverName = 'repeating_' + shapedItem.SECTION_CURRENCY + '_' + playerCurrencyBlock.silver + '_' + shapedItem.SUFFIX_CURRENCY_QUANTITY;
var copperName = 'repeating_' + shapedItem.SECTION_CURRENCY + '_' + playerCurrencyBlock.copper + '_' + shapedItem.SUFFIX_CURRENCY_QUANTITY;
return [shapedItem.getCoinCount(characterId, platinumName), shapedItem.getCoinCount(characterId, goldName), shapedItem.getCoinCount(characterId, electrumName), shapedItem.getCoinCount(characterId, silverName), shapedItem.getCoinCount(characterId, copperName)];
}), _defineProperty(_shapedItem, 'saveAccount', function saveAccount(character, account) {
var characterId = character.id;
log('Saving Shaped Account');
printAccount(account);
var playerCurrencyBlock = shapedItem.getRepeatingSectionItemIdsByName(characterId, shapedItem.SECTION_CURRENCY);
var platinumName = 'repeating_' + shapedItem.SECTION_CURRENCY + '_' + playerCurrencyBlock.platinum + '_' + shapedItem.SUFFIX_CURRENCY_QUANTITY;
var goldName = 'repeating_' + shapedItem.SECTION_CURRENCY + '_' + playerCurrencyBlock.gold + '_' + shapedItem.SUFFIX_CURRENCY_QUANTITY;
var electrumName = 'repeating_' + shapedItem.SECTION_CURRENCY + '_' + playerCurrencyBlock.electrum + '_' + shapedItem.SUFFIX_CURRENCY_QUANTITY;
var silverName = 'repeating_' + shapedItem.SECTION_CURRENCY + '_' + playerCurrencyBlock.silver + '_' + shapedItem.SUFFIX_CURRENCY_QUANTITY;
var copperName = 'repeating_' + shapedItem.SECTION_CURRENCY + '_' + playerCurrencyBlock.copper + '_' + shapedItem.SUFFIX_CURRENCY_QUANTITY;
setattr(characterId, platinumName, parseFloat(account[0]));
setattr(characterId, goldName, parseFloat(account[1]));
setattr(characterId, electrumName, parseFloat(account[2]));
setattr(characterId, silverName, parseFloat(account[3]));
setattr(characterId, copperName, parseFloat(account[4]));
}), _shapedItem);
on('chat:message', function (msg) {
if (msg.type !== 'api') return;
if (msg.content.startsWith('!cm') !== true) return;
var subcommands = msg.content.split(';;');
if (playerIsGM(msg.playerid)) {
msg.who = msg.who.replace(' (GM)', ''); // remove the (GM) at the end of the GM name
}
// Initialize State object
initCM();
// Log the received command
log('CM Command: ' + msg.content);
// Execute each operation
subcommands.forEach(function (subcommand) {
log('CM Subcommand: ' + subcommand);
var argTokens = subcommand.split(/\s+/);
// Operations that do not require a selection
if (subcommand === '!cm' || argTokens.includes('-help') || argTokens.includes('-h')) {
//! help
sendChat(scname, '/w gm
CashMaster
A currency management script for the D&D 5e OGL sheets on Roll20.net.
Please use !cm for inline help and examples.
Setup
Player Setup
- Create a macro bar button for the command
!cm -menu - Press the CashMaster button you just created. It will display a menu in the chat log.
- Click on your character token
- Press the chat UI button titled Set Default Character.
GM Setup
- Create a macro bar button for the command
!cm -menu - Press the CashMaster button you just created. It will display a menu in the chat log.
- Select the character tokens of ALL party members and companion NPCs. Do not select pets or mounts unless the party considers them an equal member of the party (and thus should be a valid recipient for
-loot and similar commands). Such creatures must have full character sheets with currency fields. - Press the chat UI button titled Set Party to Selected.
- For each shopkeeper, press the [Show Shop on Selected] button in the menu. The shopkeeper must have a character sheet and the token must point to it.
Player Commands
Help Commands
!cm or !cm -help or !cm -h will show this help overview!cm -menu or !cm -tool to bring up the user menu!cm -status or !cm -ss to display your current coin purse contents
Accounting commands
A character has a tracked account if it has discrete values for its saved coins. PCs necessarily have tracked accounts while NPCs usually do not, instead having as much gold as the DM feels is necessary at that moment.
!cm -transfer "[recipient character name]" [amount][currency] or !cm -t "[recipient character name]" [amount][currency] to transfer coins to the recipient's tracked account.!cm -invoice "[recipient character name]" [amount][currency] or !cm -i "[recipient character name]" [amount][currency] to request coins to the recipient's tracked account.!cm -giveNPC "[NPC Name, service rendered]" [amount][currency] or !cm -dropWithReason "[reason for dropping coins]" [amount][currency] to move coins from the player's tracked account to an untracked account.
Shop Commands
!cm -viewShop or !cm -vs will display the shop data of the selected token to all players. The GM is presented with both the player view and the edit view.!cm -viewItem "[item name]^[shopkeeper name]" will display the details of a particular item from the specified shopkeeper. This is used when the GM sets verbosity to low.!cm -buy "[item name]^[shopkeeper name]" will attempt to purchase the named item from the named shopkeeper. If you have sufficient funds, the funds will be removed and you will be presented with a purchase message. If you are using the OGL character sheet, you will also find it transferred into your inventory.
GM Commands
In addition to the above commands, the GM has access to the following commands.
Help commands
!cm or !cm -help or !cm -h will show this help overview!cm -overview or !cm -o to get an overview over the party's cash!cm -overview --usd will also give you an overview and a rough conversion to USD (default value: 1 gp equals roughly 110 USD).
Accounting Commands
These operations directly add and remove gold from party inventories.
!cm -add [amount][currency] or !cm -a [amount][currency] to add an equal amount of money to each selected party member,!cm -loot [amount][currency] or !cm -l [amount][currency] to split up a certain amount of coins between the party members, like a found treasure. Note that in this case, no conversion between the different coin types is made - if a party of 5 shares 4 pp, then 4 party members receive one pp each, and the last member won't get anything.!cm -sub [amount][currency] or !cm -pay [amount][currency] or !cm -p [amount][currency] to let each selected party member pay a certain amount. The script will even try to take higher and lower coin types to get the full amount. E.g. to pay 1gp when the character has no gold, the script will use 1pp (and return 9gp), or it will take 2ep, 10sp or 100cp - or any other valid combination of coins - to pay the desired amount.
Admin Commands
Use caution when using the below commands.
!cm -merge or !cm -m to merge coins to the densest denomination possible.!cm -share or !cm -s to reallocate and share the money equally between selected party members, converting the amount into the best combination of gold, silver and copper (this should be used in smaller stores),!cm -best-share or !cm -bs to reallocate and share the money equally between selected party members, converting the amount into the best combination of platinum, gold, electrum, silver and copper (this should only be used in larger stores that have a fair amount of cash),!cm -setParty or !cm -sp to set the default party list. These will be the default targets for party actions if you have nothing selected.!cm -revert or !cm -r reverts a given transaction id. This is an internal command used to revert transactions.
Shop Commands
These commands set up and manipulate shops.
!cm -makeShop "[shop name]^[shop location]^[shop appearance]^[shopkeeper appearance] creates a shop for the selected character token with the assigned attributes. The token must point to a character sheet. If you wish to have a single shop with multiple shopkeepers, use rollable tables and have them feed to the same character sheet for the shop.!cm -setShopVerbosity [high|low] sets the amount of information that is printed out when displaying a shop.!cm -updateShop "[parameter name]^[parameter value]" will update the named parameter to the new value.!cm -addItem "[item name]^[price]^[description]^[quantity]^[weight]^[properties]^[modifiers]" creates a new item in the selected token's shop.!cm -updateItem "[item name]^[parameter name]^[parameter value]" will look up the specified item name in the selected token's shop and update the named parameter to be the specified value.!cm -updateItem "[item name]^DELETE^YES" will delete the specified item from the selected token's shop.!cm -updateItem "[item name]^DUPLICATE^YES" will duplicate the specified item, adding a new item to the end of the shop's list of items named "Copy of [item name]".
Tips
-noToken and -nt will cause CashMaster to disregard what character is selected.- You can use several coin values at once, e.g.
!cm -loot 50gp 150sp 2000cp or !cm -pay 2sp 5cp. - You can combine multiple subcommands into a single chat command with a double-semicolon
;; between them. For example, !cm -add 5gp; -merge - You can select multiple subjects and targets using Advanced Mode. For example,
!cm -transfer -S "Billy Bob^Joe Bob" -T "Sarah Bob^Sonya Bob" -C "10gp". When using multiple subjects and targets, it will perform an operation for each subject-target pair. In that case, it will perform four transactions of 10gp each. - In Advanced Mode, you don't need to specify
-C "[amoung][currency]" and can instead just use the standard [amount][currency]. The -C currency tag allows you to specify the gp value at a particular point in the command in case you have strangley-named or inconveniently ID'd characters. - CashMaster will parse both character IDs and character names. You can even mix and match! If Billy Bob's ID was
-L4ncF3ych3ZLtWaY3uY, Instead of the example in Tip 4, you could use !cm -transfer -S "-L4ncF3ych3ZLtWaY3uY^Joe Bob" -T "Sarah Bob^Sonya Bob" -C "10gp". - CashMaster is compatible with
@{selected|character_id} and @{target|character_id} as they will simply be parsed down to IDs. As an example, your players could use !cm -t -T "@{target|character_id}" 1gp to transfer a gold amongst themselves.
Examples
!cm -overview will show a cash overview.!cm -add 50gp will add 50 gp to every selected character.!cm -loot 50gp will (more or less evenly) distribute 50 gp among the party members.!cm -pay 10gp will subtract 10gp from each selected character. It will try to exchange the other coin types (e.g. it will use 1pp if the player doesn't have 10gp).!cm -share will collect all the money and share it evenly on the members, using gp, sp and cp only (pp and ep will be converted). Can also be used for one character to 'exchange' money.!cm -transfer "Tazeka Liranov" 40gp will transfer 40 gp from the selected token to the character sheet named Tazeka Liranov.!cm -convert - same as !cm -share, but will also use platinum and electrum.
Credits
With thanks to Kryx/mlenser and Michael G./VoltCruelerz for their contributions.
'); // eslint-disable-line quotes
}
// Display the CashMaster Menu
if (argTokens.includes('-menu') || argTokens.includes('-toolbar') || argTokens.includes('-tool')) {
var menuContent = '/w "' + msg.who + '" &{template:' + rt[0] + '} {{' + rt[1] + '=
Cash Master
' + '
Universal Commands
' + '
Tools' + '
[Toolbar](!cm -tool)' + '
[Status](!cm -status)' + '
Operations' + ('
[Transfer to PC](!cm -transfer "?{Recipient' + getRecipientOptions() + '}" ?{Currency to Transfer})') + '
[Transfer to NPC](!cm -giveNPC "?{List recipient name and reason}" ?{Currency to Transfer})' + ('
[Invoice Player](!cm -invoice "?{Invoicee' + getRecipientOptions() + '}" ?{Currency to Request})') + '
Utilities' + '
[Set Default Character](!cm -sc ?{Will you set a new default character|Yes})' + '
[Remove Default Character](!cm -rc ?{Will you remove your default character|Yes})';
if (playerIsGM(msg.playerid)) {
menuContent = menuContent + '
GM-Only Commands
' + '
Base Commands' + '
[Readme](!cm -help)
[Party Overview](!cm -overview)' + '
[Selected USD](!cm -overview --usd)' + '
Accounting Commands' + '
[Credit Each Selected](!cm -add ?{Currency to Add})' + '
[Bill Each Selected](!cm -sub ?{Currency to Bill})' + '
[Split Among Selected](!cm -loot ?{Amount to Split})' + '
[Transaction History](!cm -th)' + '
Shop Commands' + '
[Show Shop of Selected](!cm -vs)' + '
[Create Shop on Selected](!cm -makeShop "?{Shop Name}^?{Describe the area around the store}^?{Describe the appearance of the store}^?{Describe the shopkeeper}")' + '
[Set Shop Verbosity](!cm -setShopVerbosity ?{Set Verbosity|High|Low})' + '
Admin Commands' + '
[Compress Coins of Selected](!cm -merge)' + '
[Reallocate Coins](!cm -s ?{Will you REALLOCATE party funds evenly|Yes})' + '
[Set Party to Selected](!cm -sp ?{Will you SET the party to selected|Yes})';
}
menuContent += '}}';
sendChat(scname, menuContent);
return;
}
// Selectionless GM commands
if (playerIsGM(msg.playerid)) {
if (argTokens.includes('-transactionHistory') || argTokens.includes('-th')) {
var sender = msg.who;
printTransactionHistory(sender);
return;
}
if (argTokens.includes('-revert') || argTokens.includes('-r')) {
var id = parseFloat(argTokens[1]);
var tx = state.CashMaster.TransactionHistory.find(function (element) {
return element.Id === id;
});
tx.Reverted = true;
var _sender = msg.who;
printTransactionHistory(_sender);
return;
}
if (argTokens.includes('-setShopVerbosity')) {
var result = argTokens[2];
if (result === 'High') {
state.CashMaster.ShopDisplayVerbose = true;
sendChat(scname, '/w gm Updated Verbosity Setting: high');
} else if (result === 'Low') {
state.CashMaster.ShopDisplayVerbose = false;
sendChat(scname, '/w gm Updated Verbosity Setting: low');
} else {
sendChat(scname, '/w gm Invalid Verbosity Setting');
}
return;
}
}
// From this point forward, there must at minimum be a Subject (possibly targets as well).
var parsedSubcommand = parseSubcommand(msg, subcommand, argTokens);
if (parsedSubcommand === null) {
log('Invalid Input. Validate that a subject is provided and input is not malformed.');
sendChat(scname, '/w gm **ERROR:** Invalid Input. Validate that a subject is provided and input is not malformed. In response to ' + msg.who + '\'s command ' + subcommand);
return;
}
var subjects = parsedSubcommand.Subjects;
var targets = parsedSubcommand.Targets;
if (subjects === null) {
log('Invalid Input (null subjects). Aborting.');
sendChat(scname, '/w gm **ERROR:** No subject provided by ' + msg.who + ' in command ' + subcommand);
return;
}
if (subjects.length === 0) {
log('Invalid Input (no subjects). Aborting.');
sendChat(scname, '/w gm **ERROR:** No subject provided by ' + msg.who + ' in command ' + subcommand + '.');
return;
}
// Display the shop. The GM is presented with both the normal shop and the editable shop
if (argTokens.includes('-viewShop') || argTokens.includes('-vs')) {
subjects.forEach(function (subject) {
if (!subject) {
sendChat(scname, '/w ' + msg.who + ' Subject is undefined. Token may not have assigned character sheet.');
sendChat(scname, '/w gm ' + msg.who + ' attempted a command, but Subject was undefined.');
return;
}
subject.get('gmnotes', function (source) {
log('Existing Notes: ' + source);
// If you haven't selected anything, source will literally be the following string: 'null'
if (source && source !== 'null') {
// Parse the GM Notes
var gmNotes = parseGmNotes(source);
if (!gmNotes) {
sendChat(scname, '/w gm Target does not possess a GM Notes block to parse.');
return;
}
// Display Shop to Users
if (gmNotes.CashMaster) {
if (gmNotes.CashMaster.Shop) {
var shop = gmNotes.CashMaster.Shop;
displayShop(subject, shop, getAttrByName(subject.id, 'character_name'), '');
return;
}
}
sendChat(scname, '/w ' + msg.who + ' Unable to find Shop objects.');
} else {
sendChat(scname, '/w ' + msg.who + ' Please select a valid shop.');
}
});
});
return;
}
// Coin Transfer between players
if (argTokens.includes('-transfer') || argTokens.includes('-t')) {
subjects.forEach(function (subject) {
targets.forEach(function (target) {
output = '';
var transactionOutput = '';
var subjectOutput = '';
var targetOutput = '';
var subjectName = getAttrByName(subject.id, 'character_name');
var targetName = getAttrByName(target.id, 'character_name');
// Check that the sender is not attempting to send money to themselves
if (subject.id === target.id) {
sendChat(scname, '**ERROR:** target character must not be selected character.');
return;
}
// Verify subject has enough to perform transfer
// Check if the player attempted to steal from another and populate the transaction data
transactionOutput += '
Transaction Data';
if (ppa !== null) {
var val = parseFloat(ppa[3]);
transactionOutput += '
' + ppa[2] + '';
if (val < 0 && !playerIsGM(msg.playerid)) {
sendChat(scname, '**ERROR:** ' + msg.who + ' may not demand payment from ' + targetName + '.');
return;
}
}
if (gpa !== null) {
var _val = parseFloat(gpa[3]);
transactionOutput += '
' + gpa[2] + '';
if (_val < 0 && !playerIsGM(msg.playerid)) {
sendChat(scname, '**ERROR:** ' + msg.who + ' may not demand payment from ' + targetName + '.');
return;
}
}
if (epa !== null) {
var _val2 = parseFloat(epa[3]);
transactionOutput += '
' + epa[2] + '';
if (_val2 < 0 && !playerIsGM(msg.playerid)) {
sendChat(scname, '**ERROR:** ' + msg.who + ' may not demand payment from ' + targetName + '.');
return;
}
}
if (spa !== null) {
var _val3 = parseFloat(spa[3]);
transactionOutput += '
' + spa[2] + '';
if (_val3 < 0 && !playerIsGM(msg.playerid)) {
sendChat(scname, '**ERROR:** ' + msg.who + ' may not demand payment from ' + targetName + '.');
return;
}
}
if (cpa !== null) {
var _val4 = parseFloat(cpa[3]);
transactionOutput += '
' + cpa[2] + '';
if (_val4 < 0 && !playerIsGM(msg.playerid)) {
sendChat(scname, '/w gm **ERROR:** ' + msg.who + ' may not demand payment from ' + targetName + '.');
return;
}
}
// Load subject's existing account
var subjectAccount = loadAccount(subject);
var subjectInitial = duplicateAccount(subjectAccount);
if (ppa !== null) subjectAccount = changeMoney(subjectAccount, ppa[2]);
if (gpa !== null) subjectAccount = changeMoney(subjectAccount, gpa[2]);
if (epa !== null) subjectAccount = changeMoney(subjectAccount, epa[2]);
if (spa !== null) subjectAccount = changeMoney(subjectAccount, spa[2]);
if (cpa !== null) subjectAccount = changeMoney(subjectAccount, cpa[2]);
// Verify subject has enough to perform transfer
subjectOutput += '
' + subjectName + ' has ';
if (subjectAccount === 'ERROR: Not enough cash.') {
subjectOutput += 'not enough cash!';
} else {
// Update subject account and update output
subjectOutput += '
' + subjectAccount[0] + 'pp';
subjectOutput += '
' + subjectAccount[1] + 'gp';
subjectOutput += '
' + subjectAccount[2] + 'ep';
subjectOutput += '
' + subjectAccount[3] + 'sp';
subjectOutput += '
' + subjectAccount[4] + 'cp';
// targetFunds
var targetAccount = loadAccount(target);
var _targetInitial = duplicateAccount(targetAccount);
if (ppa !== null) targetAccount[0] += parseFloat(ppa[3]);
if (gpa !== null) targetAccount[1] += parseFloat(gpa[3]);
if (epa !== null) targetAccount[2] += parseFloat(epa[3]);
if (spa !== null) targetAccount[3] += parseFloat(spa[3]);
if (cpa !== null) targetAccount[4] += parseFloat(cpa[3]);
targetOutput += '
' + targetName + ' has ';
targetOutput += '
' + targetAccount[0] + 'pp';
targetOutput += '
' + targetAccount[1] + 'gp';
targetOutput += '
' + targetAccount[2] + 'ep';
targetOutput += '
' + targetAccount[3] + 'sp';
targetOutput += '
' + targetAccount[4] + 'cp';
saveAccountsAndTransaction(msg.who, [subject, target], [subjectName, targetName], [subjectAccount, targetAccount], [subjectInitial, _targetInitial], 'Transfer to PC');
}
sendChat(scname, '/w gm &{template:' + rt[0] + '} {{' + rt[1] + '=
GM Transfer Report' + subjectName + '>' + targetName + '
' + transactionOutput + subjectOutput + targetOutput + '}}');
sendChat(scname, '/w "' + msg.who + '" &{template:' + rt[0] + '} {{' + rt[1] + '=
Sender Transfer Report' + subjectName + ' > ' + targetName + '
' + output + transactionOutput + subjectOutput + '}}');
sendChat(scname, '/w "' + targetName + '" &{template:' + rt[0] + '} {{' + rt[1] + '=
Recipient Transfer Report' + subjectName + ' > ' + targetName + '
' + output + transactionOutput + targetOutput + '}}');
});
});
return;
}
// Invoice between players
if (argTokens.includes('-invoice') || argTokens.includes('-i')) {
subjects.forEach(function (subject) {
targets.forEach(function (target) {
output = '';
var transactionOutput = '';
var targetOutput = '';
var invoiceAmount = '';
var subjectName = getAttrByName(subject.id, 'character_name');
var targetName = getAttrByName(target.id, 'character_name');
// Check that the sender is not attempting to send money to themselves
if (subject.id === target.id) {
sendChat(scname, '**ERROR:** target character must not be selected character.');
return;
}
// Verify subject has enough to perform transfer
// Check if the player attempted to reverse-invoice themselves
transactionOutput += '
Requested Funds:';
if (ppa !== null) {
var val = parseFloat(ppa[3]);
transactionOutput += '
' + ppa[2];
invoiceAmount += ' ' + ppa[2];
if (val < 0 && !playerIsGM(msg.playerid)) {
sendChat(scname, '**ERROR:** ' + msg.who + ' may not reverse-invoice themselves.');
return;
}
}
if (gpa !== null) {
var _val5 = parseFloat(gpa[3]);
transactionOutput += '
' + gpa[2];
invoiceAmount += ' ' + gpa[2];
if (_val5 < 0 && !playerIsGM(msg.playerid)) {
sendChat(scname, '**ERROR:** ' + msg.who + ' may not reverse-invoice themselves.');
return;
}
}
if (epa !== null) {
var _val6 = parseFloat(epa[3]);
transactionOutput += '
' + epa[2];
invoiceAmount += ' ' + epa[2];
if (_val6 < 0 && !playerIsGM(msg.playerid)) {
sendChat(scname, '**ERROR:** ' + msg.who + ' may not reverse-invoice themselves.');
return;
}
}
if (spa !== null) {
var _val7 = parseFloat(spa[3]);
transactionOutput += '
' + spa[2];
invoiceAmount += ' ' + spa[2];
if (_val7 < 0 && !playerIsGM(msg.playerid)) {
sendChat(scname, '**ERROR:** ' + msg.who + ' may not reverse-invoice themselves.');
return;
}
}
if (cpa !== null) {
var _val8 = parseFloat(cpa[3]);
transactionOutput += '
' + cpa[2];
invoiceAmount += ' ' + cpa[2];
if (_val8 < 0 && !playerIsGM(msg.playerid)) {
sendChat(scname, '/w gm **ERROR:** ' + msg.who + ' may not reverse-invoice themselves.');
return;
}
}
// Load target's existing account
var targetAccount = loadAccount(target);
targetOutput += '
Current Funds of ' + targetName + '';
targetOutput += '
' + targetAccount[0] + 'pp';
targetOutput += '
' + targetAccount[1] + 'gp';
targetOutput += '
' + targetAccount[2] + 'ep';
targetOutput += '
' + targetAccount[3] + 'sp';
targetOutput += '
' + targetAccount[4] + 'cp';
sendChat(scname, '/w gm &{template:' + rt[0] + '} {{' + rt[1] + '=
GM Invoice Report' + subjectName + '>' + targetName + '
' + transactionOutput + targetOutput + '}}');
sendChat(scname, '/w "' + msg.who + '" &{template:' + rt[0] + '} {{' + rt[1] + '=
Invoice Sent to ' + targetName + '
' + transactionOutput + '}}');
sendChat(scname, '/w "' + targetName + '" &{template:' + rt[0] + '} {{' + rt[1] + '=
Invoice Received from ' + subjectName + '
' + transactionOutput + targetOutput + '
[Pay](!cm -transfer -S "' + targetName + '" -T "' + subjectName + '" -C "' + invoiceAmount + '")}}');
});
});
return;
}
// Display coin count to player
if (argTokens.includes('-status') || argTokens.includes('-ss')) {
subjects.forEach(function (subject) {
var coinStatus = playerCoinStatus(subject);
sendChat(scname, '/w "' + msg.who + '" &{template:' + rt[0] + '} {{' + rt[1] + '=
Coin Purse Status
' + coinStatus[0] + '}}');
});
return;
}
// Displays item details to the user
if (argTokens.includes('-viewItem')) {
if (targets.length === 2) {
var itemName = targets[0];
var shopkeeperName = targets[1];
var shopkeeper = getCharByAny(shopkeeperName);
if (shopkeeper) {
shopkeeper.get('gmnotes', function (source) {
if (source) {
var gmNotes = parseGmNotes(source);
if (gmNotes) {
if (gmNotes.CashMaster) {
if (gmNotes.CashMaster.Shop) {
var shop = gmNotes.CashMaster.Shop;
var item = shop.Items.find(function (p) {
return p.Name === itemName;
});
if (item) {
var itemDisplay = displayItem(item, shopkeeperName, true);
sendChat(scname, '/w ' + msg.who + ' ' + itemDisplay);
}
}
}
}
}
});
}
}
return;
}
// Drop Currency or Give it to an NPC
if (argTokens.includes('-dropWithReason') || argTokens.includes('-giveNPC') || argTokens.includes('-buy')) {
subjects.forEach(function (subject) {
output = '';
var transactionOutput = '';
var subjectName = getAttrByName(subject.id, 'character_name');
var reason = targets[0];
if (argTokens.includes('-buy')) {
if (targets.length === 2) {
var _shopkeeperName = targets[1];
log('Attempting to purchase ' + reason + ' from ' + _shopkeeperName);
var _shopkeeper = getCharByAny(_shopkeeperName);
var _itemName = reason;
if (_shopkeeper) {
_shopkeeper.get('gmnotes', function (source) {
if (source) {
var gmNotes = parseGmNotes(source);
if (gmNotes) {
if (gmNotes.CashMaster) {
if (gmNotes.CashMaster.Shop) {
var shop = gmNotes.CashMaster.Shop;
var item = shop.Items.find(function (p) {
return p.Name === _itemName;
});
if (item) {
if (item.Quantity > 0) {
log('Valid Buy Target. Purchaser: ' + subjectName + ', Seller: ' + _shopkeeperName + ' of ' + shop.Name + 'x' + item.Quantity + ', Item: ' + _itemName);
item.Quantity -= 1;
// Prepare updated notes
var gmNoteString = JSON.stringify(gmNotes, null, 4);
var userNotes = source.substr(0, source.indexOf(gmNotesHeaderString));
log('User Notes: ' + userNotes);
var formattedOutput = formatShopData(gmNoteString);
var gmNoteOutput = '' + userNotes + formattedOutput;
// The coin parser requires space at the beginning to prevent overlap with strange player names
populateCoinContents(' ' + item.Price);
// Verify subject has enough to perform transfer
// Check if the player attempted to steal from another and populate the transaction data
transactionOutput += '
Transaction Data';
if (ppa !== null) {
transactionOutput += '
' + ppa[2];
}
if (gpa !== null) {
transactionOutput += '
' + gpa[2];
}
if (epa !== null) {
transactionOutput += '
' + epa[2];
}
if (spa !== null) {
transactionOutput += '
' + spa[2];
}
if (cpa !== null) {
transactionOutput += '
' + cpa[2];
}
var subtractResult = subtractPlayerCoinsIfPossible('', msg.who, subject, subjectName, 'Buy ' + item.Name + ' from ' + shop.Name);
if (subtractResult.Success) {
// Schedule major write operations
setTimeout(function () {
_shopkeeper.set('gmnotes', gmNoteOutput);
}, 0);
setTimeout(function () {
oglItem.createOrIncrementItem(subject, item);
}, 0);
}
if (state.CashMaster.Sheet === '5E-Shaped') {
transactionOutput += '
InfoAutomatic item insertion not yet supported on 5e Shaped Script. Please add it manually.';
}
sendChat(scname, '/w gm &{template:' + rt[0] + '} {{' + rt[1] + '=
GM Transfer Report' + subjectName + '
' + reason + '
' + transactionOutput + subtractResult.Output + '}}');
sendChat(scname, '/w ' + subjectName + ' &{template:' + rt[0] + '} {{' + rt[1] + '=
Buyer Transfer Report' + subjectName + '
' + reason + '
' + output + transactionOutput + subtractResult.Output + '}}');
if (state.CashMaster.Sheet === '5E-Shaped') {
sendChat(scname, '/w ' + subjectName + ' {displayItem(item, shopkeeperName, false)}');
}
setTimeout(displayShop, 500, subject, shop, _shopkeeperName, subjectName);
}
}
}
}
}
}
});
}
}
} else {
if (ppa !== null) {
var val = parseFloat(ppa[3]);
transactionOutput += '
' + ppa[2];
if (val < 0 && !playerIsGM(msg.playerid)) {
sendChat(scname, '**ERROR:** ' + msg.who + ' tried to steal.');
return;
}
}
if (gpa !== null) {
var _val9 = parseFloat(gpa[3]);
transactionOutput += '
' + gpa[2];
if (_val9 < 0 && !playerIsGM(msg.playerid)) {
sendChat(scname, '**ERROR:** ' + msg.who + ' tried to steal.');
return;
}
}
if (epa !== null) {
var _val10 = parseFloat(epa[3]);
transactionOutput += '
' + epa[2];
if (_val10 < 0 && !playerIsGM(msg.playerid)) {
sendChat(scname, '**ERROR:** ' + msg.who + ' tried to steal.');
return;
}
}
if (spa !== null) {
var _val11 = parseFloat(spa[3]);
transactionOutput += '
' + spa[2];
if (_val11 < 0 && !playerIsGM(msg.playerid)) {
sendChat(scname, '**ERROR:** ' + msg.who + ' tried to steal.');
return;
}
}
if (cpa !== null) {
var _val12 = parseFloat(cpa[3]);
transactionOutput += '
' + cpa[2];
if (_val12 < 0 && !playerIsGM(msg.playerid)) {
sendChat(scname, '**ERROR:** ' + msg.who + ' tried to steal.');
return;
}
}
// Verify subject has enough to perform transfer
// Check if the player attempted to steal from another and populate the transaction data
transactionOutput += '
Transaction Data';
var subtractResult = subtractPlayerCoinsIfPossible('', msg.who, subject, subjectName, 'Transfer to NPC');
sendChat(scname, '/w gm &{template:' + rt[0] + '} {{' + rt[1] + '=
GM Transfer Report' + subjectName + '
' + reason + '
' + transactionOutput + subtractResult.Output + '}}');
sendChat(scname, '/w ' + subjectName + ' &{template:' + rt[0] + '} {{' + rt[1] + '=
Sender Transfer Report' + subjectName + '
' + reason + '
' + output + transactionOutput + subtractResult.Output + '}}');
}
});
return;
}
// Set the default character for a given player
if (argTokens.includes('-setDefaultCharacterName') || argTokens.includes('-sc')) {
var setNewCharacter = false;
if (msg.selected) {
var pcToken = msg.selected[0];
var token = getObj('graphic', pcToken._id); // eslint-disable-line no-underscore-dangle
if (token) {
var pc = getObj('character', token.get('represents'));
if (pc) {
pcName = getAttrByName(pc.id, 'character_name');
if (pcName) {
var mapLog = 'Mapping Speaker ' + msg.playerid + ' to PC ' + pcName;
log(mapLog);
state.CashMaster.DefaultCharacterNames[msg.playerid] = pcName;
sendChat(scname, '/w gm ' + mapLog);
sendChat(scname, '/w "' + msg.who + '" Updated Default Character to ' + pcName);
setNewCharacter = true;
}
}
}
}
if (!setNewCharacter) {
sendChat(scname, '/w "' + msg.who + '" **ERROR:** You did not have a named character token selected.');
}
}
// Removes the default character for a given player
if (argTokens.includes('-removeDefaultCharacterName') || argTokens.includes('-rc')) {
if (state.CashMaster.DefaultCharacterNames[msg.playerid]) {
delete state.CashMaster.DefaultCharacterNames[msg.playerid];
sendChat(scname, '/w gm Erased Default Character for ' + msg.who);
sendChat(scname, '/w "' + msg.who + '" Erased Default Character');
} else {
sendChat(scname, '/w "' + msg.who + '" You do not have a default character assigned.');
}
}
// GM-Only Commands
if (playerIsGM(msg.playerid)) {
// Calculate pre-existing party total
var partytotal = 0;
var partycounter = 0;
var partymember = null;
var partyGoldOperation = false;
// Create party gold output string
partymember = subjects.length;
subjects.forEach(function (subject) {
if (!subject) {
sendChat(scname, '/w ' + msg.who + ' Subject is undefined.');
sendChat(scname, '/w gm ' + msg.who + ' attempted a command, but Subject was undefined.');
return;
}
partycounter += 1;
name = getAttrByName(subject.id, 'character_name');
var subjectAccount = loadAccount(subject);
total = goldEquivalent(subjectAccount);
partytotal = total + partytotal;
});
partytotal = Math.round(partytotal * 100, 0) / 100;
// Merge a player's coin into the densest possible
if (argTokens.includes('-merge') || argTokens.includes('-m')) {
output = '';
var transactionEffects = [];
subjects.forEach(function (subject) {
// Load player's existing account
var subjectName = getAttrByName(subject.id, 'character_name');
var playerAccount = loadAccount(subject);
var playerInitial = duplicateAccount(playerAccount);
var mergeResult = mergeMoney(playerAccount);
if (mergeResult.length == null) {
return;
}
// Update subject account and update output
saveAccountAndTransaction(subject, subjectName, mergeResult, playerInitial, msg.who, 'Merge');
output += '
' + subjectName + ' has ';
output += '
' + mergeResult[0] + 'pp';
output += '
' + mergeResult[1] + 'gp';
output += '
' + mergeResult[2] + 'ep';
output += '
' + mergeResult[3] + 'sp';
output += '
' + mergeResult[4] + 'cp';
});
sendChat(scname, '/w gm &{template:' + rt[0] + '} {{' + rt[1] + '=
Coin Merge Report
' + output + '}}');
partyGoldOperation = true;
}
// Reallocate existing resources of party as if all coin purses were thrown together and split evenly
if (argTokens.includes('-share') || argTokens.includes('-best-share') || argTokens.includes('-s') || argTokens.includes('-bs')) {
output = '';
var cashshare = partytotal / partycounter;
var newcounter = 0;
var pps = Math.floor(cashshare / 10);
if (argTokens.includes('-share') || argTokens.includes('-s')) {
pps = 0;
}
var rest = cashshare - pps * 10;
var gps = Math.floor(rest);
rest = (rest - gps) * 2;
var eps = Math.floor(rest);
if (argTokens.includes('-share') || argTokens.includes('-s')) {
eps = 0;
}
rest = (rest - eps) * 5;
var sps = Math.floor(rest);
rest = (rest - sps) * 10;
var cps = Math.round(rest);
rest = (rest - cps) * partycounter;
sendChat(scname, '/w gm &{template:' + rt[0] + '} {{' + rt[1] + '=
Let\u2019s share this!
Everyone receives the equivalent of ' + toUsd(cashshare) + ' gp: ' + pps + ' platinum, ' + gps + ' gold, ' + eps + ' electrum, ' + sps + ' silver, and ' + cps + ' copper.}}');
var subjectNames = [];
var subjectAccounts = [];
var subjectInitials = [];
subjects.forEach(function (subject) {
// enough copper coins? If not, the last one in the group has to take the diff
if ((rest > 0.999 || rest < -0.999) && newcounter === partycounter) {
cps += Math.round(rest);
}
subjectAccounts.push([pps, gps, eps, sps, cps]);
subjectNames.push(getAttrByName(subject.id, 'character_name'));
subjectInitials.push(loadAccount(subject));
partyGoldOperation = true;
});
saveAccountsAndTransaction(msg.who, subjects, subjectNames, subjectAccounts, subjectInitials, 'Share');
}
// Add coin to target
if (argTokens.includes('-add') || argTokens.includes('-a') || argTokens.includes('-credit')) {
output = '';
// Perform operations on each target
var _transactionEffects = [];
subjects.forEach(function (subject) {
var subjectName = getAttrByName(subject.id, 'character_name');
var subjectInitial = loadAccount(subject);
var subjectFinal = duplicateAccount(subjectInitial);
total = goldEquivalent(subjectInitial);
partytotal = total + partytotal;
output += '
' + subjectName + '';
if (ppa) {
output += '
' + ppa[2];
subjectFinal[0] += parseFloat(ppa[3]);
}
if (gpa) {
output += '
' + gpa[2];
subjectFinal[1] += parseFloat(gpa[3]);
}
if (epa) {
output += '
' + epa[2];
subjectFinal[2] += parseFloat(epa[3]);
}
if (spa) {
output += '
' + spa[2];
subjectFinal[3] += parseFloat(spa[3]);
}
if (cpa) {
output += '
' + cpa[2];
subjectFinal[4] += parseFloat(cpa[3]);
}
saveAccount(subject, subjectFinal);
_transactionEffects.push(getPlayerEffect(subjectName, getDelta(subjectFinal, subjectInitial)));
sendChat(scname, '/w "' + subjectName + '" &{template:' + rt[0] + '} {{' + rt[1] + '=
GM has Disbursed Coin
' + output + '}}');
partyGoldOperation = true;
});
var type = msg.content.includes('-revert ') ? 'Revert Transaction' : 'Add';
recordTransaction(type, msg.who, _transactionEffects);
var s = subjects.length > 1 ? 's' : '';
sendChat(scname, '/w gm &{template:' + rt[0] + '} {{' + rt[1] + '=
Disbursement to Player' + s + '
' + output + '}}');
}
// Subtract coin from target
if (argTokens.includes('-pay') || argTokens.includes('-p') || argTokens.includes('-subtract') || argTokens.includes('-sub') || argTokens.includes('-bill')) {
output = '';
var _transactionEffects2 = [];
subjects.forEach(function (subject) {
partycounter += 1;
var subjectName = getAttrByName(subject.id, 'character_name');
var subjectInitial = loadAccount(subject);
var subjectFinal = duplicateAccount(subjectInitial);
if (ppa !== null) subjectFinal = changeMoney(subjectFinal, ppa[2]);
if (gpa !== null) subjectFinal = changeMoney(subjectFinal, gpa[2]);
if (epa !== null) subjectFinal = changeMoney(subjectFinal, epa[2]);
if (spa !== null) subjectFinal = changeMoney(subjectFinal, spa[2]);
if (cpa !== null) subjectFinal = changeMoney(subjectFinal, cpa[2]);
output += '
' + subjectName + ' has ';
if (subjectFinal === 'ERROR: Not enough cash.') output += 'not enough cash!';else {
output += '
' + subjectFinal[0] + 'pp';
output += '
' + subjectFinal[1] + 'gp';
output += '
' + subjectFinal[2] + 'ep';
output += '
' + subjectFinal[3] + 'sp';
output += '
' + subjectFinal[4] + 'cp';
saveAccount(subject, subjectFinal);
_transactionEffects2.push(getPlayerEffect(subjectName, getDelta(subjectFinal, subjectInitial)));
}
sendChat(scname, '/w "' + subjectName + '" &{template:' + rt[0] + '} {{' + rt[1] + '=
GM has Removed Coin
' + output + '}}');
partyGoldOperation;
});
recordTransaction('Subtract', msg.who, _transactionEffects2);
var _s = msg.selected.length > 1 ? 's' : '';
sendChat(scname, '/w gm &{template:' + rt[0] + '} {{' + rt[1] + '=
Bill Collection from Player' + _s + '
' + output + '}}');
}
// Evenly distribute sum of coin to group of players
if (argTokens.includes('-loot') || argTokens.includes('-l')) {
populateCoinContents(subcommand);
output = '';
var _transactionEffects3 = [];
subjects.forEach(function (subject) {
var subjectName = getAttrByName(subject.id, 'character_name');
var subjectInitial = loadAccount(subject);
var subjectFinal = duplicateAccount(subjectInitial);
if (ppa !== null) {
ppt = cashsplit(ppa[3], subject, subjects.length);
}
if (gpa !== null) {
gpt = cashsplit(gpa[3], subject, subjects.length);
}
if (epa !== null) {
ept = cashsplit(epa[3], subject, subjects.length);
}
if (spa !== null) {
spt = cashsplit(spa[3], subject, subjects.length);
}
if (cpa !== null) {
cpt = cashsplit(cpa[3], subject, subjects.length);
}
output += '
' + name + '';
if (ppa) {
subjectFinal[0] = subjectInitial[0] + parseFloat(ppt);
output += '
' + ppt + 'pp';
}
if (gpa) {
subjectFinal[1] = subjectInitial[1] + parseFloat(gpt);
output += '
' + gpt + 'gp';
}
if (epa) {
subjectFinal[2] = subjectInitial[2] + parseFloat(ept);
output += '
' + ept + 'ep';
}
if (spa) {
subjectFinal[3] = subjectInitial[3] + parseFloat(spt);
output += '
' + spt + 'sp';
}
if (cpa) {
subjectFinal[4] = subjectInitial[4] + parseFloat(cpt);
output += '
' + cpt + 'cp';
}
saveAccount(subject, subjectFinal);
sendChat(scname, '/w "' + name + '" &{template:' + rt[0] + '} {{' + rt[1] + '=
Distributing Loot
' + output + '}}');
_transactionEffects3.push(getPlayerEffect(name, getDelta(targetFinal, targetInitial)));
partyGoldOperation = true;
});
recordTransaction('Distribute Loot', msg.who, _transactionEffects3);
sendChat(scname, '/w gm &{template:' + rt[0] + '} {{' + rt[1] + '=
Distributing Loot
' + output + '}}');
}
// Create a new shop stub
if (argTokens.includes('-makeShop')) {
var subject = subjects[0];
if (targets.length !== 4) {
sendChat(scname, '/w gm Invalid Shop Creation Command');
}
var shopName = targets[0];
var locale = targets[1];
var appearance = targets[2];
var _shopkeeper2 = targets[3];
subject.get('gmnotes', function (source) {
log('Existing Notes: ' + source);
var gmNotes = '';
// If you haven't selected anything, source will literally be the following string: 'null'
if (!source || source === 'null') {
source = '';
gmNotes = '';
} else if (source.includes(gmNotesHeaderString)) {
// Parse the GM Notes
gmNotes = parseGmNotes(source);
log('GM Notes: ' + gmNotes);
if (gmNotes) {
log('gmnotes exist');
if (gmNotes.CashMaster) {
log('cm exists');
if (gmNotes.CashMaster.Shop) {
log('Shop already exists.');
sendChat(scname, '/w gm Shop already exists.');
return;
}
}
}
}
log('Creating new shop.');
log(' Name: ' + shopName);
log(' Location: ' + locale);
log(' Appearance: ' + appearance);
log(' Shopkeeper: ' + _shopkeeper2);
var shop = {
Name: shopName,
Location: locale,
Appearance: appearance,
Shopkeeper: _shopkeeper2,
Items: []
};
var shopTemplate = {
CashMaster: {
Shop: shop
}
};
var parseableStart = source.indexOf(gmNotesHeaderString);
var userNotes = parseableStart > -1 ? source.substr(0, parseableStart) : source;
var shopString = JSON.stringify(shopTemplate, null, 4);
var formattedOutput = formatShopData(shopString);
var gmNoteOutput = '' + userNotes + formattedOutput;
setTimeout(function () {
subject.set('gmnotes', gmNoteOutput);
}, 0);
sendChat(scname, '/w gm Creating new shop ' + shopName + '...');
setTimeout(displayShop, 500, subject, shop, getAttrByName(subject.id, 'character_name'), '');
});
return;
}
// Update the specified shop field
if (argTokens.includes('-updateShop')) {
if (targets.length !== 2) {
sendChat(scname, '/w gm Invalid shop update command.');
return;
}
var param = targets[0];
var paramValue = targets[1];
var _subject = subjects[0];
_subject.get('gmnotes', function (source) {
if (!source || source === 'null') {
sendChat(scname, '/w gm No shop exists.');
return;
}
var gmNotes = parseGmNotes(source);
if (gmNotes) {
if (gmNotes.CashMaster) {
var shop = gmNotes.CashMaster.Shop;
if (shop) {
switch (param) {
case 'Name':
shop.Name = paramValue;
break;
case 'Location':
shop.Location = paramValue;
break;
case 'Appearance':
shop.Appearance = paramValue;
break;
case 'Shopkeeper':
shop.Shopkeeper = paramValue;
break;
default:
sendChat(scname, '/w gm Invalid Shop Parameter');
return;
}
var parseableStart = source.indexOf(gmNotesHeaderString);
var userNotes = parseableStart > -1 ? source.substr(0, parseableStart) : source;
var shopString = JSON.stringify(gmNotes, null, 4);
var formattedOutput = formatShopData(shopString);
var gmNoteOutput = '' + userNotes + formattedOutput;
setTimeout(function () {
_subject.set('gmnotes', gmNoteOutput);
}, 0);
sendChat(scname, '/w gm Shop Updating...');
setTimeout(displayShop, 500, _subject, shop, getAttrByName(_subject.id, 'character_name'), '');
}
}
}
});
return;
}
// Edit the specified item field
if (argTokens.includes('-updateItem')) {
if (targets.length !== 3) {
sendChat(scname, '/w gm Invalid item update command.');
return;
}
var _itemName2 = targets[0];
var _param = targets[1];
var _paramValue = targets[2];
var _subject2 = subjects[0];
_subject2.get('gmnotes', function (source) {
if (!source || source === 'null') {
sendChat(scname, '/w gm No shop exists.');
return;
}
var gmNotes = parseGmNotes(source);
if (gmNotes) {
if (gmNotes.CashMaster) {
var shop = gmNotes.CashMaster.Shop;
if (shop) {
var items = shop.Items;
if (items) {
log('Items: ' + JSON.stringify(items));
if (items.length > 0) {
var itemIndex = items.map(function (i) {
return i.Name;
}).indexOf(_itemName2);
if (itemIndex === -1) {
sendChat(scname, '/w gm Item does not exist');
return;
}
if (_param === 'DELETE' && _paramValue === 'YES') {
items.splice(itemIndex, 1);
sendChat(scname, '/w gm Item Deleting...');
} else if (_param === 'DUPLICATE' && _paramValue === 'YES') {
var item = items[itemIndex];
items.push({
Name: 'Copy of ' + item.Name,
Price: item.Price,
Description: item.Description,
Quantity: item.Quantity,
Weight: item.Weight,
Properties: item.Properties,
Modifiers: item.Modifiers
});
sendChat(scname, '/w gm Item Duplicating...');
} else {
var _item = items[itemIndex];
log('Item: ' + JSON.stringify(_item));
switch (_param) {
case 'Name':
_item.Name = _paramValue;
break;
case 'Price':
_item.Price = _paramValue;
break;
case 'Description':
_item.Description = _paramValue;
break;
case 'Quantity':
_item.Quantity = _paramValue;
break;
case 'Weight':
_item.Weight = _paramValue;
break;
case 'Properties':
_item.Properties = _paramValue.split('|').join(', ');
break;
case 'Modifiers':
_item.Modifiers = _paramValue.split('|').join(', ');
break;
default:
sendChat(scname, '/w gm Invalid Item Parameter or Delete Cancelled.');
return;
}
sendChat(scname, '/w gm Item Updating...');
}
var parseableStart = source.indexOf(gmNotesHeaderString);
var userNotes = parseableStart > -1 ? source.substr(0, parseableStart) : source;
var shopString = JSON.stringify(gmNotes, null, 4);
var formattedOutput = formatShopData(shopString);
var gmNoteOutput = '' + userNotes + formattedOutput;
setTimeout(function () {
_subject2.set('gmnotes', gmNoteOutput);
}, 0);
setTimeout(displayShop, 500, _subject2, shop, getAttrByName(_subject2.id, 'character_name'), '');
}
}
}
}
}
});
return;
}
// Add Item to Shop
if (argTokens.includes('-addItem')) {
if (targets.length !== 7) {
sendChat(scname, '/w gm Invalid item add command.');
return;
}
var _subject3 = subjects[0];
var _itemName3 = targets[0];
var price = targets[1];
var desc = targets[2];
var quantity = targets[3];
var weight = targets[4];
var properties = targets[5].split('|').join(', ');
var modifiers = targets[6].split('|').join(', ');
_subject3.get('gmnotes', function (source) {
if (!source || source === 'null') {
sendChat(scname, '/w gm No shop exists.');
return;
}
var gmNotes = parseGmNotes(source);
if (gmNotes) {
if (gmNotes.CashMaster) {
if (gmNotes.CashMaster.Shop) {
var shop = gmNotes.CashMaster.Shop;
if (shop.Items) {
var items = shop.Items;
items.push({
Name: _itemName3,
Price: price,
Description: desc,
Quantity: quantity,
Weight: weight,
Properties: properties,
Modifiers: modifiers
});
var parseableStart = source.indexOf(gmNotesHeaderString);
var userNotes = parseableStart > -1 ? source.substr(0, parseableStart) : source;
var shopString = JSON.stringify(gmNotes, null, 4);
var formattedOutput = formatShopData(shopString);
var gmNoteOutput = '' + userNotes + formattedOutput;
setTimeout(function () {
_subject3.set('gmnotes', gmNoteOutput);
}, 0);
sendChat(scname, '/w gm Adding Item...');
setTimeout(displayShop, 500, _subject3, shop, getAttrByName(_subject3.id, 'character_name'), '');
}
}
}
}
});
return;
}
// Set Party to selected
if (argTokens.includes('-setParty') || argTokens.includes('-sp')) {
var partyList = [];
if (!argTokens.includes('-clear')) {
msg.selected.forEach(function (obj) {
var token = getObj('graphic', obj._id); // eslint-disable-line no-underscore-dangle
var pc = void 0;
if (token) {
pc = getObj('character', token.get('represents'));
if (pc) {
pcName = getAttrByName(pc.id, 'character_name');
if (pcName) {
partyList.push(pcName);
}
}
}
});
}
log('Party List: ' + partyList);
state.CashMaster.Party = partyList;
sendChat(scname, '/w gm **Party:' + partyList.length + '**
' + partyList);
}
// Calculate party gold value
if (partyGoldOperation || argTokens.includes('-overview') || argTokens.includes('-o')) {
//! overview
partytotal = 0;
partycounter = 0;
if (!argTokens.includes('--usd')) usd2 = 0;else usd2 = usd;
output = '/w gm &{template:' + rt[0] + '} {{' + rt[1] + '=
Party\u2019s cash overview';
subjects.forEach(function (subject) {
output += playerCoinStatus(subject, usd2)[0];
partytotal += playerCoinStatus(subject, usd2)[1];
});
partytotal = Math.round(partytotal * 100, 0) / 100;
output += '
Party total: ' + toUsd(partytotal, usd2) + '}}';
sendChat(scname, output);
}
}
});
});
});