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

auto rotate token to point at other token

Is there any way to have a static token rotate to always be looking at another token? Something similar to the Twins API but just to point at the other token. I've got a maze with a statue at the centre, i'd like the statue to rotate to point at one of my players at all times, is this possible with an already existing api?
1674225621
timmaugh
Pro
API Scripter
If there isn't a dedicated script for this, I'd be surprised. Understand that there are really 2 levels to what you're asking: one just to orient the token, and one to keep the token oriented without further action from you. The second is definitely more involved. However, if you can't find a specific script that answers this, you can at least do the first part with metascripts (Fetch + ZeroFrame + MathOps + APILogic) and TokenMod. Then you could have a button you click to orient all fixed-floating-orientation tokens at once. Fetch can get you the relative pieces: @(selected.top) @(selected.left) @(Statue Name.top) @(Statue Name.left) Then MathOps can run asin() or acos() to get the angle of rotation. APILogic gives you the ability to do conditionals (if necessary) -- so if the token is above/below, left/right of the fixed point of orientation. You could probably handle this with math, but the conditionals might make it easier. And ZeroFrame is the organizing script that will keep the other metascripts running in the proper order (so that you don't try to take the arcsin of values you haven't yet Fetched). Once you have that, you feed the rotation into a TokenMod command to orient the token, and Bob's your uncle. Ooh, bonus metascript interaction: If you also install SelectManager, you can run a forselected to iterate over and orient tokens individually. You don't actually have to select them as you can "virtually" select them using SelectManager's {&select... } syntax, supplying a list of tokens to virtually select. That list can be hard-coded into the macro/ability, or it can be dynamically retrieved from an attribute on a mule character. So you could take a list of fixed-orientation tokens that exist on a page and store it in an attribute that would be tied to that page. So on page "The Castle Shack", you have a list of 4 fixed-orientation tokens, and on the "Village Dragon Hatchery" you could have 2. Store them on a mule character dedicated to housing game data like this, in attributes you create and name referring to the page: The Castle Shack: Token Name1, TokenID2, Token Name3, Token Name4 Village Dragon Hatchery: Token Name5, TokenID6 Then you can use Fetch to get your page, and use that inside a Fetch construction to get the attribute, INSIDE a select construction to get those tokens. Building that from the inside-out that's: your page = @(player.timmaugh.lastpagename) using that page as the name of an attribute in a Fetch construction: @(MuleCharacter.@(player.timmaugh.lastpagename)) using the returned list in a {&select} construction: {&select  @(MuleCharacter.@(player.timmaugh.lastpagename)) } using ZeroFrame deferral to order the select to run AFTER the fetch:  {\&select  @(MuleCharacter.@(player.timmaugh.lastpagename)) } That means, that your final command line would look like: !forselected token-mod --set rotation| <<equation derived by arcsin>>   {\&select  @(MuleCharacter.@(player.timmaugh.lastpagename)) } And no matter what page you were on and no matter what tokens you had selected on that page, as long as you had stored the list of fixed-orientation tokens as described, above, you would immediately orient them all. Now I haven't actually constructed the equation for the rotation yet... we'll see if there really is a dedicated script people might suggest. But, if there isn't a dedicated script and if you can't work out the math yourself, I can try to work one up for you.
1674229569
The Aaron
Roll20 Production Team
API Scripter
I'm not aware of one that exists... ...so I wrote one! !look-at --target @{target|Who to look at|token_id} --looker @{target|Who should look|token_id} It assumes that the up direction for the graphic is where it looks from.  If that's not correct, you can specify a rotation to be applied to the watching token whenever it's updated.  for example, if your token by default is looking down, you could use this: !look-at --target @{target|Who to look at|token_id} --looker @{target|Who should look|token_id} --rotation 180 Code: on('ready',()=>{ const scriptName = 'LookAt'; const version = '0.1.0'; const schemaVersion = 0.1; const lastUpdate = 1674229371; const checkInstall = () => { log(`-=> ${scriptName} v${version} <=- [${lastUpdate}]`); 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, watched: {}, options: {} }; break; } } }; checkInstall(); const watch = (watchid, watcherid, rotation) => { const s = state[scriptName]; s.watched[watchid]= s.watched[watchid] || {}; s.watched[watchid][watcherid]={...(s.watched[watchid][watcherid]||{}),rotation}; }; const showHelp = (who) => { sendChat('',`/w "${who}" <div><code>!look-at --target [token_id] --looker [token_id] --rotation [number]</code></div>`); }; const GetAngleTo = (target,src) => ( ((180/Math.PI)*Math.atan2(target.y-src.y, target.x-src.x))+90); const handleChangeGraphic = (obj,prev) => { const s = state[scriptName]; if(s.watched.hasOwnProperty(obj.id)){ Object.keys(s.watched[obj.id]) .forEach(id=>{ let t = getObj('graphic',id); if(t){ let rotation = GetAngleTo( {x: obj.get('left'), y: obj.get('top')}, {x: t.get('left'), y: t.get('top')} ) + s.watched[obj.id][t.id].rotation; t.set('rotation',rotation); } else { delete s.watched[obj.id][id]; } }); } }; on('change:graphic',handleChangeGraphic); class DoubleDashArgs { #cmd; #args; constructor(line){ let p=line.split(/\s+--/); this.#cmd = p.shift(); this.#args = [...p.map(a=>a.split(/\s+/))]; } get cmd() { return this.#cmd; }; has(arg) { return !!this.#args.find(a=>arg===a[0]); } params(arg) { return [...(this.#args.find(a=>arg===a[0])||[])]; } toObject() { return {cmd:this.#cmd,args:this.#args}; } } // !look-at --target @{target|Target|token_id} --looker @{target|Looker|token_id} --rotation 90 on('chat:message',msg=>{ if('api'===msg.type && /^!look-at(\b\s|$)/i.test(msg.content) && playerIsGM(msg.playerid)){ let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname'); let DDArgs = new DoubleDashArgs(msg.content); if(!DDArgs.has('help') && DDArgs.has('target') && DDArgs.has('looker')){ DDArgs.params('looker') .slice(1) .forEach( id => { const t = getObj('graphic', DDArgs.params('target')[1]); if(t) { watch( t.id, id, (parseFloat(DDArgs.params('rotation')[1]) || 0) ); handleChangeGraphic(t,{}); } }); } else { showHelp(who); return; } } }); const cleanupWatchTable = () => { let keys = Object.keys(state[scriptName].watched); const burndown = () => { let k = keys.shift(); if(k){ let o = getObj('graphic',k); if(!o){ delete state[scriptName].watched[k]; } setTimeout(burndown,0); } }; burndown(); }; cleanupWatchTable(); });
I do not know whether to worship you as gods or burn you as witches, either way you are freakin magic, thanks so much guys. @aaron it works like a treat!
1674235081
The Aaron
Roll20 Production Team
API Scripter
Great!  Let me know if there are further enhancements or requests. =D