Compare commits

..

5 commits
v0.3.2 ... main

Author SHA1 Message Date
6d4c5b74dc 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.
2025-07-06 21:48:18 +01:00
67d4b0387a 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.
2025-07-06 21:32:25 +01:00
eba433cc05 module: Update verified compatibility
The module has been tested with newer releases of the
system and socketlib, so add them to the verified verisons.
2025-06-10 23:04:34 +01:00
b1bde69a7d 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.
2025-06-10 23:03:19 +01:00
3914c979b2 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.
2025-06-10 22:54:18 +01:00
9 changed files with 178 additions and 11 deletions

View file

@ -1,4 +1,4 @@
# The Mill's Fabula - v0.3.2
# 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.
@ -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
@ -49,6 +51,14 @@ As this is where the nameplate of the token should be, move it above the token i
- 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

@ -22,7 +22,16 @@
"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."
}
}
},
"Fortune": {
"Name": "Fortune",
"Settings": {
"DisplayEnabled": {
"Name": "Fortune display",
"Hint": "Display and track \"Fortune\" currency in the character and party sheets."
}
}
}

View file

@ -22,7 +22,16 @@
"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."
}
}
},
"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.2",
"version": "0.4.0",
"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"
]
}
}
]
@ -51,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,
@ -82,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.2.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)
}
})

View file

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

View file

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

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");
}