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

[Bast L.][AOE Template Generator] modify output to drawings after write to screen?

After the tokens are written I have been using tokenmod to convert them to drawings ( !token-mod --on isdrawing ), but wish this API - which I prefer as its is easier to understand - did not draw them or leave them drawn as tokens.  The idea being to center spells that avoid harming allies or friendly NPCs, or target around cover, is less functional when you can't pinpoint place or angle precisely.  Would it be possible to convert the token to a drawing after it writes to screen by drawing on TheAaron's code for tokenmod's conversion method (with his permission, of course)?  My players always love the shapes to target spells, but hate (as I sometimes forget) to wait till I convert them to drawings so they can do that with precise location and angle.
1536518279
The Aaron
Pro
API Scripter
If you modify the 3 calls to createObj() to additionally pass the isdrawing: true property, it will always spawn drawing graphics: //TemplateGenerator //make the macro seen in other file. change "/w gm" to "/w PlayerName" with player name being the player's name. //Make a rollable table with template shapes, and names being: Circle, Cone, Square, Line. Table name is: TemplateShapes on("ready", function() { var getCleanImgsrc = function (imgsrc) { var parts = imgsrc.match(/(.*\/images\/.*)(thumb|med|original|max)([^\?]*)(\?[^?]+)?$/); if(parts) { return parts[1]+'thumb'+parts[3]+(parts[4]?parts[4]:`?${Math.round(Math.random()*9999999)}`); } return; }; on("chat:message", function (msg) { if (msg.type === "api" && msg.content.split(' ')[0] === "!TemplateGenerator") { MakeShape(msg); } /*if (msg.type === "api" && msg.content.split(' ')[0] === "!ClearTemplates") { ClearShapes(); }*/ }); function MakeShape(msg) { let campaign = findObjs({type: "campaign"})[0]; let pageID = campaign.get("playerpageid"); if(!msg.selected) { sendChat("", "/w " + msg.who + " no token selected for generation point."); return; } let playerToken = getObj(msg.selected[0]._type, msg.selected[0]._id); let shapeName = msg.content.split(' ')[1]; let shapeTable = findObjs({_type: 'rollabletable', name: 'TemplateShapes'})[0]; let shapeItem = findObjs({_type: 'tableitem', _rollabletableid: shapeTable.id, name: shapeName})[0]; let shapeSize = msg.content.split(' ')[2]; if (shapeName == "Circle") {shapeSize *= 2; } let playerID = msg.playerid; if (shapeName != 'Line' && shapeName != 'Cone') { createObj('graphic', { imgsrc: getCleanImgsrc(shapeItem.get('avatar')), name: shapeName, left: playerToken.get('left'), top: playerToken.get('top'), _pageid: pageID, layer: "objects", height: parseInt(shapeSize)*14, width: parseInt(shapeSize)*14, isdrawing: true, controlledby: playerID }); } else if (shapeName === 'Line'|| shapeName === 'Cone') { let lineOffsetTop = 0; let lineOffsetLeft = 0; let lineAngle = 0; if (msg.content.split(' ')[3]) { lineAngle = parseInt(msg.content.split(' ')[3]); lineAngleRad = lineAngle*Math.PI/180; if (lineAngle == 0 || lineAngle == 90 || lineAngle == 180 || lineAngle == 270) { lineOffsetTop = ((5+ parseInt(shapeSize))/2)*Math.sin(lineAngleRad); lineOffsetLeft = ((5+ parseInt(shapeSize))/2)*Math.cos(lineAngleRad); } else { lineOffsetTop = ((7+ parseInt(shapeSize))/2)*Math.sin(lineAngleRad); lineOffsetLeft = ((7+ parseInt(shapeSize))/2)*Math.cos(lineAngleRad); } } if (shapeName === 'Line') { createObj('graphic', { imgsrc: getCleanImgsrc(shapeItem.get('avatar')), name: shapeName, left: playerToken.get('left') + lineOffsetLeft*14, top: playerToken.get('top') - lineOffsetTop*14, _pageid: pageID, layer: "objects", height: 70, width: parseInt(shapeSize)*14, controlledby: playerID, isdrawing: true, rotation: -1*lineAngle }); } if (shapeName === 'Cone') { createObj('graphic', { imgsrc: getCleanImgsrc(shapeItem.get('avatar')), name: shapeName, left: playerToken.get('left') + lineOffsetLeft*14, top: playerToken.get('top') - lineOffsetTop*14, _pageid: pageID, layer: "objects", height: parseInt(shapeSize)*14, width: parseInt(shapeSize)*14, controlledby: playerID, isdrawing: true, rotation: -1*(lineAngle + 90) }); } } } /*function ClearShapes() { let campaign = findObjs({type: "campaign"})[0]; let pageID = campaign.get("playerpageid"); let circles = findObjs({type: 'graphic', _pageid: pageID, name: 'Circle'}); for (var i = 0; i< circles.length; i++) { circles[i].remove(); } }*/ });
1536549426

Edited 1536549598
Bast L.
API Scripter
Aaron's provided it, though I'll mention that holding alt while moving the token will also allow precise placement. Oh, and for angles, alt should probably work, though I usually use e + mousewheel, or alt e + mousewheel, for more precision Also, glad someone's getting use out of it!  :)
Bast L. said: Aaron's provided it, though I'll mention that holding alt while moving the token will also allow precise placement. Oh, and for angles, alt should probably work, though I usually use e + mousewheel, or alt e + mousewheel, for more precision Also, glad someone's getting use out of it!  :) Very much so!  It's a little easier to work with than Stephen's Script.  I love his graphics, yes, but Roll20 and Marketplace Graphics are often problematic, and sadly I'm a little bit more on the layman side of understanding things, so complexity like his is difficult for me to understand, whereas yours is pop in and go crazy.  Any plan on updating this script or making it available in Roll20's script library?  I don't know how to implement the changes Aaron suggested, myself.
The Aaron said: If you modify the 3 calls to createObj() to additionally pass the isdrawing: true property, it will always spawn drawing graphics: //TemplateGenerator //make the macro seen in other file. change "/w gm" to "/w PlayerName" with player name being the player's name. //Make a rollable table with template shapes, and names being: Circle, Cone, Square, Line. Table name is: TemplateShapes on("ready", function() { var getCleanImgsrc = function (imgsrc) { var parts = imgsrc.match(/(.*\/images\/.*)(thumb|med|original|max)([^\?]*)(\?[^?]+)?$/); if(parts) { return parts[1]+'thumb'+parts[3]+(parts[4]?parts[4]:`?${Math.round(Math.random()*9999999)}`); } return; }; on("chat:message", function (msg) { if (msg.type === "api" && msg.content.split(' ')[0] === "!TemplateGenerator") { MakeShape(msg); } /*if (msg.type === "api" && msg.content.split(' ')[0] === "!ClearTemplates") { ClearShapes(); }*/ }); function MakeShape(msg) { let campaign = findObjs({type: "campaign"})[0]; let pageID = campaign.get("playerpageid"); if(!msg.selected) { sendChat("", "/w " + msg.who + " no token selected for generation point."); return; } let playerToken = getObj(msg.selected[0]._type, msg.selected[0]._id); let shapeName = msg.content.split(' ')[1]; let shapeTable = findObjs({_type: 'rollabletable', name: 'TemplateShapes'})[0]; let shapeItem = findObjs({_type: 'tableitem', _rollabletableid: shapeTable.id, name: shapeName})[0]; let shapeSize = msg.content.split(' ')[2]; if (shapeName == "Circle") {shapeSize *= 2; } let playerID = msg.playerid; if (shapeName != 'Line' && shapeName != 'Cone') { createObj('graphic', { imgsrc: getCleanImgsrc(shapeItem.get('avatar')), name: shapeName, left: playerToken.get('left'), top: playerToken.get('top'), _pageid: pageID, layer: "objects", height: parseInt(shapeSize)*14, width: parseInt(shapeSize)*14, isdrawing: true, controlledby: playerID }); } else if (shapeName === 'Line'|| shapeName === 'Cone') { let lineOffsetTop = 0; let lineOffsetLeft = 0; let lineAngle = 0; if (msg.content.split(' ')[3]) { lineAngle = parseInt(msg.content.split(' ')[3]); lineAngleRad = lineAngle*Math.PI/180; if (lineAngle == 0 || lineAngle == 90 || lineAngle == 180 || lineAngle == 270) { lineOffsetTop = ((5+ parseInt(shapeSize))/2)*Math.sin(lineAngleRad); lineOffsetLeft = ((5+ parseInt(shapeSize))/2)*Math.cos(lineAngleRad); } else { lineOffsetTop = ((7+ parseInt(shapeSize))/2)*Math.sin(lineAngleRad); lineOffsetLeft = ((7+ parseInt(shapeSize))/2)*Math.cos(lineAngleRad); } } if (shapeName === 'Line') { createObj('graphic', { imgsrc: getCleanImgsrc(shapeItem.get('avatar')), name: shapeName, left: playerToken.get('left') + lineOffsetLeft*14, top: playerToken.get('top') - lineOffsetTop*14, _pageid: pageID, layer: "objects", height: 70, width: parseInt(shapeSize)*14, controlledby: playerID, isdrawing: true, rotation: -1*lineAngle }); } if (shapeName === 'Cone') { createObj('graphic', { imgsrc: getCleanImgsrc(shapeItem.get('avatar')), name: shapeName, left: playerToken.get('left') + lineOffsetLeft*14, top: playerToken.get('top') - lineOffsetTop*14, _pageid: pageID, layer: "objects", height: parseInt(shapeSize)*14, width: parseInt(shapeSize)*14, controlledby: playerID, isdrawing: true, rotation: -1*(lineAngle + 90) }); } } } /*function ClearShapes() { let campaign = findObjs({type: "campaign"})[0]; let pageID = campaign.get("playerpageid"); let circles = findObjs({type: 'graphic', _pageid: pageID, name: 'Circle'}); for (var i = 0; i< circles.length; i++) { circles[i].remove(); } }*/ }); Or is that the whole script right there?  And Aaron, would the "!me" work for "/w PlayerName"?
1536553257

Edited 1536553590
Bast L.
API Scripter
Wolf Thunderspirit said: Bast L. said: Aaron's provided it, though I'll mention that holding alt while moving the token will also allow precise placement. Oh, and for angles, alt should probably work, though I usually use e + mousewheel, or alt e + mousewheel, for more precision Also, glad someone's getting use out of it!  :) Very much so!  It's a little easier to work with than Stephen's Script.  I love his graphics, yes, but Roll20 and Marketplace Graphics are often problematic, and sadly I'm a little bit more on the layman side of understanding things, so complexity like his is difficult for me to understand, whereas yours is pop in and go crazy.  Any plan on updating this script or making it available in Roll20's script library?  I don't know how to implement the changes Aaron suggested, myself. I'm not actually sure how to submit to roll20. I have very little experience with source control stuff, pull requests, etc. The only reason I have a scripter tag is that a friend of mine submitted some scripts I wrote :) That said, I'm sure it's not too tough. I think I just clone the repo, add my files, and issue a pull request. But there's something about a json, and maybe some other issues to worry about. I probably should make it a habit of submitting them. I'm getting a lot of use out of my starfinder helm controls script, and these AoE scripts.  edit: If you have any feedback for update ideas, I'd love to hear them. I do prefer the shapes to be tokens, so they line up on the grid, but Aaron's fix should work for you. Also, I just updated the macro in the gist to have 15 foot squares, something I forgot when I made it.
1536559651
The Aaron
Pro
API Scripter
You should be able to just copy paste the script I pasted. I bolded the changes for clairity. 
edit: If you have any feedback for update ideas, I'd love to hear them. I do prefer the shapes to be tokens, so they line up on the grid, but Aaron's fix should work for you. Also, I just updated the macro in the gist to have 15 foot squares, something I forgot when I made it. I'm good with that, but there is an easy way to provide both options to users of your API: Make a part of the script at the top for settings, that your default is present: var MakeDraw = false; // Change to true for the shapes to be drawings, the default - false - makes them as tokens. and then change the part Aaron added to operate on the variable isdrawing: MakeDraw , I don't know how people do it that this kind of variable setting would persist if you updated the script in Roll20, and that was fed to the user.  Usually this would be a setting in a chat menu that feeds back to the API somehow and stores it, but that kind of thing is beyond me.
The reason I ask for this is for the players.  They can call the API themselves to target their spells.  If the setting already makes the object as art, it means I don't have to change it to move from snapping on the grid.  This presents them with better options when targeting.  They aren't as familiar with Roll20 Operations, so I do as much as possible to make it easier on us both.  Telling them to hit Alt and move isn't as easy as you think.  They sometimes don't understand the rotation of angles on the drawing as is ... lol ... and often reshape the drawing instead of moving it or changing its angle ...
1536565593

Edited 1536565737
I have another suggestion, albeit I'm not so sure how possible this one is, to give the option of uniquely naming the AoE Template on Cast, just for persistent spells like Entangle or Wall of Fire that would last multiple rounds.  On the spawn, if a variable was sent to the template gen of "foo", it could make a name of "foo" appear for the object visible to all. In other words, do something like: !TemplateGenerator Circle 10 ?{Description} with "Wall of Fire (Dorick)" entered in prompt -or-  !TemplateGenerator Square 20   ?{Description} with "Entangle (Del)" entered in prompt to output: If that's too much, I can easily write those in, if only to keep track of for myself.
Thanks for the feedback. A msg.who assigned to the token name, along with the name being shown, and shown to all players could work. I'd avoid the query, as it makes input more of a chore. 
Bast L. said: Thanks for the feedback. A msg.who assigned to the token name, along with the name being shown, and shown to all players could work. I'd avoid the query, as it makes input more of a chore.  I think a non-entry would be default to nothing and look exactly as it always has.  But if someone supplied it directly or did a query to supply it, then it could.  You know what I mean?  Like tokenmod, CSA, or other programs don't have to have a complete script to do a single thing, but if something it recognizes is there, it could be processed.  Perhaps an invoker variable is needed.  Something like --shapename|"[any name or string following appears as the name]" , i.e.: In other words, do something like: !TemplateGenerator Circle 10  --shapename|" ?{Description}" with Wall of Fire (Dorick) entered in prompt -or direct input-  !TemplateGenerator Square 20   --shapename|" Entangle (Del)" or the current norm, of nothing, yields no name: !TemplateGenerator Circle 10