tposney / midi-qol

Other
5 stars 0 forks source link

Feature request: send token to "On Use Macro" #165

Open tposney opened 3 years ago

tposney commented 3 years ago

In GitLab by @Akaito1 on Dec 9, 2020, 07:08

Use case: I'm starting to use the "On Use Macro" to play some TokenMagic FX / JB2A effects. Such as arrows firing from the user of a bow to any hitTargets. But getting the origin of the shot to place/angle/scale the effect is a hassle, because token is just whatever token was most recently selected, and args[0].actor only knows its prototype token.

So currently, to find the token that the attack actually originated from (because names can be ambiguous ("Goblin")), I'm doing this:

let itemId = args[0].item._id;
let sourceToken = canvas.tokens.objects.children.filter(t => t.actor.items.get(itemId) != null)[0];

It'd be handy to instead go let sourceToken = args[0].token or similar to get the token the attack originated from.

For completeness, here's the whole macro I'm using. Credit for 90% of it due to contributors to JB2A / TokenMagic FX. I just did the MidiQOL "glue" to find the attack source.

// To be set as an On Item Use macro in the MidiQOL-provided option on your weapon.
//console.log('FX MidiQOL Shoot Arrow | args[0]', args[0]);

let itemId = args[0].item._id;
let sourceToken = canvas.tokens.objects.children.filter(t => t.actor.items.get(itemId) != null)[0];
//console.log('FX MidiQOL Shoot Arrow | sourceToken', sourceToken);

args[0].hitTargets.forEach(function (target) {
  playFx(sourceToken.data, target);
});

function playFx(origin, target) {
let halfGrid = canvas.scene.data.grid/2;
let srcX = (origin.x + (origin.width*halfGrid));
let srcY = (origin.y + (origin.height*halfGrid));
let tarX = (target.x + (target.width*halfGrid));
let tarY = (target.y + (target.height*halfGrid));
let outerRad = Math.sqrt(Math.pow(target.width*halfGrid,2) + Math.pow(origin.width*halfGrid,2));

let anDeg = -(Math.atan(((srcY - tarY)/(srcX - tarX)))*57.3);
let anDist = Math.sqrt(Math.pow(srcX-tarX,2)+Math.pow(srcY-tarY,2));
async function wait(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

let anFile = "modules/JB2A_DnD5e/Library/Generic/Weapon_Attacks/Ranged/Arrow_Simple_01_30ft_1200x200.webm";
let anFileSize = 1200;
switch(true){
 case (anDist<=1200):
    anFileSize = 1200;
    anFile = "modules/JB2A_DnD5e/Library/Generic/Weapon_Attacks/Ranged/Arrow_Simple_01_30ft_1200x200.webm";
    break;
 case (anDist<=2400):
    anFileSize = 2400;
    anFile = "modules/JB2A_DnD5e/Library/Generic/Weapon_Attacks/Ranged/Arrow_Simple_01_60ft_2400x200.webm";
    break;
 default:
    anFileSize = 3600;
    anFile = "modules/JB2A_DnD5e/Library/Generic/Weapon_Attacks/Ranged/Arrow_Simple_01_90ft_3600x200.webm";
    break;
}

let anScale = Math.sqrt(Math.pow(srcX-tarX,2)+Math.pow(srcY-tarY,2))/anFileSize;
let anScaleY = anScale;
if (anDist<=600){anScaleY = 0.6}
if (anDist>=800){anScaleY = anScale}

if(srcX>tarX){anDeg = anDeg+180}
if(srcX==tarX){if(srcY>tarY){anDeg=90}else{anDeg=-90}}

let spellAnim = 
                    {
                     file: anFile,
                      position: {
                        x: srcX,
                        y: srcY
                      },
                      anchor: {
                       x: 0,
                       y: 0.5
                      },
                      angle: anDeg,
                      scale: {
                       x: anScale,
                       y: anScaleY
                      }
                    }; 

canvas.fxmaster.playVideo(spellAnim);
game.socket.emit('module.fxmaster', spellAnim);
}
tposney commented 3 years ago

There is a tokenId stored in the workflow. However that tokenId it keeps is either item.actor.token.id (which is correct for unlinked tokens) or ChatMessage.getSpeaker.token which is often the selected token.

In the next release I'll pass the tokenId as one field in args[0], but you might have to experiment

tposney commented 3 years ago

In GitLab by @Akaito1 on Dec 10, 2020, 04:19

Thanks! I'll check it out after updating. I'll be comparing it to this, below, which I started using instead of the 'ownedTokens' filter described previously:

function getChatCardToken(chatId) {
  let chatCard = content.ui.chat.collection.get(chatId);
  let speaker = chatCard.data.speaker;
  if (speaker.token !== null) {
    return canvas.tokens.get(speaker.token);
  }
  else {
    let fauxToken = canvas.scene.data.tokens.find(t => {return t.actorId == speaker.actor});
    return canvas.tokens.get(fauxToken._id);
  }
}

let sourceToken = getChatCardToken(args[0].itemCardId);
tposney commented 3 years ago

In GitLab by @Akaito1 on Dec 10, 2020, 04:43

Ah, unfortunately args[0].tokenId isn't quite right. Just tried changing to let sourceToken = canvas.tokens.get(args[0].tokenId). It has the same problem as the existing speaker that could be found somewhere in the args[0] objects. It's totally fine and valid iff you happen to have the speaker/attacker selected. It acts a bit like canvas.tokens.controlled.