Try this updated code here . I use a combination of AddCustomTurn with AddInvisibleCustomTurn and a custom 'AddSpell' (just a modified version of ACT that has a specific image for the Turn Tracker item) with this macro: !?{Spell, Visible, Hidden?|Spell,actspell|Visible,act|Hidden,aict} ?{Countdown Number|-1} ?{Number of Turns|10} --?{Caster|@{selected|token_name}}: "?{Effect Name|BlessSleepEtc}" ?{Concentration?||Yes,«🇨» |No, |} /w GM [?{Caster}: "?{Effect Name}" ?{Concentration?} added to turn tracker for ?{Number of Turns} turns](#" style="background: none; background-color: transparent; border: none; padding: 0px 7px; text-decoration: none; display: inline-block; outline: 2px dashed purple;) And this code (it's a little hacky and could be consolidated a lot): /* AddTurnTrackerEffects Start */
/*
// Replaces SpellTurnTrackerEffects
// Combines AddCustomTurn and AddInvisibleCustomTurn
// AddCustomTurn: <a href="https://app.roll20.net/forum/permalink/10212772/" rel="nofollow">https://app.roll20.net/forum/permalink/10212772/</a>
// AddInvisibleCustomTurn: <a href="https://app.roll20.net/forum/permalink/6606389/" rel="nofollow">https://app.roll20.net/forum/permalink/6606389/</a>
// Modified version of AddCustomTurn from deukalion <a href="https://app.roll20.net/forum/post/10847052/addcustomturn-for-shield-spell-get-tracker-position-slash-index/?pageforid=10848698#post-10848698" rel="nofollow">https://app.roll20.net/forum/post/10847052/addcustomturn-for-shield-spell-get-tracker-position-slash-index/?pageforid=10848698#post-10848698</a>
// <a href="https://app.roll20.net/forum/permalink/10212772/" rel="nofollow">https://app.roll20.net/forum/permalink/10212772/</a>
*/
// Github: <a href="https://github.com/shdwjk/Roll20API/blob/master/AddCustomTurn/AddCustomTurn.js" rel="nofollow">https://github.com/shdwjk/Roll20API/blob/master/AddCustomTurn/AddCustomTurn.js</a>
// By: The Aaron, Arcane Scriptomancer
// Contact: <a href="https://app.roll20.net/users/104025/the-aaron" rel="nofollow">https://app.roll20.net/users/104025/the-aaron</a>
var API_Meta = API_Meta||{}; //eslint-disable-line no-var
API_Meta.AddCustomTurn={offset:Number.MAX_SAFE_INTEGER,lineCount:-1};
{try{throw new Error('');}catch(e){API_Meta.AddCustomTurn.offset=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-6);}}
const AddCustomTurn = (() => { // eslint-disable-line no-unused-vars
const scriptName = "AddCustomTurn";
const version = '0.1.1';
API_Meta.AddCustomTurn.version = version;
const lastUpdate = 1625442265; // eslint-disable-line no-unused-vars
const schemaVersion = 0.1;
const DEFAULT_NAME = '[Custom Turn]';
const checkInstall = () => {
//CHANGE 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.0:
/* break; // intentional dropthrough */ /* falls through */
case 'UpdateSchemaVersion':
state[scriptName].version = schemaVersion;
break;
default:
state[scriptName] = {
version: schemaVersion
};
break;
}
}
};
/* eslint-disable no-unused-vars */
const getTurnArray = () => ( '' === Campaign().get('turnorder') ? [] : JSON.parse(Campaign().get('turnorder')));
const getTurnArrayFromPrev = (prev) => ( '' === prev.turnorder ? [] : JSON.parse(prev.turnorder));
const setTurnArray = (ta) => Campaign().set({turnorder: JSON.stringify(ta)});
const addTokenTurn = (id, pr) => setTurnArray([...getTurnArray(), {id,pr}]);
const addCustomTurn = (custom, pr) => setTurnArray([...getTurnArray(), {id:"-1",custom,pr}]);
const removeTokenTurn = (tid) => setTurnArray(getTurnArray().filter( (to) => to.id !== tid));
const removeCustomTurn = (custom) => setTurnArray(getTurnArray().filter( (to) => to.custom !== custom));
const clearTurnOrder = () => Campaign().set({turnorder:'[]'});
const sorter_asc = (a, b) => a.pr - b.pr;
const sorter_desc = (a, b) => b.pr - a.pr;
const sortTurnOrder = (sortBy = sorter_desc) => Campaign().set({turnorder: JSON.stringify(getTurnArray().sort(sortBy))});
/* eslint-enable no-unused-vars */
const checkFormulaOnTurn = (prevTo) => {
let to=getTurnArray();
if(to.length && to[0].id==='-1' && prevTo[0].custom !== to.custom){
sendChat('',`[[${to[0].pr}+(${to[0].formula||0})]]`,(r)=>{
to[0].pr=r[0].inlinerolls[0].results.total;
setTurnArray(to);
handleTurnorderChange(to,prevTo);
});
}
};
const opToText = (op) => {
switch(op){
case 'LT': return 'less than';
case 'LE': return 'less than or equal to';
case 'EQ': return 'equal to';
case 'GE': return 'greater than or equal to';
case 'GT': return 'greater than';
default: return '';
}
};
const describeAutoDelete = (entry) =>{
if(entry.autoDelete){
let dc =(entry.deleteCondition||{});
return `Delete when ${opToText(dc.op)} ${dc.val||0}.`;
}
return '';
};
const outputEvent = (event, entry, who) =>{
switch(event){
case 'expire': {
if(!playerIsGM(entry.player)){
sendChat('ACT',`/w gm <div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"><div style="background-color: #ffeeee;"><b>${entry.custom}</b> expired and was removed.</div></div>`);
}
sendChat('ACT',`/w "${who||entry.who}" <div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"><div style="background-color: #ffeeee;"><b>${entry.custom}</b> expired and was removed.</div></div>`);
}
break;
case 'remove': {
if(!playerIsGM(entry.player)){
sendChat('ACT',`/w gm <div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"><div style="background-color: #ffeeee;"><b>${entry.custom}</b> was removed.</div></div>`);
}
sendChat('ACT',`/w "${who||entry.who}" <div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"><div style="background-color: #ffeeee;"><b>${entry.custom}</b> was removed.</div></div>`);
}
break;
case 'add': {
sendChat('ACT',`/w gm <div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"><div style="background-color: #ffeeee;"><b>${who||entry.who}</b> added custom turn: <b>${entry.custom}</b> at <b>${entry.pr}</b> (<b>${entry.formula}</b>). ${describeAutoDelete(entry)}</div></div>`);
}
break;
}
};
const isDeleteCondition = (entry) => {
if(entry.autoDelete){
let dc =(entry.deleteCondition||{});
switch(dc.op){
case 'LT': return (parseInt(entry.pr)<dc.val);
case 'LE': return (parseInt(entry.pr)<=dc.val);
case 'EQ': return (parseInt(entry.pr)==dc.val);
case 'GE': return (parseInt(entry.pr)>=dc.val);
case 'GT': return (parseInt(entry.pr)>dc.val);
}
}
};
const handleTurnorderChange = (to,p)=>{
if(!Array.isArray(p)) {
to=getTurnArray();
p = getTurnArrayFromPrev(p);
}
if(to.length && to[0].id==='-1' && to[0].custom !== p[0].custom && isDeleteCondition(to[0])){
setTurnArray(to.slice(1));
outputEvent('delete',to[0]);
}
};
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;
}
};
const ch = (c) => {
const entities = {
'<' : 'lt',
'>' : 'gt',
'&' : 'amp',
"'" : '#39',
'@' : '#64',
'{' : '#123',
'|' : '#124',
'}' : '#125',
'[' : '#91',
']' : '#93',
'"' : 'quot',
'*' : 'ast',
'/' : 'sol',
' ' : 'nbsp'
};
if( entities.hasOwnProperty(c) ){
return `&${entities[c]};`;
}
return '';
};
const _h = {
outer: (...o) => `<div style="border: 1px solid black; background-color: white; padding: 3px 3px;">${o.join(' ')}</div>`,
title: (t,v) => `<div style="font-weight: bold; border-bottom: 1px solid black;font-size: 130%;">${t} v${v}</div>`,
subhead: (...o) => `<b>${o.join(' ')}</b>`,
minorhead: (...o) => `<u>${o.join(' ')}</u>`,
optional: (...o) => `${ch('[')}${o.join(` ${ch('|')} `)}${ch(']')}`,
required: (...o) => `${ch('<')}${o.join(` ${ch('|')} `)}${ch('>')}`,
header: (...o) => `<div style="padding-left:10px;margin-bottom:3px;">${o.join(' ')}</div>`,
section: (s,...o) => `${_h.subhead(s)}${_h.inset(...o)}`,
paragraph: (...o) => `<p>${o.join(' ')}</p>`,
items: (o) => `<li>${o.join('</li><li>')}</li>`,
ol: (...o) => `<ol>${_h.items(o)}</ol>`,
ul: (...o) => `<ul>${_h.items(o)}</ul>`,
grid: (...o) => `<div style="padding: 12px 0;">${o.join('')}<div style="clear:both;"></div></div>`,
cell: (o) => `<div style="width: 130px; padding: 0 3px; float: left;">${o}</div>`,
inset: (...o) => `<div style="padding-left: 10px;padding-right:20px">${o.join(' ')}</div>`,
join: (...o) => o.join(' '),
pre: (...o) =>`<div style="border:1px solid #e1e1e8;border-radius:4px;padding:8.5px;margin-bottom:9px;font-size:12px;white-space:normal;word-break:normal;word-wrap:normal;background-color:#f7f7f9;font-family:monospace;overflow:auto;">${o.join(' ')}</div>`,
preformatted: (...o) =>_h.pre(o.join('<br>').replace(/\s/g,ch(' '))),
code: (...o) => `<code>${o.join(' ')}</code>`,
attr: {
bare: (o)=>`${ch('@')}${ch('{')}${o}${ch('}')}`,
selected: (o)=>`${ch('@')}${ch('{')}selected${ch('|')}${o}${ch('}')}`,
target: (o)=>`${ch('@')}${ch('{')}target${ch('|')}${o}${ch('}')}`,
char: (o,c)=>`${ch('@')}${ch('{')}${c||'CHARACTER NAME'}${ch('|')}${o}${ch('}')}`
},
bold: (...o) => `<b>${o.join(' ')}</b>`,
italic: (...o) => `<i>${o.join(' ')}</i>`,
font: {
command: (...o)=>`<b><span style="font-family:serif;">${o.join(' ')}</span></b>`
}
};
const showHelp = (who) => {
sendChat('',`/w "${who}" ${
_h.outer(
_h.title(scriptName,version),
_h.header(
_h.paragraph(`${scriptName} provides an easy way to add (and remove) custom turns which increment or decrement, and have auto delete features.`)
),
_h.subhead('Commands'),
_h.inset(
_h.font.command(
`!act`,
_h.optional(
_h.required('formula'),
_h.optional(
_h.required('initial')
)
),
_h.required(`--${_h.required('custom turn name')}`),
_h.optional(
`--delete-on-zero`,
`--delete-lt ${_h.required('number')}`,
`--delete-le ${_h.required('number')}`,
`--delete-eq ${_h.required('number')}`,
`--delete-ge ${_h.required('number')}`,
`--delete-gt ${_h.required('number')}`,
`--after`,
`--index ${_h.required('number')}`,
`--help`
)
),
_h.paragraph('Add a custom turn to the Turn Order, with options for adjusting it each turn, and removing it when it has a specified value.'),
_h.minorhead('Positional Arguments'),
_h.ul(
`${_h.bold(_h.required('formula'))} -- The formula for changing the turn. ${_h.code('+')} or ${_h.code('-')} prepended to the number specify the direction. (${_h.bold('Default')}: ${_h.code('+1')} )`,
`${_h.bold(_h.required('initial'))} -- The initial value for the custom turn. (${_h.bold('Default')}: ${_h.code('0')} )`
),
_h.minorhead('Dash Arguments'),
_h.ul(
`${_h.bold(`--${_h.required('custom turn name')}`)} -- The name of the custom turn to add.`,
`${_h.bold('--delete-on-zero')} -- deletes the custom turn when its value is less than or equal to 0. Shorthand for ${_h.code('--delete-le 0')}.`,
`${_h.bold(`--delete-lt ${_h.required('number')}`)} -- deletes the custom turn when its value is less than ${_h.code(_h.required('number'))}.`,
`${_h.bold(`--delete-le ${_h.required('number')}`)} -- deletes the custom turn when its value is less than or equal to ${_h.code(_h.required('number'))}.`,
`${_h.bold(`--delete-eq ${_h.required('number')}`)} -- deletes the custom turn when its value is equal to ${_h.code(_h.required('number'))}.`,
`${_h.bold(`--delete-ge ${_h.required('number')}`)} -- deletes the custom turn when its value is greater than or equal to ${_h.code(_h.required('number'))}.`,
`${_h.bold(`--delete-gt ${_h.required('number')}`)} -- deletes the custom turn when its value is greater than ${_h.code(_h.required('number'))}.`,
`${_h.bold(`--after`)} -- adds the custom turn after the current turn. Shorthand for ${_h.code('--index 1')}.`,
`${_h.bold(`--index ${_h.required('number')}`)} -- adds the custom turn after the entry at index ${_h.code(_h.required('number'))}.`,
`${_h.bold('--help')} -- Shows the Help screen.`
),
_h.paragraph(''),
_h.font.command(
`!dct`,
_h.optional(
`${_h.required('custom turn name')}`,
`--help`
)
),
_h.paragraph('Remove a custom turn from the turn order by name. When used by the GM, removes the first custom turn with the given name. When used by a player, removes the first custom turn they created with the given name.'),
_h.minorhead('Positional Arguments'),
_h.ul(
`${_h.bold(_h.required('custom turn name'))} -- The name of the custom turn to remove.`
),
_h.minorhead('Dash Arguments'),
_h.ul(
`${_h.bold('--help')} -- Shows the Help screen.`
),
_h.paragraph('')
),
_h.subhead('Examples'),
_h.inset(
_h.paragraph(`Add a turn that just counts up. Custom turn names can contain spaces:`),
_h.inset(
_h.pre('!act --Counter for Rounds')
),
_h.paragraph(`Add a turn that counts down from 10:`),
_h.inset(
_h.pre('!act -1 10 --Bless')
),
_h.paragraph(`Add a turn that counts down from 10 and is after the first item in the turn order (All examples are the same):`),
_h.inset(
_h.pre('!act -1 10 --Bless --after'),
_h.pre('!act -1 10 --Bless --index 1'),
_h.pre('!act -1 10 --after --Bless'),
_h.pre('!act -1 10 --index 1 --Bless')
),
_h.paragraph(`Add a turn that counts down from 10 and removes itself when it reaches 0 (All examples are the same):`),
_h.inset(
_h.pre('!act -1 10 --Bless [Bob the Slayer] --delete-on-zero'),
_h.pre('!act -1 10 --Bless [Bob the Slayer] --delete-le 0'),
_h.pre('!act -1 10 --delete-on-zero --Bless [Bob the Slayer]'),
_h.pre('!act -1 10 --delete-le 0 --Bless [Bob the Slayer]')
),
_h.paragraph(`Supports multi-line syntax by wrapping with ${_h.code('{{')} and ${_h.code('}}')}:`),
_h.inset(
_h.preformatted(
'!act -1 10 {{',
' --Bless [Bob the Slayer]',
' --delete-on-zero',
' --after',
'}}'
)
),
_h.paragraph(`Removing a turn named ${_h.code("Bless")}:`),
_h.inset(
_h.pre('!dct Bless')
),
_h.paragraph(`Removing a turn named ${_h.code("Bless [Bob the Slayer]")}:`),
_h.inset(
_h.pre('!dct Bless [Bob the Slayer]')
)
)
)
}`);
};
const handleInput = (msg) => {
if('api' === msg.type) {
if(msg.content.match(/^!act(\b\s|$)/) ){
let who=(getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');
let args = processInlinerolls(msg)
.replace(/<br\/>\n/g, ' ')
.replace(/(\{\{(.*?)\}\})/g," $2 ")
.split(/\s+--/);
let cmds=args.shift().split(/\s+/);
let change=parseFloat(cmds[1]);
change = Number.isNaN(change) ? '+1' : change;
change = `${/^[+-]\d/.test(change)?'':'+'}${change}`;
let initial = parseFloat(cmds[2])||0;
let entry = {
id: "-1",
pr: initial,
formula: change,
custom: DEFAULT_NAME,
autoDelete: false,
player: msg.playerid,
who: who,
source: 'AddCustomTurn'
};
//CHANGE Default add new entries to last position
let idx = 9999;
args.forEach(a=>{
let parts = a.split(/\s+/);
switch(parts[0].toLowerCase()){
case 'help':
return showHelp(who);
//CHANGE <a href="https://app.roll20.net/forum/post/10847052/addcustomturn-for-shield-spell-get-tracker-position-slash-index/?pageforid=10848698#post-10848698" rel="nofollow">https://app.roll20.net/forum/post/10847052/addcustomturn-for-shield-spell-get-tracker-position-slash-index/?pageforid=10848698#post-10848698</a>
case 'before':
let position = getTurnArray().findIndex(o => o.id === parts[1])
idx = position > 0 ? position : 9999
break;
case 'after':
idx = 1;
break;
case 'index':
idx = parseInt(parts[1])||0;
break;
case 'delete-on-zero':
entry.autoDelete = true;
entry.deleteCondition = { op: 'LE', val: 0};
break;
case 'delete-lt':
entry.autoDelete = true;
entry.deleteCondition = { op: 'LT', val: parseInt(parts[1])};
break;
case 'delete-le':
entry.autoDelete = true;
entry.deleteCondition = { op: 'LE', val: parseInt(parts[1])};
break;
case 'delete-eq':
entry.autoDelete = true;
entry.deleteCondition = { op: 'EQ', val: parseInt(parts[1])};
break;
case 'delete-ge':
entry.autoDelete = true;
entry.deleteCondition = { op: 'GE', val: parseInt(parts[1])};
break;
case 'delete-gt':
entry.autoDelete = true;
entry.deleteCondition = { op: 'GT', val: parseInt(parts[1])};
break;
default: {
let custom = parts.join(' ');
if(DEFAULT_NAME === entry.custom){
entry.custom = custom;
}
}
break;
}
});
if(DEFAULT_NAME !== entry.custom){
let to=getTurnArray();
setTurnArray([...to.slice(0,idx),entry,...to.slice(idx)]);
if(!playerIsGM(msg.playerid)){
outputEvent('add',entry);
}
} else {
showHelp(who);
}
} else if(msg.content.match(/^!eot/i)){
let to=getTurnArray();
setTimeout(()=>checkFormulaOnTurn(to),100);
} else if(msg.content.match(/^!dct(\b\s|$)/i)){
let who=(getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');
let args = processInlinerolls(msg)
.replace(/<br\/>\n/g, ' ')
.replace(/(\{\{(.*?)\}\})/g," $2 ")
.split(/\s+--/);
let cmds=args.shift().split(/\s+/);
let ctname=cmds.slice(1).join(' ');
if(ctname.length){
let to = getTurnArray();
let pigm = playerIsGM(msg.playerid);
let idx = to.findIndex(e=>e.custom === ctname && (pigm || e.player === msg.playerid));
if(-1 !== idx) {
let e = to[idx];
to = [...to.slice(0,idx),...to.slice(idx+1)];
setTurnArray(to);
outputEvent('remove',e);
}
} else {
showHelp(who);
}
}
}
};
const registerEventHandlers = () => {
on('chat:message', handleInput);
on('change:campaign:turnorder',handleTurnorderChange);
};
on('ready', () => {
checkInstall();
registerEventHandlers();
});
return {
// Public interface here
};
})();
{try{throw new Error('');}catch(e){API_Meta.AddCustomTurn.lineCount=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-API_Meta.AddCustomTurn.offset);}}
// <a href="https://app.roll20.net/forum/permalink/6606389/" rel="nofollow">https://app.roll20.net/forum/permalink/6606389/</a>
on('ready',function(){
"use strict";
//CHANGE const aictImgSrc = "<a href="https://s3.amazonaws.com/files.d20.io/images/58732795/pfn3AoNw630KlzHP0dGMWw/thumb.png?1532188342" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/58732795/pfn3AoNw630KlzHP0dGMWw/thumb.png?1532188342</a>";
const aictImgSrc = "<a href="https://s3.amazonaws.com/files.d20.io/images/283869561/KJ0kTqL_ICOpVrSlkcLqsw/thumb.png?1651853449" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/283869561/KJ0kTqL_ICOpVrSlkcLqsw/thumb.png?1651853449</a>";
const activeAICTs = findObjs({type:'graphic',layer:'object'})
//CHANGE .filter((t)=>/images\/58732795\/pfn3AoNw630KlzHP0dGMWw\//.test(t.get('imgsrc')))
.filter((t)=>/images\/283869561\/KJ0kTqL_ICOpVrSlkcLqsw\//.test(t.get('imgsrc')))
.filter((()=>{
const toIds = JSON.parse(Campaign().get('turnorder')||'[]').map(to=>to.id);
return t => {
if( ! toIds.includes(t.id)){
t.remove();
return false;
}
return true;
};
})())
.map((t)=>t.id)
;
const getPageForPlayer = (playerid) => {
let player = getObj('player',playerid);
if(playerIsGM(playerid)){
return player.get('lastpage');
}
let psp = Campaign().get('playerspecificpages');
if(psp[playerid]){
return psp[playerid];
}
return Campaign().get('playerpageid');
};
let lastTurnCache = '';
const checkFormulaOnTurn = _.debounce(() => {
let to=JSON.parse(Campaign().get('turnorder')||'[]');
if(to.length){
if(to[0].id!==lastTurnCache && activeAICTs.includes(to[0].id)) {
sendChat('',`[[${to[0].pr}+(${to[0].formula||0})]]`,(r)=>{
to[0].pr=r[0].inlinerolls[0].results.total;
Campaign().set('turnorder',JSON.stringify(to));
});
}
lastTurnCache = to[0].id;
}
},10);
on('chat:message',function(msg){
var args,cmds,who,initial,change,entry;
if('api' === msg.type) {
if(msg.content.match(/^!aict\b/) ){
who=(getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');
if(_.has(msg,'inlinerolls')){
msg.content = _.chain(msg.inlinerolls)
.reduce(function(m,v,k){
var ti=_.reduce(v.results.rolls,function(m2,v2){
if(_.has(v2,'table')){
m2.push(_.reduce(v2.results,function(m3,v3){
m3.push(v3.tableItem.name);
return m3;
},[]).join(', '));
}
return m2;
},[]).join(', ');
m['$[['+k+']]']= (ti.length && ti) || v.results.total || 0;
return m;
},{})
.reduce(function(m,v,k){
return m.replace(k,v);
},msg.content)
.value();
}
args = msg.content
.replace(/<br\/>\n/g, ' ')
.replace(/(\{\{(.*?)\}\})/g," $2 ")
.split(/\s+--/);
cmds = args.shift().split(/\s+/);
change = parseFloat(cmds[1]);
change = ( isNaN(change) ? '+1': change );
change = `${/^[+-]\d/.test(change)?'':'+'}${change}`;
initial = parseFloat(cmds[2])||0;
entry = args.join(' ');
if(entry.length){
let to=JSON.parse(Campaign().get('turnorder')||'[]');
let pageid = getPageForPlayer(msg.playerid);
let token = createObj('graphic', {
subtype: 'token',
imgsrc: aictImgSrc,
pageid: pageid,
layer: 'objects',
//CHANGE layer: 'gmlayer',
top: -100,
left: -100,
width: 70,
height: 70,
name: entry,
showname: true
});
activeAICTs.push(token.id);
// to.unshift({
to.push({
id: token.id,
pr: initial,
_pageid: pageid,
formula: change
});
Campaign().set('turnorder',JSON.stringify(to));
if(!playerIsGM(msg.playerid)){
sendChat('ACT',`/w gm <div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"><div style="background-color: #ffeeee;"><b>${who}</b> added entry for <b>${entry}</b> starting at <b>${initial}</b> and changing by <b>${change}</b>.</div></div>`);
}
} else {
sendChat('ACT',`/w "${who}" <div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"><div style="background-color: #ffeeee;">Use <b><pre>!aict [formula] [starting value] --[description]</pre></b></div></div>`);
}
} else if(msg.content.match(/^!eot/i)){
_.defer(checkFormulaOnTurn);
}
}
});
});
// <a href="https://app.roll20.net/forum/permalink/6606389/" rel="nofollow">https://app.roll20.net/forum/permalink/6606389/</a>
on('ready',function(){
"use strict";
const actspellImgSrc = "<a href="https://s3.amazonaws.com/files.d20.io/images/283883646/i6JP4_CXy9yITrdnNWHJzg/thumb.png?1651859319" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/283883646/i6JP4_CXy9yITrdnNWHJzg/thumb.png?1651859319</a>";
const activeACTSpells = findObjs({type:'graphic',layer:'object'})
//CHANGE .filter((t)=>/images\/58732795\/pfn3AoNw630KlzHP0dGMWw\//.test(t.get('imgsrc')))
.filter((t)=>/images\/283883646\/i6JP4_CXy9yITrdnNWHJzg\//.test(t.get('imgsrc')))
.filter((()=>{
const toIds = JSON.parse(Campaign().get('turnorder')||'[]').map(to=>to.id);
return t => {
if( ! toIds.includes(t.id)){
t.remove();
return false;
}
return true;
};
})())
.map((t)=>t.id)
;
const getPageForPlayer = (playerid) => {
let player = getObj('player',playerid);
if(playerIsGM(playerid)){
return player.get('lastpage');
}
let psp = Campaign().get('playerspecificpages');
if(psp[playerid]){
return psp[playerid];
}
return Campaign().get('playerpageid');
};
let lastTurnCache = '';
const checkFormulaOnTurn = _.debounce(() => {
let to=JSON.parse(Campaign().get('turnorder')||'[]');
if(to.length){
if(to[0].id!==lastTurnCache && activeACTSpells.includes(to[0].id)) {
sendChat('',`[[${to[0].pr}+(${to[0].formula||0})]]`,(r)=>{
to[0].pr=r[0].inlinerolls[0].results.total;
Campaign().set('turnorder',JSON.stringify(to));
});
}
lastTurnCache = to[0].id;
}
},10);
on('chat:message',function(msg){
var args,cmds,who,initial,change,entry;
if('api' === msg.type) {
if(msg.content.match(/^!actspell\b/) ){
who=(getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');
if(_.has(msg,'inlinerolls')){
msg.content = _.chain(msg.inlinerolls)
.reduce(function(m,v,k){
var ti=_.reduce(v.results.rolls,function(m2,v2){
if(_.has(v2,'table')){
m2.push(_.reduce(v2.results,function(m3,v3){
m3.push(v3.tableItem.name);
return m3;
},[]).join(', '));
}
return m2;
},[]).join(', ');
m['$[['+k+']]']= (ti.length && ti) || v.results.total || 0;
return m;
},{})
.reduce(function(m,v,k){
return m.replace(k,v);
},msg.content)
.value();
}
args = msg.content
.replace(/<br\/>\n/g, ' ')
.replace(/(\{\{(.*?)\}\})/g," $2 ")
.split(/\s+--/);
cmds = args.shift().split(/\s+/);
change = parseFloat(cmds[1]);
change = ( isNaN(change) ? '+1': change );
change = `${/^[+-]\d/.test(change)?'':'+'}${change}`;
initial = parseFloat(cmds[2])||0;
entry = args.join(' ');
if(entry.length){
let to=JSON.parse(Campaign().get('turnorder')||'[]');
let pageid = getPageForPlayer(msg.playerid);
let token = createObj('graphic', {
subtype: 'token',
imgsrc: actspellImgSrc,
pageid: pageid,
layer: 'objects',
//CHANGE layer: 'gmlayer',
top: -100,
left: -100,
width: 70,
height: 70,
name: entry,
showname: true,
showplayers_name: true
});
activeACTSpells.push(token.id);
// to.unshift({
to.push({
id: token.id,
pr: initial,
_pageid: pageid,
formula: change
});
Campaign().set('turnorder',JSON.stringify(to));
if(!playerIsGM(msg.playerid)){
sendChat('ACT',`/w gm <div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"><div style="background-color: #ffeeee;"><b>${who}</b> added entry for <b>${entry}</b> starting at <b>${initial}</b> and changing by <b>${change}</b>.</div></div>`);
}
} else {
sendChat('ACT',`/w "${who}" <div style="padding:1px 3px;border: 1px solid #8B4513;background: #eeffee; color: #8B4513; font-size: 80%;"><div style="background-color: #ffeeee;">Use <b><pre>!actspell [formula] [starting value] --[description]</pre></b></div></div>`);
}
} else if(msg.content.match(/^!eot/i)){
_.defer(checkFormulaOnTurn);
}
}
});
});
/* AddTurnTrackerEffects End */