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

Determining Player ID of API ADD Events

November 13 (4 years ago)

Edited November 13 (4 years ago)

When an add:graphic event occurs, is there a way to determine the player ID of who triggered the event?

I've been looking and researching this for a few days now, but as far as I can tell, there is not... Is there a way to determine this? As in, I don't want my players able to place their character tokens anywhere on a map that is currently active for said players...

Thanks!

November 13 (4 years ago)

Edited November 13 (4 years ago)

AFAIK you can't but you can use the TokenLock script as inspiration to achieve this. 

November 13 (4 years ago)
Pat
Pro
API Scripter

Well, on adding a graphic, you can check for all other tokens on the tabletop that have _pageid=Campaign().playerpageid and check for any that point to existing character sheets that are player-character controlled. If it's a duplicate of an existing character-sheet-connected-token that is player-controlled, destroy it. 

So first, pull all token-layer graphics in a list that share the current playerpageid, run through them and check their character sheets and players and image URLs against the new one. If there is a match, it's a duplicate, and you can destroy it. If you as the GM are doing the same, your character sheets have no controlling player, so they're safe. 

November 13 (4 years ago)

Edited November 13 (4 years ago)
The Aaron
Roll20 Production Team
API Scripter

I'll echo what Pat and Martijn said.  There is not a definitive way to know who creates a graphic, it isn't part of the event.  The best you can do is narrow it down to who could have created it.

Here's a script that will remove Potentially Player Created Tokens.  It will remove the token if the following are true:

  1. The created token can be controlled by a player.
  2. A player that could create the token is on the page where the token was created (either by ribbon or by party split)
  3. Any player that could have created the token is logged into the game.

The token is then removed.  A message will be issued to the chat to the GM to let them know it happened, in case you intended to create the token.

Big Caveat: Many scripts assume that tokens that were added to the page will still be there for them to process.  If they perform any sort of asynchronous operations and then try to adjust the token, they will crash.  TokenNameNumber, for example will definitely be upset when it goes to change the number on a token (which must be done asynchronously for various reasons) and find the token doesn't exist anymore.  I'd like to say I'll fix that, but be aware that it may happen and the message may be strange.  YMMV.

The code:

on('ready',()=>{
  const playerCanControl = (obj, playerid='any') => {
    const playerInControlledByList = (list, playerid) => list.includes('all') || list.includes(playerid) || ('any'===playerid && list.length);
    let players = obj.get('controlledby')
      .split(/,/)
      .filter(s=>s.length);
    if(playerInControlledByList(players,playerid)){
      return true;
    }

    if('' !== obj.get('represents') ) {
      players = (getObj('character',obj.get('represents')) || {get: function(){return '';} } )
      .get('controlledby').split(/,/)
      .filter(s=>s.length);
      return  playerInControlledByList(players,playerid);
    }
    return false;
  };

  const getPlayersOnPage = (pageid) => {
    let pages = {};
    let ribbonPage = Campaign().get('playerpageid');
    let psp = Campaign().get('playerspecificpages');
    findObjs({type:'player'})
    .forEach(p=>{
      if(playerIsGM(p.id)){
        const lp = p.get('lastpage') || Campaign().get('playerpageid');
        pages[lp]=pages[lp]||[];
        pages[lp].push(p.id);
      } else if(psp.hasOwnProperty(p.id)){
        pages[psp[p.id]]=pages[psp[p.id]]||[];
        pages[psp[p.id]].push(p.id);
      } else {
        pages[ribbonPage]=pages[ribbonPage]||[];
        pages[ribbonPage].push(p.id);
      }
    });
    if(pageid){
      return pages[pageid]||[];
    }
    return pages;
  };

  const playerIsOnline = (playerid) => (getObj('player',playerid)||{get:()=>{}}).get('online');

  const RemoveIfPlayerCreated = (token) => {
    let players = getPlayersOnPage(token.get('pageid')).filter(playerIsOnline);

    if(players.length){
      let res = players.some(p=>playerCanControl(token,p));
      if(res){
        sendChat('',`/w gm Removed Player Token for: <code>${token.get('name')}</code>`);
        token.remove();
      }
    }
    
  };

  let tokenIds = [];

  on('add:graphic',(obj)=>{
    tokenIds.push(obj.id);
    let id = obj.id;
    let c = 10;
    const checkToken = ()=>{
      let token = getObj('graphic',id);
      if(token) {
        RemoveIfPlayerCreated(token);
        tokenIds=tokenIds.filter(e=>e!==id);
      } else if(--c>0){
          setTimeout(checkToken,100);
      } else {
        tokenIds=tokenIds.filter(e=>e!==id);
      }
    };
    setTimeout(checkToken,100);
  });
});

November 13 (4 years ago)

Is the assumption to make this work (requirement to make this work) that the GM must have placed all player tokens on the map before moving the ribbon?

November 13 (4 years ago)
The Aaron
Roll20 Production Team
API Scripter

Or that the players are not logged in at the moment.  Not that the token must be controllable by a player, so even if the players are on the page, dragging npcs and monsters onto the board should be unaffected.

November 13 (4 years ago)

Edited November 13 (4 years ago)
Oosh
Sheet Author
API Scripter

What's the end goal for this, out of interest? Is it just to stop players cheating by dragging their tokens to unexplored areas?

I'm just wondering if setting the players' default tokens to sightless might be a simple solution.

Then giving yourself a button for a tokenmod macro to give the selected token "has sight", for occasions when you need to give them a new token?


Very low-tech, but you can also use plain old fog of war to obscure parts of the map despite DL settings.

November 13 (4 years ago)
Pat
Pro
API Scripter

On this vein I used to have an "Oubliette" page that I would throw players to who misbehaved (jokingly) but (not jokingly) dragging a player out of the general player ribbon who is being a problem is a way to get their attention and put them in the dark at the same time. Might be able to automate that too. 

November 15 (4 years ago)

Edited November 15 (4 years ago)

Thanks all for the responses, suggestions, and ideas! Good stuff, some of which I have thought about (like the sightless tokens), but was indeed looking for a cleaner way to keep players from intentional or unintentional spawning of the tokens under their control (who are not a GM) to areas of the map I do not want them to be.

The first brilliant (sarcasm) idea I tried was to not have the player characters have a default token, then having a GM controlled default token for each PC (that the players couldn't see in the journal) as a separate character which I would place on the map and then assign it to the appropriate player character. I had never used a character without a default token before, and assumed that they wouldn't be able to be placed if they didn't have an assigned token, but alas, I'm sure you all know that's not how it works. Though I'm curious if I could use the absence of a default token (character _defaulttoken) with the presence of a non-zero length string for that character's controlledby property as a means to achieve my ends...