Created
May 19, 2025 01:37
-
-
Save scooper4711/e835b45442812828e00215c77681cc01 to your computer and use it in GitHub Desktop.
Pathfinder Society Challenge Point Calculator
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
| // 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