Trigger Happy: Traps

Hello! This a tutorial to make a trap for your players. when they step on it they will need to make a Saving throw. There is a bunch of different methods to do this, but this one will automate the process.

To get started you’ll need these modules:

Dynamic Effects.
Minor-QOL.
Furnace.
Trigger Happy.

Make sure you have them all installed and enabled. Make sure you also have a Trigger Happy Journal Entry or Folder.

There are 3 options on how to deal with “Traps”, each one as viable as the other.

Example 1 - Semi-Automated (Asks players to make a roll)
Example 2 - Fully Automated (Rolls for a player)
Example 3 - Pauses The Game. Can add Chat message**


Example 1 - Semi Automated

For this option, You’ll need The module: LMRTFY (Let me roll that for you)

Macro

To start off, make a macro.
Name it: DoTrapAttack
Make sure it is set to: Script
Disable Execute Macro As GM

Minor-QOL Macro
let tactor = game.actors.entities.find(a => a.name === args[0])
if (!tactor) return `/Whisper GM "DoTrap: Target token ${args[0]} not found"`
let item = tactor.items.find(i=>  i.name === args[1])
if (!item) return `/Whisper GM "DoTrap: Item ${args[1]} not found"`
let oldTargets = game.user.targets;
game.user.targets = new Set().add(token);

Hooks.once("MinorQolRollComplete", () => {
  MinorQOL.forceRollDamage=false;
  game.user.targets=oldTargets;
})
MinorQOL.forceRollDamage = true;
await MinorQOL.doCombinedRoll({actor, item, event, token})
let trapToken = canvas.tokens.placeables.find(t=>t.name === args[2])
if (trapToken) await DynamicEffects.setTokenVisibility(trapToken.id, true);

Midi-QOL Macro
// Requires midi-qol to work at all
// Rolls an attack from actor args[0] using item args[1] and display a token args[2]
let tactor = game.actors.entities.find(a => a.name === args[0])
if (!tactor) return `/Whisper GM "DoTrap: Target token ${args[0]} not found"`
let item = tactor.items.find(i=>  i.name === args[1])
if (!item) return `/Whisper GM "DoTrap: Item ${args[1]} not found"`
let trapToken = canvas.tokens.placeables.find(t=>t.name === args[2])
new MidiQOL.TrapWorkflow(tactor, item, [token], trapToken.center)

Save it.
Note: Nothing in this macro should be changed, other than the name.

Character, Token & Weapons

First character you have to create, is where the “Trap Damage/Saving Throw” is coming from. This needs to be an actor.

Create an actor, in this example, we will call it Traps.
Go inside this actors inventory, and create an Item, in this example, we’ll call it Spikes
When making this item, go to details.
Action Type -> Saving throw

Here you can mess around with the different settings, set how much damage etc.
For mine, I’ve made it into a sort of Pit Trap with spikes. They’ll have to make a dexterity saving throw against the DC of 15. Just set it to flat unless you want something special.

image

Important: You can make all the items on this character.

Now we’ll make the actual trap, so you can set those nice trap icons from 2minutetabletop.

Make a new character, I’ll call this actor Spike Trap. Set my actor token picture, and all of that nice stuff :wink:
Then drag it onto the board where you want the trap to be. On the board Double Right-click the token, and rename it. In my case, I’ll name it Trap4.
Make sure that it is not linked.
Update the token.
image

Now Hide the token.
image

Trigger Happy

Now Trigger Happy, open your Journal and you should do something along these lines(ctrl-shift-v to clear formatting on paste):

@Token[Trap4] @Trigger[capture move] @ChatMessage[/DoTrapAttack Traps "Spikes" Trap4]

Where:

Token[Trap4] is the token of the trap.
@Trigger[capture move] is “when this token is landed/walked on” - “When Trap4 is landed/walked on”
Do:
@chatMessage[/DoTrapAttack Traps "Spikes" Trap4
DoTrapAttack is the macro we made.
Traps is the Character that holds the trap items.
"Spikes" is the name of the Item it will use to determine damage and saving throw. Yes, it needs " ".
Trap4 on the end is what token to Unhide, in this case, it wants to reveal itself.

Settings

Last thing you’ll need to do, is just enable go into Minor-QOL settings, and set Prompt Players to Roll Saves to Let Me Roll That For You

image


Example 2 (Fully Automated)

Macro

To start off, make a macro.
Name it: DoTrapAttack
Make sure it is set to: Script
Disable Execute Macro As GM

Minor-QOL Macro
let tactor = game.actors.entities.find(a => a.name === args[0])
if (!tactor) return `/Whisper GM "DoTrap: Target token ${args[0]} not found"`
let item = tactor.items.find(i=>  i.name === args[1])
if (!item) return `/Whisper GM "DoTrap: Item ${args[1]} not found"`
let oldTargets = game.user.targets;
game.user.targets = new Set().add(token);

Hooks.once("MinorQolRollComplete", () => {
  MinorQOL.forceRollDamage=false;
  game.user.targets=oldTargets;
})
MinorQOL.forceRollDamage = true;
await MinorQOL.doCombinedRoll({actor, item, event, token})
let trapToken = canvas.tokens.placeables.find(t=>t.name === args[2])
if (trapToken) await DynamicEffects.setTokenVisibility(trapToken.id, true);

Midi-QOL Macro
// Requires midi-qol to work at all
// Rolls an attack from actor args[0] using item args[1] and display a token args[2]
let tactor = game.actors.entities.find(a => a.name === args[0])
if (!tactor) return `/Whisper GM "DoTrap: Target token ${args[0]} not found"`
let item = tactor.items.find(i=>  i.name === args[1])
if (!item) return `/Whisper GM "DoTrap: Item ${args[1]} not found"`
let trapToken = canvas.tokens.placeables.find(t=>t.name === args[2])
new MidiQOL.TrapWorkflow(tactor, item, [token], trapToken.center)

Save it.
Note: Nothing in this macro should be changed, other than the name.

Character, Token & Weapons

First character you have to create, is where the “Trap Damage/Saving Throw” is coming from. This needs to be an actor.

Create an actor, in this example, we will call it Traps.
Go inside this actors inventory, and create an Item, in this example, we’ll call it Spikes
When making this item, go to details.
Action Type -> Saving throw

Here you can mess around with the different settings, set how much damage etc.
For mine, I’ve made it into a sort of Pit Trap with spikes. They’ll have to make a dexterity saving throw against the DC of 15. Just set it to flat unless you want something special.

image

Important: You can make all the items on this character.

Now we’ll make the actual trap, so you can set those nice trap icons from 2minutetabletop.

Make a new character, I’ll call this actor Spike Trap. Set my actor token picture, and all of that nice stuff :wink:
Then drag it onto the board where you want the trap to be. On the board Double Right-click the token, and rename it. In my case, I’ll name it Trap4.
Make sure that it is not linked.
Update the token.

image

Now Hide the token.
image

Trigger Happy

Now Trigger Happy, open your Journal and you should do something along these lines(ctrl-shift-v to clear formatting on paste):

@Token[Trap4] @Trigger[capture move] @ChatMessage[/DoTrapAttack Traps "Spikes" Trap4]

Where:

Token[Trap4] is the token of the trap.
@Trigger[capture move] is “when this token is landed/walked on” - “When Trap4 is landed/walked on”
Do:
@chatMessage[/DoTrapAttack Traps "Spikes" Trap4
DoTrapAttack is the macro we made.
Traps is the Character that holds the trap items.
"Spikes" is the name of the Item it will use to determine damage and saving throw. Yes, it needs " ".
Trap4 on the end is what token to Unhide, in this case, it wants to reveal itself.

Settings

This will by default be automated.
If it isn’t, go into Minor-QOL settings and set Prompt Players to Roll Saves to chat


Example 3 - Pause Game

You’ll not need Minor QOL for this to work.

Macro

To pause the game when a trap is stepped on, and reveal the token you’ll need this macro:

Reveal Trap Macro:
Name it: RevealTrap
Make sure it is set to: Script
Enable Execute Macro As GM

game.togglePause(true, true); 
let trapToken = canvas.tokens.placeables.find(t=>t.name === args[0])
if (trapToken) await DynamicEffects.setTokenVisibility(trapToken.id, true);

image
Note: If you don’t want to pause, delete game.togglePause(true, true);

Token

Now we just need to make the trap token. First create a character, for this I’ll call it Bear Trap
Customize the token to what picture you like, I’ll give mine a neat Beartrap look.

Drag it onto the board. Now we’ll double right-click the token on the board to rename it. For this tutorial, I’ll call it BearTrap1.
Make sure that it is not linked.
Update The Token.
image

Trigger Happy

In Trigger Happy, you’ll just need to specify the name of the token you’ve made.

Mine looks like(ctrl-shift-v to clear formatting on paste):

@Token[BearTrap1] @Trigger[capture move] @ChatMessage[/RevealTrap BearTrap1] @ChatMessage[Someone Stepped in a trap!]

Where:

Token[BearTrap1] is the token of the trap.
@Trigger[capture move] is “when this token is landed/walked on” - “When BearTrap1 is landed/walked on”
Do:
@ChatMessage[/RevealTrap BearTrap1]
Where BearTrap1 is the trap we want to reveal.
@ChatMessage[Someone Stepped in a trap!] is giving the chat message “Someone Stepped in a trap!” in chat.


Troubleshooting

Folder

If you’re using a folder for Trigger Happy, that the Journal you’re using is not named “Trigger Happy”. This will cause it to trigger twice, and make some weird bugs. Just re-name the Journal Entry.

The Folder or Journal Entry is case sensitive. Make sure it is Capital T and H in Trigger Happy.

Token

Make sure that you’ve edited the Token Name. It doesn’t need to represent anything. Also, make sure that the Token is Not Linked.
image

Currently known Bugs:

Weapons
You should leave flavour text in the weapons empty. It might cause issues to the item.
image
If you want flavour text, either make a macro, or do @Chatmessage[YOU FOOL, STEPPED IN MY TRAP] in Trigger Happy.

GM & Players
There is currently a bug with what order people join the game in. Recommended is for the GM to join first, then the rest of the players. If there is multiple GM’s, it may cause problems sadly.

There have been a couple of issues with traps and minor-qol saving rolls.
The not applying damage issue should only be an issue when there was more than 1 dm logged in and then only for one of the dm clients. That should now be fixed.
The not rolling saving throws issue is (I think) related to clients not rgistering as logged on the dm client. That is a problem since minor-qol checks to see that the player is active (i.e. logged in and connected to the gm client) before using LMRTFY to roll saves.
When you notice saves not being rolled can you checck the player list and see if the player is listed on the gm client. I have noticed that refreshing the clients in the wrong order can cause the player not to be recognised by the gm client. The better order for refresh is GM first, wait for that to complete, then player, then check the client list on the gm.

One last thing to mention. The save is only requested for the “primary” (or whatever the word is) character for the player - the one listed in brackets after the player on the player list.
@tposney

Settings

I would recommend these settings:


image

Got any other issues?

You can either leave a comment here, in Kakarotos Discord Server, or in Foundrys #modules. Just ping @Kevin-#0001.


3 Likes

Hi. i should be a little dumb, but i really cant put this to work. If i hit the execute Macro, it simply doesn´t execute. Im trully sorry if im bothering you all, but can you help a friend ?
i made the trap, the char, walk over the trap with impunity lol. and nothing happens.

thanks for any help.

Hello there @rvianarpg, do you have a discord or something alike so I can help you out realtime?
Feel free to add me: Kevin-#0001
And I’ll help you the best I can. I forgot to add that part yet ^^
Thanks for reminding me I have to add the troubleshooting section.

Updated the troubleshooting portion now, feel free to join the discords, It is going to be hard to help out without screenshots and such ^^

The do trap attack macro should be updated to this which solves a race condition and will roll the damage even if auto roll damage is disabled by default.

let tactor = game.actors.entities.find(a => a.name === args[0])
if (!tactor) return `/Whisper GM "DoTrap: Target token ${args[0]} not found"`
let item = tactor.items.find(i=>  i.name === args[1])
if (!item) return `/Whisper GM "DoTrap: Item ${args[1]} not found"`
let oldTargets = game.user.targets;
game.user.targets = new Set().add(token);

Hooks.once("MinorQolRollComplete", () => {
  MinorQOL.forceRollDamage=false;
  game.user.targets=oldTargets;
})
MinorQOL.forceRollDamage = true;
await MinorQOL.doCombinedRoll({actor, item, event, token})
let trapToken = canvas.tokens.placeables.find(t=>t.name === args[2])
if (trapToken) await DynamicEffects.setTokenVisibility(trapToken.id, true);

Can you also update it for midi-qol?

I-I-I have, I think? Is it not working properly?

Yeah, that’s working. I thought it wasn’t updated and I would get some bugs at some point. Sorry. My bad.

Hello There !
(General Kenobi.)

I wonder if anyone tried to do traps with Trigger Happy, LMRTFY and The Furnace but without MidiQOL for Pathfinder 2e since MidiQOL doesn’t work for this one…
Please give it a thought !
Thank you in advance,
MJAjani

Hello @mjajani so I believe what you’re looking for is:

let tactor = game.actors.entities.find(a => a.name === args[0])
let item = tactor.items.find(i=>  i.name === args[1])
let damage = (await item.rollDamage({event: {shiftKey: true}})).total
let newHP = actor.data.data.attributes.hp.value - damage
await actor.update({"data.attributes.hp.value": newHP})

That is the only pf2 macro I know for this <3 It’s really old though so you would have to check it.

@Kevin I tried your macro, but it didn’t work because of the item.rollDamage function which don’t exist, according to the F12 console.
Yet, I’m not informed enough to know which function or variables exist on the PF2 world system.
Yet, I found a solution with LMRTFY and a few simple macros (three, to be exact), and here’s my solution for anyone interested.

First of all, go to your PF2 compendium with The Furnace and LMRTFY installed, and then import the roll-skill macro THREE TIMES, because we’ll need to adjust them according to which roll we want to do.
Second, modify one of your roll-skill macro to be a roll-ability or roll-saves macro. In the roll-saves scenario, do as follow :

/* This macro requires the advanced macros of Furnace and the LMRTFY module, v0.7+ 
 * This will request a saving throw for the currently selected player using LMRTFY roll request dialog
 * Takes the save as its first argument, or if not set, requests all
 * Example: /roll-saves "reflex"
 */

const saves = args[0]
if (!actor) return;
LMRTFYRoller.requestSavingThrows(actor, saves);

Do the same for the two others, replacing with the right function at the end.
Then setup your trap token, name it, hide it, just like usual.
Now on the Trigger Happy end :
If you want your player to roll a reflex save, do as follows :
@Token[trapName] @ChatMessage[/roll-saves reflex] @Trigger[capture move]
It will do the following thing :
image
For now, this technique does not allow to roll damage automatically, but I’m working on it. If any of you got any idea about how to improve this, feel free to answer this !

Thanks a lot
MJAjani

As of earlier this month, Dynamic Active Effects now requires dnd5e as a system. This renders many Trigger Happy functions unusable for any other game system. Is there any chance of a solution for this issue?

I need a version of this guide for pathfinder 1e in Foundry.

Just started using Trigger Happy - Traps, it seems to be working but for a couple of issues.

#1 the trap tokens do not reveal themselves if spung.
#2 Regardless of successfully making a given saving throw, the damage is still applied.

Are these two statements how the macro is supposed to function or do I have a setting wrong?

if you’re using the reveal macro, make the following change

game.togglePause(true, true); 
let trapToken = canvas.tokens.placeables.find(t=>t.name === args[0])
if (trapToken) await *DynamicEffects*.setTokenVisibility(trapToken.id, true);

//game.togglePause(true, true);
let trapToken = canvas.tokens.placeables.find(t=>t.name === args[0])
if (trapToken) await DAE.setTokenVisibility(trapToken.id, true);

Naturally you need to have the dynamic effects using active effects installed.

How do I hide or prevent chat messages from being broadcast to my “player” with these traps? I do not have actual “players” in Foundry. I have a table of face to face players looking at a TV screen. I control a token that represents the party during exploration from a second PC. Once combat begins everyone uses miniatures on top of the TV screen. Old school style. While I can hide the side bar, I don’t see any reason at all to send myself a message. Thoughts?

That would be in the Midi settings. You can configure what gets sent there :smiley:

I am still having a lot of problems getting this to work. . Can anyone help me?

Can anyone help. I have followed the directions for the automatic one but it is not working. I did notice the picture having a different macro than what is revealed when hitting the carrot. Any one else having issues.

Unable get teleport token to work ive uninstalled everything reinstalled from scratch downloaded the modules it was working in 0.7.9 but just updated to 0.8.5

The Document#entity property has been deprecated in favor of Document#documentName. Support will be removed in 0.9.0
get entity @ foundry.js:9728
_executeTriggers @ trigger.js:116
(anonymous) @ trigger.js:273
_call @ VM2912:4
callAll @ foundry.js:153
callback @ foundry.js:8863
(anonymous) @ foundry.js:8833
_handleUpdateEmbeddedDocuments @ foundry.js:8833
_updateEmbeddedDocuments @ foundry.js:8722
async function (async)
_updateEmbeddedDocuments @ foundry.js:8711
update @ backend.mjs:153
async function (async)
update @ backend.mjs:151
updateDocuments @ document.mjs:365
updateEmbeddedDocuments @ document.mjs:531
moveMany @ foundry.js:19561
_handleMovement @ foundry.js:812
(anonymous) @ foundry.js:781
setTimeout (async)
_onMovement @ foundry.js:781
_handleKeys @ foundry.js:558
_onKeyDown @ foundry.js:511

This is probably because furnace has not been updated, and none of the modules should be currently working properly on 0.8.*.

0.8.x is not compatible with this tutorial, but I’ll update it when it is.