This is a bit of a weird one, and is hard to understand if you don't have some debugging scripts to track what's happening. Here's the minimal changes to get your script working: on('chat:message', function(message) {
// Listen for a command like "!togglegrid"
if(message.type == "api" && message.content.indexOf("!togglegrid") !== -1) {
let currentPage = Campaign().get("playerpageid"); // Get the current page ID
let page = getObj("page", currentPage); // Get the page object
if(page) {
let isGridEnabled = page.get("showgrid"); // Get current grid status
let snapping_increment = isGridEnabled ? 0 : 1;
// Toggle the grid status
page.set({
showgrid: !isGridEnabled ,
snapping_increment
});
}
}
});
I've bolded the important parts. It's not obvious, but changing the show grid toggle in the UI actually changes 2 things: showgrid is toggled between true and false snapping_increment is toggled between 0 and 1 In reality, it's toggled from whatever it is (say 2 if you've set your cell width to 140px squares) to 0 then to 1. The above will set the increment to 0 and 1 based on what the grid is changing to, just like the UI. There are a few issues with this script: It works on the page the player ribbon is on, not the page the GM is on. It works for players and the GM (possibly not intended) It loses the snapping increment for pages. Here's how I'd write it to address all three of those: on('ready',()=>{
const scriptName = 'ShowGrid';
const version = '0.1.0';
const schemaVersion = 0.1;
const lastUpdate = 1693243590;
const checkInstall = () => {
log(`-=> ${scriptName} v${version} <=- [${lastUpdate}]`);
if (
!state.hasOwnProperty(scriptName) ||
state[scriptName].version !== schemaVersion
) {
log(` > Updating Schema to v${schemaVersion} <`);
switch (state[scriptName] && state[scriptName].version) {
case 0.1:
/* break; // intentional dropthrough */ /* falls through */
case "UpdateSchemaVersion":
state[scriptName].version = schemaVersion;
break;
default:
state[scriptName] = {
version: schemaVersion,
pageSnappingIncrementMap: {}
};
break;
}
}
};
const getPageForPlayer = (playerid) => {
let player = getObj('player',playerid);
if(playerIsGM(playerid)){
return player.get('lastpage') || Campaign().get('playerpageid');
}
let psp = Campaign().get('playerspecificpages');
if(psp[playerid]){
return psp[playerid];
}
return Campaign().get('playerpageid');
};
const toggleGrid = (pageid) => {
let p = getObj('page',pageid);
if(p){
let showgrid = p.get('showgrid');
if(showgrid) {
state[scriptName].pageSnappingIncrementMap[pageid] = p.get('snapping_increment');
p.set({
showgrid: false,
snapping_increment: 0
});
return `Disabled grid for <code>${p.get('name')||'[Unnamed map]'}</code>.`;
} else {
p.set({
showgrid: true,
snapping_increment: (state[scriptName].pageSnappingIncrementMap[pageid]||1)
});
delete state[scriptName].pageSnappingIncrementMap[pageid];
return `Enabled grid for <code>${p.get('name')||'[Unnamed map]'}</code>.`;
}
}
};
on('chat:message',msg=>{
if('api'===msg.type && /^!togglegrid(\b\s|$)/i.test(msg.content) && playerIsGM(msg.playerid)){
let pageid = getPageForPlayer(msg.playerid);
let note = toggleGrid(pageid);
const who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');
sendChat('',`/w "${who}" <div>${note}</div>`);
}
});
checkInstall();
});
For making sure it works for the GM's current page, you need to check a couple things. I've got a function that handles them, getPageForPlayer(), and it works for players and GMs. Players can be split to their own pages based on "playerspecificpages", or they are on the player ribbon page stored in "playerpageid". GMs add the additional possibility of being on a page stored on the GM's player object in the property "lastpage". The function handles all of that and just returns the page they are most likely to be on (because there are a few undetectable edge cases that are unlikely to happen but can't be accounted for.). For making sure only the GM can toggle the grid, you can check if the player calling the command is a GM with the playerIsGM() function built into Roll20. For the snapping_increment, you have to use a persistent storage to backup the value, which is generally the "state" object. The "state" object is a persisted object accessible to all scripts. Be sure you aren't stomping on it and breaking other scripts. Generally, you create a key on it for your script and do what you like in there. I added a state property for ShowGrid and maintain a mapping from pageid to snapping_increment and then use it to restore the value when toggling the grid on. More info on state: <a href="https://wiki.roll20.net/API:Objects#state" rel="nofollow">https://wiki.roll20.net/API:Objects#state</a> Some other things you might consider in this version if you plan to write more API Scripts: It's a good idea to wrap your script in the on('ready',...) event so that it doesn't initialize until the full sandbox is loaded. There are some cases you might not want to do that, but you won't need them until you've written enough scripts to know what they are. When looking for a command, you should make sure the message starts with the command, rather than just contains it. If someone is using your script with some other script, you might get weird behavior matching the command in the middle. It's always nice to see people diving into the API, so definitely ask questions if you have them! Here are some links you might find useful: <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>