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 .
×

Get property top and left of path gives error.

on("ready", () => {     on("add:path", function(obj, prev) {           let path = obj.get("_path") ;        let left = obj.get("left");         sendChat("path", path);        sendChat("Info:left", left);        //let paths = findObjs({ type: "path"});       //sendChat("path--", paths);      }); }); please help
okay. this is working now.  on("add:path", function(obj, prev) {           let path = obj.get("_path") ;        let left = obj.get("left")        let top = obj.get("top")        sendChat("path", path);        sendChat("Info:left", left + "--");        sendChat("Info:top", top + "--");        //let paths = findObjs({ type: "path"});       //sendChat("path--", paths);      }); but I was getting a error every time I hit get top on this. on("chat:message", msg => {         if(msg.content.split(" ")[0].toLowerCase() === '!getpathbyid') {             let id_ = msg.content.split(" ")[1];             sendChat("000--", id_);             const pcs = findObjs({_id : id_});             let _id = pcs[0].get("_id");             sendChat("001--", _id);              let path_ = pcs[0].get("_path");             sendChat("002--", path_);              let top = pcs[0].get("top")             sendChat("003--", top);                       }     });
1599762886

Edited 1599763027
The Aaron
Roll20 Production Team
API Scripter
If you already have the id, it's better to do something like: on("chat:message", msg => { if(/^!getpathbyid/i.test(msg.content) ) { let args = msg.content.split(/\s+/); let path = getObj('path',args[1]); if(path){ sendChat('000--',`id: ${path.id} type: ${path.get('type')} location: (${path.get('left')},${path.get('top')})`); sendChat('002--',`path data: ${path.get('path')}`); } } }); getObj() is more efficient.  You should also be checking to make sure that you actually got something back.  In the case of getObj(), you'll get undefined if an object of the type and id doesn't exist (like if you pass a graphic id and a path type).  for findObjs(), you'll get an empty array.  Note that findObjs() is optimized around object type and a few other things, so it's an order of magnitude faster to search for {type: path, id}, than just for {id}. Often times, it's better to use the selected property of the msg: { "content": "!getpathbyid -MGtG1SH03ztXp7qjpb0", "playerid": "-MA-6LlhK58UhtPa2ib7", "selected": [ { "_id": "-MGtG1SH03ztXp7qjpb0", "_type": "path" } ], "type": "api", "who": "The Aaron (GM)" } Note that for .get() and .set(), you don't need to use the leading _ on property names.  You still need them on the prev javascript object passed as the second parameter of change events.  I feel like it's a good idea to not use the leading _ on object property names for a few reasons.  It's annoying to type and kind of clutters things up, and if properties are moved from readonly to read-write, you have to change your code.  Probably personal preference, really, but there you have it.
1599763607
timmaugh
Roll20 Production Team
API Scripter
The one place I've found I have to use the underscore is for the selected property of a msg object. Unless I'm doing something wrong, those aren't Roll20 objects. So I can't do: msg.selected.map(t => r === 'id' ? t.id : t.get(r)); ...because the elements don't have an 'id' property promoted from the '_id' variation (nor do they have get() methods). I have to first get the R20 object by using the '_id' variation, then I have access to the full suite of interface handles: msg.selected.map(t => getObj('graphic', t._id))     .filter(t => t)     .map(t => r === 'id' ? t.id : t.get(r));
1599764316
The Aaron
Roll20 Production Team
API Scripter
Yup, those are just simple javascript objects, like the prev parameter to change. 
The Aaron. on("chat:message", msg => { if(/^!getpathbyid/i.test(msg.content) ) { let args = msg.content.split(/\s+/); let path = getObj('path',args[1]); if(path){ sendChat('000--',`id: ${path.id} type: ${path.get('type')} location: (${path.get('left')},${path.get('top')})`); sendChat('002--',`path data: ${path.get('path')}`); } } }); good code. I am going to have to use it. I have a script that lest me create a path starting on top of a token. when i close the path the token moves along it. my 1st real go at a API script.  I think this code will clean it up. and maybe some other scripts I make as well.
1600098135
The Aaron
Roll20 Production Team
API Scripter
Sounds good!  Definitely let us know if there's anything you run into and want another set of eyes or brains on. =D
1600098460

Edited 1600098574
well here is the code. some debugging in it. kind of hacky, but it is a learning script. lots of me leaning the API.  but fell free to comment on it and give me your thoughts. var movementArr = []; var myTimerVar; var stepstaken = 0; var tokenToMove = null; var holdObj; on("ready", () => {     on("add:path", function(obj, prev) {          holdObj = obj;         let path = obj.get("_path") ;          let left = obj.get("left");         let top = obj.get("top");         let width = obj.get("width");         let height = obj.get("height");         let id = obj.get("_id")          //this will give up the top left point of the box the path is in.         left = left - (width/2);         top = top - (height/2);         //sendChat("Info:top", "=================");          //sendChat("id:", id);         //sendChat("path", path);          //sendChat("Info:left", left + "--");          //sendChat("Info:width", width + "--");         //sendChat("Info:top", top + "--");           //sendChat("Info:height", height + "--");                   let m = [];         let y = path.replace(/,/g,' ');         y = y.replace(/\[/g,'');         y = y.replace(/\]/g,'');         y = y.replace(/\"/g,'');         y = y.replace(/M/g,'');         y = y.replace(/L/g,'');         //sendChat("Info", "y :" + y)         m = y.split(" ");         movementArr = getPath(m,left,top)                         let lPoint = parseInt(movementArr[0][0])         let TPoint = parseInt(movementArr[0][1])                           //sendChat("Info::", "lPoint:" + lPoint + "--TPoint:" + TPoint);                 const tokens = findObjs({ subtype: 'token', layer: 'objects'});         tokenToMove = findstartToken(tokens,lPoint ,TPoint);                  holdObj.remove();         if(tokenToMove != null){                          if(movementArr.length > 0){                 stepstaken = 0;                 myTimerVar = setInterval(myTimer, 400);             }                    }     }); }); function myTimer() {     if(stepstaken >= movementArr.length) {         myStopFunction();     }else{         //sendChat("Info", "stepstaken :" + stepstaken + " of " + movementArr.length) ;         if(tokenToMove != null){             var t = parseInt(movementArr[stepstaken][1])             var l = parseInt(movementArr[stepstaken][0])             ///sendChat("Info", "Top :" + t) ;             //sendChat("Info", "Left :" + l);             tokenToMove.set("top", t); //walk!             tokenToMove.set("left", l); //walk         }         stepstaken++;     } } function myStopFunction() {   clearInterval(myTimerVar);   stepstaken = 0;   tokenToMove = null;   movementArr = [];   holdObj = null; } function findstartToken(tokens, pleft, ptop){     for(let i = 0; i < tokens.length; i++){         if(tokens[i].get("name") !== '' && tokens[i].get("represents").toString().length == 0){                          let top = tokens[i].get("top")-30;             let left = tokens[i].get("left")-30;             let width = tokens[i].get("width")+30;             let height = tokens[i].get("height")+30;                          //sendChat("name:", tokens[i].get("name"));             //sendChat("width   clicked:", pleft + ":" + left + "--" + (left + width));              //sendChat("height  clicked:", ptop + ":" + top + "--" + (top + height));                           if(ptop > top && ptop < (top + height)){                 if(pleft > left && pleft < (left + width)){                                          //sendChat("token:", "found");                     return tokens[i]                 }                              }         }     }     return null;      } function getPath(pathArr, left, top){     //sendChat("Info", "pathArr :" + pathArr.length);     returnArr = [];     var t = 0;     var l = 0;     //sendChat("Info", "length :" + pathArr.length );     for(let i = 0; i < pathArr.length; i++){         l = pathArr[i];         if(l == ''){             i++;             l = pathArr[i];         }        // sendChat("Info", "i1 :" + i  + "'" + l + "'");         i++;         t = pathArr[i];         if(t == ''){             i++;             t = pathArr[i];         }         //sendChat("Info", "l :" + l + " t:" + t );         let lmove = parseInt(left) + parseInt(l);         let tmove = parseInt(top) + parseInt(t);         returnArr.push([lmove,tmove]);         //sendChat("Info", "top: " + tmove.toString() + " left :"+ lmove.toString() );     }     return returnArr; }
1600099006
The Aaron
Roll20 Production Team
API Scripter
I can't look too deeply right now, but here's a slight adjustment.&nbsp; It's a good idea to put all of your code in the on('ready',...) event, or inside a dedicated closure.&nbsp; This prevents polluting the global namespace shared by all scripts and can prevent problems if more than one script declare a variable named "myTimer" or the like.&nbsp; Also, the new 'let' and 'const' variable declarations are much better than 'var', so I'd suggest switching over to using let wherever you would have had var in the past or in examples.&nbsp; Also, I always tell people to use === and !== instead of == and != until you know the difference between them, at which point you'll never want to use the short forms. =D on("ready", () =&gt; { let movementArr = []; let myTimerVar; let stepstaken = 0; let tokenToMove = null; let holdObj; on("add:path", function(obj, prev) { holdObj = obj; let path = obj.get("_path") ; let left = obj.get("left"); let top = obj.get("top"); let width = obj.get("width"); let height = obj.get("height"); let id = obj.get("_id") //this will give up the top left point of the box the path is in. left = left - (width/2); top = top - (height/2); //sendChat("Info:top", "================="); //sendChat("id:", id); //sendChat("path", path); //sendChat("Info:left", left + "--"); //sendChat("Info:width", width + "--"); //sendChat("Info:top", top + "--"); //sendChat("Info:height", height + "--"); let m = []; let y = path.replace(/,/g,' '); y = y.replace(/\[/g,''); y = y.replace(/\]/g,''); y = y.replace(/\"/g,''); y = y.replace(/M/g,''); y = y.replace(/L/g,''); //sendChat("Info", "y :" + y) m = y.split(" "); movementArr = getPath(m,left,top) //we know that the point click (Left) is going to be the top + path[0][0] //we know that the point click (Top) is going to be the top + path[0][1] let lPoint = parseInt(movementArr[0][0]) let TPoint = parseInt(movementArr[0][1]) //sendChat("Info::", "lPoint:" + lPoint + "--TPoint:" + TPoint); const tokens = findObjs({ subtype: 'token', layer: 'objects'}); tokenToMove = findstartToken(tokens,lPoint ,TPoint); holdObj.remove(); if(tokenToMove != null){ if(movementArr.length &gt; 0){ stepstaken = 0; myTimerVar = setInterval(myTimer, 400); } } }); function myTimer() { if(stepstaken &gt;= movementArr.length) { myStopFunction(); }else{ //sendChat("Info", "stepstaken :" + stepstaken + " of " + movementArr.length) ; if(tokenToMove != null){ let t = parseInt(movementArr[stepstaken][1]) let l = parseInt(movementArr[stepstaken][0]) ///sendChat("Info", "Top :" + t) ; //sendChat("Info", "Left :" + l); tokenToMove.set("top", t); //walk! tokenToMove.set("left", l); //walk } stepstaken++; } } function myStopFunction() { clearInterval(myTimerVar); stepstaken = 0; tokenToMove = null; movementArr = []; holdObj = null; } function findstartToken(tokens, pleft, ptop){ for(let i = 0; i &lt; tokens.length; i++){ if(tokens[i].get("name") !== '' &amp;&amp; tokens[i].get("represents").toString().length === 0){ let top = tokens[i].get("top")-30; let left = tokens[i].get("left")-30; let width = tokens[i].get("width")+30; let height = tokens[i].get("height")+30; //sendChat("name:", tokens[i].get("name")); //sendChat("width clicked:", pleft + ":" + left + "--" + (left + width)); //sendChat("height clicked:", ptop + ":" + top + "--" + (top + height)); if(ptop &gt; top &amp;&amp; ptop &lt; (top + height)){ if(pleft &gt; left &amp;&amp; pleft &lt; (left + width)){ //sendChat("token:", "found"); return tokens[i] } } } } return null; } function getPath(pathArr, left, top){ //sendChat("Info", "pathArr :" + pathArr.length); returnArr = []; let t = 0; let l = 0; //sendChat("Info", "length :" + pathArr.length ); for(let i = 0; i &lt; pathArr.length; i++){ l = pathArr[i]; if(l === ''){ i++; l = pathArr[i]; } // sendChat("Info", "i1 :" + i + "'" + l + "'"); i++; t = pathArr[i]; if(t === ''){ i++; t = pathArr[i]; } //sendChat("Info", "l :" + l + " t:" + t ); let lmove = parseInt(left) + parseInt(l); let tmove = parseInt(top) + parseInt(t); returnArr.push([lmove,tmove]); //sendChat("Info", "top: " + tmove.toString() + " left :"+ lmove.toString() ); } return returnArr; } }); Here are a few links to API coding discussions that you might find helpful: <a href="https://app.roll20.net/forum/post/6605115/namespaces-novice-seeks-help-exploring-the-revealing-module-pattern" rel="nofollow">https://app.roll20.net/forum/post/6605115/namespaces-novice-seeks-help-exploring-the-revealing-module-pattern</a> <a href="https://app.roll20.net/forum/post/6584105/creating-an-object-that-holds-specific-character-dot-id-and-character-name/?pagenum=1" rel="nofollow">https://app.roll20.net/forum/post/6584105/creating-an-object-that-holds-specific-character-dot-id-and-character-name/?pagenum=1</a> <a href="https://app.roll20.net/forum/post/6237754/slug%7D" rel="nofollow">https://app.roll20.net/forum/post/6237754/slug%7D</a>