Skip to content

Instantly share code, notes, and snippets.

@scooper4711
Created May 19, 2025 01:37
Show Gist options
  • Select an option

  • Save scooper4711/e835b45442812828e00215c77681cc01 to your computer and use it in GitHub Desktop.

Select an option

Save scooper4711/e835b45442812828e00215c77681cc01 to your computer and use it in GitHub Desktop.
Pathfinder Society Challenge Point Calculator
// This FoundryVTT macro will calculate the challenge points for
// a pathfinder society game.
// It pops up a dialog asking how many players (2-6), and
// the lowest allowed level for the adventure.
// Based on those answers, it will ask for the level for each
// character.
// it will then output a chat card with the number of pre-generated
// NPCs needed, the level for the pre-gens and how many
// Challenge points.
// Challenge points are calculated according to the following formula
// a PC at the lowest allowed level gives 2 challenge points.
// For each level above that, give one more challenge point.
// So a PC that is level 5 for an adventure that is from levels 3-6
// gives 4 challenge points.
// After adding up the challenge points, if the table has fewer than 4
// PCs, then use the following rules to determine how many pre-gen
// NPCS to add to the party and how they impact the party Challenge points.
// Lowest allowed level: 1 ; PCs: 2; CP: <8 => 2 level 1 pregens and add 4 CP
// Lowest allowed level: 1 ; PCs: 2; CP: 8+ => 2 level 3 pregens and add 8 CP
// Lowest allowed level: 1 ; PCs: 3; CP: <12 => 1 level 1 pregens and add 2 CP
// Lowest allowed level: 1 ; PCs: 3; CP: 12+ => 1 level 3 pregens and add 4 CP
// Lowest allowed level: 3 ; PCs: 2; CP: <8 => 2 level 3 pregens and add 4 CP
// Lowest allowed level: 3 ; PCs: 2; CP: 8+ => 2 level 5 pregens and add 8 CP
// Lowest allowed level: 3 ; PCs: 3; CP: <12 => 1 level 3 pregens and add 2 CP
// Lowest allowed level: 3 ; PCs: 3; CP: 12+ => 1 level 5 pregens and add 4 CP
// Lowest allowed level: 5 ; PCs: 2; CP: any => 2 level 5 pregens and add 4 CP
// Lowest allowed level: 5 ; PCs: 3; CP: any => 1 level 5 pregens and add 2 CP
// Lowest allowed level: 7 ; PCs: 3; CP: <12 => no pregens and add 2 CP
// Lowest allowed level: 7 ; PCs: 3; CP: 12+ => no pregens and add 4 CP
async function computePFSChallenge() {
// Dialog 1: Number of Players and Lowest Adventure Level
const initialDialogContent = `
<form>
<div class="form-group">
<label for="numPlayers">Number of Players (2-6):</label>
<input type="number" id="numPlayers" name="numPlayers" value="4" min="2" max="6" style="width: 80px;" required />
</div>
<div class="form-group">
<label for="lowestAdventureLevel">Lowest Allowed Adventure Level:</label>
<input type="number" id="lowestAdventureLevel" name="lowestAdventureLevel" value="1" min="1" style="width: 80px;" required />
</div>
</form>
`;
new Dialog({
title: "Pathfinder Society - Party Setup",
content: initialDialogContent,
buttons: {
next: {
icon: "<i class='fas fa-arrow-right'></i>",
label: "Next",
callback: async (html) => {
const numPlayers = parseInt(html.find("#numPlayers").val());
const lowestAdventureLevel = parseInt(html.find("#lowestAdventureLevel").val());
if (isNaN(numPlayers) || numPlayers < 2 || numPlayers > 6) {
ui.notifications.error("Number of players must be between 2 and 6.");
return;
}
if (isNaN(lowestAdventureLevel) || lowestAdventureLevel < 1) {
ui.notifications.error("Lowest adventure level must be a positive integer.");
return;
}
await promptForCharacterLevels(numPlayers, lowestAdventureLevel);
}
},
cancel: {
icon: "<i class='fas fa-times'></i>",
label: "Cancel"
}
},
default: "next",
render: data => { $(data[0]).find("#numPlayers").focus(); }
}).render(true);
}
async function promptForCharacterLevels(numPlayers, lowestAdventureLevel) {
let characterLevelInputs = "";
for (let i = 1; i <= numPlayers; i++) {
characterLevelInputs += `
<div class="form-group">
<label for="playerLevel${i}">Level for Player ${i}:</label>
<input type="number" id="playerLevel${i}" name="playerLevel${i}" value="${lowestAdventureLevel}" min="1" style="width: 80px;" required />
</div>
`;
}
const levelDialogContent = `<form>${characterLevelInputs}</form>`;
new Dialog({
title: "Pathfinder Society - Character Levels",
content: levelDialogContent,
buttons: {
calculate: {
icon: "<i class='fas fa-calculator'></i>",
label: "Calculate",
callback: (html) => {
const playerLevels = [];
let validInput = true;
for (let i = 1; i <= numPlayers; i++) {
const level = parseInt(html.find(`#playerLevel${i}`).val());
if (isNaN(level) || level < 1) {
ui.notifications.error(`Invalid level for Player ${i}. Please enter a positive integer.`);
validInput = false;
break;
}
playerLevels.push(level);
}
if (validInput) {
calculateAndDisplayChallenge(numPlayers, lowestAdventureLevel, playerLevels);
}
}
},
cancel: {
icon: "<i class='fas fa-times'></i>",
label: "Cancel"
}
},
default: "calculate",
render: data => { $(data[0]).find("#playerLevel1").focus(); }
}).render(true);
}
function calculateAndDisplayChallenge(numPlayers, lowestAdventureLevel, playerLevels) {
// Calculate Initial Player CP
let initialPlayerCP = 0;
playerLevels.forEach(level => {
initialPlayerCP += 2 + Math.max(0, level - lowestAdventureLevel);
});
let numPregensNeeded = 0;
let pregenLevelForDisplay = "N/A";
let additionalCPFromRules = 0;
if (numPlayers < 4) {
if (lowestAdventureLevel === 1) {
if (numPlayers === 2) {
if (initialPlayerCP < 8) { numPregensNeeded = 2; pregenLevelForDisplay = 1; additionalCPFromRules = 4; }
else { numPregensNeeded = 2; pregenLevelForDisplay = 3; additionalCPFromRules = 8; }
} else if (numPlayers === 3) {
if (initialPlayerCP < 12) { numPregensNeeded = 1; pregenLevelForDisplay = 1; additionalCPFromRules = 2; }
else { numPregensNeeded = 1; pregenLevelForDisplay = 3; additionalCPFromRules = 4; }
}
} else if (lowestAdventureLevel === 3) {
if (numPlayers === 2) {
if (initialPlayerCP < 8) { numPregensNeeded = 2; pregenLevelForDisplay = 3; additionalCPFromRules = 4; }
else { numPregensNeeded = 2; pregenLevelForDisplay = 5; additionalCPFromRules = 8; }
} else if (numPlayers === 3) {
if (initialPlayerCP < 12) { numPregensNeeded = 1; pregenLevelForDisplay = 3; additionalCPFromRules = 2; }
else { numPregensNeeded = 1; pregenLevelForDisplay = 5; additionalCPFromRules = 4; }
}
} else if (lowestAdventureLevel === 5) {
if (numPlayers === 2) { numPregensNeeded = 2; pregenLevelForDisplay = 5; additionalCPFromRules = 4; }
else if (numPlayers === 3) { numPregensNeeded = 1; pregenLevelForDisplay = 5; additionalCPFromRules = 2; }
} else if (lowestAdventureLevel === 7) {
if (numPlayers === 3) { // Only rule for Lvl 7 is for 3 PCs
numPregensNeeded = 0; // "no pregens"
pregenLevelForDisplay = "N/A (No Pre-gens)";
if (initialPlayerCP < 12) { additionalCPFromRules = 2; }
else { additionalCPFromRules = 4; }
}
// If numPlayers === 2 and lowestAdventureLevel === 7, no specific rule applies from the table.
}
// If lowestAdventureLevel is not 1, 3, 5, or 7, no pre-gen rules from the table apply.
}
const finalChallengePoints = initialPlayerCP + additionalCPFromRules;
// Output Chat Card
let content = `
<h2>Pathfinder Society Challenge Calculation</h2>
<p><strong>Number of Players:</strong> ${numPlayers}</p>
<p><strong>Lowest Allowed Adventure Level:</strong> ${lowestAdventureLevel}</p>
<p><strong>Player Levels:</strong> ${playerLevels.join(', ')}</p>
<hr>
<p><strong>Initial Challenge Points (from PCs):</strong> ${initialPlayerCP}</p>
`;
let pregenInfoHtml = "";
if (numPlayers < 4) {
// Check if any pre-gen rules were triggered or explicitly mentioned (like Lvl 7, 3 PCs)
const rulesApplied = (lowestAdventureLevel === 1 && (numPlayers === 2 || numPlayers === 3)) ||
(lowestAdventureLevel === 3 && (numPlayers === 2 || numPlayers === 3)) ||
(lowestAdventureLevel === 5 && (numPlayers === 2 || numPlayers === 3)) ||
(lowestAdventureLevel === 7 && numPlayers === 3);
if (rulesApplied) {
pregenInfoHtml = `
<hr>
<p><strong>Pre-generation & Adjustments (for <4 PCs):</strong></p>
<ul>
<li>Pre-generated NPCs Needed: ${numPregensNeeded}</li>
<li>Level for Pre-gens: ${pregenLevelForDisplay}</li>
<li>Challenge Points Added by Rules: ${additionalCPFromRules}</li>
</ul>
`;
} else {
pregenInfoHtml = `
<hr>
<p><em>No specific pre-generation rules from the table applied for ${numPlayers} PCs at lowest adventure level ${lowestAdventureLevel}.</em></p>
`;
}
}
content += pregenInfoHtml;
content += `<hr><p><strong>Total Final Challenge Points:</strong> ${finalChallengePoints}</p>`;
content += `<p><small><em>Challenge Points for PCs: 2 CP per PC at lowest adventure level, +1 CP for each additional level. Pre-gen rules apply for parties with fewer than 4 PCs based on specific conditions.</em></small></p>`;
ChatMessage.create({
user: game.user?.id || null,
speaker: ChatMessage.getSpeaker({ alias: "PFS Challenge Calculator" }),
content: content,
type: CONST.CHAT_MESSAGE_TYPES.OOC
});
}
// Start the process
computePFSChallenge();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment