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

CFX target/source problem

Hello there !

I recently had a problem using the !cfx command. Indeed, for explosion-type fx, the command inverts target and source. As a consequence, it appears that the caster explodes (instead of the target).

I saw that someone had the same problem on a roll20 topic named "CFX & DELAY", but this strided from the original subject. Hence this new topic.

So I think that !cfx commands are functioning for beam-like fx but not explosion fx. Indeed when I actually use a command like : 

!cfx burst-fire @{selected|character_id} @{target|token_id}

the burst effect takes place at the caster's feet . I found a temporary solution. Let me explain : the !cfx command works like this : 

!cfx [effect] [source character id] [destination token id]

If you call it like that and the effect is (for example) "beam-fire" it works fine. But if the effect is "explosion-fire" then the target and the source are switched (target is fine, source explodes). Hence I tried to call the function with a "reverse" progression : 

!cfx [effect] [destination character id] [source token id]

and it worked perfectly fine as long as I had only ONE token representing the character I want to target physically on the map.


Let me be a bit more precise. For instance, if I type "!cfx burst-fire @{target|character_id} @{selected|token_id}" at an ogre (which is the "target") (the "selected" token is the red-horned tiefling) and if there is only ONE token "ogre" on the map, then the ogre will burst into flame, great.

                                  Fig. 1 : 1 ogre burst into flames

But if I try the same script at a goblin, and let's say there are 3 goblins on the map, all 3 goblins burst into flame (and that's logic because the script calls as an argument the character_id, and all three goblins are linked to the same character, the same NPC Sheet. Hence they have the same character_id).

                                    Fig.2 : All 3 goblins burst into flames

Hence, the "normal" sequence ([effect] [source] [target]) does not work for explosion-type fx but works perfectly fine for beam-like fx. The reverse "sequence" ([effect] [target] [source]) only works for explosion-type fx if there is only ONE token representing the target on the map (and as a consequence this sequence is also not a viable solution).

I thought to some solutions : 

1) Keep the "reverse" sequence for explosion-types fx but instead of using "character_id" use "token_id" (as each token has a unique token_id, all 3 goblins should not all burst into flame but only the one I targeted)

2) Keep the "reverse" sequence for explosion-types fx but instead of using "character_id" use "token_name" AND a script like TokenNameNumber so that every token has a different name (same conclusions).

The problem is : I'm not familiar with roll20 programming language (and Object Oriented Programming in general ^^) and I don't know how to modify the code in order to do that ..! If someone can help it would be really nice :)

Thanks !

May 09 (5 years ago)

Edited August 03 (5 years ago)
The Aaron
Roll20 Production Team
API Scripter

Ah, thanks for tracking that down.  Here's a rewritten version that corrects that issue.

I've made some other enhancements:

  •  You can now specify a token or character id for the source.  If you use a character id, it will take the first token it finds that represents that character on the caller's current page.
  • It now supports splitting the party and the gm being on a different page.  Note that for calling it from the API, it only supports finding characters on the party ribbon page.  FX are always on the page for the destination tokens.
  • You can specify either a character or token id for the destination token. If you use a character id, it will target all the tokens that represent that character on the caller's current page.
  • You can now specify as many targets as you like simply by adding more ids to the end of the command.  These can be character or token ids.

Code:

on('ready',()=>{

  const isBuiltinFx = (name) => /(beam|bomb|breath|bubbling|burn|burst|explode|glow|missile|nova|splatter)-(acid|blood|charm|death|fire|frost|holy|magic|slime|smoke|water)/.test(name);

  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');
  };


  on('chat:message',(msg)=>{
    if('api' !== msg.type ) {
      return;
    }

    let args = msg.content.split(/\s+/);

    switch(args.shift().toLowerCase()){
      case '!cfx': {
        if (args.length < 3){
          sendChat("CFX", `/w "${who}" Use <code>!cfx [effect] [source id] [destination id ...]</code><br><code>id</code>s can be token or character ids.  You can specify as many destination ids as you like to target multiple tokens or creatures.`);
          return;
        }

        let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');
        let srcToken = getObj('graphic',args[1]);
        let pageid = getPageForPlayer(msg.playerid);
        if(!srcToken) {
          let character = getObj('character',args[1]);
          if(character){
            let ts = findObjs({
              type: 'graphic',
              represents: character.id,
              pageid: pageid
            });
            if(ts.length){
              srcToken = ts[0];
            }
          }
        }

        let destTokens = args.slice(2)
          .map((id)=>getObj('graphic',id) || findObjs({type:'graphic', represents:id, pageid}))
          .reduce((m,o)=>[...m,...(Array.isArray(o) ? o : [o])],[])
          ;

        if(!srcToken){
          sendChat("CFX", `/w "${who}" No matching tokens source character or token ID on your current page.`);
          return;
        }
        if(0 === destTokens.length){
          sendChat("CFX", `/w "${who}" No destination tokens found for supplied IDs.`);
          return;
        }
        let srcPt = {
          x: srcToken.get('left'),
          y: srcToken.get('top')
        };

        let targeted = false;
        let effect = args[0];
        if(!isBuiltinFx(effect)){
          let custfx = findObjs({type: 'custfx', name: effect}, {caseInsensitive: true})[0];
          if(!custfx){
            sendChat("CFX", `/w "${who}" Not a built in or custom effect: <code>${effect}</code>`);
            return;
          }
          targeted = (-1 === parseInt((custfx.get('definition')||{}).angle));
          effect = custfx.id;
        } else {
          targeted = /^(beam|breath|splatter)-/.test(effect);
        }

        let spawner = targeted
          ? (s,d,e,p) => spawnFxBetweenPoints(s,d,e,p)
          : (s,d,e,p) => spawnFx(d.x,d.y,e,p)
          ;


        destTokens.forEach((t)=>{
          let destPt = {
            x: t.get('left'),
            y: t.get('top')
          };
          spawner(srcPt,destPt,effect,t.get('pageid'));
        });
      }
      break;
    }
  });
  log("-=> CFX command loaded (!cfx) <=-");
});
Edit: Add Breath and Splatter effects to the targeted method of creation.

Hi ! Thanks a lot ! Just tried it and it works perfectly fine ! That's great !

May 10 (5 years ago)
The Aaron
Roll20 Production Team
API Scripter

Sweet! =D

How do you use this code on the actual roll20? i so far am only able to use the macros

August 03 (5 years ago)

Edited August 03 (5 years ago)

Hey, this is working great for most cases, but there is one that I can't get working. When I use a built-in effect and provide a token id for the source and target nothing happens. Am I doing something wrong? Custom effects work fine with the same syntax.

Example:

!cfx breath-frost @{target|Attacker|token_id} @{target|Target|token_id}

Edit: actually even providing character id instead of token id for the source doesn't seem to work for built-in effects.

Works with original cfx script but not this new one:

!cfx breath-frost @{selected|character_id} @{target|Target|token_id}

August 03 (5 years ago)
The Aaron
Roll20 Production Team
API Scripter

Thanks Zach, I found the issue and corrected the code above.  Breath, like Beam, requires two points to create the effect between, but I wasn't supplying that for Breath.  I also added Splatter to that list after some experimentation.  Cheers!

August 03 (5 years ago)

Nice! Thank you, Aaron, for fixing that so quickly. I do still see a problem when using this with character id in conjunction with !delay though (for custom and built-in fx). It's not a big deal for me because I'll probably use token id for the most part (at least for the time being), but thought I'd mention it still in case others want to use it.

Example:

!delay .7 --!cfx breath-frost @{selected|character_id} @{target|Target|token_id}

September 27 (4 years ago)

Edited September 27 (4 years ago)

Having a bit of trouble with CFX and I'm wondering if I can get a little help.

The below macro works fine:

!roll20AM --audio,nomenu,play|SFX_ArcLightning
/fx ArcZap @{selected|token_id} @{target|token_id}
/fx ArcLightning @{target|token_id}

The custom effects trigger;  the first (ArcZap) from the source to the target, and the second (ArcLightning) triggers on the target.  However, they are out of sync with the sound effect, so I've been trying to use !delay + !cfx to line them up.  However, as soon as I change the /fx to !cfx for the ArcLightning effect, it breaks the API and I need to quit and restart it:

!roll20AM --audio,nomenu,play|SFX_ArcLightning
!cfx ArcZap @{selected|token_id} @{target|token_id}
!cfx ArcLightning @{target|token_id}

If I ONLY change ArcZap to !cfx (and leave ArcLightning as /fx), it still works fine.  The problem only happens when I change ArcLightning.

Here's the error report:

ReferenceError: Cannot access 'who' before initialization ReferenceError: Cannot access 'who' before initialization at apiscript.js:2088:34 at eval (eval at <anonymous> (/home/node/d20-api-server/api.js:154:1), <anonymous>:65:16) at Object.publish (eval at <anonymous> (/home/node/d20-api-server/api.js:154:1), <anonymous>:70:8) at /home/node/d20-api-server/api.js:1661:12 at /home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:93:560 at hc (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:39:147) at Kd (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:93:546) at Id.Mb (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:93:489) at Zd.Ld.Mb (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:94:425) at /home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:111:400

Your help is appreciated!

I think I've got the solution.  I guess I need to put both source AND target for all effects, whether or not they are actually traveling from a source to a target?  Doing that seems to have solved the issue.