Skip to content
This repository has been archived by the owner on Oct 11, 2024. It is now read-only.

Commit

Permalink
#minor pf2e ALPHA release (#402)
Browse files Browse the repository at this point in the history
* pf2e attack roll and setup

* On new attack create new event

* Add spell and weapon damage

* Ability and Skill checks tracked

* Add tests

* Add verified pf2e system version

* Update README
  • Loading branch information
johnnolan authored Nov 24, 2022
1 parent 450d92c commit c14fa7a
Show file tree
Hide file tree
Showing 15 changed files with 967 additions and 305 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ test-results/
.vscode/
eslint_report.json
build-copy.js
build-copy-pi.js
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@

Capture your Players attacks, damage, healing, dice rolls, custom events and more. Store the results into a Journal for individual encounters and the entire campaign.

## Supported Systems

* `dnd5e`
* `pf2e` (**ALPHA** - please do not submit bug tickets yet, once in BETA I would love your feedback! See the issues on github tagged [PF2e](https://github.com/johnnolan/encounter-stats/labels/pf2e) for upcoming work)

**NOTE FOR MIDI-QOL USERS: You must use targeting in order for this module to record your stats correctly**

## Current features are

* Works with standard roles, `midi-qol` and standalone `dnd5e`
* Works with standard roles and `midi-qol`
* [Track your own custom campaign events via Macros](#track-your-own-custom-campaign-events)
* Enemies must be targetted in order for kills to be registered
* Multiple targets will show roll damage and roll damage x targets
Expand Down
3 changes: 2 additions & 1 deletion jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ module.exports = {
"!scripts/scss.ts",
"!scripts/Settings.ts",
"!scripts/Template.ts",
"!scripts/SetupHooks.ts",
"!scripts/SetupHooksPF2e.ts",
"!scripts/SetupHooksDND5e.ts",
"!scripts/EncounterJournal.ts",
"!scripts/**/*.test.ts",
"!scripts/types/*.ts",
Expand Down
9 changes: 9 additions & 0 deletions module.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@
"minimum": "2.0.2",
"verified": "2.0.2"
}
},
{
"id": "pf2e",
"type": "system",
"manifest": "https://github.com/foundryvtt/pf2e/releases/download/4.4.0/system.json",
"compatibility": {
"minimum": "4.4.0",
"verified": "4.4.1"
}
}
]
},
Expand Down
3 changes: 3 additions & 0 deletions scripts/Handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import CampaignStat from "./CampaignStat";
import { ChatType, RoleType } from "./enums";
import Logger from "./Helpers/Logger";
import { EncounterWorkflow } from "EncounterWorkflow";
import PF2eStat from "./stats/PF2eStat";

export function OnCustomEvent(customEvent: HookCustomEvent): void {
CampaignStat.AddCustomEvent(customEvent);
Expand Down Expand Up @@ -139,6 +140,8 @@ export async function OnEncounterWorkflowComplete(
let stat: DND5eStat | MidiQolStat;
if (chatType === ChatType.DND5e) {
stat = new DND5eStat();
} else if (chatType === ChatType.PF2e) {
stat = new PF2eStat();
} else if (chatType === ChatType.MidiQol) {
stat = new MidiQolStat();
} else {
Expand Down
309 changes: 6 additions & 303 deletions scripts/SetupHooks.ts
Original file line number Diff line number Diff line change
@@ -1,311 +1,14 @@
import {
OnRenderCombatTracker,
OnCreateCombat,
OnUpdateCombat,
OnEncounterWorkflowComplete,
OnUpdateHealth,
OnTrackKill,
OnTrackDice,
OnTrackDiceRoll,
OnCustomEvent,
OnTrackRollStreak,
} from "./Handlers";
import StatManager from "./StatManager";
import DND5e from "./parsers/DND5e";
import MidiQol from "./parsers/MidiQol";
import { CombatDetailType, ChatType } from "./enums";
import Stat from "./stats/Stat";
import SetupHooksDND5e from "./SetupHooksDND5e";
import SetupHooksPF2e from "./SetupHooksPF2e";

export default class SetupHooks {
static SOCKET_NAME = "module.encounter-stats";

static async Setup() {
if (game.user?.isGM) {
SetupHooks._setupSockerListeners();
window.Hooks.on(
"renderCombatTracker",
async function (
_combatTracker: CombatTracker,
_element: string,
combatData: HookRenderCombatTrackerData
) {
OnRenderCombatTracker(combatData);
}
);
window.Hooks.on("createCombat", async function (data: Combat) {
OnCreateCombat(data);
});
window.Hooks.on(
"updateCombat",
async function (_combat: Combat, data: HookUpdateCombatRound) {
OnUpdateCombat(data.round);
}
);

window.Hooks.on(
"updateActor",
async function (actor: Actor, diff: unknown) {
await SetupHooks.updateActorToken(actor, diff);
}
);

window.Hooks.on(
"updateToken",
async function (actor: Actor, diff: unknown) {
await SetupHooks.updateActorToken(actor, diff);
}
);

if (game.modules.get("midi-qol")?.active) {
window.Hooks.on(
"midi-qol.RollComplete",
async function (workflow: MidiQolWorkflow) {
const rollCheck = MidiQol.RollCheck(workflow);
const midiWorkflow = MidiQol.ParseWorkflow(workflow);
OnEncounterWorkflowComplete(midiWorkflow, ChatType.MidiQol);
OnTrackDice(rollCheck);

if (rollCheck && midiWorkflow) {
OnTrackRollStreak(
midiWorkflow.diceTotal,
rollCheck.name,
midiWorkflow.actor.id
);
}
}
);
}

Hooks.on(
"encounter-stats.customEvent",
async function (customEvent: HookCustomEvent) {
OnCustomEvent(customEvent);
}
);
} else {
window.Hooks.on(
"updateActor",
async function (actor: Actor, diff: unknown) {
game.socket?.emit(SetupHooks.SOCKET_NAME, {
event: "updateActor",
data: { data: actor, diff: diff },
});
}
);
window.Hooks.on(
"updateToken",
async function (actor: Actor, diff: unknown) {
game.socket?.emit(SetupHooks.SOCKET_NAME, {
event: "updateToken",
data: { data: actor, diff: diff },
});
}
);
if (game.modules.get("midi-qol")?.active) {
window.Hooks.on(
"midi-qol.RollComplete",
async function (workflow: MidiQolWorkflow) {
game.socket?.emit(SetupHooks.SOCKET_NAME, {
event: "midi-qol.RollComplete",
data: {
workflow: MidiQol.ParseWorkflow(workflow),
rollCheck: MidiQol.RollCheck(workflow),
},
});
}
);
}

window.Hooks.on("dnd5e.useItem", async function (item: Item) {
if (!game.modules.get("midi-qol")?.active) {
game.socket?.emit(SetupHooks.SOCKET_NAME, {
event: "dnd5e.useItem",
data: {
EncounterWorkflow: await DND5e.ParseHook(
item,
item.actor,
CombatDetailType.ItemCard,
undefined
),
ChatType: ChatType.DND5e,
},
});
}
});

window.Hooks.on(
"dnd5e.rollAttack",
async function (item: Item5e, roll: Roll) {
if (!game.modules.get("midi-qol")?.active) {
game.socket?.emit(SetupHooks.SOCKET_NAME, {
event: "dnd5e.rollAttack",
data: {
EncounterWorkflow: await DND5e.ParseHook(
item,
item.actor,
CombatDetailType.Attack,
roll
),
ChatType: ChatType.DND5e,
},
});
}
}
);

window.Hooks.on(
"dnd5e.rollDamage",
async function (item: Item5e, roll: Roll) {
if (!game.modules.get("midi-qol")?.active) {
game.socket?.emit(SetupHooks.SOCKET_NAME, {
event: "dnd5e.rollDamage",
data: {
EncounterWorkflow: await DND5e.ParseHook(
item,
item.actor,
CombatDetailType.Damage,
roll
),
ChatType: ChatType.DND5e,
},
});
}
}
);

window.Hooks.on(
"dnd5e.rollAbilityTest",
async function (actor: Actor, roll: Roll) {
game.socket?.emit(SetupHooks.SOCKET_NAME, {
event: "dnd5e.rollAbilityTest",
data: {
result:
roll?.terms[0]?.results?.find((f) => f.active === true)
.result ?? 0,
alias: actor.name,
actorId: actor.id,
flavor: roll.options.flavor,
},
});
}
);

window.Hooks.on(
"dnd5e.rollAbilitySave",
async function (actor: Actor, roll: Roll) {
game.socket?.emit(SetupHooks.SOCKET_NAME, {
event: "dnd5e.rollAbilitySave",
data: {
result:
roll?.terms[0]?.results?.find((f) => f.active === true)
.result ?? 0,
alias: actor.name,
actorId: actor.id,
flavor: roll.options.flavor,
},
});
}
);

window.Hooks.on(
"dnd5e.rollSkill",
async function (actor: Actor, roll: Roll) {
game.socket?.emit(SetupHooks.SOCKET_NAME, {
event: "dnd5e.rollSkill",
data: {
result:
roll?.terms[0]?.results?.find((f) => f.active === true)
.result ?? 0,
alias: actor.name,
actorId: actor.id,
flavor: roll.options.flavor,
},
});
}
);

window.Hooks.on(
"encounter-stats.customEvent",
async function (customEvent: HookCustomEvent) {
game.socket?.emit(SetupHooks.SOCKET_NAME, {
event: "encounter-stats.customEvent",
data: {
customEvent: customEvent,
},
});
}
);
}
}

static _setupSockerListeners() {
game.socket?.on(SetupHooks.SOCKET_NAME, async function (payload: unknown) {
switch (payload.event) {
case "encounter-stats.customEvent":
OnCustomEvent(payload.data.customEvent);
break;
case "midi-qol.RollComplete":
OnEncounterWorkflowComplete(payload.data.workflow, ChatType.MidiQol);
OnTrackDice(payload.data.rollCheck);
OnTrackRollStreak(
payload.data.workflow.diceTotal,
payload.data.rollCheck.name,
payload.data.workflow.actor.id
);
break;
case "dnd5e.rollAttack":
OnEncounterWorkflowComplete(
payload.data.EncounterWorkflow,
payload.data.ChatType
);
OnTrackRollStreak(
payload.data.EncounterWorkflow.diceTotal,
payload.data.EncounterWorkflow.actorName,
payload.data.EncounterWorkflow.actorId
);
break;
case "dnd5e.useItem":
case "dnd5e.rollDamage":
OnEncounterWorkflowComplete(
payload.data.EncounterWorkflow,
payload.data.ChatType
);
break;
case "dnd5e.rollAbilityTest":
case "dnd5e.rollAbilitySave":
case "dnd5e.rollSkill":
OnTrackDiceRoll(
payload.data.result,
payload.data.alias,
payload.data.flavor
);
OnTrackRollStreak(
payload.data.result,
payload.data.alias,
payload.data.actorId
);
break;
}
});
}

static async updateActorToken(actor: Actor, diff: unknown) {
if (StatManager.IsInCombat()) {
if (
actor.name &&
!actor.hasPlayerOwner &&
diff.system?.attributes?.hp?.value === 0 &&
game.combat?.current?.tokenId
) {
OnTrackKill(actor.name, game.combat.current.tokenId);
}
}
if (
diff.system?.attributes?.hp &&
actor.id &&
Stat.IsValidCombatant(actor?.type)
) {
OnUpdateHealth(actor);
if (game.system.id === "dnd5e") {
SetupHooksDND5e.Setup();
} else if (game.system.id === "pf2e") {
SetupHooksPF2e.Setup();
}
}
}
Loading

0 comments on commit c14fa7a

Please sign in to comment.