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

Want to learn how to write my own script. Any advice?

Hello all! I'd like to begin learning how to write my own script for Roll20. I've used Java and JavaScript in the past, and I have a decent understanding of the basics of programming in general. What I'd like to know is how to interact with Roll20's API. What special call-outs/declarations do I need for certain scenarios (such as interacting with tokens or characters)? How do display things in the chat log? Stuff like that. I've been trying to read over some source code from other scripts, but unfortunately I can't quite make heads or tails of it. Any assistance would be highly appreciated!
1563872666
GiGs
Pro
Sheet Author
API Scripter
The wiki pages on objects, functions, etc are the places to look, and there are some example scripts there. Start here . The language is javascript, with some tweaks for roll20, so you can get tips from a lot of online javascript help sites. To display things in the script log you can use log(' whataver you want'); to display in the browser console, use console.log('your message here'); and to display in the campaign chat log, use sendChat('name of sender, can be left as empty string','message displayed in chat'); sendChat('','message displayed in chat'); A good way to get started is to think of something simple you want to do, and ask for help on how to do it. It's hard to give broad, unfocussed help. There's just too much to cover. We need an objective to narrow it down.
Thank you, this is a great starting point! As for my goal (currently) I want to make a simple "Cash Tracker" for my players/characters. Something where I can manage all of their Currency if need be (such as loot payouts or if an NPC steals from them) and where my players can manage their own wallet. I'm already aware of the Cash Master script, but as far as I understand (and have currently tested) it requires use of the Roll20 character sheets. As for why we don't switch everything over to allow Cash Master: we started this campaign a long time ago, and I was new to Roll20, plus not all of my players are very familiar with the website. It will simply be too much hassle to convert everything over and teach them how to use all the features. (I barely manage to get them to use their macro buttons). I hope this is relatively simple, but I'm fully aware that in the field of Computer Science even the simplest things can potentially become major challenges. I will study up on my own, and whatever further assistance you provide would be wonderful!
1563883266
The Aaron
Roll20 Production Team
API Scripter
(Note: console.log will only work in sheet workers, not the API.) API scripts run on a server "in the cloud". They are event-based, not interactive. Your script will get executed exactly once, thereafter, it will get called into wherever any events you explicitly registered for occurs. The 'ready' event is issued once the API sandbox is finished spinning up, it's usually best to delay registering for events (particularly 'add:' events) until that point. &nbsp;API commands that players send (as well as any chat message) cause 'chat:message' events, pretty much all the other events are lifecycle events on Roll20 Objects. ('add:', 'change:', 'destroy:') &nbsp;changes by the API do not trigger those events, with the exception of sendChat() which does trigger 'chat:message' (careful not to write an infinite loop with you handler for it—if you do, you'll need to clear your chat history). Roll20 Objects (graphic, drawing, handout, character, player, etc) have properties you can get and set with the .get() and .set() methods. You may omit the leading _ on properties that have them (Sets on those properties are ignored). id can be accessed directly. The Roll20 API scripts behave most like Node.js Modules, so reading about them can be a big help (asynchronous, event driven, etc). There is no require or import. All API scripts are concatenated at runtime, do be sure you end in a ; (and possibly start with one), and don't pollute the global namespace—wrap your code in a closure, I usually pass a function to on('ready',...) or create an object from a closure assigned to a global name as an interface.&nbsp; Don't be afraid to ask questions. We love questions. If you search the API forum, there are several longer posts on suggestions to get started with the API. Here's one:&nbsp; <a href="https://app.roll20.net/forum/post/5061355/help-getting-started-on-writing-a-script/?pageforid=5061414#post-5061414" rel="nofollow">https://app.roll20.net/forum/post/5061355/help-getting-started-on-writing-a-script/?pageforid=5061414#post-5061414</a>
1563883691

Edited 1563883721
GiGs
Pro
Sheet Author
API Scripter
There are a couple of experience tracking scripts that can probably be repurposed for what you need. I cant remember the names, and I'm going to bed shortly, but I'll see what i can dig up later. In the meantime, if you can describe exactly what you'll be tracking: what coins, etc for each pc. There are multiple ways you could track it: The simplest would probably be having a journal (character sheet) purpose made for it, with the script creating and managing attributes on it. You wouldnt have to interact directly with the character sheet, the script would do that - it would just be a place to hold the records. Another would be to use the state, a special data store only accessible to script users. The more complicated one would be to use a handout, and have the script update that with current totals.&nbsp; For scripts, you have to think about the user interface: how players are going to interact with it. One common way is a chat menu - a group of buttons that are posted to chat. You could have a button for each coinage type, and + or -, and have it so that when a player clicked the button, they'd be prompted for the amount, and their treasury would be updated with the result reported to chat. You as GM would have extra buttons - to update all the players at once. With something like coin tracking, we'd have to think carefully how to display that for efficiency. But anyway, have a look for experience scripts and see if you find anything you can use, and if you're lucky, Aaron will come along and write the complete script in his lunchbreak. Edit: &nbsp;an Aaron sighting! what a ninja.
1563883856
The Aaron
Roll20 Production Team
API Scripter
(Hahahaha! Aaron's lunch breaks are much reduced in efficiency since he doesn't work remotely at home anymore, but I'll still help how I can. =D )
1563883919
The Aaron
Roll20 Production Team
API Scripter
GiGs said: &nbsp; Edit: &nbsp;an Aaron sighting! what a ninja. I'm just getting up as you're going to bed. =D
Thank you all so much! I have to leave for work myself shortly, so it'll be a while before I post again. I want to track Platinum, Gold, Silver, and Copper (we've decided to not use Electrum due to the odd conversion rate). That's it. Not tracking gems, art, or any of the other loot table things. I think having the journal sheet method is a good way to go. That way if anything gets borked in the script, I still have a method of updating a player's wallet. Which do you think would be best? Having a sheet for each character's wallet, or having one sheet for all characters? The players should have 2 buttons/commands - Spend and Convert. Spend would subtract an amount of the currency they choose, and convert would exchange one currency for another. We use the default 1:10 ratio for these currencies (Platinum &gt; Gold &gt; Silver &gt; Copper). I (the DM) plan to have Give, Spend, Convert, and Loot (Loot divides currencies as equally as possible between all characters, with remainders being randomly assigned). Whenever a player or the DM performs one of these actions, a chat message should display stating the change and the players wallet after the change. ("Player A spent 10 Gold and now has: Platinum: X Gold: X Silver: X Copper: X") My plan of action: Set up Give and Spend, confirm functionality, set up Convert, confirm function, then set up Loot and finalize. Once again, thank you all so much for your willingness to assist me. It really means a lot! &lt;3
1563901160

Edited 1563901525
DXWarlock
Sheet Author
API Scripter
I can pass you mine if you would like to tear it apart, its for the Pathfinder community sheet. Its not all what you need, its a simple spend/gain macro based one that asks for values, But it might give you a place to start figuring out how to add what you need and tweak it to work for your system. I made it with The Aarons help (like most my scripts it seems) on the conversion. (Say you have 1pp and spend 1g it will figure out the 'change'..or you spend 1g and have 12s it will take 10s and leave you 2s). [ignore the values its my test GM character..he's loaded it seems from all the treasure and coin purse testing..lol]
1563903699
The Aaron
Roll20 Production Team
API Scripter
That was fun. I have a bunch of other test scripts I wrote along side that. I've always wanted to write a multi-currency system that supports exchange rates and shifting markets. I think my players would Murder me if I ever get a complete (if simple) economic model set up in game (donating 1000gp to the local church, eh? Get ready for bread to cost 3gp a loaf...)
DXWarlock - Sure! Wouldn't hurt to take a look, and I completely understand getting random and unexpected results from testing. LOL It's still a good looking script though, very compact and efficient.
How do I test my JS file without using HTML or loading it into Roll20 before I'm ready to do so? (I'm aware that I'll need to test it in Roll20 at some point, but I want to get the core concept prepared first) Or, a better way to phrase the question: Is there a way to run JS files without using HTML? All of the learning sources act like I'm adding a script to a webpage. I mean, I guess technically I am, but I just want to test the Script. Here's what I have so far. This is my first time using Arrays, so I just want to make sure this works the way I understand it: var wTest = { plat: 100,&nbsp; gold: 100,&nbsp; silv: 100,&nbsp; copp: 100 }; console.log(wTest.gold); EDIT: I managed to test the array in Chrome, but it wasn't very intuitive so any suggestions when trying to test the script would still be nice. Thankfully this short snippet output the correct value.
Also, should I edit this topic or start a new one at this point? I feel like we're a bit beyond the "Where to start" stage.
1563988699

Edited 1563989969
DXWarlock
Sheet Author
API Scripter
Well you are still getting started, so I think its applicable :) Edited: to add some details of HOW to use my script...that would have been helpful. And sorry took me a bit, been busy last few days, here you go: Not sure if you know what rolltemplates are, I'm using the pathfinder community one for chat output, but you can log() things as you run it. It assumes the person rolling has a character sheet (they need a character to add money to..so it tries to find the one assigned to them even if they chat as 'player' using the 'RollRight(whoPC)' at the bottom. The way it tells which character is by finding one assigned to them and GM notes are blank for that sheet (just how I do it and works for me and my game..horses and such assigned to them I put NPC in the gm notes.) Macro to call it and ask values: &nbsp;!coins ?{Type|Spend|Add} cp:?{CP|0} sp:?{SP|0} gp:?{GP|0} pp:?{PP|0} <a href="https://raw.githubusercontent.com/dxwarlock/CoinPurse/master/Purse.js" rel="nofollow">https://raw.githubusercontent.com/dxwarlock/CoinPurse/master/Purse.js</a> Also I find running it in Roll20 the easiest to test for me. Since scripts require so much 'custom' stuff from roll 20 like the sendchats, and getting attributes. It's impossible to test those parts outside roll20. For example to test what you have, just make it do it on startup and everytime you save it will show you the results: on('ready', function () { var wTest = {plat: 100, gold: 100, silv: 100, copp: 100}; log(wTest.gold); }); Or if that's too annoying to watch the log window. Wrap it in an on chat , and edit &amp; save, then just type !test and it will tell you in chat the result. on('chat:message', function (msg) { if (msg.type === "api" &amp;&amp; msg.content.indexOf("!test") !== -1) { //see if its an API message, and if that message is !test //DO STUFF HERE----- var wTest = {plat: 100, gold: 100, silv: 100, copp: 100}; sendChat('APITEST', wTest.gold); //END OF STUFF TO DO----- } }); I know The Aaron would probably beat me over the head for just tossing variables into global namespace like that. But I think for learning the basics and testing I think it's ok (only in my opinion, I'm known to do messy messy code).
Ok, so I have some new questions: 1) Based on what I've seen, how does one NOT add variables to the Global Namespace? I understand why that's bad (I think) but I also don't understand how you avoid that, strictly based on the code I've read (and understood). 2) DXWarlock - I know you said you're using Pathfinder, but is this script calling from the Roll20 integrated Character Sheets, or are they calling Journal Characters/Handouts? That's not 100% clear to me. 3) How does your script identify and differentiate between the GM and the Players? I thought I understood it for a moment, then I became really lost. I think I'm starting to get an idea of how it'll be written. I'm pretty sure I understand the "API" message type handling and such on('chat:message', function (msg) { if (msg.type === "api" &amp;&amp; msg.content.indexOf("!coins") !== -1) { ... But what I need to understand now is how to call and manipulate the Journal Sheets and how the script will determine who is using it. I think I only need to differentiate between specific Player IDs for my campaign. That will probably be the easiest way for me to filter who can do what with the script. I'm not planning on making this script public or anything (at least not yet).
1563992230

Edited 1563993687
DXWarlock
Sheet Author
API Scripter
1) Well technically they aren't in global namespace since&nbsp; on('chat:message', function (msg) {&nbsp; is a function that has its own 'scope'. Now if you did: var foo = 1; var bar = 2; on('ready', function () { log(foo); log(bar); }); That would be bad, as foo and bar would be 'global' its available to all scripts to read and edit. So say you did that, and later made another script that you put 'foo' into (forgetting you used it). You would change the value of that one. Which you might be using somewhere else. IE: var foo = 1; var bar = 2; function1 () { log(foo) [1]; log(bar) [2]; }); function2 () { foo = 5 }); log(foo) [5] (and will always be 5 everywhere..even if you need the first one to stay 1) vs function1 () { var foo = 1; var bar = 2; log(foo) [1]; log(bar) [2]; }); function2 () { var foo = 5 log(foo) [5]; }); log(foo) [undefined] Foo doesn't exist outside of the functions themselves. So an example of "a generally bad practice" for global scope: if you look at my script RollRight() is a global function, its not scoped inside of JUST that script. All scripts are 'stitched' together in one big one on the backend when you hit save. Despite being different tabs in the editor. The tabs are just for human convenience of organization. So to make it more clear. you COULD paste ALL your scripts in one script tab in a row..and it wouldn't matter. The server does that anyway when it uses it. Which means any other scripts can call RollRight(). Which is good in my case (not right way to do it, but the effect is intentional as I use it all over the place)..but can be bad if say you had a script with it, and someone elses script you installed called a function that also. So if Im not careful, and someone else happens to name a function "RollRight()" and they made it global, and I install their script..it might return what they do with it, vs what mine does..depending on which is 'seen' first. 2) Its looking for character sheets, not journal or handouts. The sheet itself and what system it's for doesn't matter. (well other than what the money attribute is called on it). Just changing a few variables in it it will work for any sheet. For example D&amp;D sheet might call gold GP, and Some other sheet calls it "GoldPieces". Script doesn't care the sheet, just what its looking for on a sheet. its changing what that players character has on their sheet in this part (This is a pathfinder sheet): 3) Mine doesn't care if they are GM or player since its just adding/removing money off of a character sheet assigned to them. If I, as GM, run it..it finds my "GMTest" character I made and adjusts their money. For the last part: When you say journal sheets , do you mean handouts and who can see/edit them, or actual character sheets and who they are assigned to?&nbsp;
Well in my campaign we don't use the Character Sheets that Roll20 provides. My players made all of their characters on paper at the time because they were brand new to the game, and I was brand new to Roll20 and I had no idea what was available to me. (I've only added scripts a couple of months ago and we've been playing this campaign for 2 years.) So I use Handouts for characters. Here's what I mean: ^^ That leads to something like this: That's how I do characters. And yes, that's what I mean by Journal Sheets, because these are all in your "Journal" tab in Roll20. Seriously, that's what it's called. Hopefully that clarifies things. As I've mentioned before, getting all of my players to convert their stuff to an online character sheet for this campaign is going to be too complicated and people will get confused. Trust me, I know my group, it's more trouble than it's worth.
1563993480

Edited 1563993618
DXWarlock
Sheet Author
API Scripter
AH, that's still a character sheet. It just doesn't have a fancy HTML layout attached to it. Before the fancy sheets was an extra tab on that many years ago, we all did it that way. The picture in my last post of the CP SP GP PP, is just a fancy HMTL render the sheet option does, of showing the same attributes like you have: (She has 198 on the sheet image in above post, that's just pulled from this attribute). And someone could PROBABLY write a script that converts your characters to the right attribute names on a new sheet,&nbsp; if you wanted to use a sheet. not sure if that would be more work writing it, than just remaking them by hand though..lol
1563994036

Edited 1563994121
DXWarlock
Sheet Author
API Scripter
For example: If you added attributes called PP GP SP CP onto a test character and set value to 0/0, assigned "controlled by" to you (Figured out the hard way NEVER test on a players character sheet with scripts..) the script would work. Chat messages wouldn't as it needs the sheet template. but could change that to the default roll20 template its only one line that needs editing.
1563995191
DXWarlock
Sheet Author
API Scripter
I pasted it in a more easily read codeshare and commented what each part does, maybe this helps: <a href="https://codeshare.io/G8o134" rel="nofollow">https://codeshare.io/G8o134</a>
Ah ok. We're on the same page now. For now I just plan on using isolated "wallet" sheets for each of my players until I'm comfortable plugging in those attributes into their main sheet. Also no, I don't plan on giving it ultra-fancy HTML stuff. I just need it to work. lol In order to accomplish that, I need to understand how the script would read from and write to a specific sheet and attribute. From your script I can almost decipher it, but like I mentioned earlier I got a bit lost. Also, now I understand the Global Variables much better. Thank you, that's EXTREMELY useful info. I've got a Give/Spend system already written, here's what it looks like right now (yes I'll fix the global variables, this is just my testing script I plug into an empty HTML page): //Declare Player and Wallet variables, Player as String and Wallet as Array var TestPlayer = "BetaBoy"; var Command = "Null"; //Determines the action being taken - Spend, Give, Convert, Loot (Convert and Loot not implemented) var Currency = "Null"; //Determines what currency is used var Amount = 0; //How much is being Spent or Given var wTest = { plat: 10,&nbsp; gold: 200,&nbsp; silv: 500,&nbsp; copp: 3000 }; //Testing Wallet console.log("PP: " + wTest.plat + " GP: " + wTest.gold + " SP: " + wTest.silv + " CP: " + wTest.copp); while (Command == "Null") { //Give or Spend Test Loop Command = prompt("Give or Spend?"); //Chose Spend if(Command == "S" || "s") { Currency = prompt("Plat, Gold, Silv, or Copp?"); if(Currency == "p" || "P") { Amount = prompt("How much PLATINUM do you want to spend?"); wTest.plat = (wTest.plat - Amount); window.alert("Spent " + Amount + " PLATINUM."); console.log("PP: " + wTest.plat + " GP: " + wTest.gold + " SP: " + wTest.silv + " CP: " + wTest.copp); }; if(Currency == "g" || "G") { Amount = prompt("How much GOLD do you want to spend?"); wTest.gold = (wTest.gold - Amount); window.alert("Spent " + Amount + " GOLD."); console.log("PP: " + wTest.plat + " GP: " + wTest.gold + " SP: " + wTest.silv + " CP: " + wTest.copp); }; if(Currency == "s" || "S") { Amount = prompt("How much SILVER do you want to spend?"); wTest.silv = (wTest.silv - Amount); window.alert("Spent " + Amount + " SILVER."); console.log("PP: " + wTest.plat + " GP: " + wTest.gold + " SP: " + wTest.silv + " CP: " + wTest.copp); }; if(Currency == "c" || "C") { Amount = prompt("How much COPPER do you want to spend?"); wTest.copp = (wTest.copp - Amount); window.alert("Spent " + Amount + " COPPER."); console.log("PP: " + wTest.plat + " GP: " + wTest.gold + " SP: " + wTest.silv + " CP: " + wTest.copp); }; } //Chose Give else if(Command == "G" || "g") { Currency = prompt("Plat, Gold, Silv, or Copp?"); if(Currency == "p" || "P") { Amount = prompt("How much PLATINUM do you want to add?"); wTest.plat = (wTest.plat + Amount); window.alert("Added " + Amount + " PLATINUM."); console.log("PP: " + wTest.plat + " GP: " + wTest.gold + " SP: " + wTest.silv + " CP: " + wTest.copp); }; if(Currency == "g" || "G") { Amount = prompt("How much GOLD do you want to add?"); wTest.gold = (wTest.gold + Amount); window.alert("Added " + Amount + " GOLD."); console.log("PP: " + wTest.plat + " GP: " + wTest.gold + " SP: " + wTest.silv + " CP: " + wTest.copp); }; if(Currency == "s" || "S") { Amount = prompt("How much SILVER do you want to add?"); wTest.silv = (wTest.silv + Amount); window.alert("Added " + Amount + " SILVER."); console.log("PP: " + wTest.plat + " GP: " + wTest.gold + " SP: " + wTest.silv + " CP: " + wTest.copp); }; if(Currency == "c" || "C") { Amount = prompt("How much COPPER do you want to add?"); wTest.copp = (wTest.copp + Amount); window.alert("Added " + Amount + " COPPER."); console.log("PP: " + wTest.plat + " GP: " + wTest.gold + " SP: " + wTest.silv + " CP: " + wTest.copp); }; } else { Command = "Null"; }; }; This is ultimately proof of concept. I'm going to work on how this specifically will be written for Roll20 next. Once we get everything to a functional state, then we can discuss making it user-friendly (my group REALLY needs that or they'll never use it)
1563995643

Edited 1563995688
DXWarlock
Sheet Author
API Scripter
The only thing you might run into is if they have the value in money, but not that coin..that is what I had to tap The Aaron to figure out for me with his Arcane Scriptomancer skills :) Say they have 0PP, 700G; and they say "I want to spend 1 PP". It SHOULD go "OK you have 690 gold left". Instead of telling them you don't have enough money. I suppose that's optional. If they are OK doing the math and taking 10G off vs 1PP.
I didn't see your last 2 posts until after I sent this. (forgot to refresh and work called) And I have a plan for lack of proper currency (or over-spending). I'm just trying to take things one step at a time. lol
Also, for the "on('chat:mesage')" Functions, can those be named anything we want? For example, I plan on having each command separated into its own function, which will then call the variables it needs and whatever equations need to happen for that instance. aka, will this work? EDIT: Wait, this can be condensed... on('chat:mesage', function(msgSpend) {&nbsp; if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Spend") !== -1) { //commands here }; }); on('chat:mesage', function(msgGive) {&nbsp; if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Give") !== -1) { //commands here }; }); on('chat:mesage', function(msgConvert) {&nbsp; if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Convert") !== -1) { //commands here }; }); on('chat:mesage', function(msgLoot) {&nbsp; if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Loot") !== -1) { //commands here }; });
Much better! (I'm sure this could still be optimized though :P ) on('chat:mesage', function(msg) {&nbsp; if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Spend") !== -1) { //commands here } else if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Give") !== -1) { //commands here } else if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Convert") !== -1) { //commands here } else if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Loot") !== -1) { //commands here } });
1563997745

Edited 1563998698
DXWarlock
Sheet Author
API Scripter
The last one is better! and the first would need a small tweaking to work: on('chat:mesage', function(msgSpend) { if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Spend") !== -1) { //commands here }; }); Would fail as you call the 'variable' passed to it msgSpend, but then try to look up msg in&nbsp; msg.content.indexOf("!Spend")&nbsp; it would need to be&nbsp; msgSpend.content.indexOf("!Spend"); since you defined the message being sent as&nbsp;msgSpend You could also do: on('chat:message', function (msg) { if (msg.type != "api") return; var msgFormula = msg.content.split(/\s+/); switch (msgFormula[0]) { case "!Spend": //commands here break; case "!Give": //commands here break; case "!Convert": //commands here break; case "!Loot": //commands here break; default: return; } }); or this: on('chat:mesage', function(msg) { if(msg.type !== "api") return; var msgFormula = msg.content.split(/\s+/); } if(msgFormula[0] == "!Spend") { //commands here } else if(msgFormula[0] == "!Give") { //commands here } else if(msgFormula[0] == "!Convert") ) { //commands here } else if(msgFormula[0]== "!Loot") { //commands here } else return; }); One isn't really better than the other just different, they both do the same thing in the end.&nbsp; Its that idea ask 10 coders how to do something, you will get 11 answers. Im sure one is more 'standardized' than the other..But neither are dangerous or bad per say.
Ah, ok. That makes sense.
1563998546

Edited 1563998628
DXWarlock
Sheet Author
API Scripter
Doing the&nbsp; var msgFormula = msg.content.split(/\s+/); would be useful for more than just the command, because it would split all the parts into bits you would need to start with (or however you wanted to split them, doesn't need to be .split) Say your text sent was !Spend 0pp 1gp 3sp 10cp It would do this for you: msgFormula[0] = !Spend msgFormula[1] = 0pp msgFormula[2] = 1gp msgFormula[3] = 3sp msgFormula[4] = 10cp Then you already have all the important bits to parse and play with how you want. But up to you. As you might not need that, or sending the !command without values.&nbsp;
Yeah, my goal is to ultimately make these commands easy to use for my players, because I promise you that my less computer-literate players will end up making mistakes or somehow break something. I want to avoid that at all costs. Even if it's not the most efficient, I need to make this code basically baby-proof. lmao A chat command as long an error prone as that will work for testing, but not the final result. (Oh wait, I can use Macro buttons, DUH)
1563999502

Edited 1563999988
DXWarlock
Sheet Author
API Scripter
I try to mostly do that to some extent. Mine can be broke if you input say "Q" instead a number for any of the values. But my players usually don't screw up that bad.&nbsp; I could account for it, and make sure all the inputs are numbers, but it's rare they fat finger something weird in it..and when they do, I go the lazy fix way. Hit 'save' to restart the sandbox and joke to them "Q" is not a quantity of gold they can spend..haha. I suppose looking at it, if they put in 1,000 vs 1000 it would break it too..but hasn't happened yet. I weight the "How often might they do it, vs the effort to code it catching them doing it" Not the BEST practice if your making a script public. But for me and my private scripts...good enough :) Edit: That's how mine is, a marco. A global macro they can add to the bottom bar that sends: !coins ?{Type|Spend|Add} cp:?{CP|0} sp:?{SP|0} gp:?{GP|0} pp:?{PP|0} So they just have to answer 5 questions. Everyone has this button on the macrobar: (which looking at it now, I should call Purse..not Spend, since it does both give and take. Ah well they are use to it being called that now..lol)
Perfection! I'm working on getting my script to identify the person who activated it. I'll update you when I've made more significant progress (or if I need help, lol)
1563999962

Edited 1564000030
DXWarlock
Sheet Author
API Scripter
Easy way: Make sure they have the character picked in the drop down "Talking as" box. It will always refer to the character not the player. Harder way: figuring out a way that works for your game to know which character is their main one (if they have more than one).
Ah, I see. Is there a way to tie it to their PlayerID instead? I feel like that will be more reliable for this specific instance. They all use their Player Names in chat. I always have their macros tied to the Character IDs they control when they use macros. I had completely forgotten about that drop-down box. We never use it. If using their PlayerID is not possible then so be it, but I feel like that's the easiest option for me right now. Having something ready for them to use right out of the gate is better than asking them to do something as "Set Up" because somebody either won't do it or will find a way to fuck it up. Happens every time. EDIT: They only have one character, but I sometimes give them "Control" of other players if the party splits so they can still see what's happening. Using their PlayerID to have a more "strict" attachment to only one character will prevent odd instances of one player spending another's money on accident.
1564001085

Edited 1564001407
DXWarlock
Sheet Author
API Scripter
Thats what my RollRight does, Takes the ID of who sent the message and figures out which sheet they are assigned to. You would just need to change to something that works for your game. This is it with notes on the end: function RollRight(whoPC) { var character1 = findObjs({ type: 'character', controlledby: whoPC }); //Find all characters controlled by playerID, which I named whoPC. var SimpleObj = (o)=&gt;JSON.parse(JSON.stringify(o)); //Turn the character object into a string I can read as text. var PCsheet = character1.filter(c=&gt;SimpleObj(c).gmnotes===''); //Find the sheet assigned to them with a blank GMNote. var character = PCsheet[0]; //Incase of more than one, assume the first one...which should be the only one. if (character == undefined) { //If they have no sheet, let everyone know in chat. sendChat("system", "/direct No character found for: " + whoPC + ", please set!"); } else { return character; //return the character. } }; Not sure if that would work exactly for you, or if you need to tweak what defines "Main" character vs just ones they have control of. For example if you was having 2 sheets like you said, one just for money with PP GP SP CP as attributes. You could change the gmnotes lookup to "Find the one that has GP as an attribute." or such. Edit: Player ID isnt always one to one with characters. One playerID COULD have 10 characters assigned to them with "Controlled By". You need a way to tell which is the 'wallet' character you are looking in those cases.
Ah, ok I think this will work. Instead of having a blank GM note, I'll have it look for "Player Wallet" in the GM notes. That shouldn't ever need to be changed or duplicated, at least for this game anyways.
Ok, so I finally fully understand what you're doing with the "msgFormula" thing. I might end up having to use that.
Now the problem is: I don't understand how you're updating the value of their purse. I understand how you're finding it, but not how you get it to update after money is Given or Spent. Here's what I have so far. I want to keep the commands separate. I think this will be the last that I work on the script for today. If you (or anyone) has any suggestions on how to proceed from here, I'd be very appreciative. on('chat:mesage', function(msg) {&nbsp; if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Spend") !== -1) { //LOOK FOR CHARACTER NAMED FOR WHO SENT THE MESSAGE &nbsp; &nbsp; &nbsp; &nbsp; var cWho = findObjs({ type: 'character', name: msg.who })[0];&nbsp; //GET CHARACTER NAME FROM PLAYER IF NOT PICKED IN DROPDOWN &nbsp; &nbsp; &nbsp; &nbsp; if (cWho === undefined) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cWho = RollRight(msg.playerid); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; msg.who = cWho.get("name"); var plat = findObjs({name: "PP", type: "attribute", characterid: cWho.id},{caseInsensitive: true})[0]; var gold = findObjs({name: "GP", type: "attribute", characterid: cWho.id},{caseInsensitive: true})[0]; var silv = findObjs({name: "SP", type: "attribute", characterid: cWho.id},{caseInsensitive: true})[0]; var copp = findObjs({name: "CP", type: "attribute", characterid: cWho.id},{caseInsensitive: true})[0]; var respond = ["null", "null", "null", "null", "null"]; if (plat == undefined) { plat = 0; } if (gold == undefined) { gold = 0; } if (silv == undefined) { silv = 0; } if (copp == undefined) { copp = 0; } /* I have no idea where to go from here. Then again maybe I'm just tired... */ } else if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Give") !== -1) { //commands here } else if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Convert") !== -1) { //commands here } else if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Loot") !== -1) { //commands here } else { return; } }); function RollRight(whoPC) {&nbsp; &nbsp;&nbsp; &nbsp; &nbsp; var character1 = findObjs({ type: 'character', controlledby: whoPC }); //Find all characters controlled by playerID, which I named whoPC. &nbsp; &nbsp; var SimpleObj = (o)=&gt;JSON.parse(JSON.stringify(o)); //Turn the character object into a string I can read as text. &nbsp; &nbsp; var PCsheet = character1.filter(c=&gt;SimpleObj(c).gmnotes==='Player Wallet'); //Find the sheet assigned to them with a specific GMNote. &nbsp; &nbsp; var character = PCsheet[0]; //Incase of more than one, assume the first one...or only one if only 1. &nbsp; &nbsp; if (character == undefined) { //If they have no sheet, let everyone know in chat. &nbsp; &nbsp; &nbsp; &nbsp; sendChat("system", "/direct No character found for: " + whoPC + ", please set!"); &nbsp; &nbsp; } &nbsp; &nbsp; else {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return character; //return the character. &nbsp; &nbsp; } };
1564006067

Edited 1564006289
DXWarlock
Sheet Author
API Scripter
It's with: _.each(newBalance, function (value, key) { var oC = findObjs({ name: key, _type: "attribute", characterid: cWho.id }, { caseInsensitive: true })[0]; oC.setWithWorker('current', value); }); setWithWorker does it. "oC" stands for the Original Coin type. I'm sending the final coins in the purse as an object that looks like: {"cp":5,"sp":46,"gp":45,"pp":4} Then doing an _.each so it steps through each one, finding the attribute called its name with&nbsp; findObjs &nbsp;(cp, sp, etc). then setting that attribute to the 'value' of that key with&nbsp; setWithWorker .
1564007395

Edited 1564008148
DXWarlock
Sheet Author
API Scripter
A quick and dirty hardcoded example that will set whatever character it finds to 1 PP, 0GP, 0SP, 0CP. (because I'm not sure how you want to handle input of the info, or your change/convert solution): on ( 'chat:mesage' , function ( msg ) { &nbsp;&nbsp;&nbsp;&nbsp; if (msg.type === "api" &amp;&amp; msg.content. indexOf ( "!Spend" ) !== - 1 ) { var cWho = findObjs ({ type: 'character' , name: msg.who })[ 0 ]; if (cWho === undefined ) cWho = RollRight (msg.playerid); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;msg.who = cWho. get ( "name" ); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //lookup characters money &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var plat = findObjs ({name: "PP" , type: "attribute" , characterid: cWho.id},{caseInsensitive: true })[ 0 ]; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var gold = findObjs ({name: "GP" , type: "attribute" , characterid: cWho.id},{caseInsensitive: true })[ 0 ]; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var silv = findObjs ({name: "SP" , type: "attribute" , characterid: cWho.id},{caseInsensitive: true })[ 0 ]; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var copp = findObjs ({name: "CP" , type: "attribute" , characterid: cWho.id},{caseInsensitive: true })[ 0 ]; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //if not all set tell them to set any missing to 0. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (oPP === undefined || oGP === undefined || oSP === undefined || oCP === undefined ) { sendChat ( 'Coin Purse' , "Not All Coins Set, set blank ones to 0" ); return ; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} /*GETS CURRENT WALLET INFO FROM CHARACTER YOU FOUND Returns 'purse' as this format: {"cp":0,"sp":0,"gp":0,"pp":0}*/ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var purse = GetMoney (plat, gold, silv, copp); /*THIS BELOW IS JUST HARDCODED TEST TO SHOW HOW TO SET THE ATTRIBUTE VALUES NOT SURE HOW YOU AND SENDING THE INFO, OR HOW YOU WANT TO DO CHANGE. SO THIS IS JUST AN EXAMPLE..WILL SET THE CHARACTER TO 1 PP WHEN THEY TYPE '!Spend' &nbsp;&nbsp;&nbsp;&nbsp; var newBalance = new Object () &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Do math to figure out what they spent, vs what they have. &nbsp;&nbsp;&nbsp;&nbsp; var newBalance = { "cp" : 0 , "sp" : 0 , "gp" : 0 , "pp" : 1 } //EXAMPLE INPUT I SPOKE OF IM USING, THE ABOVE MATH WILL TAKE ITS PLACE. &nbsp;&nbsp;&nbsp;&nbsp; -&gt;To set your own you can do "newBalance.cp = 0;" or such for each coin.-&lt;*/ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_. each (newBalance, function ( value , key ) { //EXAMPLE OF THE KEY:VALUE = "cp":0 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var oC = findObjs ({ name: key, _type: "attribute" , characterid: cWho.id }, { caseInsensitive: true })[ 0 ]; //looks for attribute named KEY IE: PP &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;oC. setWithWorker ( 'current' , value); //SET TO VALUE IE: 1 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}); //------------------------------------------------------- &nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp; else if (msg.type === "api" &amp;&amp; msg.content. indexOf ( "!Give" ) !== - 1 ) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //commands here &nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp; else if (msg.type === "api" &amp;&amp; msg.content. indexOf ( "!Convert" ) !== - 1 ) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //commands here &nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp; else if (msg.type === "api" &amp;&amp; msg.content. indexOf ( "!Loot" ) !== - 1 ) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //commands here &nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp; else { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return ; &nbsp;&nbsp;&nbsp;&nbsp;} }); /*---GET CHARACTER MONEY FUNCTION USED ABOVE---*/ function GetMoney ( oPP , oGP , oSP , oCP ) { let coins = {}; coins.cp = parseInt (oPP. get ( "current" ), 10 ); coins.sp = parseInt (oGP. get ( "current" ), 10 ); coins.gp = parseInt (oSP. get ( "current" ), 10 ); coins.pp = parseInt (oCP. get ( "current" ), 10 ); return coins; } function RollRight ( whoPC ) { var character1 = findObjs ({ type: 'character' , controlledby: whoPC }); var SimpleObj = ( o ) =&gt; JSON . parse ( JSON . stringify (o)); var PCsheet = character1. filter ( c =&gt; SimpleObj (c).gmnotes === 'Player Wallet' ); var character = PCsheet[ 0 ]; if (character == undefined ) { sendChat ( "system" , "/direct No character found for: " + whoPC + ", please set!" ); } else { return character; } };
1564035130
GiGs
Pro
Sheet Author
API Scripter
This discussion has moved quite fast since I was here last :) If you are going to be handling money denominations manually (converting between GP and SP, etc.), you'll be fine, but if you are going to handle money changing automatically, you will probably find it trickier than you expect. This was discussed a couple months back, here's a set of functions from Aaron that can handle it .
1564054522

Edited 1564056259
The Aaron
Roll20 Production Team
API Scripter
Regarding the speed, I totally agree! &nbsp;It makes me miss working from home on a less challenging job. =D Writing that spending function was loads of fun and reminds me of an earlier question about how you can test Javascript without uploading it to Roll20 every time. I do a couple of things: I use an editor with context highlighting for Javascript. It's Vim for me, but there are others you can use (Sublime Text, Ultraedit, etc). Get one for your platform you're comfortable with. &nbsp;Context highlighting will help you avoid most syntactic problems.&nbsp; Make sure your editor supports a static analysis tool like ESLint. For me, when I save the file, it runs ESLint on it and displays any errors/potential errors. This works best with a proper config file, I posted mine a while back (I'm on my phone now or I'd repost), but the default one will catch a fair amount.&nbsp; You can set up a javascript environment like Node.js and use a unittests tool like Jest. Depending on your environment (and developer pain threshold), that could be difficult.&nbsp; Edit: My .eslintrc.js file (you'll probably want to take out the Unix part, unless you're on a Mac): module.exports = { "env": { }, "globals": { "WeakMap": true, "Set": true, "setAttrs": true, "setInterval": true, "clearInterval": true, "clearTimeout": true, "setTimeout": true, "playerIsGM": true, "getObj": true, "findObjs": true, "filterObjs": true, "createObj": true, "sendChat": true, "log": true, "toFront": true, "toBack": true, "randomInteger": true, "setDefaultTokenForCharacter": true, "spawnFx": true, "spawnFxBetweenPoints": true, "spawnFxWithDefinition": true, "playJukeboxPlaylist": true, "stopJukeboxPlaylist": true, "sendPing": true, "state": true, "globalconfig": true, "_": true, "Campaign": true, "getAllObjs": true, "getAttrByName": true, "onSheetWorkerCompleted": true, "on": true, "Promise":true, "Uint32Array":true, "takeCardFromPlayer":true, "giveCardToPlayer":true, "recallCards":true, "shuffleDeck":true, "drawCard":true, "cardInfo":true, "playCardToTable":true }, "extends": "eslint:recommended", "parserOptions": { "ecmaVersion": 2017, "ecmaFeatures": { "impliedStrict": true, "experimentalObjectRestSpread": true }, "sourceType": "module" }, "plugins": [ ], "rules": { "no-console": "warn", "linebreak-style": [ "error", "unix" ], "semi": [ "error", "always" ], "comma-dangle": [ "error", "never" ] } };
1564063461
GiGs
Pro
Sheet Author
API Scripter
GiGs said: This discussion has moved quite fast since I was here last :) If you are going to be handling money denominations manually (converting between GP and SP, etc.), you'll be fine, but if you are going to handle money changing automatically, you will probably find it trickier than you expect. This was discussed a couple months back, here's a set of functions from Aaron that can handle it . I also completely forgot there's a complete solution later in that thread which does a few extra things: <a href="https://github.com/blawson69/PurseStrings" rel="nofollow">https://github.com/blawson69/PurseStrings</a> I havent tried it, but looks pretty comprehensive, and but it looks like it will work for any sheet where attribute names are not in a repeating section. You just need to change the attribute names in this line near the top: attributes = {cp : ' pursestrings_cp ' ,sp : ' pursestrings_sp ' ,ep : ' pursestrings_ep ' ,gp : ' pursestrings_gp ' ,pp : ' pursestrings_pp ' }, It does use EP though. (for instance, change 'pursestrings_cp' to whatever the attribute name you use for CP, and so on.) Maybe worth checking out.
Yeah, this topic kinda blew up. LOL As far as a testing environment, I just created a blank HTML page that I load into my browser locally, then the HTML page just calls the local ".js" file.&nbsp; Honestly easier than I expected, but I had programmed in Python previously and I guess I was a little spoiled. I have some ideas for how to make all of these commands behave properly. Now that I have a general understanding of how to write the necessary commands, I'm going to start writing and testing the first iteration and see how that goes. Once again, thank you all so much for your help! I might make a new thread once I reach that point, but I'll def keep using this post as a reference if that happens (bookmarked).
ok, so, I'm wanting to test what I have so far. But when I add the script to Roll20 and try to invoke a command, nothing happens. No error log in the script output console, nothing in the game-chat, simply nothing. I'll post my code if necessary (which I assume it probably will), but any ideas off the top? I know I said I had a test environment in a previous reply but that was just a "proof of concept" test environment. I didn't have any Roll20 code in there yet.
1564111195
The Aaron
Roll20 Production Team
API Scripter
Are you logged into the game?&nbsp; The API sandbox won't start up if no one is in the game.&nbsp; &nbsp;&nbsp;
Yes, I'm logged into the game, refreshed and everything. I have the script and console open in a secondary tab. I'm not currently aware of checking the sandbox in a different way.
Here is my code as of right now. I'm just trying to test my "Spend" command, since it's the only thing I have written (yes, I know it's massive and inefficient, we'll fix that later). Side note: the way the spend function works is from player request. on('chat:mesage', function(msg) {&nbsp; if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Spend") !== -1) { //LOOK FOR CHARACTER NAMED FOR WHO SENT THE MESSAGE &nbsp; &nbsp; &nbsp; &nbsp; var cWho = findObjs({ type: 'character', name: msg.who })[0];&nbsp; //GET CHARACTER NAME FROM PLAYER IF NOT PICKED IN DROPDOWN &nbsp; &nbsp; &nbsp; &nbsp; if (cWho === undefined) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cWho = RollRight(msg.playerid); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; msg.who = cWho.get("name"); var plat = findObjs({name: "PP", type: "attribute", characterid: cWho.id},{caseInsensitive: true})[0]; var gold = findObjs({name: "GP", type: "attribute", characterid: cWho.id},{caseInsensitive: true})[0]; var silv = findObjs({name: "SP", type: "attribute", characterid: cWho.id},{caseInsensitive: true})[0]; var copp = findObjs({name: "CP", type: "attribute", characterid: cWho.id},{caseInsensitive: true})[0]; var respond = ["null", 0, "null", "null", "null"]; //response array; WIP //0 = Main Currency; 1 = Main Amount; 2 = Secondary Currency (yes/no); var mDifference = 0; //Main difference var sDifference = 0; //Secondary difference var tDifference = 0; //Tertiary difference var tempDiff = 0; //temporary difference (used for multi-step conversion) if (plat == undefined) { plat = 0; } if (gold == undefined) { gold = 0; } if (silv == undefined) { silv = 0; } if (copp == undefined) { copp = 0; } while(respond[0] == "null") { respond[0] = prompt("Spending Platinum, Gold, Silver, or Copper?")//ask for main currency if (respond[0] == "P" || respond[0] == "p" || respond[0] == "Platinum" || respond[0] == "platinum") { respond[1] = prompt("How much are you spending?")//ask how much they're spending if (plat &lt; respond[1]) { respond[2] = prompt("You do not have that much PLATINUM. Convert other currency to cover? Lesser currency will be converted first. (Warning: If you say yes, this process cannot be reversed)")//ask if they want to convert; "NO" will cancel the loop if (respond[2] == "Yes" || respond[2] == "yes" || respond[2] == "Y" || respond[2] == "y") { mDifference = plat - respond[1]; //convert copper first if (copp &gt;= mDifference*1000) { //if there's enough copper to cover cost copp = copp - (mDifference*1000); //removes required copper plat = 0; //spends all platinum } else { //if there's NOT enough copper to cover cost; see if the player has enough conversion value to cover the cost. If they do, find how much Copper CAN be converted, then move on to Silver, then Gold tempDiff = mDifference - floor((copp/1000) + (silv/100) + (gold/10)); if (tempDiff &gt; 0) { window.alert("You do not have enough money to cover this."); break; } tempDiff = floor(copp/1000); //finds how much platinum can be converted from copper copp = copp - (tempDiff*1000); //removes required copper //plat = plat + tempDiff; //converts removed copper to platinum, maybe be unnecessary? mDifference = mDifference - tempDiff; //lower Main Difference by how much the copper covers tempDiff = floor(silv/100);//finds how much platinum can be converted from silver if (mDifference &lt;= tempDiff) { //if there's enough Silver to cover the rest of the cost silv = silv - (mDifference*100); //removes required silver plat = 0; //spends all platinum } else { //if there's not enough silver to cover the rest of the cost, convert any available copper to silver and re-check sDifference = (mDifference - tempDiff)*100; //finds needed Silver tDifference = (sDifference - silv)*10; //finds needed Copper to compensate if (copp &gt;= tDifference) { //if there's enough copper to cover missing silver, convert needed copper copp = copp - tDifference; //Removes needed copper silv = 0; //spends all silver plat = 0; //spends all platinum } else { //if there's not enough copper to cover missing silver, convert as much copper to silver as possible, then find new Main Difference tempDiff = floor(copp/10); //find how much copper can be converted to silver copp = copp - (tempDiff*10); //removes converted copper silv = silv + tempDiff; //adds converted silver //if there is not enough silver to convert into 1 platinum, return old values in case gold can cover if (silv &lt; 100){ silv = silv - tempDiff; //return old silver value copp = copp + (tempDiff*10);//return old copper value } //now run check similar to copper tempDiff = floor(silv/100); //finds how much silver can be converted to platinum after copper conversion if (tempDiff &gt;= 1) { silv = silv - (tempDiff*100);//remove required Silver } mDifference = mDifference - tempDiff; //lower Main Difference by how much the Silver covers //now move on to gold tempDiff = floor(gold/10); if (mDifference &lt;= tempDiff) { //if there's enough gold to cover the rest of the cost gold = gold - (mDifference*10); //removes required Silver plat = 0; //spend all platinum } else { //if there is not enough gold to cover the cost on its own, convert all currencies since we've already pre-determined that the player has enough money //convert copper to silver tempDiff = floor(copp/10); copp = copp - (tempDiff*10); silv = silv + tempDiff; //convert silver to gold tempDiff = floor(silv/10); silv = silv - (tempDiff*10); gold = gold + tempDiff; //convert gold into platinum tempDiff = floor(gold/10); gold = gold - (tempDiff*10); plat = plat + tempDiff; plat = plat - respond[1]; //check for over-conversion, return all possible gold from remaining platinum if (plat &gt;= 1) { gold = gold + (plat*10); plat = 0; } } } } } } else { break; //if they do not convert currency } } else { plat = plat - respond[1]; } } else if (respond[0] == "Gold" || respond[0] == "gold" || respond[0] == "G" || respond[0] == "g") { } } //spendMoney(); } else if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Give") !== -1) { //commands here } else if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Convert") !== -1) { //commands here } else if(msg.type === "api" &amp;&amp; msg.content.indexOf("!Loot") !== -1) { //commands here } else { return; } }); function RollRight(whoPC) {&nbsp; &nbsp;&nbsp; &nbsp; &nbsp; var character1 = findObjs({ type: 'character', controlledby: whoPC }); //Find all characters controlled by playerID, which I named whoPC. &nbsp; &nbsp; var SimpleObj = (o)=&gt;JSON.parse(JSON.stringify(o)); //Turn the character object into a string I can read as text. &nbsp; &nbsp; var PCsheet = character1.filter(c=&gt;SimpleObj(c).gmnotes==='Player Wallet'); //Find the sheet assigned to them with a specific GMNote. &nbsp; &nbsp; var character = PCsheet[0]; //Incase of more than one, assume the first one...or only one if only 1. &nbsp; &nbsp; if (character == undefined) { //If they have no sheet, let everyone know in chat. &nbsp; &nbsp; &nbsp; &nbsp; sendChat("system", "/direct No character found for: " + whoPC + ", please set!"); &nbsp; &nbsp; } &nbsp; &nbsp; else {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return character; //return the character. &nbsp; &nbsp; } };
I apologize for whatever my noob mistake is. EDIT: I just realized I'm not using ".setWithWorker". I'm sure that's a problem in itself and will fix that, but I feel like that's not the culprit for my current problem.
1564112142

Edited 1564112607
GiGs
Pro
Sheet Author
API Scripter
Roll20 is a custom version of javascript - some features are disabled for security and other reasons. You cant use the prompt function in roll20.
1564112946

Edited 1564117741
GiGs
Pro
Sheet Author
API Scripter
The way you'd handle that while block in roll20 would be through parameters in your chat message. You;d send it something like !spend PP 50 or !spend ?{What coin type?|PP,GP,SP,CP} ?{How much?|0} The second version uses native roll20 input feature to get the dropdown boxes. You just cant easily do conditional if/then prompts. And then in the on(chat) function let args msg.content.split(/\s+/); // so args would be ['!spend','PP','50'] let coinType = args[1]; let coinAmount = args[2]; This isn't how I would do it, it's just the simplest illustration of the technique. You'd need to validate the args, the countype, the amount, then do anything remaining in your while loop: for instance, ending the function earlier and reporting messages (using sendChat) if there wasnt enough money tp spend, and so on.
1564113050
The Aaron
Roll20 Production Team
API Scripter
Going back to what I mentioned earlier, the API is event driven.&nbsp; If you want to have a dialogue with your players, you'll need to be using sendChat() to send them messages that contain buttons they click to send you data, likely as part of ?{query} roll queries. Usually, you'll design a command for your script that takes all the arguments in one go, so for example: !spend ?{amount (use pp, gp, sp, cp on your denominations)} Then you parse whatever they type in as arguments to the !spend command.&nbsp; Or you could do: !spend ?{pp}pp ?{gp}gp ?{sp}sp ?{cp}cp etc.&nbsp; The more you need to go back and forth, the more cases you'll have to handle in your 'chat:message' handler.&nbsp;&nbsp;&nbsp;&nbsp;