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.