From 3914c979b2c7fd28f3f7db8b926714b38bbc9be2 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Tue, 10 Jun 2025 22:49:42 +0100 Subject: [PATCH 1/5] Borders: don't check turns when combat has not started When trying to figure out if a token has played or not, we might get an active combat which hasn't started. In this case the Fabula Ultima flags do not exist yet, and trying to access them will error. Check that the combat we found has indeed been started, otherwise there's no way a token can have played and we don't need to check. --- scripts/token_combat_border.mjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/token_combat_border.mjs b/scripts/token_combat_border.mjs index 22235a3..3291bb5 100644 --- a/scripts/token_combat_border.mjs +++ b/scripts/token_combat_border.mjs @@ -161,6 +161,10 @@ function token_has_played(token, round = -1) { } } + // The token can't have played if the combat hasn't started yet. + if (!combat.started) + return false; + let round_turns_taken = combat?.flags.projectfu.CombatantsTurnTaken[round < 0 ? combat?.round : round] // No token has taken a turn, or all turns were reverted. From b1bde69a7d22424e3a48d2d8feae1584f8dc4a3b Mon Sep 17 00:00:00 2001 From: trotFunky Date: Tue, 10 Jun 2025 23:00:08 +0100 Subject: [PATCH 2/5] UI: Move the status effect indicators to the right As the token square is already busy due to the border, the status effect indicators get hidden. Move them entirely to the right of the token, so they don't conflict anymore. Do it only for PCs, as NPC tokens do not have the border and the GM will probably have their token UI open more often, which would hide the status effect indicators. --- README.md | 8 +++++--- lang/en.json | 2 +- lang/fr.json | 2 +- scripts/token_ui_adjust.mjs | 6 ++++++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 07ecad1..f63a1d1 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,12 @@ The module supports going back and forth in the combat rounds, as well as going ## Token UI adjustments -Given that we add a border *on* the tokens, it conflicts with the base attribute bars which are drawn over the token's -square. +Given that we add a border *on* the tokens, it conflicts with the base attribute bars and status effect indicators which +are drawn over the token's square. The token UI adjustments move the two attribute bars below the token, outside its space, first HP then mana. -As this is where the nameplate of the token should be, move it above the token instead. +As this is where the nameplate of the token should be, move it above the token instead. +The status effect indicators are moved entirely outside the token space as well, to the right, but as it is not useful +for NPCs this is only done for PC tokens. ### Limitations diff --git a/lang/en.json b/lang/en.json index 9a0f3ce..a6a782c 100644 --- a/lang/en.json +++ b/lang/en.json @@ -22,7 +22,7 @@ "Settings": { "Enabled": { "Name": "Token UI adjustments", - "Hint": "Moves both attribute bars below the token and the nameplate above." + "Hint": "Moves both attribute bars below the token, the nameplate above and, for PCs, the status icons to the right." } } } diff --git a/lang/fr.json b/lang/fr.json index de723f1..3630ef7 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -22,7 +22,7 @@ "Settings": { "Enabled": { "Name": "Ajustements d'interface des tokens", - "Hint": "Déplace les barres d'attributs sous le token et le nom au dessus." + "Hint": "Déplace les barres d'attributs sous le token, le nom au dessus et, pour les PJs, les indicateurs de statut à droite." } } } diff --git a/scripts/token_ui_adjust.mjs b/scripts/token_ui_adjust.mjs index 44af60e..bff8393 100644 --- a/scripts/token_ui_adjust.mjs +++ b/scripts/token_ui_adjust.mjs @@ -21,6 +21,8 @@ Hooks.once("setup", () => { /** Padding above and below the token, percentage of a grid square. */ const top_bot_padding = 5; + /** Padding left and right the token, percentage of a grid square. */ + const left_right_padding = 5; Hooks.on("drawToken", (drawn_token) => { /* @@ -49,5 +51,9 @@ Hooks.once("setup", () => { base_refreshNameplate() drawn_token.nameplate.position.set(token_width/2, -(padding + drawn_token.nameplate.height)) } + + if (drawn_token.document.hasPlayerOwner) + drawn_token.effects.position.x += drawn_token.getSize().width + + drawn_token.scene.grid.size * (left_right_padding/100); }) }) From eba433cc0594a75575f24e1767e3456af15e61ff Mon Sep 17 00:00:00 2001 From: trotFunky Date: Tue, 10 Jun 2025 23:04:34 +0100 Subject: [PATCH 3/5] module: Update verified compatibility The module has been tested with newer releases of the system and socketlib, so add them to the verified verisons. --- README.md | 2 +- module.json | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f63a1d1..b9dbd08 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# The Mill's Fabula - v0.3.2 +# The Mill's Fabula - v0.3.3 This little [FoundryVTT](https://foundryvtt.com/) module is a collection of compendiums and functionalities to power our Fabula Ultima campaigns. diff --git a/module.json b/module.json index 6ee5515..9531077 100644 --- a/module.json +++ b/module.json @@ -2,7 +2,7 @@ "id": "the-mills-fabula", "title": "Fabula Moletrina", "description": "A module implementing our idiosyncratic ideas for the Mill's campaigns.", - "version": "0.3.2", + "version": "0.3.3", "compatibility": { "minimum": "12", "verified": "12", @@ -28,7 +28,8 @@ "compatibility": { "verified": [ "2.4.10", - "3.0.2" + "3.0.2", + "3.1.7" ] } } @@ -38,7 +39,10 @@ "id": "socketlib", "type": "module", "compatibility": { - "verified": "1.1.2" + "verified": [ + "1.1.2", + "1.1.3" + ] } } ] @@ -82,5 +86,5 @@ "readme": "README.md", "url": "https://git.tfk-astrodome.net/trotFunky/TheMillsFabula/src/branch/release", "manifest": "https://git.tfk-astrodome.net/trotFunky/TheMillsFabula/raw/branch/release/module.json", - "download": "https://git.tfk-astrodome.net/trotFunky/TheMillsFabula/archive/v0.3.2.zip" + "download": "https://git.tfk-astrodome.net/trotFunky/TheMillsFabula/archive/v0.3.3.zip" } From 67d4b0387ad8987ec43c3e0ce676329095e71e42 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Sun, 6 Jul 2025 21:32:25 +0100 Subject: [PATCH 4/5] Fortune: Introduce fortune display Add a new sub-module that displays Fortune in character and party sheets. In the character sheet, this is an input field and is automatically tracked thanks to flags. Add an icon for the party sheet and the according CSS class. --- README.md | 10 ++- assets/fortune.png | Bin 0 -> 565 bytes lang/en.json | 9 +++ lang/fr.json | 9 +++ module.json | 10 ++- scripts/fortune_integration.mjs | 118 ++++++++++++++++++++++++++++++++ styles/mills_fabula.css | 3 + 7 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 assets/fortune.png create mode 100644 scripts/fortune_integration.mjs create mode 100644 styles/mills_fabula.css diff --git a/README.md b/README.md index b9dbd08..f269d36 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# The Mill's Fabula - v0.3.3 +# The Mill's Fabula - v0.4.0 This little [FoundryVTT](https://foundryvtt.com/) module is a collection of compendiums and functionalities to power our Fabula Ultima campaigns. @@ -51,6 +51,14 @@ for NPCs this is only done for PC tokens. - The current implementation relies on monkey patching, which make it vulnerable to compatibility issues with other modules manipulating the same methods. +## Fortune display + +In our game, "Fortune" is a key currency in the world. +This adds an interactible display in the character sheets, allowing it to be tracked, and in the party sheet, +for a nice overview. + +The value is stored in flags, and will not be lost when disabling the module or option. + # Compendia - The only thing in the compendium pack is a macro automating the tinkerer's alchemy potions, diff --git a/assets/fortune.png b/assets/fortune.png new file mode 100644 index 0000000000000000000000000000000000000000..4d360b39a134559b23e83e3a02310c7848558c68 GIT binary patch literal 565 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-TA0 z-G$*l2rk&Wd@@jkv%n*=n1O*?7=#%aX3dcRTG!+0;usQfI5|Ng;ef}wV@|n?|DX8t zZ}oGDm(N~-IPvT5IUIb!wFa&1DwlJ=)RWDdIMpDtun^Wyz+`mk#?`=6%|4`mB(?8}JT%A*`E z_>|$9{(X*96QzZGu5jel@GY6e-0@MVPO<81y zhvxQ&eLJeURSFkzD=j)zy~1BoLPFwS)@s2J=P)S+EiH~8Vs{01ykeTuDX?03!w;Tx z4FO4^iekov6B=3%9cOkwQO0sGhohW@vDr?}qUo~Yjwbp1W)|i2DbpOKH15V~O#7tZ z%IIGvba?v8&p?wUB!0-|ba=Js99b5&gWv1)zv&gf1w;6`d*`j4{UI%P((1>cfU4e> zcSzv*Nxwq^$|;sd&wdihw-@P7Jm|mXX-MXTgw1nA4mjnWPQ2)xFjf#fsw`F>FVdQ&MBb@0EdFx#{d8T literal 0 HcmV?d00001 diff --git a/lang/en.json b/lang/en.json index a6a782c..377da2d 100644 --- a/lang/en.json +++ b/lang/en.json @@ -25,6 +25,15 @@ "Hint": "Moves both attribute bars below the token, the nameplate above and, for PCs, the status icons to the right." } } + }, + "Fortune": { + "Name": "Fortune", + "Settings": { + "DisplayEnabled": { + "Name": "Fortune display", + "Hint": "Display and track \"Fortune\" currency in the character and party sheets." + } + } } } } diff --git a/lang/fr.json b/lang/fr.json index 3630ef7..b8280a5 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -25,6 +25,15 @@ "Hint": "Déplace les barres d'attributs sous le token, le nom au dessus et, pour les PJs, les indicateurs de statut à droite." } } + }, + "Fortune": { + "Name": "Fortune", + "Settings": { + "DisplayEnabled": { + "Name": "Affichage de la Fortune", + "Hint": "Affiche et gère une monnaie, la \"Fortune\", dans les fiches de personnage et de groupe." + } + } } } } diff --git a/module.json b/module.json index 9531077..fbe6c83 100644 --- a/module.json +++ b/module.json @@ -2,7 +2,7 @@ "id": "the-mills-fabula", "title": "Fabula Moletrina", "description": "A module implementing our idiosyncratic ideas for the Mill's campaigns.", - "version": "0.3.3", + "version": "0.4.0", "compatibility": { "minimum": "12", "verified": "12", @@ -55,7 +55,11 @@ ], "esmodules": [ "scripts/token_combat_border.mjs", - "scripts/token_ui_adjust.mjs" + "scripts/token_ui_adjust.mjs", + "scripts/fortune_integration.mjs" + ], + "styles": [ + "styles/mills_fabula.css" ], "socket": true, @@ -86,5 +90,5 @@ "readme": "README.md", "url": "https://git.tfk-astrodome.net/trotFunky/TheMillsFabula/src/branch/release", "manifest": "https://git.tfk-astrodome.net/trotFunky/TheMillsFabula/raw/branch/release/module.json", - "download": "https://git.tfk-astrodome.net/trotFunky/TheMillsFabula/archive/v0.3.3.zip" + "download": "https://git.tfk-astrodome.net/trotFunky/TheMillsFabula/archive/v0.4.0.zip" } diff --git a/scripts/fortune_integration.mjs b/scripts/fortune_integration.mjs new file mode 100644 index 0000000..dc92409 --- /dev/null +++ b/scripts/fortune_integration.mjs @@ -0,0 +1,118 @@ +import * as MillsFabula from "./mills_fabula.mjs"; + +const fortune_flag = "char_fortune" + +const FortuneSettings = Object.freeze({ + EnableFortuneDisplay: "enableFortuneDisplay", +}) + +/** + * Find all the characters in the group sheet and insert their Fortune amount + * within the resources, copying the system's style. + * @param {DocumentSheet|foundry.applications.api.DocumentSheetV2} app The source Application + * @param {HTMLElement} html The rendered HTML + * @returns {Promise} Does not return + */ +async function party_sheet_fortune(app, html) { + if (!(app instanceof foundry.applications.api.DocumentSheetV2)) + html = html[0] /* Retrieve the internal DOM element */ + else + console.warn("AppV2 is not properly tested yet") + + let character_divs = html.querySelectorAll(".section-container.plate.character") + for (let char_div of character_divs) { + /** @type{FUActor} */ + let actor = await fromUuid(char_div.getAttribute("data-uuid")) + let resources = char_div.getElementsByClassName("resources")[0] + let icon = document.createElement("i"); + icon.classList.add("fuk", "icon-aff", "mf-fortune") + icon.setAttribute("data-tooltip", game.i18n.localize('MF.Fortune.Name')) + + /* Insert the Fortune elements before Fabula points, so find it */ + let fp_icon = resources.getElementsByClassName("fu-fp")[0] + resources.insertBefore(icon, fp_icon) + /* The system also has whitespace for spacing */ + resources.insertBefore( + document.createTextNode(` ${actor.getFlag(MillsFabula.id, fortune_flag) ?? 0} `), + fp_icon) + } +} + +/** + * Insert a Fortune input at the top of the character sheet, + * following the same styling as the system for Fabula points. + * @param {DocumentSheet|foundry.applications.api.DocumentSheetV2} app The source Application + * @param {HTMLElement} html The rendered HTML + */ +function character_sheet_fortune(app, html) { + /* Only player characters have Fortune. */ + if (app.actor.type !== "character") + return + + if (!(app instanceof foundry.applications.api.DocumentSheetV2)) + html = html[0] /* Retrieve the internal DOM element */ + else + console.warn("AppV2 is not properly tested yet") + + let resources_div = html.getElementsByClassName("header-center")[0].lastElementChild + resources_div.classList.replace("grid-3col", "grid-4col") + + /* + * We want the Fortune to have the same importance/weight as Fabula points, + * so we'll copy the style and bundle them in a 2 column div. + */ + /* TODO: This all should probably be some form of template ? */ + let fortune_fabula_div = document.createElement("div") + fortune_fabula_div.classList.add("grid", "grid-2col") + + let fortune_div = document.createElement("div") + fortune_div.classList.add("flex-group-center", "resource-content") + let fortune_label = document.createElement("span") + fortune_label.classList.add("resource-label-l") + let fortune_icon = document.createElement("i") + fortune_icon.classList.add("fas", "fa-leaf", "icon") + fortune_label.appendChild(fortune_icon) + fortune_label.appendChild(document.createTextNode(game.i18n.localize('MF.Fortune.Name'))) + fortune_div.appendChild(fortune_label) + let fortune_data_div = document.createElement("div") + fortune_data_div.classList.add("buttons-inc") + let fortune_input = foundry.applications.fields.createNumberInput({ + /* + * By setting the name of the field using this format, the backing + * flag will automatically be picked up by Foundry's code and updated + * when the field is changed on the sheet. + * This works even if it wasn't set previously ! + */ + name: `flags.${MillsFabula.id}.${fortune_flag}`, + value: app.document.getFlag(MillsFabula.id, fortune_flag) ?? 0, + step: 1, + min: 0, + type: "number" + }) + fortune_input.classList.add("fp-resource-inputs") + fortune_data_div.appendChild(fortune_input) + fortune_div.appendChild(fortune_data_div) + + fortune_fabula_div.appendChild(fortune_div) + /* The last element should always be the Fabula Points div. */ + fortune_fabula_div.appendChild(resources_div.lastElementChild) + + resources_div.appendChild(fortune_fabula_div) +} + +Hooks.once("init", () => { + game.settings.register(MillsFabula.id, FortuneSettings.EnableFortuneDisplay, { + name: game.i18n.localize('MF.Fortune.Settings.DisplayEnabled.Name'), + hint: game.i18n.localize('MF.Fortune.Settings.DisplayEnabled.Hint'), + type: Boolean, + config: true, + scope: 'world', + requiresReload: true, + default: true + }); + + if (game.settings.get(MillsFabula.id, FortuneSettings.EnableFortuneDisplay)) { + Hooks.on("renderFUPartySheet", party_sheet_fortune) + Hooks.on("renderFUStandardActorSheet", character_sheet_fortune) + } +}) diff --git a/styles/mills_fabula.css b/styles/mills_fabula.css new file mode 100644 index 0000000..6ea8d11 --- /dev/null +++ b/styles/mills_fabula.css @@ -0,0 +1,3 @@ +.mf-fortune { + background-image: url("/modules/the-mills-fabula/assets/fortune.png"); +} From 6d4c5b74dcf694d125967424af6b6e1312a4a5c6 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Sun, 6 Jul 2025 21:48:18 +0100 Subject: [PATCH 5/5] Fortune: Fix indenting in character sheet code When moving this part of the code to a dedicated function, the IDE "helpfully" "fixed" the indentation, making the organization of this part of the code less clear. Re-introduce the indentation to make the structure clearer. --- scripts/fortune_integration.mjs | 46 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/scripts/fortune_integration.mjs b/scripts/fortune_integration.mjs index dc92409..5a34984 100644 --- a/scripts/fortune_integration.mjs +++ b/scripts/fortune_integration.mjs @@ -67,30 +67,30 @@ function character_sheet_fortune(app, html) { let fortune_div = document.createElement("div") fortune_div.classList.add("flex-group-center", "resource-content") - let fortune_label = document.createElement("span") - fortune_label.classList.add("resource-label-l") - let fortune_icon = document.createElement("i") - fortune_icon.classList.add("fas", "fa-leaf", "icon") - fortune_label.appendChild(fortune_icon) - fortune_label.appendChild(document.createTextNode(game.i18n.localize('MF.Fortune.Name'))) + let fortune_label = document.createElement("span") + fortune_label.classList.add("resource-label-l") + let fortune_icon = document.createElement("i") + fortune_icon.classList.add("fas", "fa-leaf", "icon") + fortune_label.appendChild(fortune_icon) + fortune_label.appendChild(document.createTextNode(game.i18n.localize('MF.Fortune.Name'))) fortune_div.appendChild(fortune_label) - let fortune_data_div = document.createElement("div") - fortune_data_div.classList.add("buttons-inc") - let fortune_input = foundry.applications.fields.createNumberInput({ - /* - * By setting the name of the field using this format, the backing - * flag will automatically be picked up by Foundry's code and updated - * when the field is changed on the sheet. - * This works even if it wasn't set previously ! - */ - name: `flags.${MillsFabula.id}.${fortune_flag}`, - value: app.document.getFlag(MillsFabula.id, fortune_flag) ?? 0, - step: 1, - min: 0, - type: "number" - }) - fortune_input.classList.add("fp-resource-inputs") - fortune_data_div.appendChild(fortune_input) + let fortune_data_div = document.createElement("div") + fortune_data_div.classList.add("buttons-inc") + let fortune_input = foundry.applications.fields.createNumberInput({ + /* + * By setting the name of the field using this format, the backing + * flag will automatically be picked up by Foundry's code and updated + * when the field is changed on the sheet. + * This works even if it wasn't set previously ! + */ + name: `flags.${MillsFabula.id}.${fortune_flag}`, + value: app.document.getFlag(MillsFabula.id, fortune_flag) ?? 0, + step: 1, + min: 0, + type: "number" + }) + fortune_input.classList.add("fp-resource-inputs") + fortune_data_div.appendChild(fortune_input) fortune_div.appendChild(fortune_data_div) fortune_fabula_div.appendChild(fortune_div)