Ok, this version should do what you want. Let me know if there are any issues. I used a slightly updated base version from the version you were using. /*
This is an improved Summoning API that was built upon.
Original author: Brandon W.
Original here: <a href="https://app.roll20.net/forum/post/466778/scipt-auto-create-a-token#post-466778" rel="nofollow">https://app.roll20.net/forum/post/466778/scipt-auto-create-a-token#post-466778</a>
Special thanks to Brian for helping me with some of the script.
I hope this serves others well, do not hesistate to contact me regarding bugs or improvements.
-GM King
Usage:
1. Select token you want the monster to appear next to.
2. In chat, type "!summon (name of monster) (number to summon)" without brackets or quotation marks.
3. By default the monster will take up a 5x5 ft square (medium or small creature). To summon larger creatures add a letter to the end of the message:
F= Fine, D = Diminutive, T= Tiny, L = large, H = Huge, G = Gargantuan, C = Colossal, cust = customised size (this tag must be followed by two numbers, see examples).
4. You can also summon a torch that will give 40 ft of vision with 20 ft of dim light (torch must be in the name).
5. !summon-r will cause a summonned token with muliple sides to have a random side selected.
NB. The name of the monster must be in your character sheet list and the image must be the token image you wish to use (marketplace images do not work).
Examples:
!summon torch
!summon eagle
!summon eagle 2
!summon eagle 1d3
!summon eagle 1d3+1
!summon cave troll 1d3+1 H
!summon flame sphere cust 140 140
!summon-r onomatopoeia H
NB. When making a custom size every 70 = 1 square.
*/
/* global TokenNameNumber */
on('ready',function(){
const getCleanImgsrc = (imgsrc) => {
let parts = (imgsrc||'').match(/(.*\/images\/.*)(thumb|med|original|max)([^?]*)(\?[^?]+)?$/);
if(parts) {
let leader = parts[1].replace(/^https:\/\/s3.amazonaws.com\/files.d20.io\//,'<a href="https://files.d20.io/" rel="nofollow">https://files.d20.io/</a>');
return `${leader}thumb${parts[3]}${parts[4] ? parts[4] : `?${Math.round(Math.random()*9999999)}`}`;
}
};
const processCreated = (( 'undefined' !== typeof TokenNameNumber && TokenNameNumber.NotifyOfCreatedToken )
? TokenNameNumber.NotifyOfCreatedToken
: _.noop )
;
const summonToken = (character,pageid,baseX,baseY,baseW,baseH,randomSide) => {
baseW=baseW||70;
baseH=baseH||70;
character.get('defaulttoken',function(defaulttoken){
const dt = JSON.parse(defaulttoken);
if(randomSide && dt.sides){
let sides = dt.sides.split(/\|/).map(decodeURIComponent);
let side = randomInteger(sides.length)-1;
let imgsrc = getCleanImgsrc(sides[side]);
if(imgsrc) {
dt.imgsrc = imgsrc;
dt.currentSide = side;
} else {
sendChat('',`/w gm Cannot switch to side ${side+1} for <b>${character.get('name')}</b>: <code>Marketplace image</code>`);
}
}
if(dt && getCleanImgsrc(dt.imgsrc)){
dt.imgsrc=getCleanImgsrc(dt.imgsrc);
dt.left=baseX;
dt.top=(baseY-(baseH/2)-(dt.height/2));
if(dt.top-(dt.height/2)<0){
dt.top=(baseY+(baseH/2)+(dt.height/2));
}
dt.pageid = pageid;
dt.layer="objects";
processCreated(createObj('graphic',dt));
} else {
let isMarkplace = false;
if(dt) {
isMarkplace = /\/marketplace\//i.test(dt.imgsrc);
}
sendChat('',`/w gm Cannot create token for <b>${character.get('name')}</b> ${isMarkplace ? "<code>Marketplace image</code>" : ''}`);
}
});
};
const processInlinerolls = (msg) => {
if(msg.hasOwnProperty('inlinerolls')){
return msg.inlinerolls
.reduce((m,v,k) => {
let ti=v.results.rolls.reduce((m2,v2) => {
if(v2.hasOwnProperty('table')){
m2.push(v2.results.reduce((m3,v3) => [...m3,(v3.tableItem||{}).name],[]).join(", "));
}
return m2;
},[]).join(', ');
return [...m,{k:`$[[${k}]]`, v:(ti.length && ti) || v.results.total || 0}];
},[])
.reduce((m,o) => m.replace(o.k,o.v), msg.content);
} else {
return msg.content;
}
};
on("chat:message", (msg) => {
if (msg.type == "api" && /^!summon(-r?)?(\b|$)/i.test(msg.content)) {
let size1 = 0;
let size2 = size1;
let s = 0;
let inputName = "";
let howMuch = 0;
let amount = 0; // Amount of summoned monsters.
let mod = 0; // Modifier to amount.
let skip = false;
let action = "Summons ";
let section = processInlinerolls(msg).split(/\s+/); // Breaks API call into sections.
let [,randomSide] = msg.content.match(/^!summon(?:-(r)?)?/i);
if (section.length > 1)
{
for (let j=1;j<section.length;j++)
{
if (typeof parseInt(section[j]) === 'number' && Math.round(parseInt(section[j])) % 1 == 0 || section[j].length <= 4 && section[j].toLowerCase().search("d") != -1 && /\dd/.test(section[j]) || section[j].toLowerCase() == 'cust')
{
if (section[j].toLowerCase() == 'cust')
{
howMuch = j;
break;
}
else
{
if (j != section.length-1)
{
for (let m = j+1; m < section.length; m++)
{
if (typeof parseInt(section[m]) === 'number' && Math.round(parseInt(section[m])) % 1 == 0 || section[m].length <= 4 && section[m].toLowerCase().search("d") != -1 && /\dd/.test(section[m]))
{
skip = true;
}
else
{
skip = false;
}
}
}
else
{
skip = false;
}
if (skip == false)
{
howMuch = j;
break;
}
}
}
}
if (howMuch > 0)
{
for (let k=1;k<howMuch;k++)
{
if (k == howMuch-1)
{
inputName = inputName + section[k];
}
else
{
inputName = inputName + section[k] + " ";
}
}
if (section[howMuch].toLowerCase().search("d") != -1) // If user chose to roll.
{
if (section[howMuch].indexOf("+") != -1)
{
mod = section[howMuch].split("+"); // If user specified a modfier.
section[howMuch] = mod[0];
mod = parseInt(mod[1],10);
}
let count = 0;
let diceRoll = section[howMuch].split("d");
diceRoll[0] = diceRoll[0].replace(/\D/g,'');
diceRoll[1] = diceRoll[1].replace(/\D/g,'');
let lowEnd = parseInt(diceRoll[0]);
let highEnd = parseInt(diceRoll[1]);
while (count < lowEnd)
{
amount = amount + randomInteger(highEnd); // Rolls dice.
count++;
log("Summoner: rolls a " + amount);
}
}
else if (section[howMuch].toLowerCase() != 'cust')
{
amount = parseInt(section[howMuch],10); // Creates variable for number of summoned monsters.
}
else
{
amount == 1;
}
amount = amount + mod;
}
else if (howMuch == 0)
{
if (section[section.length-1].length == 1)
{
s = 1;
howMuch = section.length - 2;
}
amount = 1;
for (let k=1;k<section.length-s;k++)
{
if (k == section.length-1-s)
{
inputName = inputName + section[k];
}
else
{
inputName = inputName + section[k] + " ";
}
}
}
if (section.length > (howMuch+1) && section[howMuch].toLowerCase() != "cust")
{
if (section[howMuch+1].toLowerCase() == "f")
{
size1 = -63;
size2 = size1;
}
else if (section[howMuch+1].toLowerCase() == "d")
{
size1 = -56;
size2 = size1;
}
else if (section[howMuch+1].toLowerCase() == "t")
{
size1 = -35;
size2 = size1;
}
else if (section[howMuch+1].toLowerCase() == "l")
{
size1 = 70;
size2 = size1;
}
else if (section[howMuch+1].toLowerCase() == "h")
{
size1 = 140;
size2 = size1;
}
else if (section[howMuch+1].toLowerCase() == "g")
{
size1 = 210;
size2 = size1;
}
else if (section[howMuch+1].toLowerCase() == "c")
{
size1 = 280;
size2 = size1;
}
else if (section[howMuch+1].toLowerCase() == "cust")
{
if (typeof section[howMuch+2] != 'undefined')
{
size1 = parseInt(section[howMuch+2]);
}
else
{
sendChat(msg.who, "/w gm Please ensure the \"cust\" tag is followed by two values (every 70 = 1 square)."); // If monster is not in character list.
}
if (typeof section[howMuch+3] != 'undefined')
{
size2 = parseInt(section[howMuch+3]);
}
else
{
sendChat(msg.who, "/w gm Please ensure the \"cust\" tag is followed by two values (every 70 = 1 square)."); // If monster is not in character list.
}
}
}
else if (section[howMuch].toLowerCase() == "cust")
{
if (typeof section[howMuch+1] != 'undefined')
{
size1 = parseInt(section[howMuch+1]);
}
else
{
sendChat(msg.who, "/w gm Please ensure the \"cust\" tag is followed by two values (every 70 = 1 square)."); // If monster is not in character list.
}
if (typeof section[howMuch+2] != 'undefined')
{
size2 = parseInt(section[howMuch+2]);
}
else
{
sendChat(msg.who, "/w gm Please ensure the \"cust\" tag is followed by two values (every 70 = 1 square)."); // If monster is not in character list.
}
}
}
else
{
sendChat(msg.who, "/w gm No monsters specified to summon"); // If user writes too many things.
}
let check = findObjs({ _type: "character", name: inputName },{caseInsensitive: true})[0];
if (typeof check == 'undefined')
{
sendChat(msg.who, "/w gm Monster of name \"" + inputName + "\" does not exist."); // If monster is not in character list.
}
else
{
let list = findObjs({ _type: "character", name: inputName},{caseInsensitive: true});
let characterId = list[0].id; // Extract character ID from character sheet
let characterName = list[0].get('name'); // Extract character name from character sheet
let character;
let selected = msg.selected||[];
_.each(selected, function(obj) {
let tok = getObj("graphic", obj._id); // Create variable for selected token (for summoned monster positioning)
let HP = findObjs({ _type: "attribute", name: "hitpoints", _characterid: characterId },{caseInsensitive: true})[0]; //finds the health from charactersheet
if (typeof HP == 'undefined')
{
HP = "-1";
}
else
{
HP = (HP) ? HP.get("current") : 0; // Changes HP to useable string
}
let SP = findObjs({ _type: "attribute", name: "speed", _characterid: characterId },{caseInsensitive: true})[0]; //finds the speed from charactersheet
if (typeof SP == 'undefined')
{
SP = "-1";
}
else
{
SP = (SP) ? SP.get("current") : 0; // Changes SP to useable string
}
let AC = findObjs({ _type: "attribute", name: "armorclass", _characterid: characterId },{caseInsensitive: true})[0]; // finds the ac from charactersheet
if (typeof AC == 'undefined')
{
AC = "-1";
}
else
{
AC = (AC) ? AC.get("current") : 0; // Changes AC to useable string
}
if (amount == 0)
{
amount = 1;
}
else if (amount > 20)
{
let tooMany = amount;
amount = 20;
log("Summoner: User specified " + tooMany + " monsters. Scaling to 20."); // If user spams the field.
}
for (let i=0;i<amount;i++)
{
if (HP == "-1" && AC == "-1" && msg.content.toLowerCase().search("torch") != -1)
{
let characterImage = getCleanImgsrc(list[0].get('avatar')); // Extract character image URL from character sheet
if (!characterImage)
{
sendChat(msg.who, "/w gm Monster of name \"" + inputName + "\" has a marketplace image or is missing. <code>"+list[0].get('avatar')+"</code>"); // Error message
} else {
action = "Lights";
createObj("graphic",
{
represents: characterId, // Links new token to charactersheet
left: tok.get("left")+70,
top: tok.get("top"),
width: 70+size1,
height: 70+size2,
name: characterName,
pageid: tok.get("pageid"),
imgsrc: characterImage,
layer: "objects",
light_radius: 40,
light_dimradius: 20,
light_otherplayers: true
});
}
}
else if (HP == "-1" && AC == "-1")
{
character = getObj('character',characterId);
if(character){
summonToken(
character,
tok.get('pageid'),
tok.get('left'),
tok.get('top'),
tok.get('width'),
tok.get('height'),
!!randomSide
);
} else {
sendChat('','/w gm No character for id: <b>'+characterId+'</b>');
}
}
else
{
character = getObj('character',characterId);
if(character){
summonToken(
character,
tok.get('pageid'),
tok.get('left'),
tok.get('top'),
tok.get('width'),
tok.get('height'),
!!randomSide
);
} else {
sendChat('','/w gm No character for id: <b>'+characterId+'</b>');
}
}
}
});
if (amount > 1)
{
inputName = inputName + "s"; // If more than 1 summon, adds plural.
}
else if (amount == 1 && action == "Lights")
{
amount = " a";
inputName = "torch";
}
if (typeof amount === 'number')
{
amount = amount.toString();
}
// sendChat(msg.who, GM + action + amount + " " + inputName + "!"); // Announces how many and what Summoner has summoned.
}
}
});
});