Skip to content

Commit

Permalink
PF2e Encounter Builder (#195)
Browse files Browse the repository at this point in the history
* add: encounter class for pf2e

* edit:renamed function

* add & refactor: added encounter difficulty and some formatting

* edit:add pf2e to xp system selection

* refactor: formatting

* refactor: implement PR changes

* edit: Add trivial coloring (gray)

* edit: edit cssClass comment

* edit: add correct logic for threat levels

* fix: prevent .split null calls

* edit: fix tracker bar labels

* fix: PR changes

---------

Co-authored-by: Caedis <[email protected]>
  • Loading branch information
lslavkov and Caedis authored Aug 2, 2023
1 parent 466f48a commit 618c78d
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 5 deletions.
3 changes: 3 additions & 0 deletions src/encounter/ui/Encounter.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,9 @@
.easy .difficulty-label {
color: green;
}
.trivial .difficulty-label {
color: #AAAAAA;
}
.icons {
display: flex;
}
Expand Down
3 changes: 3 additions & 0 deletions src/encounter/ui/EncounterRow.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@
.easy .difficulty-label {
color: green;
}
.trivial .difficulty-label {
color: #AAAAAA;
}
.icons {
display: flex;
}
Expand Down
8 changes: 6 additions & 2 deletions src/tracker/ui/Difficulty.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import { tweened } from "svelte/motion";
import { cubicOut } from "svelte/easing";
import { getContext } from "svelte";
import { getRpgSystem } from "src/utils"
import type { RpgSystem } from "src/utils/rpg-system/rpgSystem";
import type InitiativeTracker from "src/main";
import { tracker } from "../stores/tracker";
Expand All @@ -11,6 +13,8 @@
const dif = difficulty(plugin);
const rpgSystem: RpgSystem = getRpgSystem(plugin);
const difficultyBar = tweened(0, {
duration: 400,
easing: cubicOut
Expand All @@ -26,7 +30,7 @@
</script>

<div class="difficulty-bar-container" aria-label={summary}>
<span>Easy</span>
<span>{rpgSystem.SystemDifficulties[0]}</span>

This comment has been minimized.

Copy link
@Caedis

Caedis Aug 2, 2023

Author Contributor

Can you update this and line 44 to .systemDifficulties? I forgot to check it.

<span
><meter
class="difficulty-bar"
Expand All @@ -37,7 +41,7 @@
value={$difficultyBar}
/></span
>
<span>Deadly</span>
<span>{rpgSystem.SystemDifficulties.slice(-1)}</span>
</div>

<style>
Expand Down
5 changes: 5 additions & 0 deletions src/utils/rpg-system/dnd5e-lazygm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ export class Dnd5eLazyGmRpgSystem extends RpgSystem {
plugin: InitiativeTracker;
dnd5eRpgSystem: Dnd5eRpgSystem;

override systemDifficulties: [string, string, ...string[]] = [
"Not Deadly",
"Deadly"
]

constructor(plugin: InitiativeTracker) {
super();
this.plugin = plugin;
Expand Down
7 changes: 7 additions & 0 deletions src/utils/rpg-system/dnd5e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ const XP_PER_CR: Record<string, number> = {
export class Dnd5eRpgSystem extends RpgSystem {
plugin: InitiativeTracker;

override systemDifficulties: [string, string, ...string[]] = [
"Easy",
"Medium",
"Hard",
"Deadly"
]

constructor(plugin: InitiativeTracker) {
super();
this.plugin = plugin;
Expand Down
14 changes: 11 additions & 3 deletions src/utils/rpg-system/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ import type { SRDMonster } from "../../../index";
import type InitiativeTracker from "../../main";
import { Dnd5eRpgSystem } from "./dnd5e";
import { Dnd5eLazyGmRpgSystem } from "./dnd5e-lazygm";
import { Pathfinder2eRpgSystem } from "./pf2e"
import { RpgSystem } from "./rpgSystem";
import { DEFAULT_UNDEFINED } from "../constants";

export type GenericCreature = Creature | SRDMonster;

export type DifficultyLevel = {
/** Name of the difficulty level, eg "trivial". Used to display the difficulty level in encounters. */
displayName: string,
/** The CSS class to apply when formatting the display name. */
/** The CSS class to apply when formatting the display name. Should map to DnD 5e thresholds (low, easy, medium, hard, extreme) + trivial
* so systems do not have have their own custom styling.
*/
cssClass: string,
/** Associated value for the difficulty level. This should be the value used to calculate the difficulty level. */
value: number,
Expand Down Expand Up @@ -49,11 +53,14 @@ export type IntermediateValues = { label: string, value: number }[]

export enum RpgSystemSetting {
Dnd5e = "dnd5e",
Dnd5eLazyGm = "dnd5e-lazygm"
Dnd5eLazyGm = "dnd5e-lazygm",
Pathfinder2e = "pathfinder2e"
}


class UndefinedRpgSystem extends RpgSystem {}
class UndefinedRpgSystem extends RpgSystem {
systemDifficulties: [string, string, ...string[]] = [DEFAULT_UNDEFINED, DEFAULT_UNDEFINED];
}

/**
* Returns the RpgSystem associated with the settings value. If not provided,
Expand All @@ -63,6 +70,7 @@ export function getRpgSystem(plugin: InitiativeTracker, settingId?: string): Rpg
switch (settingId ? settingId : plugin.data.rpgSystem) {
case RpgSystemSetting.Dnd5e: return new Dnd5eRpgSystem(plugin);
case RpgSystemSetting.Dnd5eLazyGm: return new Dnd5eLazyGmRpgSystem(plugin);
case RpgSystemSetting.Pathfinder2e: return new Pathfinder2eRpgSystem(plugin);
}
return new UndefinedRpgSystem();
}
135 changes: 135 additions & 0 deletions src/utils/rpg-system/pf2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { RpgSystem } from './rpgSystem'
import { crToString, getFromCreatureOrBestiary } from '..'
import type { DifficultyLevel, GenericCreature, DifficultyThreshold } from '.'
import type InitiativeTracker from '../../main'

// level without proficiency variant
// const xpVariantCreatureDifferences = new Map([
// [-7, 9],
// [-6, 12],
// [-5, 14],
// [-4, 18],
// [-3, 21],
// [-2, 26],
// [-1, 32],
// [0, 40],
// [1, 48],
// [2, 60],
// [3, 72],
// [4, 90],
// [5, 108],
// [6, 135],
// [7, 160],
// ]);

const XP_CREATURE_DIFFERENCES: Record<string, number> = {
'-4': 10,
'-3': 15,
'-2': 20,
'-1': 30,
'0': 40,
'1': 60,
'2': 80,
'3': 120,
'4': 160,
}

const XP_SIMPLE_HAZARD_DIFFERENCES: Record<string, number> = {
'-4': 2,
'-3': 3,
'-2': 4,
'-1': 6,
'0': 8,
'1': 12,
'2': 16,
'3': 24,
'4': 32,
}

const PF2E_DND5E_DIFFICULTY_MAPPING: Record<string, string> = {
'trivial': 'trivial',
'low': 'easy',
'moderate': 'medium',
'severe': 'hard',
'extreme': 'deadly'
}

export class Pathfinder2eRpgSystem extends RpgSystem {
plugin: InitiativeTracker

override systemDifficulties: [string, string, ...string[]] = [
"Trivial",
"Low",
"Moderate",
"Severe",
"Extreme"
]

constructor(plugin: InitiativeTracker) {
super()
this.plugin = plugin
this.displayName = 'Pathfinder 2e'
}

getCreatureDifficulty(
creature: GenericCreature,
playerLevels?: number[]
): number {
const lvl = getFromCreatureOrBestiary(
this.plugin,
creature,
(c) => c?.level
)?.toString().split(' ').slice(-1)
if (lvl == null || lvl == undefined) return 0
const partyLvl = playerLevels?.length ?? 0 > 0 ? playerLevels.reduce((a, b) => a + b) / playerLevels.length : 0

const creature_differences = String(
lvl - partyLvl
)

return XP_CREATURE_DIFFERENCES[creature_differences] ?? 0
}

getDifficultyThresholds(playerLevels: number[]): DifficultyThreshold[] {
const budget = playerLevels.length * 20
const encounterBudget: Record<string, number> = {
trivial: Math.floor(budget * 0.5),
low: Math.floor(budget * 0.75),
moderate: budget,
severe: Math.floor(budget * 1.5),
extreme: Math.floor(budget * 2),
}
return Object.entries(encounterBudget).map(([name, value]) => ({
displayName: name.charAt(0).toUpperCase() + name.slice(1),
minValue: value
})).sort((a, b) => a.minValue - b.minValue)
}

getEncounterDifficulty(
creatures: Map<GenericCreature, number>,
playerLevels: number[]
): DifficultyLevel {
const creatureXp = [...creatures].reduce(
(acc, [creature, count]) =>
acc + this.getCreatureDifficulty(creature, playerLevels) * count,
0
)

const thresholds = this.getDifficultyThresholds(playerLevels)
const displayName = thresholds.find(threshold => creatureXp <= threshold.minValue)?.displayName ?? "Trivial"
const thresholdSummary = thresholds.map((threshold) => `${threshold.displayName}: ${threshold.minValue}`).join('\n')
const summary = `Encounter is ${displayName}
Total XP: ${creatureXp}
Threshold
${thresholdSummary}`;

return {
displayName,
summary,
cssClass: PF2E_DND5E_DIFFICULTY_MAPPING[displayName.toLowerCase()] ?? "trivial",
value: creatureXp,
title: "Total XP",
intermediateValues: [{ label: "Total XP", value: creatureXp }],
};
}
}
2 changes: 2 additions & 0 deletions src/utils/rpg-system/rpgSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { DEFAULT_UNDEFINED } from "../constants";
import type { GenericCreature, DifficultyLevel, DifficultyThreshold } from "./index";

export abstract class RpgSystem {
abstract systemDifficulties: [string, string, ...string[]];

/** The display name of the RPG system, used in the UI. */
displayName: string = DEFAULT_UNDEFINED;

Expand Down

0 comments on commit 618c78d

Please sign in to comment.