diff --git a/module.json b/module.json
index 6a06b21..399eba0 100644
--- a/module.json
+++ b/module.json
@@ -1,24 +1,26 @@
{
- "id": "module",
- "title": "New Module",
- "description": "",
+ "id": "pf2e-rpg-numbers",
+ "title": "PF2E RPG Numbers",
+ "description": "A module that gives damage pop up text when you roll damage in pf2e.",
"version": "#{VERSION}#",
"library": "false",
"manifestPlusVersion": "1.2.0",
"compatibility": {
"minimum": 10,
- "verified": 10,
+ "verified": 11,
"maximum": 11
},
"authors": [
{
- "name": "The League of Extraordinary FVTT Developers",
- "url": "https://github.com/League-of-Foundry-Developers",
- "discord": "discordID#0001"
+ "name": "cadowtin"
}
],
"relationships": {
- "systems": [],
+ "systems": [{
+ "id": "pf2e",
+ "type": "system",
+ "manifest": "https://github.com/foundryvtt/pf2e/releases/latest/download/system.json"
+ }],
"requires": []
},
"esmodules": [
diff --git a/scripts/exampls.js b/scripts/exampls.js
new file mode 100644
index 0000000..cea342d
--- /dev/null
+++ b/scripts/exampls.js
@@ -0,0 +1,304 @@
+export const example_msg = {
+ "user": "J3oConv1eQRLao6j",
+ "type": 5,
+ "content": "8",
+ "sound": "sounds/dice.wav",
+ "speaker": {
+ "scene": "lQkXSdxvO9CRxohD",
+ "token": "dU9vPXfBrDzO2Cn5",
+ "actor": "dSV36QRQvNR4pwkF",
+ "alias": "Mirakk"
+ },
+ "flavor": "Damage Roll: Beak (Hit)
Attack
FinesseUnarmed
1d6 PiercingStrength +2
",
+ "flags": {
+ "core": {
+ "canPopout": true
+ },
+ "pf2e": {
+ "context": {
+ "type": "damage-roll",
+ "sourceType": "attack",
+ "actor": "dSV36QRQvNR4pwkF",
+ "token": "dU9vPXfBrDzO2Cn5",
+ "target": {
+ "actor": "Actor.nnav0XpUTVkI3bpI",
+ "token": "Scene.lQkXSdxvO9CRxohD.Token.28jYopJkmUUEkviT"
+ },
+ "domains": [
+ "drF7WOj3ddyS4i6z-damage",
+ "beak-damage",
+ "strike-damage",
+ "damage",
+ "unarmed-damage",
+ "brawling-weapon-group-damage",
+ "str-damage",
+ "trained-damage"
+ ],
+ "options": [
+ "armor:base:leather-armor",
+ "armor:category:light",
+ "armor:group:leather",
+ "armor:id:a4DFvEM5cg0GDYtc",
+ "attack",
+ "attack:advanced:rank:0",
+ "attack:martial:rank:1",
+ "attack:simple:rank:1",
+ "attack:unarmed:rank:1",
+ "attribute:cha:mod:2",
+ "attribute:con:mod:1",
+ "attribute:dex:mod:4",
+ "attribute:int:mod:0",
+ "attribute:str:mod:2",
+ "attribute:wis:mod:0",
+ "check:outcome:success",
+ "class:swashbuckler",
+ "defense:heavy-barding:rank:0",
+ "defense:heavy:rank:0",
+ "defense:light-barding:rank:0",
+ "defense:light:rank:1",
+ "defense:medium:rank:0",
+ "defense:unarmored:rank:1",
+ "deity",
+ "deity:primary:alignment:cn",
+ "deity:primary:alignment:follower:ce",
+ "deity:primary:alignment:follower:cg",
+ "deity:primary:alignment:follower:cn",
+ "deity:primary:favored-weapon:nine-ring-sword",
+ "deity:primary:hei-feng",
+ "feat:acrobat-dedication",
+ "feat:assurance",
+ "feat:goading-feint",
+ "feat:lie-to-me",
+ "feat:steady-balance",
+ "feat:tengu-weapon-familiarity",
+ "feat:tumble-behind-swashbuckler",
+ "feature:confident-finisher",
+ "feature:fencer",
+ "feature:great-fortitude",
+ "feature:low-light-vision",
+ "feature:opportune-riposte",
+ "feature:panache",
+ "feature:precise-strike",
+ "feature:sharp-beak",
+ "feature:stylish-tricks",
+ "feature:swashbucklers-style",
+ "feature:vivacious-speed",
+ "finesse",
+ "first-weapon:chain-sword",
+ "hands-free:0",
+ "hands-free:but-really:0",
+ "heritage:skyborn-tengu",
+ "hp-percent:94",
+ "item:beak",
+ "item:bulk:light",
+ "item:category:unarmed",
+ "item:damage-dice:1",
+ "item:damage:category:physical",
+ "item:damage:die:faces:6",
+ "item:damage:die:number:1",
+ "item:damage:type:piercing",
+ "item:equipped",
+ "item:group:brawling",
+ "item:hands-held:1",
+ "item:id:drF7WOj3ddyS4i6z",
+ "item:level:0",
+ "item:melee",
+ "item:proficiency:rank:1",
+ "item:slug:beak",
+ "item:trait:finesse",
+ "item:trait:unarmed",
+ "item:type:weapon",
+ "item:usage:hands:1",
+ "melee",
+ "perception:rank:2",
+ "proficiency:trained",
+ "save:fortitude:rank:2",
+ "save:reflex:rank:2",
+ "save:will:rank:2",
+ "self:ancestry:tengu",
+ "self:armored",
+ "self:caster",
+ "self:creature",
+ "self:deity:hei-feng",
+ "self:effect:loaded-hand-crossbow-bolts",
+ "self:effect:scouting",
+ "self:effect:search",
+ "self:heritage:skyborn-tengu",
+ "self:level:3",
+ "self:mode:living",
+ "self:size:2",
+ "self:size:medium",
+ "self:trait:chaotic",
+ "self:trait:good",
+ "self:trait:humanoid",
+ "self:trait:tengu",
+ "self:type:character",
+ "skill:acr:rank:2",
+ "skill:arc:rank:0",
+ "skill:ath:rank:1",
+ "skill:cra:rank:0",
+ "skill:dec:rank:1",
+ "skill:dip:rank:1",
+ "skill:itm:rank:0",
+ "skill:med:rank:0",
+ "skill:nat:rank:0",
+ "skill:occ:rank:0",
+ "skill:prf:rank:1",
+ "skill:rank:1",
+ "skill:rel:rank:0",
+ "skill:soc:rank:0",
+ "skill:ste:rank:1",
+ "skill:sur:rank:0",
+ "skill:thi:rank:1",
+ "speed:land",
+ "target",
+ "target:ancestry:human",
+ "target:caster",
+ "target:creature",
+ "target:deity:atheism",
+ "target:distance:50",
+ "target:effect:investigate",
+ "target:effect:mage-armor",
+ "target:effect:scouting",
+ "target:has-familiar",
+ "target:heritage:versatile-heritage",
+ "target:level:3",
+ "target:mode:living",
+ "target:size:2",
+ "target:size:medium",
+ "target:trait:good",
+ "target:trait:human",
+ "target:trait:humanoid",
+ "target:type:character",
+ "unarmed"
+ ],
+ "notes": [],
+ "secret": false,
+ "rollMode": "publicroll",
+ "traits": [],
+ "skipDialog": false,
+ "outcome": "success",
+ "unadjustedOutcome": null
+ },
+ "target": {
+ "actor": "Actor.nnav0XpUTVkI3bpI",
+ "token": "Scene.lQkXSdxvO9CRxohD.Token.28jYopJkmUUEkviT"
+ },
+ "modifiers": [
+ {
+ "slug": "str",
+ "label": "Strength",
+ "modifier": 2,
+ "type": "ability",
+ "ability": "str",
+ "adjustments": [],
+ "force": false,
+ "enabled": true,
+ "ignored": false,
+ "source": null,
+ "custom": false,
+ "damageType": null,
+ "damageCategory": null,
+ "predicate": [],
+ "critical": null,
+ "traits": [],
+ "notes": "",
+ "hideIfDisabled": false,
+ "kind": "modifier"
+ },
+ {
+ "selector": "strike-damage",
+ "slug": "finisher",
+ "label": "Precise Strike",
+ "diceNumber": 2,
+ "dieSize": "d6",
+ "critical": null,
+ "category": "precision",
+ "damageType": null,
+ "override": null,
+ "ignored": true,
+ "enabled": false,
+ "custom": false,
+ "predicate": [
+ "class:swashbuckler",
+ "self:effect:panache",
+ "finisher",
+ {
+ "or": [
+ "item:melee",
+ {
+ "and": [
+ "feat:flying-blade",
+ "item:thrown",
+ "target:range-increment:1"
+ ]
+ }
+ ]
+ },
+ {
+ "or": [
+ "item:trait:agile",
+ "item:trait:finesse"
+ ]
+ }
+ ]
+ }
+ ],
+ "origin": {
+ "uuid": "Actor.dSV36QRQvNR4pwkF.Item.drF7WOj3ddyS4i6z",
+ "type": "weapon"
+ },
+ "strike": {
+ "actor": "Actor.dSV36QRQvNR4pwkF",
+ "index": 2,
+ "damaging": true,
+ "name": "Beak",
+ "altUsage": null
+ },
+ "preformatted": "both"
+ },
+ "df-chat-enhance": {
+ "ChatTime": {
+ "WorldTime": 3008721
+ }
+ },
+ "pf2e-target-damage": {
+ "targets": [
+ {
+ "id": "28jYopJkmUUEkviT",
+ "tokenUuid": "Scene.lQkXSdxvO9CRxohD.Token.28jYopJkmUUEkviT",
+ "actorUuid": "Actor.nnav0XpUTVkI3bpI"
+ }
+ ]
+ },
+ "pf2e-dorako-ui": {
+ "userAvatar": {
+ "name": "Mirakk",
+ "image": "pics/Duck%20DM%20Closeup.webp",
+ "type": "avatar"
+ },
+ "combatantAvatar": null,
+ "tokenAvatar": {
+ "name": "Mirakk",
+ "image": "tokenizer/pc-images/mirakk.Tokenn3D0XYnojhSAf5Sn.webp?1682362900010",
+ "type": "token",
+ "scale": 1,
+ "isSmall": false
+ },
+ "actorAvatar": {
+ "name": "Mirakk",
+ "image": "tokenizer/pc-images/mirakk.Avatarn3D0XYnojhSAf5Sn.webp?1682362900010",
+ "type": "actor"
+ },
+ "wasTokenHidden": false
+ }
+ },
+ "rolls": [
+ "{\"class\":\"DamageRoll\",\"options\":{\"rollerId\":\"J3oConv1eQRLao6j\",\"damage\":{\"name\":\"Damage Roll: Beak\",\"notes\":[],\"traits\":[\"attack\"],\"materials\":[],\"modifiers\":[{\"slug\":\"str\",\"label\":\"Strength\",\"modifier\":2,\"type\":\"ability\",\"ability\":\"str\",\"adjustments\":[],\"force\":false,\"enabled\":true,\"ignored\":false,\"source\":null,\"custom\":false,\"damageType\":null,\"damageCategory\":null,\"predicate\":[],\"critical\":null,\"traits\":[],\"notes\":\"\",\"hideIfDisabled\":false,\"kind\":\"modifier\"},{\"selector\":\"strike-damage\",\"slug\":\"finisher\",\"label\":\"Precise Strike\",\"diceNumber\":2,\"dieSize\":\"d6\",\"critical\":null,\"category\":\"precision\",\"damageType\":null,\"override\":null,\"ignored\":true,\"enabled\":false,\"custom\":false,\"predicate\":[\"class:swashbuckler\",\"self:effect:panache\",\"finisher\",{\"or\":[\"item:melee\",{\"and\":[\"feat:flying-blade\",\"item:thrown\",\"target:range-increment:1\"]}]},{\"or\":[\"item:trait:agile\",\"item:trait:finesse\"]}]}],\"domains\":[\"drF7WOj3ddyS4i6z-damage\",\"beak-damage\",\"strike-damage\",\"damage\",\"unarmed-damage\",\"brawling-weapon-group-damage\",\"str-damage\",\"trained-damage\"],\"damage\":{\"base\":[{\"diceNumber\":1,\"dieSize\":\"d6\",\"modifier\":0,\"damageType\":\"piercing\",\"category\":null,\"materials\":[]}],\"dice\":[{\"selector\":\"strike-damage\",\"slug\":\"finisher\",\"label\":\"Precise Strike\",\"diceNumber\":2,\"dieSize\":\"d6\",\"critical\":null,\"category\":\"precision\",\"damageType\":null,\"override\":null,\"ignored\":true,\"enabled\":false,\"custom\":false,\"predicate\":[\"class:swashbuckler\",\"self:effect:panache\",\"finisher\",{\"or\":[\"item:melee\",{\"and\":[\"feat:flying-blade\",\"item:thrown\",\"target:range-increment:1\"]}]},{\"or\":[\"item:trait:agile\",\"item:trait:finesse\"]}]}],\"modifiers\":[{\"slug\":\"str\",\"label\":\"Strength\",\"modifier\":2,\"type\":\"ability\",\"ability\":\"str\",\"adjustments\":[],\"force\":false,\"enabled\":true,\"ignored\":false,\"source\":null,\"custom\":false,\"damageType\":null,\"damageCategory\":null,\"predicate\":[],\"critical\":null,\"traits\":[],\"notes\":\"\",\"hideIfDisabled\":false,\"kind\":\"modifier\"},{\"slug\":\"precise-strike\",\"label\":\"Precise Strike\",\"modifier\":2,\"type\":\"untyped\",\"ability\":null,\"adjustments\":[],\"force\":false,\"enabled\":false,\"ignored\":true,\"source\":\"Actor.dSV36QRQvNR4pwkF.Item.hrqBQFNQSeziFd1h\",\"custom\":false,\"damageType\":null,\"damageCategory\":\"precision\",\"predicate\":[\"class:swashbuckler\",\"self:effect:panache\",{\"or\":[\"item:melee\",{\"and\":[\"feat:flying-blade\",\"item:thrown\",\"target:range-increment:1\"]}]},{\"or\":[\"item:trait:agile\",\"item:trait:finesse\"]},{\"not\":\"finisher\"}],\"critical\":null,\"traits\":[],\"notes\":\"\",\"hideIfDisabled\":false,\"kind\":\"modifier\"}],\"ignoredResistances\":[],\"formula\":{\"criticalFailure\":null,\"failure\":\"{1d6[piercing]}\",\"success\":\"{(1d6 + 2)[piercing]}\",\"criticalSuccess\":\"{(2 * (1d6 + 2))[piercing]}\"},\"breakdown\":{\"criticalFailure\":[],\"failure\":[\"1d6 Piercing\"],\"success\":[\"1d6 Piercing\",\"Strength +2\"],\"criticalSuccess\":[\"1d6 Piercing\",\"Strength +2\"]}}},\"degreeOfSuccess\":2,\"ignoredResistances\":[],\"critRule\":\"double-damage\"},\"dice\":[],\"formula\":\"{(1d6 + 2)[piercing]}\",\"terms\":[{\"class\":\"InstancePool\",\"options\":{},\"evaluated\":true,\"terms\":[\"(1d6 + 2)[piercing]\"],\"modifiers\":[],\"rolls\":[{\"class\":\"DamageInstance\",\"options\":{\"flavor\":\"piercing\",\"critRule\":\"double-damage\"},\"dice\":[],\"formula\":\"(1d6 + 2)[piercing]\",\"terms\":[{\"class\":\"Grouping\",\"options\":{\"flavor\":\"piercing\"},\"evaluated\":true,\"term\":{\"class\":\"ArithmeticExpression\",\"options\":{},\"evaluated\":true,\"operator\":\"+\",\"operands\":[{\"class\":\"Die\",\"options\":{},\"evaluated\":true,\"number\":1,\"faces\":6,\"modifiers\":[],\"results\":[{\"result\":6,\"active\":true}]},{\"class\":\"NumericTerm\",\"options\":{},\"evaluated\":true,\"number\":2}]}}],\"total\":8,\"evaluated\":true}],\"results\":[{\"result\":8,\"active\":true}]}],\"total\":8,\"evaluated\":true}"
+ ],
+ "_id": "Uevu6l2qgptJdGoE",
+ "timestamp": 1691043326733,
+ "whisper": [],
+ "blind": false,
+ "emote": false
+}
\ No newline at end of file
diff --git a/scripts/module.js b/scripts/module.js
index 86e4738..135ca76 100644
--- a/scripts/module.js
+++ b/scripts/module.js
@@ -63,11 +63,11 @@ function extractTerm(term, flavor = '') {
}
}
-function extractDamageInfo(input) {
+function extractDamageInfo(rolls) {
const result = [];
- console.log({ input })
+ console.log({ rolls })
- for (const inp of input) {
+ for (const inp of rolls) {
for (const term of inp.terms) {
for (const roll of term.rolls) {
const dmg = { type: roll.type, value: roll.total };
@@ -83,57 +83,61 @@ function extractDamageInfo(input) {
return result;
}
-function extractDamageInfoCombined(input) {
+function extractDamageInfoCombined(rolls) {
const result = [];
- console.log({ input })
- for (const inp of input) {
+ for (const inp of rolls) {
for (const term of inp.terms) {
for (const roll of term.rolls) {
const dmg = { type: roll.type, value: roll.total };
result.push(dmg);
- //console.log("----dmg----");
- //console.log(dmg);
}
- //console.log({inp, term})
- //extractTerm(term, inp?.options?.flavor ?? '');
}
}
-
return result;
}
-function generateDamageScroll(dmg_list) {
- const seq = new Sequence();
- for (const dmg of dmg_list.filter(d => d.value > 0)) {
- style.fill = colors?.[dmg.type];
- seq.scrollingText()
- .atLocation(tok, { offset: { y: topOffset }, gridUnits: true })
- .text(`${dmg.value}`, style)
- .jitter(1)
- .anchor("TOP")
- .waitUntilFinished(-1800)
+/**
+ *
+ * @param {{type: string, value: string}[]} dmg_list list of type and value
+ * @param {string[]} targets list of token ids
+ */
+function generateDamageScroll(dmg_list, targets) {
+ for (const target_id of targets) {
+ const tok = game.canvas.tokens.get(target_id);
+ const size = tok.document.texture.scaleY * tok.document.width;
+ const topOffset = size / 4;
+
+ const seq = new Sequence();
+ for (const dmg of dmg_list.filter(d => d.value > 0)) {
+ style.fill = colors?.[dmg.type] ?? 'white';
+ seq.scrollingText()
+ .atLocation(tok, { offset: { y: topOffset }, gridUnits: true })
+ .text(`${dmg.value}`, style)
+ .jitter(1)
+ .anchor("TOP")
+ .waitUntilFinished(-1800)
+ }
+ seq.play();
}
- seq.play();
}
-Hooks.on("createChatMessage", async function (msg, status, id) {
- if (msg?.flags?.pf2e?.context?.type !== 'damage-roll') return;
- const dmg_list = extractDamageInfoCombined(msg.rolls);
-
+/**
+ *
+ * @param {any} msg Message data from create Chat Message
+ * @returns {string[]} A list of all the ids of the targets
+ */
+function getTargetList(msg) {
if (msg.flags?.["pf2e-target-damage"]?.targets) {
- for (const target in msg.flags?.["pf2e-target-damage"]?.targets) {
- let tok = await game.canvas.tokens.get(target.id);
- let size = tok.document.texture.scaleY * tok.document.width;
- let topOffset = size * 0.5 / 2;
- generateDamageScroll(dmg_list, tok, topOffset)
- }
+ return msg.flags.pf2e - target - damage.targets.map(t => t.id);
} else {
- let tok = msg.target
- let size = tok.document.texture.scaleY * tok.document.width;
- let topOffset = size * 0.5 / 2;
- generateDamageScroll(dmg_list, tok, topOffset)
+ return [(await fromUuid(msg.flags.pf2e.target.token)).id];
}
+}
- //obviously triggered by a Hooks.callAll() that's gonna call every module/system that registered 'init', no matter what
+Hooks.on("createChatMessage", async function (msg, status, id) {
+ if (msg?.flags?.pf2e?.context?.type !== 'damage-roll') return;
+ const dmg_list = extractDamageInfoCombined(msg.rolls);
+ const targets = getTargetList(msg);
+ generateDamageScroll(dmg_list, targets);
});
\ No newline at end of file