DFreds / dfreds-convenient-effects

A FoundryVTT module that adds easy to use toggleable active effects for any system.
MIT License
47 stars 39 forks source link

Can't apply dFred effects via macro to unlinked tokens #129

Closed the-real-orson closed 2 years ago

the-real-orson commented 2 years ago

Describe the bug If targets are unlinked tokens, macro applies effect using addEffect (according to the console) but the effect doesn't show up on the targeted token. midi-qol and dae are both active

To Reproduce Steps to reproduce the behavior:

  1. Setup dFreds effect with name: "Darkness", Effect: Macro Execute (*) CUSTOM "Darkness Effects"
  2. Make a macro named "Darkness Effects", see code below
  3. Cast the spell in a scene and target a linked token and an unlinked token.
  4. Both tokens will have the effect "Darkness" form the spell, but only the linked token will have "Invisible" condition applied by the macro.

Expected behavior All tokens have "Darkness" and "Invisible"

Screenshots image

Additional context

if (args[0] === "on") {

//macro fires for each target, so we need to identify the current target:
const lastArg = args[args.length - 1];
let tokenOrActor = await fromUuid(lastArg.actorUuid);
let actor = tokenOrActor.actor ? tokenOrActor.actor : tokenOrActor
console.log("!!!!!!!!!",actor.uuid);

//await game.dfreds.effectInterface.toggleEffect("Invisible", { uuids: actorUuidArray });
                          //async toggleEffect(effectName, { overlay, uuids = [] } = {})

await game.dfreds.effectInterface.addEffect({effectName: "Invisible", uuid: actor.uuid});
                          //async addEffect({ effectName, uuid, origin, overlay, metadata })
}
DFreds commented 2 years ago

So it doesn't work if you ONLY target the linked token, right?

To be honest, this doesn't really seem like an issue on my end. You can definitely apply effects to linked tokens using the regular API from this module. It seems likely that the issue is somewhere else.... can you try using macro.execute.GM? That requires dae 0.9.02 or higher.

the-real-orson commented 2 years ago

It always works for the linked token, whether it is single target or part of target array.

It never works for the unlinked token, whether it is single target or part of target array.

Not sure how to implement maco.execute.GM. Happy to try it if you give me a little more direction.

I didn't know if it was an issue on your end, just started here since this kind of issue tends to get me a "it's a module issue" when I ask in #macro-polo. I was told in that channel before that unlinked tokens will respond to all the same actor methods as linked tokens, so I'm confused as to why they are behaving differently.

The reason I'm trying to do this is because I want to apply the blinded condition only if the target does not have tremor/blind/true sight/sense.

Looking for some direction or suggestions if you have any.

DFreds commented 2 years ago

So the first step you provided was this:

Setup dFreds effect with name: "Darkness", Effect: Macro Execute (*) CUSTOM "Darkness Effects"

Macro Execute puts in the effect as macro.execute. Change it to macro.execute.GM

the-real-orson commented 2 years ago

No difference when executed as GM.

The invisible effect is being applied to the unlinked token....it just somehow then isn't there. According to the chat log Invisible is applied first, then the dFreds effect Darkness (that is calling the macro) is applied, but then only Darkness remains on the unlinked token.

Console shows it being applied but does not show it being removed.

If you suspect it's a midi issue I can inquire on that GitH

DFreds commented 2 years ago

Someone else had this issue in the past, but they resolved it by working with tposney, the developer of midi and DAE. It was something around asynchronous functions... I'll see if I can find what the solution was and I'll post it here if I do. Otherwise, you'll have to ask him.

DFreds commented 2 years ago

Relevant messages from tposney: https://cdn.discordapp.com/attachments/938550435474726954/944556702248890428/unknown.png https://media.discordapp.net/attachments/938550435474726954/944556897938337812/unknown.png

Also, here is a working example from someone who asked tposney about it. You can save this as a .json and import it into a sample item in Foundry by right-clicking the item and choosing 'Import'.

{
  "name": "Tasha's Hideous Laughter",
  "type": "spell",
  "img": "icons/creatures/unholy/demon-fire-horned-mask.webp",
  "data": {
    "description": {
      "value": "<p>A creature of your choice that you can see within range perceives everything as hilariously funny and falls into fits of laughter if this spell affects it. The target must succeed on a Wisdom saving throw or fall prone, becoming Incapacitated and unable to stand up for the Duration. A creature with an Intelligence score of 4 or less isn't affected.</p><p>At the end of each of its turns, and each time it takes damage, the target can make another Wisdom saving throw. The target has advantage on the saving throw if it's triggered by damage. On a success, the spell ends.</p>",
      "chat": "",
      "unidentified": ""
    },
    "source": "",
    "activation": {
      "type": "action",
      "cost": 1,
      "condition": ""
    },
    "duration": {
      "value": 1,
      "units": "minute"
    },
    "target": {
      "value": 1,
      "width": null,
      "units": "",
      "type": "creature"
    },
    "range": {
      "value": 30,
      "long": 0,
      "units": "ft"
    },
    "uses": {
      "value": 0,
      "max": "0",
      "per": ""
    },
    "consume": {
      "type": "",
      "target": "",
      "amount": null
    },
    "ability": "",
    "actionType": "save",
    "attackBonus": 0,
    "chatFlavor": "",
    "critical": {
      "threshold": null,
      "damage": ""
    },
    "damage": {
      "parts": [],
      "versatile": "",
      "value": ""
    },
    "formula": "",
    "save": {
      "ability": "wis",
      "dc": 13,
      "scaling": "spell"
    },
    "level": 1,
    "school": "enc",
    "components": {
      "value": "",
      "vocal": true,
      "somatic": true,
      "material": true,
      "ritual": false,
      "concentration": true
    },
    "materials": {
      "value": "Tiny tarts and a feather that is waved in the air",
      "consumed": false,
      "cost": 0,
      "supply": 0
    },
    "preparation": {
      "mode": "prepared",
      "prepared": false
    },
    "scaling": {
      "mode": "none",
      "formula": ""
    },
    "attunement": 0
  },
  "effects": [
    {
      "_id": "2uNNhG0teoyGCX2W",
      "changes": [
        {
          "key": "flags.midi-qol.OverTime",
          "mode": 5,
          "value": "turn=end,saveAbility=wis,saveDC=@attributes.spelldc,label=Tashas Hideous Laughter",
          "priority": "20"
        },
        {
          "key": "macro.itemMacro",
          "mode": 0,
          "value": "",
          "priority": "20"
        }
      ],
      "disabled": false,
      "duration": {
        "startTime": null
      },
      "icon": "icons/creatures/unholy/demon-fire-horned-mask.webp",
      "label": "Tasha's Hideous Laughter",
      "transfer": false,
      "flags": {
        "core": {
          "statusId": ""
        },
        "dae": {
          "stackable": "none",
          "macroRepeat": "none",
          "specialDuration": [],
          "transfer": false,
          "durationExpression": ""
        },
        "dnd5e-helpers": {
          "rest-effect": "Ignore"
        },
        "ActiveAuras": {
          "isAura": false,
          "aura": "None",
          "radius": null,
          "alignment": "",
          "type": "",
          "ignoreSelf": false,
          "height": false,
          "hidden": false,
          "displayTemp": false,
          "hostile": false,
          "onlyOnce": false
        }
      },
      "tint": "#000000",
      "selectedKey": [
        "flags.midi-qol.OverTime",
        "macro.itemMacro"
      ]
    }
  ],
  "flags": {
    "midi-qol": {
      "effectActivation": false,
      "onUseMacroName": "[postActiveEffects]ItemMacro"
    },
    "core": {
      "sourceId": "Item.Nyl9SSFSG5lloLF0"
    },
    "itemacro": {
      "macro": {
        "data": {
          "_id": null,
          "name": "Tasha's Hideous Laughter (TPosney)",
          "type": "script",
          "author": "Xv8kOpT0YuRo8rso",
          "img": "icons/svg/dice-target.svg",
          "scope": "global",
          "command": "async function wait(ms) { return new Promise(resolve => { setTimeout(resolve, ms); }); }\n\nif (args[0].tag === \"OnUse\") {\n    let checkProne;\n    for (let targetUuid of args[0].failedSaveUuids) {\n        checkProne = await game.dfreds.effectInterface.hasEffectApplied('Prone', targetUuid);\n        if (!checkProne) {\n            await game.dfreds.effectInterface.addEffect({ effectName: 'Prone', uuid: targetUuid});\n        }\n    }\n    return;\n}\n\nconst lastArg = args[args.length - 1];\nconst tokenD = canvas.tokens.get(lastArg.tokenId);\nconst actorD = canvas.tokens.get(lastArg.tokenId).actor;\nconst itemD = lastArg.efData.flags.dae.itemData;\nconst origin = lastArg.origin;\nconst itemUuid = await fromUuid(origin);\nconst caster = itemUuid.actor;\nconst gameRound = game.combat ? game.combat.round : 0;\nconst tokenUuid = canvas.tokens.get(lastArg.tokenId).actor.uuid;\n\nasync function damageCheck(workflow) {\n    console.warn(\"args inside damageCheck: \", args)\n    let effectData = [{\n        label: \"Damage Save\",\n        icon: \"icons/skills/wounds/injury-triple-slash-bleed.webp\",\n        origin: origin,\n        disabled: false,\n        flags: { dae: { specialDuration: [\"isDamaged\"] } },\n        duration: { rounds: 10, seconds: 60, startRound: gameRound, startTime: game.time.worldTime },\n        changes: [{ key: `flags.midi-qol.advantage.ability.save.all`, mode: 2, value: 1, priority: 20 }]\n    }];\n    let damageSave = actorD.effects.find(i => i.data.label === \"Damage Save\");\n    if (!damageSave) await MidiQOL.socket().executeAsGM(\"createEffects\", { actorUuid: lastArg.actorUuid, effects: effectData });\n    await wait(600);\n    let attackWorkflow = workflow.damageList.map((i) => ({ tokenId: i?.tokenId, totalDamage: i?.totalDamage })).filter(i => i.tokenId === tokenD.id);\n    let lastAttack = attackWorkflow[attackWorkflow.length - 1];\n    if (lastAttack?.totalDamage > 0) {\n        let workflow = await MidiQOL.Workflow.getWorkflow(origin);\n        workflow.advantage = true;\n        let itemCard = await MidiQOL.showItemCard.bind(workflow.item)(false, workflow, false);\n        workflow.itemCardId = await itemCard.id;\n        await workflow.checkSaves(false);\n        await workflow.displaySaves(false, true);\n        let save = await workflow.saveResults[0];\n        let DC = workflow.item.data.data.save.dc;\n        game.dice3d?.showForRoll(save);\n        await ui.chat.scrollBottom();\n        if (save.total >= DC) {\n            let removeConc = caster.effects.find(i => i.data.label === \"Concentrating\");\n            if (removeConc) await MidiQOL.socket().executeAsGM(\"removeEffects\", { actorUuid: caster.uuid, effects: [removeConc.id] });\n        } else {\n            ChatMessage.create({\n                user: game.user._id,\n                speaker: ChatMessage.getSpeaker({ token: tokenD.document }),\n                content: `${tokenD.name} laughs maniacally`,\n                type: CONST.CHAT_MESSAGE_TYPES.EMOTE\n            });\n        }\n    }\n}\n\nif (args[0] === \"on\") {\n    let hookId = await Hooks.on(\"midi-qol.DamageRollComplete\", damageCheck);\n    await DAE.setFlag(actorD, \"hLaughter\", hookId);\n    if (!(game.modules.get(\"jb2a_patreon\")?.active || game.modules.get(\"JB2A_DnD5e\")?.active)) return {};\n    if (!(game.modules.get(\"sequencer\")?.active)) return {};\n    console.warn(\"tokenD\", tokenD);\n    new Sequence()\n        .effect()\n        .file(\"jb2a.toll_the_dead.purple.skull_smoke\")\n        .atLocation(tokenD)\n        .scaleToObject(1.5)\n        .waitUntilFinished(-500)\n    .play()\n}\n\nif (args[0] === \"off\") {\n    let hookId = await DAE.getFlag(actorD, \"hLaughter\");\n    await Hooks.off(\"midi-qol.DamageRollComplete\", hookId);\n    await DAE.unsetFlag(actorD, \"hLaughter\");\n    let conc = caster.effects.find(i => i.data.label === \"Concentration\");\n    if (conc) await MidiQOL.socket().executeAsGM(\"removeEffects\", { actorUuid: caster.uuid, effects: [conc.id] });\n}",
          "folder": null,
          "sort": 0,
          "permission": {
            "default": 0
          },
          "flags": {}
        }
      }
    },
    "exportSource": {
      "world": "tesira",
      "system": "dnd5e",
      "coreVersion": "9.249",
      "systemVersion": "1.5.7"
    },
    "scene-packer": {
      "hash": "1eb7e56d5929032bc6d02692d6f045850fda3f8f",
      "sourceId": "Item.ntH9EDjGoPzrzoTq"
    },
    "betterRolls5e": {
      "quickOther": {
        "context": "",
        "value": true,
        "altValue": true
      },
      "quickDamage": {
        "context": {
          "0": ""
        }
      },
      "quickDesc": {
        "value": true,
        "altValue": true
      },
      "quickSave": {
        "value": true,
        "altValue": true
      },
      "quickProperties": {
        "value": true,
        "altValue": true
      },
      "quickVersatile": {
        "value": false,
        "altValue": false
      },
      "quickFlavor": {
        "value": true,
        "altValue": true
      }
    }
  }
}
the-real-orson commented 2 years ago

Finally spun back around to this, missed the alert email...

Thanks for looking into this!