Here's a little script I threw together for one of my players who's playing an Echo Knight. It lets him swap his Echo and character easily. !swap @{SomeCharacter|character_id} @{SomeOtherCharacter|character_id} It will find the first token representing the supplied character on the player's current page, and swap their positions. It only works (currently) for characters the player can control (or any characters for the GM). It will send a notice to the GM about the swapping, with a button to swap back, if the player isn't a GM. It also works with token ids, should you want to use those. I will probably expand this to ask for consent from players/gms if trying to swap with something you don't control (Looking at you, transposition abilities), and possibly add some optional status marker swapping (support for elevation changes). Code: on('ready',() => {
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 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 getTokenForPlayerAndID = (pid,id) => {
let t = getObj('graphic',id);
if(!t){
let c = getObj('character',id);
if(c){
t = findObjs({
type: 'graphic',
pageid: getPageForPlayer(pid),
represents: c.id
})[0];
}
}
if(t && (playerIsGM(pid) || playerCanControl(t,pid))){
return t;
}
return;
};
on('chat:message',msg=>{
if('api'===msg.type && /^!swap(\b\s|$)/i.test(msg.content)){
let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');
let args = msg.content.split(/\s+/).slice(1);
let t1 = getTokenForPlayerAndID(msg.playerid, args[0]);
let t2 = getTokenForPlayerAndID(msg.playerid, args[1]);
if(t1 && t2) {
let o1 = {
left: t2.get('left'),
top: t2.get('top')
};
let o2 = {
left: t1.get('left'),
top: t1.get('top')
};
t1.set(o1);
t2.set(o2);
if(!playerIsGM(msg.playerid)){
sendChat('TokenSwap', `/w gm <code>${who}</code> just swapped <b>${t1.get('name')}</b> and <b>${t2.get('name')}</b>. <a href="!swap ${t1.id} ${t2.id}">Swap Back</a>`);
}
} else {
sendChat('Swap', `/w "${who}" <div>Couldn't swap tokens.${!t1?` No token for ID: [${args[0]}]`:''}${!t1?` No token for ID: [${args[0]}]`:''}</div>`);
}
}
});
});