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