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

Is there an event for initiative change?

I would like to run a macro when the initiative changes from one token to another. How would I do that?
1701716929

Edited 1701717028
GiGs
Pro
Sheet Author
API Scripter
No, because initiative is handled in many different ways across many systems, and roll20 isn't tied to one specific system. If initiative is based on an attribute in yor system, you could monitor that stat. You can also monitor turn order, if using the turn counter. There are speicfic details that owuld need to be known to implement any system. Any solution would involve a Mod script.
1701717037
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
It requires an API script. The event you're looking for is change:campaign:turnorder .
There is an event that triggers for turnorder changes.  ScriptCards has a trigger option for change:campaign:turnorder  if you'd like to use ScriptCards.  TurnMarker  also uses an initiative change to trigger on. TurnMarker code is  here  if you'd like to look at that example for your own script.
1701719332

Edited 1701719378
The Aaron
Roll20 Production Team
API Scripter
Here's an untested script that should call any Macro named "OnTurn" and any character ability named "OnTurn" whenever the turn order changes.  This will only activate for token turns, so not for Custom Turn Entries.  If you need that capability, let me know.  It also respects the !eot command used by many scripts to change the turn. 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 resolver = (token,character) => (text) => { const attrRegExp = /@{(?:([^|}]*)|(?:(selected)|(target)(?:\|([^|}]*))?)\|([^|}]*))(?:\|(max|current))?}/gm; const attrResolver = (full, name, selected, target, label, name2, type) => { let simpleToken = JSON.parse(JSON.stringify(token)); let charName = character.get('name'); type = ['current','max'].includes(type) ? type : 'current'; const getAttr = (n, t) => ( findObjs({type: 'attribute', name:n, characterid: character.id})[0] || {get:()=>getAttrByName(character.id,n,t)} ).get(t); const getFromChar = (n,t) => { if('name'===n){ return charName; } return getAttr(n,t); }; const getProp = (n, t) => { switch(n){ case 'token_name': return simpleToken.name; case 'token_id': return simpleToken._id; case 'character_name': return charName; case 'bar1': case 'bar2': case 'bar3': return simpleToken[`${n}_${'max'===t ? 'max' : 'value'}`]; } return getFromChar(n,t); }; if(name){ return getFromChar(name,type); } return getProp(name2,type); }; return text.replace(attrRegExp, attrResolver); }; const runCommandsForToken = (token) => { if(token && token.get('represents')){ let character = getObj('character',token.get('represents')); let ability = findObjs({ name: 'OnTurn', type: 'ability', characterid: character.id }, {caseinsensitive: true})[0]; let TCRes = resolver(token,character); if(ability){ let content = TCRes(ability.get('action')).replace(/\[\[\s+/g,'[['); try { sendChat(character.get('name'),content); } catch(e){ log(`OnTurn: ERROR PARSING: ${content}`); log(`OnTurn: ERROR: ${e}`); } } // look for a macro findObjs({ type: 'macro', name: 'OnTurn' }, {caseinsensitive: true}) .forEach(macro=>{ // if gm macro, always run. Otherwise, run if the token is controlled by the player if(playerIsGM(macro.get('playerid')) || playerCanControl(character, macro.get('playerid'))){ let content = TCRes(macro.get('action')).replace(/\[\[\s+/g,'[['); try { sendChat(character.get('name'),content); } catch(e){ log(`OnTurn: ERROR PARSING: ${content}`); log(`OnTurn: ERROR: ${e}`); } } }); } }; const getTurnArray = () => ( '' === Campaign().get('turnorder') ? [] : JSON.parse(Campaign().get('turnorder'))); const getCurrentTurnToken = () => getObj('graphic',(getTurnArray()[0]||{}).id); const DoOnEachTurn = () => { runCommandsForToken(getCurrentTurnToken()); }; on('chat:message', (msg)=>{ if('api'===msg.type && /^!doet\b/i.test(msg.content) && playerIsGM(msg.playerid)){ DoOnEachTurn(); } }); on('change:campaign:turnorder', ()=>setTimeout(()=>DoOnEachTurn(),1000)); on('chat:message', (msg) => { if('api'===msg.type && /^![ep]ot\b/.test(msg.content)){ setTimeout(()=>DoOnEachTurn(),1000); } }); });
Thanks for all the quick responses! I probably should have said "why" :) All I want to do is what I am doing manually now - when the turn changes, and the next up is an NPC, I want to (as GM) Speak As that token. 
This should be possible with ScriptCards and the ScriptCards triggers. Let me know if you'd like to go down that route and I can help out with that. If you want a dedicated API script, I don't think my JavaScript is up to the task for that right now but I could help with the ScriptCard portion.
1701723637
The Aaron
Roll20 Production Team
API Scripter
Oh!  Yeah, definitely better to ask for what you want. =D Here ya go: 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 GetGMs = () => findObjs({type:'player'}).filter(p=>playerIsGM(p.id)); const getTurnArray = () => ( '' === Campaign().get('turnorder') ? [] : JSON.parse(Campaign().get('turnorder'))); const getCurrentTurnToken = () => getObj('graphic',(getTurnArray()[0]||{}).id); const DoOnEachTurn = () => { let t = getCurrentTurnToken(); let speakingas = ''; if(t){ let c = getObj('character',t.get('represents')); if(c && !playerCanControl(c)){ speakingas = `character|${c.id}`; } } GetGMs().forEach(p=>p.set({speakingas})); }; const ResetGMSpeaking = () => { GetGMs().forEach(p=>p.set({speakingas:''})); }; on('change:campaign:initiativepage',()=>{ if(false == Campaign().get('initiativepage')){ ResetGMSpeaking(); } else { DoOnEachTurn(); } }); on('chat:message', (msg)=>{ if('api'===msg.type && /^!doet\b/i.test(msg.content) && playerIsGM(msg.playerid)){ DoOnEachTurn(); } }); on('change:campaign:turnorder', ()=>setTimeout(()=>DoOnEachTurn(),1000)); on('chat:message', (msg) => { if('api'===msg.type && /^![ep]ot\b/.test(msg.content)){ setTimeout(()=>DoOnEachTurn(),1000); } }); }); This will set each GM Player to speaking as the character who's turn it is if they aren not controlled by a player, and back to themselves otherwise.  It will also set the GM speaking as when the turn order is hidden and revealed.
1701724227

Edited 1701724291
timmaugh
Forum Champion
API Scripter
I haven't tested it, but a combo of existing scripts will get you where you're going. Use the script from The Aaron, just above, to allow you run an "OnTurn" command. You'll also want  this script from The Aaron lets you switch between "Speaking As" for a given player. Finally, install the MetaScriptToolbox to let you use a little meta magic from Fetch (one of the scripts in the toolbox). With all of those scripts installed, you'd want an "OnTurn" ability or macro that would look like: !{&if @(tracker.character_id[none]) != none}switch @(tracker.character_id){&end} That will get you the character id (which is what the "switch" script needs) for the current token from this page in the turn order. This matches the list the players see (while the GM might have more tokens because they see all pages). So if you are managing combat across multiple maps and you want the next token from the set of gm-visible tokens (ie, all pages), then alter the line to be: !{&if @(tracker[gm].character_id[none]) != none}switch @(tracker[gm].character_id){&end} EDIT: Ninja'd by The Aaron. Oh, well. I'll leave this up in case it might help someone.
Works like a charm! I had a copy of one of the tokens on the GM layer, and it didn't switch for that one, but after I removed the copy, everything works as intended. Very cool!
1701727569
The Aaron
Roll20 Production Team
API Scripter
It shouldn't care about the layer, but if you're using something like TurnMarker1 with GM layer skip, it would end up getting the next token.