Last active
June 18, 2025 07:50
-
-
Save thejoester/18864396a8d6a1de09bc9db61ace2f4e to your computer and use it in GitHub Desktop.
PF2e Hero Point macro
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(async () => { | |
if (!game.user.isGM) { | |
ui.notifications.warn("Only the GM can award Hero Points."); | |
return; | |
} | |
let formData; | |
try { | |
formData = await foundry.applications.api.DialogV2.prompt({ | |
window: { title: "Award Hero Points" }, | |
content: ` | |
<div style="display: flex; align-items: center; gap: 1em;"> | |
<input id="hero-amount" name="hero-amount" type="number" min="0" max="3" step="1" value="1" style="width: 60px;" /> | |
<label><input type="radio" name="mode" value="add" checked> Add</label> | |
<label><input type="radio" name="mode" value="set"> Set</label> | |
</div> | |
`, | |
ok: { | |
label: "Apply", | |
callback: (event, button) => { | |
const val = button.form.elements["hero-amount"].valueAsNumber; | |
const mode = button.form.elements["mode"].value; | |
if (val < 0 || val > 3) throw new Error("Enter a number between 0 and 3."); | |
return { amount: val, mode }; | |
} | |
} | |
}); | |
} catch { | |
console.log("Hero Point award canceled."); | |
return; | |
} | |
const { amount, mode } = formData; | |
const awarded = []; | |
const updatedActorIds = new Set(); | |
const partyActors = game.actors.party?.members ?? []; | |
if (partyActors.length === 0) { | |
ui.notifications.warn("No party members found in the Party group."); | |
return; | |
} | |
for (const actor of partyActors) { | |
if (!actor || updatedActorIds.has(actor.id)) continue; | |
const heroPoints = actor.system?.resources?.heroPoints; | |
if (!heroPoints) continue; | |
const current = heroPoints.value ?? 0; | |
const max = heroPoints.max ?? 3; | |
const newTotal = mode === "set" ? Math.min(amount, max) : Math.min(current + amount, max); | |
await actor.update({ "system.resources.heroPoints.value": newTotal }); | |
awarded.push(`<strong>${actor.name}</strong>: ${current} → ${newTotal}`); | |
updatedActorIds.add(actor.id); | |
} | |
if (awarded.length === 0) { | |
ui.notifications.info("No eligible actors were updated."); | |
return; | |
} | |
const flavorText = ` | |
<div style=" | |
background-color:#1d1c1a; | |
border: 2px solid #5f574e; | |
box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.6); | |
border-radius: 12px; | |
padding: 16px; | |
color: #e4ddc7; | |
font-family: 'serif'; | |
max-width: 500px; | |
margin: auto; | |
"> | |
<div style="text-align: center; margin-bottom: 10px;"> | |
<img src="systems/pf2e/icons/features/feats/heroic-recovery.webp" width="64" height="64" style="border: none;"> | |
</div> | |
<div style="text-align: center; font-weight: bold; font-size: 16px; letter-spacing: 1px; color: #c7b26f; margin-bottom: 12px;"> | |
HEROIC INSPIRATION | |
</div> | |
<div style="font-size: 14px; font-style: italic; margin-bottom: 12px; text-align: center;"> | |
Fortune favors the bold.<br>The tale shifts, a second chance emerges... | |
</div> | |
<hr style="border: 1px solid #5f574e;"> | |
<div style="font-size: 15px; text-align: center; margin: 12px 0;"> | |
${mode === "set" ? `Set Hero Points to <strong>${amount}</strong> for each:` : `Awarded <strong>${amount}</strong> Hero Point${amount > 1 ? "s" : ""} to each:`} | |
</div> | |
<div style="text-align: center; font-size: 14px; margin-bottom: 10px;"> | |
${awarded.join("<br>")} | |
</div> | |
<hr style="border: 1px solid #5f574e;"> | |
<div style="font-size: 12px; font-style: italic; text-align: center; color: #a09888; margin-top: 6px;"> | |
Their destinies are not yet sealed. | |
</div> | |
</div> | |
`; | |
await ChatMessage.create({ content: flavorText, whisper: [game.user.id] }); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment