Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

[Script] CashMaster 5e - a simple script to manage a party's money.

I appreciate your enthusiasm, but I don't think that is the direction the script should go. A lot of the new functions that you are mentioning should, at least for me, be resolved via roleplaying, not with script functions that just blow up the complexity.  And we also shouldn't remove the shortcuts. I'm using them all the time, and it would break half a dozen macros for me alone. The default token is something that I already had on the list but didn't really know yet how to approach it. The easiest way should be to just store a name array in the state object. So if you don't mind, can we make separate PR for the different additions?
1529432970
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Please don't remove shortcuts. :) I like using them  to construct my own interfaces. Not every feature is needed for me, and the Shaped Roll template is amazingly compact. (I really hate giant buttons). I have a small number of uber-chat menus, that have subsections for things like Cashmaster commands.
1529435143

Edited 1529435806
GM Michael
API Scripter
Arthur B said: I appreciate your enthusiasm, but I don't think that is the direction the script should go. A lot of the new functions that you are mentioning should, at least for me, be resolved via roleplaying, not with script functions that just blow up the complexity. And we also shouldn't remove the shortcuts. I'm using them all the time, and it would break half a dozen macros for me alone.The default token is something that I already had on the list but didn't really know yet how to approach it. The easiest way should be to just store a name array in the state object. So if you don't mind, can we make separate PR for the different additions? I can understand the desire to keep the shortcuts, but that's going to necessitate rewriting the token parsing.  The existing naive approach using msg.content.includes() won't cover it, especially because at present you can stick any of the tags anywhere.  If a player's name had a hyphen in it, the existing shortcut parser is at risk of breaking, misinterpreting their name as a tag.  We'd need true tokenization of the input string to handle all the cases where people could potentially put the shortcuts.  It's either that or we switch from a single hyphen to 2, but that still breaks people's existing macros and doesn't help with future-proofing against later feature additions that happen to start with the same letter. I can understand haggling and NPC->PC invoicing, but I'm curious about your issues with the other features or why you're concerned about complexity increasing.  You don't see the tags you don't use and the UI menu is nowhere near filling up the chat window, so I don't see the harm in adding situational features at this point. And yes, I was figuring default tokens would be an array in the state object, mapping a user to an entry in the party list.
Regarding the parsing: Yeah, that needs some work, unfortunately. We could switch to `msg.content.startswith()` and add the `!cm ` to the search string, so we avoid the problem with names being misinterpreted as tags, and we could keep the shortcuts. Or we'd use regular expressions, when we really need a more complex approach. We would still have some problems when any future features that start with same letters, though.  Regarding new functions: My concerns are probably just my gut feeling telling me that this isn't what I had in mind when I started with the script. But as long as it doesn't interfere with the current functionality, it's fine. 
1529439302
The Aaron
Pro
API Scripter
Looks like you could change all instances of: msg.content.includes( to argTokens.includes( and define argTokens as: let argTokens = msg.content.split(/\s+/); on line 246.
The Aaron said: Looks like you could change all instances of: msg.content.includes( to argTokens.includes( and define argTokens as: let argTokens = msg.content.split(/\s+/); on line 246. Thanks, I'll give that a try. So .includes() works differently on arrays than on strings?
1529440408

Edited 1529440426
The Aaron
Pro
API Scripter
Yup.  For strings, it's "Does this string exist in this larger one?"  For arrays, it's "is this value one of the entries of the array?"
The Aaron said: Yup.  For strings, it's "Does this string exist in this larger one?"  For arrays, it's "is this string one of the entries of the array?" Sweet, thanks!
1529443281
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
I suppose shortcuts aren't as important as keeping the raw commands. I like a consistent interface whenever possible, and if I can still construct my own...
1529445369

Edited 1529449300
GM Michael
API Scripter
keithcurtis said: I suppose shortcuts aren't as important as keeping the raw commands. I like a consistent interface whenever possible, and if I can still construct my own... I don't mind leaving them in so long as it's tokenized so there's no false positives. The Aaron's suggestion should handle it nicely. I'll add them back to my branch before I issue a PR.
Michael G. said: I don't mind leaving them in so long as it's tokenized so there's no false positives. The Aaron's suggestion should handle it nicely. I'll add them back to my branch before I issue a PR. I just merged TheAaron's suggestion into my develop branch.
1529511007

Edited 1529511563
GM Michael
API Scripter
I had another idea for a feature, one that I hope will be more amenable to all parties involved: an Undo button.  Let's say the DM pressed the wrong button to add instead of subtract, or Ao-forbid, typed -s instead of -ss (my new shortcut for -status) and accidentally threw the entire party's individual gold into a giant pile and redistributed it.  Operational commands would print a Revert button to the GM log of events.  If the GM hits revert, the effects are undone.   To handle operations that don't have an inverse (-share, -best-share, -merge, etc), this would necessitate adding a new -set command that would be embedded in the undo button and pre-populated with the previous account states of the affected players.  I'm thinking something like the following syntax... !cm -set "Player Name" [amount][currency] So then for cases like reverting -share... !cm -set "Player1 Name" [amount][currency] "Player2 Name" [amount][currency] "Player3 Name" [amount][currency]
An undo button is a fantastic idea! But instead of passing through all the affected players and currencies in the undo button, how about we write the previous account values into state every time the script executes a command and get the values from there in case of an undo?
1529520764

Edited 1529521549
GM Michael
API Scripter
Arthur B said: An undo button is a fantastic idea! But instead of passing through all the affected players and currencies in the undo button, how about we write the previous account values into state every time the script executes a command and get the values from there in case of an undo? In that case, we'd need a discrete transaction history in the event another transaction is performed after it, but before the GM presses Undo.  Then again, if we just store the last 20 (or whatever arbitrarily large number you prefer) operational commands, that should be plenty.  Then if the GM wanted to see a compressed view of recent transactions, that could be done and wouldn't rely on the chat logs still existing (I personally purge chat logs quite frequently). Now that I'm thinking about it, I think I'd rather have the revert buttons on the same listing as the transaction logs rather than the normal operation reports to reduce clutter.  So the GM would have a Transaction History button, that when pressed would bring up a list of what operations were performed, when they were performed, who initiated them, what they did, and include an Undo button in the row.
How about we store the account deltas in the history? So instead of storing the absolute values, we store by how much each currency was increased/decreased. Then we can undo with the negative values, without getting into troubles when someone changed the accounts manually in the meanwhile. And sure, a longer history instead of a single undo would then be quite simple to achieve.
1529527603

Edited 1529527656
GM Michael
API Scripter
Yeah, that'd be the best way to go about it. I put up a pull request.  I fear there may be bugs yet, but i've done my preliminary testing at least. See readme for how to use the below changes. Default parties are now possible Default characters are now possible for players Players may send Invoices to each other Players may now "send" currency to NPCs
1529614096

Edited 1529623253
GM Michael
API Scripter
Posted a PR for Transaction History and Reversion. Full changelist: Transactions are now stored and can be viewed (surprise, surprise).  At present, it tracks the last 20. Transactions are stored as deltas, per Arthur's suggestion.  This means you can roll them back independently of other actions.  When you undo a transaction, all players are rolled back independently.  Theoretically, if you wanted to, you could redo on only some of them. The Reallocate Coins button (-share) is now way less terrifying.  I still left the "are you sure you want to do this" dialog though. Added -noToken tag for the -add command, enabling it to be called by UI buttons (specifically, the revert button) or the API. Added combo commands so that several actions can be performed at once, delimited by semicolons.  When doing a revert, first, all inverse operations are performed, then the -revert command is processed. Added the -revert command.  This command does NOT actually revert a transaction by itself.  That's done by the -add commands prior to it.  -revert just marks the transaction as reverted and forces a refresh of the transaction history in the UI. To clarify, when you press the revert button, something like the below command is performed.  In this case, the original operation transferred 10gp from Isabelle to Tazeka. !cm -add -noToken "Isabelle Mori" 10gp;-add -noToken "Tazeka Liranov" -10gp;-revert 22 Basically, that command is saying that in order to revert transaction #22... run the -add command on Isabelle Mori to give her the gold she's missing.  Because this is a button that shouldn't reasonably require a user to select all targets to revert, use the -noToken tag. run -add on Tazeka Liranov with a negative number.  Once more, use -noToken. mark #22 as reverted so the button vanishes, its effects are crossed out, and the transaction list is refreshed.
This is awesome! Let me go through it. But that looks really fantastic.
1529755454

Edited 1529776699
GM Michael
API Scripter
Finally got eslint up and running with Airbnb (that turned out to be way more of a headache than expected).  So hopefully you won't have anymore lintfix commits :P Current project: IDs and multiple subjects and targets.  No clue when I'll finish it though.  Maybe next week. You'll find my syntax plans below.  I'm trying to make it as unified as possible without breaking backwards compatibility.  For single-group operations (-add, -sub, -merge, etc), only Subjects are used.  For things with a second group (such as -transfer, -invoice), a Target is required as well. // -S: Subject list. // If it's specified, it'll use it. // If no -S but -noToken specified, it'll assume the first quoted list to be the subjects // If no -S and no -noToken, it'll assume the selected tokens are the subjects // -T: Target list. // If specified, it'll know exactly what is and is not a target. // If no -T, it'll assume the first quoted list is !cm -transfer -S "Subject1 Name,Subject2 Name" -T "Target1 Name,Target2 Name" 50gp // This will make the following transfers: // S1 -50gp-> T1 // S1 -50gp-> T2 // S2 -50gp-> T1 // S2 -50gp-> T2 !cm -transfer -S #ABCDabcd1234-_# -T #DEFGHIdefghi0987_,JKLMNopqrsTuV-# 50gp // This will make the following transfers: // ABCDabcd1234-_ -50gp-> DEFGHIdefghi0987_ // ABCDabcd1234-_ -50gp-> JKLMNopqrsTuV- !cm -transfer -S "Subject1 Name" -T #DEFGHIdefghi0987_,JKLMNopqrsTuV-# 50gp // This will make the following transfers: // S1 -50gp-> DEFGHIdefghi0987_ // S1 -50gp-> JKLMNopqrsTuV- This may or may not come along with a [Give to Party] player button that would basically be a multi-target transfer, targeting everyone else in the state.Cashmaster.Party list.
Sounds great! Especially with macros that could use both @{selected} and @{target}. (I had to read it twice before understanding everything. Midsommar hangover...)
Arthur B said: Sounds great! Especially with macros that could use both @{selected} and @{target}. (I had to read it twice before understanding everything. Midsommar hangover...) The joy of inlagd sill and snaps....
… and home-brewed beer.
1530022088

Edited 1530022209
GM Michael
API Scripter
After further investigation, I'm going to also be adding a -C tag for Currency.  It will be optional in most cases, but if you're in object id mode, you will need it.  Otherwise, an object's ID could end up getting parsed as currency. ie !cm -transfer -noToken -S #L4nci99ppVZSyVntRw7# -T #L84VTtYCCHTBE35xq7C# -C "10gp 10sp" As you can see, if it were not for the -C tag, the currency parser would see the 99pp in the first object's ID and think you're trying to transfer 99 platinum.
So my branch seems to be stably supporting quotation selection of subjects and targets.  That means time to start work on the id selection system, but I was wondering, should we accept ids of characters or of tokens?  Quotation accepts character names, so there's that, but is anything ever going to actually use the character ID or just token ID?
1530040056
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Character ID would remain stable if a DM has a macro that distributes to a pre-set, unselected party, even if "Bob the Necromancer" changes his name to "Steve the Barbarian".
keithcurtis said: Character ID would remain stable if a DM has a macro that distributes to a pre-set, unselected party, even if "Bob the Necromancer" changes his name to "Steve the Barbarian". Someone's token id would change if their name changed?  I guess the I should rephrase it this way: Should the script parse char ids or token ids?  It'll have to get to char ids internally anyways to actually move the coins around, but would it be more convenient for users to supply token ids or char ids? Do tokens on different pages have different IDs maybe?  Perhaps I'm wrong, but I would guess @{selected} would get the token id, not the char id.  Is there an easy way to get the char ID without using the API?
1530042585
The Aaron
Pro
API Scripter
Token ID changes for every time you create the token, be it dragging out on a page multiple times, or dragging onto different pages.
1530042695
The Aaron
Pro
API Scripter
@{selected|character_id} will give you the character id of a selected token, if it represents a character.  In my scripts that deal with characters or tokens, I do a lookup for both, and take the character if it's found, then look for a token and get it's represents character if not.
1530043028
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Michael G. said: keithcurtis said: Character ID would remain stable if a DM has a macro that distributes to a pre-set, unselected party, even if "Bob the Necromancer" changes his name to "Steve the Barbarian". Someone's token id would change if their name changed?  I guess the I should rephrase it this way: Should the script parse char ids or token ids?  It'll have to get to char ids internally anyways to actually move the coins around, but would it be more convenient for users to supply token ids or char ids? Do tokens on different pages have different IDs maybe?  Perhaps I'm wrong, but I would guess @{selected} would get the token id, not the char id.  Is there an easy way to get the char ID without using the API? No, I meant the ability to reference the character id has permanent value. With a list of 5 character ids, a DMs "distribute treasure" macro would just take a value and split it, regardless of where anyone is or if they are on the current page or if they have changed their name. Selecting a token requires the tokens to be present to select, and token id is too volatile. Basing it on the name is simple, but breaks if the player changes their character name too drastically.
Michael G. said: Someone's token id would change if their name changed?  I guess the I should rephrase it this way: Should the script parse char ids or token ids?  It'll have to get to char ids internally anyways to actually move the coins around, but would it be more convenient for users to supply token ids or char ids? Do tokens on different pages have different IDs maybe?  Perhaps I'm wrong, but I would guess @{selected} would get the token id, not the char id.  Is there an easy way to get the char ID without using the API? I feel it should be the character id. The token ids might change all the time, as TheAaron mentioned. And as you said, at the end, we'd need the character ID anyway. Michael G. said: After further investigation, I'm going to also be adding a -C tag for Currency.  It will be optional in most cases, but if you're in object id mode, you will need it.  Otherwise, an object's ID could end up getting parsed as currency. ie !cm -transfer -noToken -S #L4nci99ppVZSyVntRw7# -T #L84VTtYCCHTBE35xq7C# -C "10gp 10sp" As you can see, if it were not for the -C tag, the currency parser would see the 99pp in the first object's ID and think you're trying to transfer 99 platinum. Or we optimize the regexp for the currency parser. Something like  /([^\w][0-9-]+ ?)pp/ instead of /([0-9 -]+)pp/ should work. The regular expressions aren't really optimized yet.. O:)
1530058801

Edited 1530059510
GM Michael
API Scripter
The Aaron said: @{selected|character_id} will give you the character id of a selected token, if it represents a character.  In my scripts that deal with characters or tokens, I do a lookup for both, and take the character if it's found, then look for a token and get it's represents character if not. That works for me. Arthur B said: Or we optimize the regexp for the currency parser. Something like  /([^\w][0-9-]+ ?)pp/ instead of /([0-9 -]+)pp/ should work. The regular expressions aren't really optimized yet.. O:) We could do that, but unless I'm mistaken, it would mean you would no longer be able to compress them all into one string when using multiple currencies, so no more 10gp10sp10ep commands.  Not that it's a big deal.  I personally like it, but it's not essential.  Theoretically, a campaign could be working in copper-only, so while extremely unlikely, it's possible an id could overlap with a valid amount of copper to transfer. If going for better regex, maybe something more like... (\s|,)+-?\d+pp(\s|$)+ That way you don't have to worry about it being at the beginning of the ID either.  You're still left with super unlikely collisions, but I'm not sure what you can do other than mandate characters you won't find in an ID like I did with the -C "<stuff>"
Michael G. said: We could do that, but unless I'm mistaken, it would mean you would no longer be able to compress them all into one string when using multiple currencies, so no more 10gp10sp10ep commands.  Not that it's a big deal.  I personally like it, but it's not essential.  Theoretically, a campaign could be working in copper-only, so while extremely unlikely, it's possible an id could overlap with a valid amount of copper to transfer. If going for better regex, maybe something more like... (\s|,)+-?\d+pp(\s|$)+ That way you don't have to worry about it being at the beginning of the ID either.  You're still left with super unlikely collisions, but I'm not sure what you can do other than mandate characters you won't find in an ID like I did with the -C "<stuff>" Well, we can still do something like `!cm -l 10gp 10sp 10ep`, with spaces in between. And when we limit the length for the amount regex token, we could exclude the ID collisions completely. I believe the IDs have a fixed length of 16 characters or something like that (on the train right now, so I can't really log in to check). Then we could do something like: [^\w](\d{1,14} ?pp)
1530106805

Edited 1530107035
GM Michael
API Scripter
They appear to be 19 long (20, if you count the '-' that they're prepended with). I don't think using [^\w] is the right approach though because it'll include all manner of things we don't necessarily want, most noticeably, a decimal point (or comma, I suppose, if you're a continental european).  [\w] also includes digits, which in rare circumstances could lead to false positives.  I'd rather be explicit. At present, I'm leaning toward this... (\s|,|^|"|')-?\d{1,16} ?(pp|PP|Pp|pP)(\s|,|$|"|') 16 places should be more than plenty and we'll cover the major delimiters. const populateCoinContents = ( input ) => { ppg = / ( \s | , | ^ | " | ')- ? \d {1,16} ? (pp | PP | Pp | pP)( \s | , | $ | " | ') / ; ppa = ppg . exec ( input ); gpg = / ( \s | , | ^ | " | ')- ? \d {1,16} ? (gp | GP | Gp | gP)( \s | , | $ | " | ') / ; gpa = gpg . exec ( input ); epg = / ( \s | , | ^ | " | ')- ? \d {1,16} ? (ep | EP | Ep | eP)( \s | , | $ | " | ') / ; epa = epg . exec ( input ); spg = / ( \s | , | ^ | " | ')- ? \d {1,16} ? (sp | SP | Sp | sP)( \s | , | $ | " | ') / ; spa = spg . exec ( input ); cpg = / ( \s | , | ^ | " | ')- ? \d {1,16} ? (cp | CP | Cp | cP)( \s | , | $ | " | ') / ; cpa = cpg . exec ( input ); }; As for -C "[amount][currency]" code, to be honest, I'd rather keep it in.  At present, my code looks for it and will parse only from it if it finds it there, but otherwise, it'll just run across the entire subcommand.  Granted, in most cases, it's not going to be overly useful, but player names are basically arbitrary strings and there's nothing to stop someone from naming themselves 99GP if they're a Warforged or something.
Sure, absolutely. And if you want to keep the "-C", I'm fine with it as well. As long as we don't break backwards compatibility (e.g. by making it mandatory), it's ok. Of course, I prefer if we can get it bullet proof, and your approach on the regex seems to be the way to go for me.
Created a PR for the SuperSelect branch. I've tried handling as many error cases as I can think of, but I probably missed something somewhere.
Fixed but a small typo that I noticed during testing, and merged your PR in. Need to give it some more testing, though, but it looks great. In the meanwhile, this is more your script than mine ;)
I would love to see this become player agnostic.  My party tracks their loot as a group, not individually.  Being able to quickly add and subtract from the party's available loot would be great.  I guess you could assign one party member to be the 'bank'.  
1531159708
GiGs
Pro
Sheet Author
API Scripter
You could create another character, and call it The Bank.
Sorry, got a bit distracted with private things, and forgot about the latest changes. :-( Just pushed out the update to v0.8.0, and sent a pull request for the one-click repo as well.
Yey, updates! However, I can't seem to get @{selected|character_id} or @{target|character_id} to work. Even the example from the first page  !cm -t -T "@{target|character_id}" 1gp (From CashMaster):   ERROR:  You need to select at least one character.
Ravenknight said: Yey, updates! However, I can't seem to get @{selected|character_id} or @{target|character_id} to work. Even the example from the first page  !cm -t -T "@{target|character_id}" 1gp (From CashMaster):   ERROR:  You need to select at least one character. Odd. Let me check that out tonight when I'm back home. Won't have much time to fix it before the weekend, though... 
1533019267

Edited 1533022457
Ravenknight
KS Backer
Arthur B said: Ravenknight said: Yey, updates! However, I can't seem to get @{selected|character_id} or @{target|character_id} to work. Even the example from the first page  !cm -t -T "@{target|character_id}" 1gp (From CashMaster):   ERROR:  You need to select at least one character. Odd. Let me check that out tonight when I'm back home. Won't have much time to fix it before the weekend, though...  Thanks Arthur! Edit: I forgot to add that I did some testing yesterday.  !cm -transfer -S "Givare" -T "@{target|character_name}" 1gp !cm -transfer -S "@{target|character_name}" -T "Tagare" 1gp Both works fine.
1533123874

Edited 1533124587
GM Michael
API Scripter
From another thread, it sounded like a useful feature would be the ability to use CM to manage gold expenditures for spells. This could sort of be done already with drop with reason, but it would be even better if we could disallow casting if you didn't have enough. Ravenknight said: Yey, updates! However, I can't seem to get @{selected|character_id} or @{target|character_id} to work. Even the example from the first page  !cm -t -T "@{target|character_id}" 1gp (From CashMaster):   ERROR:  You need to select at least one character. It looks like you're not specifying a subject. In that case, the script will check to see if you have anything selected and use that if it can. If you don't, it'll then try to use the preconfigured default token for you. Are you sure you have that all set up?  If you don't have a token selected or a default token configured, it won't know who to take the gold from.
Hiya, yup this is with a token selected. And all the other stuff seems to work just fine. It's the character_id stuff that refuse to go my way for some reason. Not that it matter much. @{character_name} is just as good.
This script is turning out amazing with the latest update. The ability to use target|token is great for creating shops and other stuff. I did find another small bug however, when using -GIVENPC we get two Sender Transfer Reports instead of just one.
Sorry I haven't had a chance to look at it yet, I've been busy with other things irl. Have you narrowed down when it happens by chance? Also, the idea of shopkeeping sounds like a nice feature to implement. Maybe allow the script to parse the GM notes section for a shop pricing block. This would keep me from having to repeat prices a million times for players and prevent typos when trying to buy.
1533541083

Edited 1533737694
Ravenknight
KS Backer
That's a great idea Michael. I use a "Shop"-character with all the relevant prices set as attributes. Took a while to set up but it's really playing out nicely. As for narrowing it down, no. :) I will see if I can put in some bug-hunting though. Edit:  !cm -transfer "PC-X" 1gp !cm -givenpc "NPX-X" 1gp !cm -dropWithReason "I lost it" 1gp All the commands above results in two Sender Transfer Reports. 
1534092630

Edited 1534103379
GM Michael
API Scripter
Finally got the opportunity to go bug hunting today.  Findings: Target ID: Not a defect.   The error message you're reporting is actually not even in 0.8.  It's part of 0.7.1 and before.  I guess go validate that you're actually running the latest version?  I don't know how the script engine knows to reload itself with new things, so maybe force a restart? Duplicate Messages: -giveNPC and -dropWithReason are the same function under the hood, so I was able to replicate the bug on them, but not on transfer.  Resolved in branch.   I wonder if the transfer issue is also an artifact of 0.7.1? Set Default Character: selecting no token will crash CashMaster.  Resolved in branch. I've submitted a pull request for my changes.  I've also added the ability to remove your default character selection (to be entirely honest, this is mainly a debug tool).  Sorry it took so long to get around to this!
I just merged your bugfixes into the main branch and released v0.8.1; I also sent a PR for the roll20 repo. Again, thanks Michael G.
Michael G. said: Finally got the opportunity to go bug hunting today.  Findings: Target ID: Not a defect.   The error message you're reporting is actually not even in 0.8.  It's part of 0.7.1 and before.  I guess go validate that you're actually running the latest version?  I don't know how the script engine knows to reload itself with new things, so maybe force a restart? Duplicate Messages: -giveNPC and -dropWithReason are the same function under the hood, so I was able to replicate the bug on them, but not on transfer.  Resolved in branch.   I wonder if the transfer issue is also an artifact of 0.7.1? Set Default Character: selecting no token will crash CashMaster.  Resolved in branch. I've submitted a pull request for my changes.  I've also added the ability to remove your default character selection (to be entirely honest, this is mainly a debug tool).  Sorry it took so long to get around to this! Hmm. I have Cashmaster as One-Click install and updated to the latest version and it was 8.0 when testing. I did have 0.7.1 installed in the same campaign before updating. Could it be an artefact?