It can be a pain to remember when some reactions are triggered. You don't have to worry about that anymore! Well, specifically the Ferocity reaction, anyway. My friend Heidi D. made this script for me to use in my PF2e campaign. Compatible with the Pathfinder Second Edition by Roll20 sheet. Implements TokenMod script commands, and by default requires players have permission to use the --ids parameter. If you don't want to allow that, you can change `+tokenName+` (including the backticks) in line 80 to gm . Designed with the assumption that HP is tracked in bar1, so if you use a different bar be sure to change the references to bar1 in lines 50, 51, 72, 80, and 103. When a token's bar1 is changed to 0 on the active page, the script checks if the token represents a character. If the character has a Ferocity reaction, it whispers a reminder that that character can use a reaction to remain at 1 HP and that doing so increases the value of their wounded condition by 1. The message includes a hyperlink button that will send a TokenMod command to set that token's bar1 to a value of 1. If the character is a PC, it searches the sheet's Actions repeating section for an entry containing 'Ferocity' in the name and whispers the reminder to that character (based on token name). Note that this won't take into account the daily limitation of such an ability; it will trigger every time that character reaches 0 HP, even if they've already used their ability that day. If it's an NPC, it searches the same criteria in that sheet's Automatic and Reactive Abilities repeating section and whispers the reminder to the GM. This message also includes the restriction that the creature can't use the ability if they have the wounded condition with a value of 3. This is a first time for either of us to share a script in the forums, so we both hope you PF2e players find it handy! Feedback is welcome! =D AmIStillUp v0.3 var AmIStillUp = AmIStillUp || (function(){ 'use Strict' const version = '0.3'; const getActivePages = () => [...new Set([ Campaign().get('playerpageid'), ...Object.values(Campaign().get('playerspecificpages')), ...findObjs({ type: 'player', online: true }) .filter((p)=>playerIsGM(p.id)) .map((p)=>p.get('lastpage')) ]) ]; getRepeatingSectionAttrs = function (charid, prefix) { // Input // charid: character id // prefix: repeating section name, e.g. 'repeating_weapons' // Output // repRowIds: array containing all repeating section IDs for the given prefix, ordered in the same way that the rows appear on the sheet // repeatingAttrs: object containing all repeating attributes that exist for this section, indexed by their name const repeatingAttrs = {}, regExp = new RegExp(`^${prefix}_(-[-A-Za-z0-9]+?|\\d+)_`); let repOrder; // Get attributes findObjs({ _type: 'attribute', _characterid: charid }).forEach(o => { const attrName = o.get('name'); if (attrName.search(regExp) === 0) repeatingAttrs[attrName] = o; else if (attrName === `_reporder_${prefix}`) repOrder = o.get('current').split(','); }); if (!repOrder) repOrder = []; // Get list of repeating row ids by prefix from repeatingAttrs const unorderedIds = [...new Set(Object.keys(repeatingAttrs) .map(n => n.match(regExp)) .filter(x => !!x) .map(a => a[1]))]; const repRowIds = [...new Set(repOrder.filter(x => unorderedIds.includes(x)).concat(unorderedIds))]; //return [repRowIds, repeatingAttrs]; return [repeatingAttrs]; }, checkIfUp = function(obj, prev){ let charID = obj.get("represents"), currentHP = parseInt(obj.get('bar1_value')), prevCurrentHP = parseInt(prev['bar1_value']), damageTaken = prevCurrentHP - currentHP, tokenName = obj.get("name"), tokenID = obj.get("_id"), pages = getActivePages(); //log(damageTaken); if( !pages.includes(obj.get("pageid"))){ return; } if (charID !== "" && currentHP <= 0 && damageTaken > 0) { let deadChar = getObj('character', charID); //log(deadChar); let sheetType = getAttrByName(charID, 'sheet_type'); log(sheetType); if (sheetType === "npc"){ let npcAttrs = getRepeatingSectionAttrs(charID, 'repeating_free-actions-reactions'); //log(npcAttrs); npcAttrsString = JSON.stringify(npcAttrs); if (npcAttrsString.includes('Ferocity')) { sendChat(`Reminder`, `/w gm &{template:rolls} {{header=Ferocity↩️}} {{subheader=Can't use if **wounded 3**.}} {{desc=[**Use reaction to remain at 1 HP**](&`+`#96;!token-mod --ids `+tokenID+` --set bar1_value|1), then increase **wounded** value by **1**.}}`); } } else if (sheetType === "character"){ let charAttrs = getRepeatingSectionAttrs(charID, 'repeating_actions'); //log(charAttrs); charAttrsString = JSON.stringify(charAttrs); if (charAttrsString.includes('Ferocity')) { sendChat(`Reminder`, `/w `+tokenName+` &{template:rolls} {{header=Ferocity↩️}} {{desc=[**Use reaction to remain at 1 HP**](&`+`#96;!token-mod --ids `+tokenID+` --set bar1_value|1), then increase **wounded** value by **1**.}}`); } } else { log('Unknown sheet type. Unable to retrieve repeating attributes.') } } }, observeTokenChange = function (handler) { if (handler && _.isFunction(handler)) { observers.tokenChange.push(handler); } }, notifyObservers = function (event, obj, prev) { _.each(observers[event], function (handler) { handler(obj, prev); }); }, registerEventHandlers = function() { on("change:graphic:bar1_value", checkIfUp); if('undefined' !== typeof TokenMod && TokenMod.ObserveTokenChange){ TokenMod.ObserveTokenChange(checkIfUp); } }; return { ObserveTokenChange: observeTokenChange, RegisterEventHandlers: registerEventHandlers }; })(); on('ready', function() { 'use strict'; AmIStillUp.RegisterEventHandlers(); });