So, after digging back through my 270 games, I found the one I made for my daughter and pulled the script out. (see below) The copy I have locally is in the middle of a rewrite, and isn't releasable yet, but this copy should work (though it doesn't support card backs yet...).
Here's how it works.
Drop all your cards and card backs on a map page named
The <Something> part will be the deck name. In the bubble for Bar3, place either the letter B (which indicates this is the card back), or a number (representing how many are in the deck). When ready, run the command:
!db
And it will either construct the deck named <Something> or update it with the cards.
Caveat: This is a simple script and currently, it rebuilds the whole deck by destroying the cards and recreating the proper counts. This includes removing the cards from the player hands.
The Code:
// By: The Aaron, Arcane Scriptomancer
// Contact: https://app.roll20.net/users/104025/the-aaron
const DeckBuilder = (() => { // eslint-disable-line no-unused-vars
const scriptName = 'DeckBuilder';
const version = '0.1.0';
const lastUpdate = 1606881820;
const schemaVersion = 0.1;
const checkInstall = () => {
log(`-=> ${scriptName} v${version} <=- [${new Date(lastUpdate*1000)}]`);
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
};
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 times = (n,f)=>Array(n).fill(n).map(f);
const handleInput = (msg) => {
if (msg.type !== "api") {
return;
}
let args = msg.content.split(/\s+/);
switch(args[0]) {
case '!db': {
let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');
let pageid = getPageForPlayer(msg.playerid);
let page = getObj('page',pageid);
if(page){
if(/^deck:\s.+$/i.test(page.get('name'))) {
let deckName = page.get('name').replace(/^deck:\s*/i,'');
let deck = findObjs({
type: 'deck',
name: deckName
})[0] || createObj('deck',{
name: deckName
});
// destroy cards in hands
findObjs({type:'hand'}).forEach(h=>h.set('currentHand',""));
findObjs({
type: 'card',
deckid: deck.id
}).forEach(c=>c.remove());
findObjs({
type: 'graphic',
layer: 'objects',
pageid
}).forEach(g=>{
let b3 = g.get('bar3_value');
if(/^\s*b\s*/i.test(b3)){
deck.set({
avatar: g.get('imgsrc'),
defaultheight: g.get('height'),
defaultwidth: g.get('width')
});
} else {
let c = parseInt(b3)||0;
let n = g.get('name');
let i = g.get('imgsrc');
times(c,()=>createObj('card',{
name: n,
deckid: deck.id,
avatar: i
}));
}
});
} else {
sendChat('DeckBuilder', `/w "${who}" Switch to a Deck page before running DeckBuilder.`);
}
}
}
break;
}
};
const registerEventHandlers = () => {
on('chat:message', handleInput);
};
on('ready', () => {
checkInstall();
registerEventHandlers();
});
return {
// Public interface here
};
})();