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

.js script cannot read properties of undefined 'get' while attempting to give roll20AM "play on page load" ability (MusicalRibbon resurrection)

Attempting to leverage an (apparently dead) .js script that allows for roll20AM to control tracks on page load; MusicalRibbon , created by Axiomsyndrom , who has also been msg'd on this topic.  (ALSO, I AM TOTALLY OPEN TO DOING THIS DIFFERENTLY) MusicalRibbon, as written, generates syntax errors on load.  Specifically; "  SyntaxError: Unexpected token '}'  ". To be fair; i has very little experience with javascript.  I have been able to edit the script to remove this error, but still receive "undefined" errors on variables (starting with 'get', but it seems to be all of them).  I've scoured the roll20 API wiki, as well as the interwebs, but i am not understanding how to effectively define my variables here, and the typical issues dont seem to match.  Apologies, Im just not javascript-savvy. Could anyone offer me a .js clue on how I am supposed to declare the variables here? tnx in advance ~t TypeError: Cannot read properties of undefined (reading 'get') var currentPageID = Campaign().get('playerpageid'); var currentPage = getObj("page", Campaign().get("playerpageid")); var name = currentPage.get("name"); if (name.indexOf("[Marker1]") > -1) { sendChat("API", "%{MacroDummy|Macro01}"); } if (name.indexOf("[Marker2]") > -1) { sendChat("API", "%{MacroDummy|Macro2}"); } if (name.indexOf("[Marker3]") > -1) { sendChat("API", "%{MacroDummy|Macro3}"); } if (name.indexOf("[TMarker4]") > -1) { sendChat("API", "%{MacroDummy|Macro4}"); } if (name.indexOf("[Marker5]") > -1) { sendChat("API", "%{MacroDummy|Macro5}"); }
1720190009

Edited 1720321127
timmaugh
Pro
API Scripter
Hey, TCM...  ".get" is just a method of obtaining information within Roll20 -- it isn't a variable. What that error is telling you is that somewhere you have tried to use the .get method on an object, but that object was never filled (IOW, it's still "undefined"... hence the "I can't read that .get property because the object whose properties I'm trying to read is 'undefined'" message you're getting). Very likely it's the third line of .get usage in what you've posted. For whatever reason, the currentPage property isn't getting filled when you look for the "playerpageid" property of the Campaign object, so when you try to pull information from your currentPage object, you get the error. There are actually several things we need to fix in what you're trying to do. I'm not sure if the original author of the scriptlet intended to embed this in another script (replacing lines of code)...? But, on its own, this will only fire when the sandbox spins up, and likely *before* the sandbox (and Campaign object) is ready. It will have no reactivity to moving the player ribbon to a new page. Also, if this was not intended to be embedded in another script, you have namespace pollution that exposes your variables to being overwritten by another script. The changes I would suggest amount to: placing everything within an on('ready'...) event should provide scoping to our variables placing everything within an on('ready'...) event should make sure that the Campaign object is ready when we try to get information from it utilizing an event listener for changes to the Campaign object to let us watch for when the playerpageid value changes, so we know to test the new page for inclusion in our list. Those are the minimum changes (as I understand the usage of this code) to make it functional. If I were going to edit it, though, I would change the workflow and setup just slightly. I would expect a character in the game (and create the character if it doesn't exist) of a particular name, like "MusicalRibbon". Then I would operate with a rule that if the name of an ability MATCHED the name of a page, that ability would be executed. That way, you could easily expand the scope of this code beyond just 5 pages... it would scale to whatever number of entries you want/need. It would also give you an in-game interface, helpful so that you don't have to go back and edit code every time you need to change/add a control for a page. I think this version should do exactly that: /* */ var API_Meta = API_Meta || {}; API_Meta.MusicalRibbon = { offset: Number.MAX_SAFE_INTEGER, lineCount: -1 }; { try { throw new Error(''); } catch (e) { API_Meta.MusicalRibbon.offset = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - (4)); } } on('ready', () => { // make sure the MusicalRibbon character exists if (!findObjs({ type: 'character', name: 'MusicalRibbon' })[0]) { createObj('character', { name: 'MusicalRibbon' }); } on('change:campaign', (obj, prev) => { // test if there was a change to the ribbon, or we no longer have a MusicalRibbon character if (prev.playerpageid === obj.get('playerpageid') || !findObjs({ type: 'character', name: 'MusicalRibbon' })[0]) { return; } let char = findObjs({ type: 'character', name: 'MusicalRibbon' })[0]; let page = getObj("page", obj.get("playerpageid")); let searchname = page.get('name').replace(/\s/,'-'); let abil = findObjs({ type: 'ability', name: searchname, characterid: char.id })[0]; if (abil) { sendChat("API", `%{MusicalRibbon|${searchname}}`); } }); }); { try { throw new Error(''); } catch (e) { API_Meta.MusicalRibbon.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.MusicalRibbon.offset); } } /* */ Note: this also makes use of the API_Meta trick so that you can do future troubleshooting using the ScriptInfo script. The trick and the way ScriptInfo can help you troubleshoot script errors is discussed at the link. EDIT: Fixed code with error that was later discovered.
Holy mother of cow. That is a lot of effort on your part.  I appreciate the heck outta that.   I'll give it a test run later on today and update... it looks like you've answered questions I didnt know I was asking.  :) tnx again! ~t
1720197711
timmaugh
Pro
API Scripter
No problem! The above was untested, however, so post back if you run into any issues!
1720256477

Edited 1720257488
Hi TCM! Apparently, my GitHub page was missing a crucial line in the script. This has been amended. The script works fine for me but it does require a bit of setup. All the script really does is look for specific text in the title of a page where the player ribbon has been moved and then triggers an ability stored on a character if the text is found. Script-wise you only have to modify the "if" and "sendchat´" statements to correspond to items in your game, and add more lines as needed. Firstly, the line if (name.indexOf("[Marker1]") > -1) { looks for specific text in the title of the page you have just moved the player ribbon to. You can title each page where a certain playlist should be played accordingly, for example "Random road [Marker1]" for the scipt to trigger. Secondly, the line sendChat("API", "%{MacroDummy|Macro01}"); looks for a character named "MacroDummy", with a macro stored as an ability named "Macro01" and execute that macro. This macro is what will change and stop the music, and relies on another script (Roll20AM) to do this. Hopefully this helps put you on the right track! I'll post my personal version of the code below to see how it might look like when edited for a working game: on("change:campaign:playerpageid", function (){     var currentPage = getObj("page",Campaign().get("playerpageid"));     var name = currentPage.get("name")     if (name.indexOf("[Travel]") > -1){         sendChat("API","%{Dummy Music|Travel}");}     if (name.indexOf("[Rest]") > -1){         sendChat("API","%{Dummy Music|Rest}");}     if (name.indexOf("[Town]") > -1){         sendChat("API","%{Dummy Music|Town}");}     if (name.indexOf("[Dungeon]") > -1){         sendChat("API","%{Dummy Music|Dungeon}");}     if (name.indexOf("[Revelry]") > -1){         sendChat("API","%{Dummy Music|Revelry}");}     if (name.indexOf("[Avernus]") > -1){         sendChat("API","%{Dummy Music|Dungeon}")         sendChat("API","%{Dummy Ambiance|Avernus}");}     if (name.indexOf("[Cave]") > -1){         sendChat("API","%{Dummy Music|Dungeon}")         sendChat("API","%{Dummy Ambiance|Damp Cave}");}     if (name.indexOf("[Sailing]") > -1){         sendChat("API","%{Dummy Music|Travel}")         sendChat("API","%{Dummy Ambiance|Sailing}");}     if (name.indexOf("[Forest]") > -1){         sendChat("API","%{Dummy Music|Dungeon}")         sendChat("API","%{Dummy Ambiance|Forest}");}     if (name.indexOf("[Mt]") > -1){         sendChat("API","%{Dummy Music|Travel}")         sendChat("API","%{Dummy Ambiance|Strong Winds}");}     if (name.indexOf("[City]") > -1){         sendChat("API","%{Dummy Music|Town}")         sendChat("API","%{Dummy Ambiance|City Street}");}     if (name.indexOf("[Camp]") > -1){         sendChat("API","%{Dummy Music|Rest}")         sendChat("API","%{Dummy Ambiance|Campfire}");}     if (name.indexOf("[Quiet]") > -1){         sendChat("API","%{Dummy Ambiance|Stop Non-weather}");}     if (name.indexOf("[Shelter]") > -1){         sendChat("API","%{Dummy Ambiance|Stop Weather}");}     if (name.indexOf("[Only]") > -1){         sendChat("API","%{Dummy Ambiance|Stop}");} });
1720271703

Edited 1720271986
TCM
Pro
@ timmaugh... thanks again for this amazing script. it creates the character, but testing doesnt fire off any audio.  I've verified that the ability is correct and functional, the ability name matches the page name... ive placed character on various page levels and I've bounced the ribbon and rejoined as a player, just to be sure.  am i missing something? @ Axiomsyndrom, im giving it a go now... I really appreciate the update! ~t
1720321077
timmaugh
Pro
API Scripter
Hey, TCM... I see what I did. I left off a brace from the code. I have tested this and it works to fire off the ability command... though I don't use Roll20AM, so you'll have to test that part out. Also, I realized that pages can have spaces in their names where abilities really can't... so I built functionality into the code now so that you can use a hyphen in the ability name to represent spaces in the page name. So, in my game, I have a page called "The Bariggian". I have an ability on the MusicalRibbon character called "The-Bariggian" that will run when the ribbon goes to that page. Make sense? Here is the new code (and I'll update the above message, too, in case someone doesn't read this far in the thread, later.) /* */ var API_Meta = API_Meta || {}; API_Meta.MusicalRibbon = { offset: Number.MAX_SAFE_INTEGER, lineCount: -1 }; { try { throw new Error(''); } catch (e) { API_Meta.MusicalRibbon.offset = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - (4)); } } on('ready', () => {     // make sure the MusicalRibbon character exists     if (!findObjs({ type: 'character', name: 'MusicalRibbon' })[0]) {         createObj('character', { name: 'MusicalRibbon' });     }     on('change:campaign', (obj, prev) => {         // test if there was a change to the ribbon, or we no longer have a MusicalRibbon character         if (prev.playerpageid === obj.get('playerpageid') ||              !findObjs({ type: 'character', name: 'MusicalRibbon' })[0]) { return; }         let char = findObjs({ type: 'character', name: 'MusicalRibbon' })[0];         let page = getObj("page", obj.get("playerpageid"));         let searchname = page.get('name').replace(/\s/,'-');         let abil = findObjs({ type: 'ability', name: searchname, characterid: char.id })[0];         if (abil) {             sendChat("API", `%{MusicalRibbon|${searchname}}`);         }     }); }); { try { throw new Error(''); } catch (e) { API_Meta.MusicalRibbon.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.MusicalRibbon.offset); } } /* */
i love this community.  ask and ye shall receive. i appreciate both of you guys... i now have two ways to achieve what I was trying to do, and I've learned a little .js in the process. +1 to the both of yaz.  well played!
TCM said: i love this community.  ask and ye shall receive. i appreciate both of you guys... i now have two ways to achieve what I was trying to do, and I've learned a little .js in the process. +1 to the both of yaz.  well played! Thank you for calling attention to a dusty old script - leading to an alternate, more elegant solution from timmaugh that I will surely try out :) Couldn't agree more regarding the community!