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.
This commit is contained in:
trotFunky 2025-07-06 21:32:25 +01:00
parent eba433cc05
commit 19f49c4c17
7 changed files with 155 additions and 4 deletions

View file

@ -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,

BIN
assets/fortune.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

View file

@ -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."
}
}
}
}
}

View file

@ -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."
}
}
}
}
}

View file

@ -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"
}

View file

@ -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<void>} 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)
}
})

3
styles/mills_fabula.css Normal file
View file

@ -0,0 +1,3 @@
.mf-fortune {
background-image: url("/modules/the-mills-fabula/assets/fortune.png");
}