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] Advanced Mapping Procedurals

1411671647

Edited 1411864941
esampson
Pro
Sheet Author
<a href="https://github.com/esampson/AMP" rel="nofollow">https://github.com/esampson/AMP</a> Just a related pair of commands. The first command creates a character object associated with a piece of artwork that contains data about all other objects on the current map and their position relative to a base object. The second command will reproduce those objects relative to a new base object which enables a GM to do things such as create a bunch of character objects for various buildings. When they want to create a new village map they can drop a graphic representing the original base object, rescale it, move it, and rotate it, and then run the second script. Very useful to prevent GMs from having to redraw lines on the dynamic lighting layer, place tokens that represent individual pieces of furniture that might get moved about, etc.Instructions are located in the github repository. I'm sure there are plenty of bugs in it that need shooting and there's a lot of cleanup to be done, so please bear with me and let me know about any troubles you have. <a href="https://gist.github.com/esampson/9efc0a1f54c6257c7524#0.5.3" rel="nofollow">https://gist.github.com/esampson/9efc0a1f54c6257c7524#0.5.3</a>
1411673521
The Aaron
Roll20 Production Team
API Scripter
Wicked! =D
1411674179
The Aaron
Roll20 Production Team
API Scripter
I'm just reading the source, but what is the setup() function you're calling? Is that in another file, or something built into JS that I've never run across?
1411675387
esampson
Pro
Sheet Author
Whoops. Yes, that was a routine from another module. I've incorporated it into the script (although it doesn't seem to be appearing in the gist preview here at the moment. You should be able to grab the corrected code from the Gist link).
1411675587
The Aaron
Roll20 Production Team
API Scripter
Edit your original post, add something to the end of the link like #v2, it will show up then.
1411676110
esampson
Pro
Sheet Author
Ah. There we go. Much thanks.
1411676331

Edited 1411745183
The Aaron
Roll20 Production Team
API Scripter
That's a butt ton of attributes you're adding. I imagine that's because it's fairly convenient to do so. Have you considered using some other encoding? I have a similar project in mind, but I'm planning to manipulate the data as an object, and then store it in the gm notes of a character/handout in a base64 encoded JSON string. The primary benefit is that you can deal with objects directly in your JS and you won't be doing a huge amount of findObjs() calls. findObjs() is horrible for performance and you can easily blowup the API by calling it too much. (I tracked down a crash bug in the Edge of the Empire Dice script that related to doing about 1800 findObjs calls to get specifically named attributes. It would crash a minute after it was done running for no apparent reason. It turns out that since CPU usage is calculated over 60 seconds, it was spiking a huge amount in the first 10 seconds, and then bombing because of the threshold.) If you're interested in pursuing migrating off of attributes for the storage, I've already ported over a base64 encoding library. I'd be happy to help you with migrating: <a href="https://gist.github.com/shdwjk/e4c798c07070ad1f2da" rel="nofollow">https://gist.github.com/shdwjk/e4c798c07070ad1f2da</a>...
1411676445
The Aaron
Roll20 Production Team
API Scripter
If you do plan to continue with attributes, I highly recommend just grabbing all the attributes (or just all the attributes for the storage character), and then just finding the one you need in setup on the local collection, adding new ones as you go.
1411676541

Edited 1411676832
The Aaron
Roll20 Production Team
API Scripter
Also, you may have seen me preach this before, but I highly recommend Javascript: The Good Parts by Douglas Crockford. It's a fast read with great info and will really power level your JS skills. =D ( Note : I don't intend to insult your programming skills (which you obviously have), just pointing it out because JS is a fickle language with some serious eccentricities and that book does a great job of explaining them. =D )
1411677041
esampson
Pro
Sheet Author
I've been considering trying to move all the attributes into a large array that is written to the GMNotes of the object. Then I could get the notes once, put all the data into an array variable and work off that. That wouldn't really prevent the findObj() calls, however. There actually aren't as many findObjs calls, however. Only 8 in the entire script, none of which are located in a loop, and those are split between the creation and conversion routines. Putting the data in the GMnotes, however, might help with some minor issues. It just would prevent the data from being quite so easy to access and read. The large number of attributes are mostly necessary. I might be able to trim some of them out but for the most part the idea is to make the copies as accurate as possible. I won't know if people are putting objects on the map layer because they want a light emitter or if they are putting a token on the object layer that can be switched between multiple pieces of art so that they can do normal versions and broken versions.
1411677371

Edited 1411677479
The Aaron
Roll20 Production Team
API Scripter
Setup() always calls findObjs(). Setup() is called in a loop on lines 181, 192, 197 (nested loop), 208, 212-221, 226 (nested loop), 228, 238, 244 (nested loop).
1411677792
esampson
Pro
Sheet Author
Oh. You're right. I missed that. That I might be able to trim. It's job is to make sure that the attribute that is being setup doesn't already exist.
1411678009

Edited 1411678160
The Aaron
Roll20 Production Team
API Scripter
Right. (Or rather, it assures that it exists and creates it if it doesn't. =D) At a minimum, you could pre-cache all the attributes for the character, adding them as needed. I've got some scripts that do this (Char Utils is one). Personally, I'm still of the opinion that if you aren't accessing it in a macro or expecting humans to edit it, it doesn't belong in an attribute. There's a bunch of overhead to using attributes like a key-value storage, and you can get the same benefit with writing it in the notes with much simpler usage code.
1411678334
esampson
Pro
Sheet Author
Well, on the conversion most of the attribute setting still has to be done. I can probably cut down on calls by checking, however, and seeing if the value to be set is the same as the default value and only changing it if necessary.
1411678707
The Aaron
Roll20 Production Team
API Scripter
Looks like marker() is just a way to mark a position you can look at? As the GM, you don't actually need to give yourself control of it (I assume that's your player id.): "controlledby":" -IdwiUbN7sGFmfnzYsM5 ",
1411679158
esampson
Pro
Sheet Author
It's a code relic. I was having problems with paths not rotating properly so I put that in there so I could see whether I was calculating the center wrong or if the error was elsewhere. I'll probably leave it for now for debugging purposes.
1411698499
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
esampson said: I've been considering trying to move all the attributes into a large array that is written to the GMNotes of the object. I am planning on doing just that.... for something I am playing around with.
1411698656
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
esampson said: It's a code relic. I was having problems with paths not rotating properly so I put that in there so I could see whether I was calculating the center wrong or if the error was elsewhere. I'll probably leave it for now for debugging purposes. Alex wrote something I use in the random dungeon generator to do just that. <a href="https://app.roll20.net/forum/post/655402/script-ra" rel="nofollow">https://app.roll20.net/forum/post/655402/script-ra</a>...
1411709282
esampson
Pro
Sheet Author
I went ahead and changed the code to reduce findObj and .get calls. All data is now stored as a single attribute instead of a large series of attributes. I tried storing the data in GMNotes but that made the interpreter cranky (I think because of the bracket characters) and I didn't want to alter the JSON string at the moment.
1411737933
The Aaron
Roll20 Production Team
API Scripter
esampson said: I went ahead and changed the code to reduce findObj and .get calls. All data is now stored as a single attribute instead of a large series of attributes. I tried storing the data in GMNotes but that made the interpreter cranky (I think because of the bracket characters) and I didn't want to alter the JSON string at the moment. Yeah, that's why I suggested the Base64 encoding: storage.set('gmnotes', Base64.encode( JSON.stringify(data) ) );
1411739960
esampson
Pro
Sheet Author
Right, but that is what I mean by 'altering the JSON string'. I'm guessing that encoding would render the string visually unreadable. Additionally when I tried to implement the function for experimental purposes the script wouldn't enable because of an unexpected comma.
1411745520
The Aaron
Roll20 Production Team
API Scripter
Ah! Sorry about that. Updated the gist. I had left out the = for assigning the version, but didn't have that in my test copy. You are correct, it will obscure the contents: {"index":[2,3,5,"two","ten",{"fruit":"tomato","weight":2.35}]} becomes: "eyJpbmRleCI6WzIsMyw1LCJ0d28iLCJ0ZW4iLHsiZnJ1aXQiOiJ0b21hdG8iLCJ3ZWlnaHQiOjIuMzV9XX0=" I would just write a function to show it to you in the format you want. I use this function quite a bit for dumping out data other than in the log: var showObj = function(obj) { sendChat('','/direct ' +'&lt;div style="border: 1px solid blue; font-size:80%;"&gt;&lt;pre&gt;' +JSON.stringify(obj,undefined,". ").replace(/\n/g,'&lt;br&gt;') +"&lt;/pre&gt;&lt;/div&gt;"); }; You could certainly write something similar and add an API command to dump out the decoded contents of a GM Note on a character. After all, I'm sure you're not scrolling the 53 pixel wide current field of an attribute to look at it's contents. =D
1411746198
esampson
Pro
Sheet Author
No, I just copy the contents out to a text editor and look at it that way. :) So that works but I need to figure out the pros and cons of encoding the data to the GMnotes as opposed to leaving it unencoded on a single Data attribute. I can see advantages to using a single attribute rather than the long series I was previously using but is there some limitation to attributes that does not exist on GMnotes that makes using this more desirable?
1411747056
esampson
Pro
Sheet Author
Oh. My mistake. While it appears to be creating the object and writing the data correctly the API is still throwing an error and halting the script when I store the Encode64 data to the gmnotes.
1411752363
The Aaron
Roll20 Production Team
API Scripter
Weird. There shouldn't be anything within it that would cause an error. What error does it throw?
1411754236
The Aaron
Roll20 Production Team
API Scripter
Testing with: var storage=findObjs({type:'character', name: 'test'})[0]; if(storage) { storage.set('gmnotes', Base64.encode(JSON.stringify(state))); } and: var storage=findObjs({type:'character', name: 'test'})[0]; if(storage) { storage.get('gmnotes',function(n){ showObj(JSON.parse(Base64.decode(n))); }); } Successfully stored and retrieved my state object.
1411756293
esampson
Pro
Sheet Author
The error generated is: Your scripts are currently disabled due to an error that was detected. Please make appropriate changes to your scripts and click the "Save Script" button and we'll attempt to start running them again. More info... For reference, the error message generated was: /home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:1 orts, require, module, __filename, __dirname) { function f(a){throw a;}var j=v ^ Error: Firebase.child failed: First argument must be a non-empty string and can't contain ".", "#", "$", "[", or "]". at Error (&lt;anonymous&gt;) at Ha (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:12:204) at G.W.H (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:126:213) at TrackedObj._doSave ( It does, however, appear to succesfully generate: WyJbXCJiYXNlXCIsXCJodHRwczovL3MzLmFtYXpvbmF3cy5jb20vZmlsZXMuZDIwLmlvL2ltYWdlcy81Njg1NTA5L01TcGZrY3lScDdyVkFqUnRkXzdkZHcvdGh1bWIucG5nPzE0MTE0MTQwNjlcIixcIlRlc3QyXCIsNzg1LjUsMzY1LjUsNTkxLDU5MSwwLFwibWFwXCIsZmFsc2UsZmFsc2UsZmFsc2UsXCJcIixcIlwiLFwiXCIsXCJcIixcIlwiLFwiXCIsXCJcIixcIlwiLFwiXCIsXCJcIixcIlwiLFwiXCIsXCJcIixcIiNGRkZGOTlcIixmYWxzZSxcIlwiLFwiIzU5RTU5NFwiLGZhbHNlLFwidHJhbnNwYXJlbnRcIixcIlwiLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLHRydWUsdHJ1ZSx0cnVlLHRydWUsdHJ1ZSx0cnVlLFwiXCIsXCJcIixmYWxzZSxmYWxzZSxcIlwiLFwiXCIsXCJcIiwwLFwiXCIsXCJ0b2tlblwiLFwiXCJdIiwiW1wiZ3JhcGhpY1wiLFwiaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL2ZpbGVzLmQyMC5pby9pbWFnZXMvNTY4NDM5NS9Fb3dVOGJscTVqZXZOZHU3ZDBKUFJBL3RodW1iLnBuZz8xNDExNDA1NjY4XCIsXCJcIiwxMDE3LDI3OSw5OC45OTQ5NDkzNjYxMTY2Niw5OC45OTQ5NDkzNjYxMTY2NiwwLFwibWFwXCIsZmFsc2UsXCJncmFwaGljXCIsZmFsc2UsXCJcIixcIlwiLFwiXCIsXCJcIixcIlwiLFwiXCIsXCJcIixcIlwiLFwiXCIsXCJcIixcIlwiLFwiXCIsXCJcIixcIiNGRkZGOTlcIixmYWxzZSxcIlwiLFwiIzU5RTU5NFwiLGZhbHNlLFwidHJhbnNwYXJlbnRcIixcIlwiLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLHRydWUsdHJ1ZSx0cnVlLHRydWUsdHJ1ZSx0cnVlLDcwLjcxMDY3ODExODY1NDc2LDAsdHJ1ZSxmYWxzZSxcIlwiLFwiXCIsXCJcIiwwLFwiXCIsXCJ0b2tlblwiLFwiXCJdIiwiW1wiZ3JhcGhpY1wiLFwiaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL2ZpbGVzLmQyMC5pby9pbWFnZXMvNTY4NDM5NS9Fb3dVOGJscTVqZXZOZHU3ZDBKUFJBL3RodW1iLnBuZz8xNDExNDA1NjY4NVwiLFwiXCIsNzg1LDM4NSw5OC45OTQ5NDkzNjYxMTY2Niw5OC45OTQ5NDkzNjYxMTY2NiwwLFwibWFwXCIsZmFsc2UsXCJncmFwaGljXCIsZmFsc2UsXCJcIixcIlwiLFwiXCIsXCJcIixcIlwiLFwiXCIsXCJcIixcIlwiLFwiXCIsXCJcIixcIlwiLFwiXCIsXCJcIixcIiNGRkZGOTlcIixmYWxzZSxcIlwiLFwiIzU5RTU5NFwiLGZhbHNlLFwidHJhbnNwYXJlbnRcIixcIlwiLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLHRydWUsdHJ1ZSx0cnVlLHRydWUsdHJ1ZSx0cnVlLDcwLjcxMDY3ODExODY1NDc2LDAsdHJ1ZSxmYWxzZSxcIlwiLFwiXCIsXCJcIiwwLFwiXCIsXCJ0b2tlblwiLFwiXCJdIiwiW1wiZ3JhcGhpY1wiLFwiaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL2ZpbGVzLmQyMC5pby9pbWFnZXMvNTY4NDM5NS9Fb3dVOGJscTVqZXZOZHU3ZDBKUFJBL3RodW1iLnBuZz8xNDExNDA1NjY4NVwiLFwiXCIsNTYyLDI3Nyw5OC45OTQ5NDkzNjYxMTY2Niw5OC45OTQ5NDkzNjYxMTY2NiwwLFwibWFwXCIsZmFsc2UsXCJncmFwaGljXCIsZmFsc2UsXCJcIixcIlwiLFwiXCIsXCJcIixcIlwiLFwiXCIsXCJcIixcIlwiLFwiXCIsXCJcIixcIlwiLFwiXCIsXCJcIixcIiNGRkZGOTlcIixmYWxzZSxcIlwiLFwiIzU5RTU5NFwiLGZhbHNlLFwidHJhbnNwYXJlbnRcIixcIlwiLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLHRydWUsdHJ1ZSx0cnVlLHRydWUsdHJ1ZSx0cnVlLDcwLjcxMDY3ODExODY1NDc2LDAsdHJ1ZSxmYWxzZSxcIlwiLFwiXCIsXCJcIiwwLFwiXCIsXCJ0b2tlblwiLFwiXCJdIiwiW1wiZ3JhcGhpY1wiLFwiaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL2ZpbGVzLmQyMC5pby9pbWFnZXMvNTY4NDM5NS9Fb3dVOGJscTVqZXZOZHU3ZDBKUFJBL3RodW1iLnBuZz8xNDExNDA1NjY4NVwiLFwiXCIsNjY1LDU3NCw5OC45OTQ5NDkzNjYxMTY2Niw5OC45OTQ5NDkzNjYxMTY2NiwwLFwibWFwXCIsZmFsc2UsXCJncmFwaGljXCIsZmFsc2UsXCJcIixcIlwiLFwiXCIsXCJcIixcIlwiLFwiXCIsXCJcIixcIlwiLFwiXCIsXCJcIixcIlwiLFwiXCIsXCJcIixcIiNGRkZGOTlcIixmYWxzZSxcIlwiLFwiIzU5RTU5NFwiLGZhbHNlLFwidHJhbnNwYXJlbnRcIixcIlwiLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLGZhbHNlLHRydWUsdHJ1ZSx0cnVlLHRydWUsdHJ1ZSx0cnVlLDcwLjcxMDY3ODExODY1NDc2LDAsdHJ1ZSxmYWxzZSxcIlwiLFwiXCIsXCJcIiwwLFwiXCIsXCJ0b2tlblwiLFwiXCJdIiwiW1wicGF0aFwiLFwidHJhbnNwYXJlbnRcIixcIiNmZjAwMDBcIiwwLDUsMCwxNzAsNDAzLDUxOCwxLDEsXCJcIixcIndhbGxzXCIsXCJbW1xcXCJNXFxcIiwwLDBdLFtcXFwiTFxcXCIsMCwxNzBdXVwiXSIsIltcInBhdGhcIixcInRyYW5zcGFyZW50XCIsXCIjZmYwMDAwXCIsMCw1LDI5NywzNyw1NzkuNSw2NjYuNSwxLDEsXCJcIixcIndhbGxzXCIsXCJbW1xcXCJNXFxcIiwwLDBdLFtcXFwiTFxcXCIsMCwzNl0sW1xcXCJMXFxcIiwyOTcsMzddLFtcXFwiTFxcXCIsMjk3LDFdXVwiXSIsIltcInBhdGhcIixcInRyYW5zcGFyZW50XCIsXCIjZmYwMDAwXCIsMCw1LDIzNywxNjgsNDA0LDkzMS41LDEsMSxcIlwiLFwid2FsbHNcIixcIltbXFxcIk1cXFwiLDAsMTY4XSxbXFxcIkxcXFwiLDIsMTAyXSxbXFxcIkxcXFwiLDIzNywxMDBdLFtcXFwiTFxcXCIsMjM2LDBdXVwiXSIsIltcInBhdGhcIixcInRyYW5zcGFyZW50XCIsXCIjZmYwMDAwXCIsMCw1LDIzNSwxMTksMTg2LjUsOTMxLjUsMSwxLFwiXCIsXCJ3YWxsc1wiLFwiW1tcXFwiTVxcXCIsMjM1LDExOV0sW1xcXCJMXFxcIiwyMzQsMF0sW1xcXCJMXFxcIiwwLDBdXVwiXSIsIltcInBhdGhcIixcInRyYW5zcGFyZW50XCIsXCIjZmYwMDAwXCIsMCw1LDIzNSwxMjYsMTg4LDYzNS41LDEsMSxcIlwiLFwid2FsbHNcIixcIltbXFxcIk1cXFwiLDIzNSwwXSxbXFxcIkxcXFwiLDAsMl0sW1xcXCJMXFxcIiwwLDEyNl1dXCJdIiwiW1wicGF0aFwiLFwidHJhbnNwYXJlbnRcIixcIiNmZjAwMDBcIiwwLDUsMTE3LDAsNDI0LDU3Ni41LDEsMSxcIlwiLFwid2FsbHNcIixcIltbXFxcIk1cXFwiLDAsMF0sW1xcXCJMXFxcIiwxMTcsMF1dXCJdIiwiW1wicGF0aFwiLFwidHJhbnNwYXJlbnRcIixcIiNmZjAwMDBcIiwwLDUsMTE2LDEsNDIyLjUsNzU4LDEsMSxcIlwiLFwid2FsbHNcIixcIltbXFxcIk1cXFwiLDExNiwxXSxbXFxcIkxcXFwiLDAsMF1dXCJdIiwiW1wicGF0aFwiLFwidHJhbnNwYXJlbnRcIixcIiMwMGZmMDBcIiwwLDUsNzEsMCw0MjQsNjY4LjUsMSwxLFwiXCIsXCJ3YWxsc1wiLFwiW1tcXFwiTVxcXCIsMCwwXSxbXFxcIkxcXFwiLDcxLDBdXVwiXSJd before it dies which I am able to decode into the correct data. So it works and it doesn't. I could probably catch the exception if I really had to.
1411757707
esampson
Pro
Sheet Author
Running some quick tests it looks like the problem is simply that it doesn't like that much data being set all at once on gmnotes (not sure why it is ok with it being created onto another attribute). The string itself is clean but even if I try and write it to gmnotes as a string literal it fails with the same error.
1411760788
The Aaron
Roll20 Production Team
API Scripter
Actually, I'm pretty sure that's the firebase/createObj bug: <a href="https://app.roll20.net/forum/post/733277/api-fireb" rel="nofollow">https://app.roll20.net/forum/post/733277/api-fireb</a>... Your fix for createObj is only targeting attributes, but the character you create has the issue. You just don't run into it because you set all the values on it at creation, and then just create attributes by accessing it's ID.
1411761945
esampson
Pro
Sheet Author
Ah. Yeah, you are probably right. I would need to tweak the createObj code so that it can do the gmnotes successfully.
1411763516
esampson
Pro
Sheet Author
Bah. So what is the problem with this code? storage = createObj("character", { gmnotes: "dString", bio: "dString", name: baseName, imgsrc : Base[0].get("imgsrc"), avatar : Base[0].get("imgsrc"), }); I recognize that when I look at the character what I should see is "dString" rather than the value for dString but currently I'm seeing nothing at all.
1411773292
The Aaron
Roll20 Production Team
API Scripter
Aside from the extra comma at the end of avatar, I don't know. To possible that gmnotes and bio can't be set at creation. They are special fields. I'd check the wiki. (I'm actually camping right now or I would!)
1411790149
esampson
Pro
Sheet Author
So it appears that yes, for some reason you cannot set the GMNotes when creating a character. I've gone ahead and updated the code to now used Base64 and to store the data in GMNotes instead of an attribute. The code does not actually contain Base64 but calls it from a separate javascript routine so anyone who wants to take advantage of this code will need to install both modules.
1411838699
The Aaron
Roll20 Production Team
API Scripter
Probably it's because the storage for those fields can't be written to synchronously. Nice job on integrating the Base64. :)
1411859680
esampson
Pro
Sheet Author
Updated once more. When the data is built it is built using objects rather than requiring attributes in a specific order. This should make any conversion created after this point compatible with any future versions of the software. It pushes up the size of the data but forward compatibility should be worth it.