From bbc42dd6c07ef6e0672314a3b6ea4cb18d2dbbc4 Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Sun, 17 Aug 2025 22:07:56 +0200 Subject: [PATCH 01/17] Prioritize Soutane font on sheets --- styles/basicfantasyrpg.css | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/styles/basicfantasyrpg.css b/styles/basicfantasyrpg.css index 1cfff7a..7187710 100644 --- a/styles/basicfantasyrpg.css +++ b/styles/basicfantasyrpg.css @@ -3,55 +3,60 @@ font-family: 'Soutane'; src: url('soutane-webfont.eot'); src: url('soutane-webfont.eot?#iefix') format('embedded-opentype'), - url('soutane-webfont.woff') format('woff'), url('soutane-webfont.ttf') format('truetype'), - url('soutane-webfont.svg#soutaneregular') format('svg'); + url('soutane-webfont.svg#soutaneregular') format('svg'), + url('soutane-webfont.woff') format('woff'); font-weight: normal; font-style: normal; + font-display: swap; } @font-face { font-family: 'Soutane'; src: url('soutanebold-webfont.eot'); src: url('soutanebold-webfont.eot?#iefix') format('embedded-opentype'), - url('soutanebold-webfont.woff') format('woff'), url('soutanebold-webfont.ttf') format('truetype'), - url('soutanebold-webfont.svg#soutanebold') format('svg'); + url('soutanebold-webfont.svg#soutanebold') format('svg'), + url('soutanebold-webfont.woff') format('woff'); font-weight: bold; font-style: normal; + font-display: swap; } @font-face { font-family: 'Soutane'; src: url('soutanebolditalic-webfont.eot'); src: url('soutanebolditalic-webfont.eot?#iefix') format('embedded-opentype'), - url('soutanebolditalic-webfont.woff') format('woff'), url('soutanebolditalic-webfont.ttf') format('truetype'), - url('soutanebolditalic-webfont.svg#soutanebold_italic') format('svg'); + url('soutanebolditalic-webfont.svg#soutanebold_italic') format('svg'), + url('soutanebolditalic-webfont.woff') format('woff'); font-weight: bold; font-style: italic; + font-display: swap; } @font-face { font-family: 'Soutane'; src: url('soutaneitalic-webfont.eot'); src: url('soutaneitalic-webfont.eot?#iefix') format('embedded-opentype'), - url('soutaneitalic-webfont.woff') format('woff'), url('soutaneitalic-webfont.ttf') format('truetype'), - url('soutaneitalic-webfont.svg#soutaneitalic') format('svg'); + url('soutaneitalic-webfont.svg#soutaneitalic') format('svg'), + url('soutaneitalic-webfont.woff') format('woff'); font-weight: normal; font-style: italic; + font-display: swap; } @font-face { font-family: 'SoutaneBlack'; src: url('soutaneblack-webfont.eot'); src: url('soutaneblack-webfont.eot?#iefix') format('embedded-opentype'), - url('soutaneblack-webfont.woff') format('woff'), url('soutaneblack-webfont.ttf') format('truetype'), - url('soutaneblack-webfont.svg#soutaneblackregular') format('svg'); + url('soutaneblack-webfont.svg#soutaneblackregular') format('svg'), + url('soutaneblack-webfont.woff') format('woff'); font-weight: normal; font-style: normal; + font-display: swap; } @font-face { @@ -374,7 +379,7 @@ /* Styles limited to basicfantasyrpg sheets */ .basicfantasyrpg.sheet.actor { - font-family: "Century Gothic", "TeX Gyre Adventor", "Soutane", var(--font-primary); + font-family: "Soutane", "Century Gothic", "TeX Gyre Adventor", var(--font-primary); } .basicfantasyrpg .sheet-header { From a36ad151cd7fbdd11d966d1c1d253cafef5f7907 Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Sun, 17 Aug 2025 22:48:32 +0200 Subject: [PATCH 02/17] Define CSS variables --- styles/basicfantasyrpg.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/styles/basicfantasyrpg.css b/styles/basicfantasyrpg.css index 7187710..360c8e7 100644 --- a/styles/basicfantasyrpg.css +++ b/styles/basicfantasyrpg.css @@ -103,6 +103,13 @@ font-style: italic; } +:root { + --font-primary: "Soutane"; + --color-border-light-primary: #b7b7af; /* neutral light grey-brown */ + --color-border-light-highlight: #ccc5b9; /* warm light beige */ +} + + /* Global styles */ .game { font-family: "Soutane", "Century Gothic", "TeX Gyre Adventor", var(--font-primary); @@ -427,6 +434,7 @@ width: 100%; height: 100%; margin: 0; + font-family: "Soutane", "Century Gothic", "TeX Gyre Adventor", var(--font-primary); } .basicfantasyrpg .sheet-tabs { From e91a9f85adea6011e40a0cececcb727cdc7192fd Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Tue, 19 Aug 2025 19:32:03 +0200 Subject: [PATCH 03/17] WIP simple sheets --- CLAUDE.md | 40 +++++++++++++++ module/basicfantasyrpg.mjs | 17 ++++++- module/sheets/simple-monster-sheet.mjs | 65 ++++++++++++++++++++++++ module/sheets/simple-sheet.mjs | 65 ++++++++++++++++++++++++ templates/actor/simple-actor-sheet.hbs | 22 ++++++++ templates/actor/simple-monster-sheet.hbs | 23 +++++++++ 6 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 CLAUDE.md create mode 100644 module/sheets/simple-monster-sheet.mjs create mode 100644 module/sheets/simple-sheet.mjs create mode 100644 templates/actor/simple-actor-sheet.hbs create mode 100644 templates/actor/simple-monster-sheet.hbs diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a527598 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,40 @@ +# Purpose of the codebase + +This codebase is the implementation of the RPG system Basic Fantasy RPG to Foundry VTT. + +The current task is to port it to Foundry v13, which introduce a number of API changes, including the +usage of the Application v2. + +# Foundry API + +One main limitation is that the code base does not run on its own, but is rather loaded by the Foundry app (which is not open source), and this severely limits what one can test. + +In fact, I can only effectively test by opening the app and checking manually if stuff works in the UI. + +# Resources + +Look for online documentation whenever necessary to understand how to migrate to Application v2 and how to properly implement necessary methods. + +Good sources can be found under the domains foundryvtt.com and foundryvtt.wiki + +# Foundry Entities + +In Foundry, there are top-level entities like "Actors" (which can be characters, monsters, +vehicles, etc.), and "Items" (which can be equipment, spells, special abilities, etc.). + +Usually, there is a single class for Actor and a single class for Items, and it also implements +subtype-specific behavior (like for monsters or characters in the case of Actor). + +Besides the Actor and Item class, there are also corresponding Sheet classes, like an Actor Sheet and +Item Sheet, responsible for rendering the relevant information about that entity on a sheet, which is +visible on the Foundry UI. + +## Entity Structure + +The structure of which types of entities exist can be found in the file `template.json`. It also defines +which attributes each type has. + +## Templates + +The folder `templates` contains HTML templates for rendering sheets. Notice that there are the higher +level actor sheet and item sheet, as well as more specific ones for monsters or vehicles. \ No newline at end of file diff --git a/module/basicfantasyrpg.mjs b/module/basicfantasyrpg.mjs index 017f2ef..d91cf7d 100644 --- a/module/basicfantasyrpg.mjs +++ b/module/basicfantasyrpg.mjs @@ -4,6 +4,9 @@ import { BasicFantasyRPGItem } from './documents/item.mjs'; // Import sheet classes. import { BasicFantasyRPGActorSheet } from './sheets/actor-sheet.mjs'; import { BasicFantasyRPGItemSheet } from './sheets/item-sheet.mjs'; +import { SimpleActorSheet } from './sheets/simple-sheet.mjs'; +import { SimpleMonsterSheet } from './sheets/simple-monster-sheet.mjs'; + // Import helper/utility classes and constants. import { preloadHandlebarsTemplates } from './helpers/templates.mjs'; import { BASICFANTASYRPG } from './helpers/config.mjs'; @@ -40,7 +43,17 @@ Hooks.once('init', async function() { // Register sheet application classes Actors.unregisterSheet('core', ActorSheet); - Actors.registerSheet('basicfantasyrpg', BasicFantasyRPGActorSheet, { makeDefault: true }); + Actors.registerSheet('basicfantasyrpg', BasicFantasyRPGActorSheet, + { makeDefault: false } + ); + Actors.registerSheet('basicfantasyrpg', + SimpleActorSheet, + { types: ['character'], makeDefault: true, label: "Simple Sheet"} + ); + Actors.registerSheet('basicfantasyrpg', + SimpleMonsterSheet, + { types: ['monster'], makeDefault: true, label: "Simple Monster Sheet"} + ); Items.unregisterSheet('core', ItemSheet); Items.registerSheet('basicfantasyrpg', BasicFantasyRPGItemSheet, { makeDefault: true }); @@ -94,7 +107,7 @@ Handlebars.registerHelper('toLowerCase', function(str) { }); Handlebars.registerHelper('selected', function(value) { - return Boolean(value) ? "selected" : ""; + return value ? "selected" : ""; }); Handlebars.registerPartial('iconDamage', ``); diff --git a/module/sheets/simple-monster-sheet.mjs b/module/sheets/simple-monster-sheet.mjs new file mode 100644 index 0000000..64ea956 --- /dev/null +++ b/module/sheets/simple-monster-sheet.mjs @@ -0,0 +1,65 @@ +const { HandlebarsApplicationMixin } = foundry.applications.api +const { ActorSheetV2 } = foundry.applications.sheets + +/** + * A very basic Actor Sheet implementation using DocumentSheetV2 + */ +export class SimpleMonsterSheet extends HandlebarsApplicationMixin(ActorSheetV2) { + + /** @override */ + static DEFAULT_OPTIONS = { + classes: ["basicfantasyrpg", "sheet", "actor", "simple", "monster"], + position: { + width: 400, + height: 600 + }, + window: { + resizable: true, + title: "Monster groaar!" + }, + form: { + handler: SimpleMonsterSheet.#onSubmitDocumentForm, + submitOnChange: true + } + }; + + /** @override */ + static PARTS = { + header: { + template: "systems/basicfantasyrpg/templates/actor/simple-monster-sheet.hbs" + } + }; + + /** @override */ + async _prepareContext(options) { + const context = await super._prepareContext(options); + + // Add the actor's basic data to the context + context.actor = this.document; + context.system = this.document.system; + context.name = this.document.name; + context.editable = true; + + return context; + } + + /** @override */ + _onRender(context, options) { + super._onRender(context, options); + + // Add any additional rendering logic here + console.log("Simple Monster Sheet rendered for:", context.name); + } + + /** + * Handle form submission for the actor sheet + * @param {SubmitEvent} event The form submission event + * @param {HTMLFormElement} form The submitted form + * @param {FormDataExtended} formData The form data + * @returns {Promise} + */ + static async #onSubmitDocumentForm(event, form, formData) { + const updates = foundry.utils.expandObject(formData.object); + return this.document.update(updates); + } +} diff --git a/module/sheets/simple-sheet.mjs b/module/sheets/simple-sheet.mjs new file mode 100644 index 0000000..7e67cb2 --- /dev/null +++ b/module/sheets/simple-sheet.mjs @@ -0,0 +1,65 @@ +const { HandlebarsApplicationMixin } = foundry.applications.api +const { ActorSheetV2 } = foundry.applications.sheets + +/** + * A very basic Actor Sheet implementation using DocumentSheetV2 + */ +export class SimpleActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { + + /** @override */ + static DEFAULT_OPTIONS = { + ...ActorSheetV2.DEFAULT_OPTIONS, + classes: ["basicfantasyrpg", "sheet", "actor", "themed", "theme-light"], + position: { + width: 400, + height: 300 + }, + window: { + resizable: true, + title: "Just an actor!" + }, + form: { + handler: SimpleActorSheet.#onSubmitDocumentForm, + submitOnChange: true + } + }; + + /** @override */ + static PARTS = { + header: { + template: "systems/basicfantasyrpg/templates/actor/simple-actor-sheet.hbs" + } + }; + + /** @override */ + async _prepareContext(options) { + const context = await super._prepareContext(options); + + // Add the actor's basic data to the context + context.actor = this.document; + context.system = this.document.system; + context.name = this.document.name; + + return context; + } + + /** @override */ + _onRender(context, options) { + super._onRender(context, options); + + // Add any additional rendering logic here + console.log("Simple Actor Sheet rendered for:", context.name); + } + + /** + * Handle form submission for the actor sheet + * @param {SubmitEvent} event The form submission event + * @param {HTMLFormElement} form The submitted form + * @param {FormDataExtended} formData The form data + * @returns {Promise} + */ + static async #onSubmitDocumentForm(event, form, formData) { + const updates = foundry.utils.expandObject(formData.object); + return this.document.update(updates); + } +} diff --git a/templates/actor/simple-actor-sheet.hbs b/templates/actor/simple-actor-sheet.hbs new file mode 100644 index 0000000..07658bf --- /dev/null +++ b/templates/actor/simple-actor-sheet.hbs @@ -0,0 +1,22 @@ +
+ + {{!-- Sheet Header --}} +
+ +
+

+ +

+ +
+
+ + {{!-- Sheet Tab Navigation (if any) --}} + {{#if tabs}} + + {{/if}} +
\ No newline at end of file diff --git a/templates/actor/simple-monster-sheet.hbs b/templates/actor/simple-monster-sheet.hbs new file mode 100644 index 0000000..59e40f0 --- /dev/null +++ b/templates/actor/simple-monster-sheet.hbs @@ -0,0 +1,23 @@ +
+
+

+ {{#if @root.editable}} + + {{else}} + {{name}} + {{/if}} +

+
+ +
+

+ Monster! p {{actor.type}} +

+ {{#if @root.editable}} +
+ + +
+ {{/if}} +
+
From 2af2d369c06ccdb7c4624f70c7eaebfcef15de58 Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Tue, 19 Aug 2025 22:59:38 +0200 Subject: [PATCH 04/17] WIP --- CLAUDE.md | 6 -- module/basicfantasyrpg.mjs | 16 ++--- module/sheets/base-actor-sheet.mjs | 60 +++++++++++++++++ module/sheets/character-sheet.mjs | 50 ++++++++++++++ module/sheets/monster-sheet.mjs | 28 ++++++++ module/sheets/simple-monster-sheet.mjs | 65 ------------------- module/sheets/simple-sheet.mjs | 65 ------------------- templates/actor/actor-character-sheet.html | 4 +- templates/actor/actor-monster-sheet.html | 4 +- .../{simple-actor-sheet.hbs => header.hbs} | 12 +--- templates/actor/parts/character-resources.hbs | 30 +++++++++ 11 files changed, 182 insertions(+), 158 deletions(-) create mode 100644 module/sheets/base-actor-sheet.mjs create mode 100644 module/sheets/character-sheet.mjs create mode 100644 module/sheets/monster-sheet.mjs delete mode 100644 module/sheets/simple-monster-sheet.mjs delete mode 100644 module/sheets/simple-sheet.mjs rename templates/actor/{simple-actor-sheet.hbs => header.hbs} (59%) create mode 100644 templates/actor/parts/character-resources.hbs diff --git a/CLAUDE.md b/CLAUDE.md index a527598..17ec148 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,12 +5,6 @@ This codebase is the implementation of the RPG system Basic Fantasy RPG to Found The current task is to port it to Foundry v13, which introduce a number of API changes, including the usage of the Application v2. -# Foundry API - -One main limitation is that the code base does not run on its own, but is rather loaded by the Foundry app (which is not open source), and this severely limits what one can test. - -In fact, I can only effectively test by opening the app and checking manually if stuff works in the UI. - # Resources Look for online documentation whenever necessary to understand how to migrate to Application v2 and how to properly implement necessary methods. diff --git a/module/basicfantasyrpg.mjs b/module/basicfantasyrpg.mjs index d91cf7d..42eb93f 100644 --- a/module/basicfantasyrpg.mjs +++ b/module/basicfantasyrpg.mjs @@ -4,8 +4,8 @@ import { BasicFantasyRPGItem } from './documents/item.mjs'; // Import sheet classes. import { BasicFantasyRPGActorSheet } from './sheets/actor-sheet.mjs'; import { BasicFantasyRPGItemSheet } from './sheets/item-sheet.mjs'; -import { SimpleActorSheet } from './sheets/simple-sheet.mjs'; -import { SimpleMonsterSheet } from './sheets/simple-monster-sheet.mjs'; +import { CharacterSheet } from './sheets/character-sheet.mjs'; +import { MonsterSheet } from './sheets/monster-sheet.mjs'; // Import helper/utility classes and constants. import { preloadHandlebarsTemplates } from './helpers/templates.mjs'; @@ -47,13 +47,13 @@ Hooks.once('init', async function() { { makeDefault: false } ); Actors.registerSheet('basicfantasyrpg', - SimpleActorSheet, - { types: ['character'], makeDefault: true, label: "Simple Sheet"} + CharacterSheet, + { types: ['character'], makeDefault: true, label: "Character Sheet V2"} + ); + Actors.registerSheet('basicfantasyrpg', + MonsterSheet, + { types: ['monster'], makeDefault: true, label: "Monster Sheet V2"} ); - Actors.registerSheet('basicfantasyrpg', - SimpleMonsterSheet, - { types: ['monster'], makeDefault: true, label: "Simple Monster Sheet"} - ); Items.unregisterSheet('core', ItemSheet); Items.registerSheet('basicfantasyrpg', BasicFantasyRPGItemSheet, { makeDefault: true }); diff --git a/module/sheets/base-actor-sheet.mjs b/module/sheets/base-actor-sheet.mjs new file mode 100644 index 0000000..b477f2e --- /dev/null +++ b/module/sheets/base-actor-sheet.mjs @@ -0,0 +1,60 @@ +import { successChatMessage } from '../helpers/chat.mjs'; +import { onManageActiveEffect, prepareActiveEffectCategories } from '../helpers/effects.mjs'; + +const { HandlebarsApplicationMixin } = foundry.applications.api; +const { ActorSheetV2 } = foundry.applications.sheets; + +/** + * Base Actor Sheet class for Basic Fantasy RPG + * Contains shared functionality for all actor types + * @extends {ActorSheetV2} + */ +export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { + + static DEFAULT_OPTIONS = { + classes: ["basicfantasyrpg", "sheet", "actor", "themed", "theme-light"], + position: { + width: 600, + height: 600 + }, + window: { + resizable: true + }, + form: { + handler: BaseActorSheet.#onSubmitDocumentForm, + submitOnChange: true + }, + }; + + // /** @override */ + // static PARTS = { + // header: { + // template: "systems/basicfantasyrpg/templates/actor/header.hbs" + // } + // }; + + /** @override */ + async _prepareContext(options) { + const context = await super._prepareContext(options); + + // Add the actor's basic data to the context + context.actor = this.document; + context.system = this.document.system; + context.name = this.document.name; + context.data = context.system; + + return context; + } + + /** + * Handle form submission for the actor sheet + * @param {SubmitEvent} event The form submission event + * @param {HTMLFormElement} form The submitted form + * @param {FormDataExtended} formData The form data + * @returns {Promise} + */ + static async #onSubmitDocumentForm(event, form, formData) { + const updates = foundry.utils.expandObject(formData.object); + return this.document.update(updates); + } +} \ No newline at end of file diff --git a/module/sheets/character-sheet.mjs b/module/sheets/character-sheet.mjs new file mode 100644 index 0000000..bc2d78a --- /dev/null +++ b/module/sheets/character-sheet.mjs @@ -0,0 +1,50 @@ +import { BaseActorSheet } from './base-actor-sheet.mjs'; + +/** + * Character Sheet for Basic Fantasy RPG + * Extends BaseActorSheet with character-specific functionality + * @extends {BaseActorSheet} + */ +export class CharacterSheet extends BaseActorSheet { + + static DEFAULT_OPTIONS = { + ...BaseActorSheet.DEFAULT_OPTIONS, + classes: [...BaseActorSheet.DEFAULT_OPTIONS.classes, "character"], + window: { + ...BaseActorSheet.DEFAULT_OPTIONS.window, + title: "Character" + }, + }; + + + static PARTS = { + header: { + template: "systems/basicfantasyrpg/templates/actor/header.hbs" + }, + resources: { + template: "systems/basicfantasyrpg/templates/actor/parts/character-resources.hbs" + } + }; + + /** @override */ + _configureRenderOptions(options) { + super._configureRenderOptions(options); + options.parts = ['header'] + } + + /** @override */ + _onRender(context, options) { + super._onRender(context, options); + + // Add character-specific rendering logic + console.log("Character Sheet rendered for:", context.name); + } + + /** @override */ + async _prepareContext(options) { + const context = await super._prepareContext(options); + + context.resourcesTemplate = "systems/basicfantasyrpg/templates/actor/parts/character-resources.hbs"; + return context; + } +} \ No newline at end of file diff --git a/module/sheets/monster-sheet.mjs b/module/sheets/monster-sheet.mjs new file mode 100644 index 0000000..ef37409 --- /dev/null +++ b/module/sheets/monster-sheet.mjs @@ -0,0 +1,28 @@ +import { BaseActorSheet } from './base-actor-sheet.mjs'; + +/** + * Monster Sheet for Basic Fantasy RPG + * Extends BaseActorSheet with monster-specific functionality + * @extends {BaseActorSheet} + */ +export class MonsterSheet extends BaseActorSheet { + + static DEFAULT_OPTIONS = { + ...BaseActorSheet.DEFAULT_OPTIONS, + classes: [...BaseActorSheet.DEFAULT_OPTIONS.classes, "monster"] + }; + + static PARTS = { + main: { + template: "systems/basicfantasyrpg/templates/actor/actor-monster-sheet.html" + } + }; + + /** @override */ + _onRender(context, options) { + super._onRender(context, options); + + // Add monster-specific rendering logic + console.log("Monster Sheet rendered for:", context.name); + } +} \ No newline at end of file diff --git a/module/sheets/simple-monster-sheet.mjs b/module/sheets/simple-monster-sheet.mjs deleted file mode 100644 index 64ea956..0000000 --- a/module/sheets/simple-monster-sheet.mjs +++ /dev/null @@ -1,65 +0,0 @@ -const { HandlebarsApplicationMixin } = foundry.applications.api -const { ActorSheetV2 } = foundry.applications.sheets - -/** - * A very basic Actor Sheet implementation using DocumentSheetV2 - */ -export class SimpleMonsterSheet extends HandlebarsApplicationMixin(ActorSheetV2) { - - /** @override */ - static DEFAULT_OPTIONS = { - classes: ["basicfantasyrpg", "sheet", "actor", "simple", "monster"], - position: { - width: 400, - height: 600 - }, - window: { - resizable: true, - title: "Monster groaar!" - }, - form: { - handler: SimpleMonsterSheet.#onSubmitDocumentForm, - submitOnChange: true - } - }; - - /** @override */ - static PARTS = { - header: { - template: "systems/basicfantasyrpg/templates/actor/simple-monster-sheet.hbs" - } - }; - - /** @override */ - async _prepareContext(options) { - const context = await super._prepareContext(options); - - // Add the actor's basic data to the context - context.actor = this.document; - context.system = this.document.system; - context.name = this.document.name; - context.editable = true; - - return context; - } - - /** @override */ - _onRender(context, options) { - super._onRender(context, options); - - // Add any additional rendering logic here - console.log("Simple Monster Sheet rendered for:", context.name); - } - - /** - * Handle form submission for the actor sheet - * @param {SubmitEvent} event The form submission event - * @param {HTMLFormElement} form The submitted form - * @param {FormDataExtended} formData The form data - * @returns {Promise} - */ - static async #onSubmitDocumentForm(event, form, formData) { - const updates = foundry.utils.expandObject(formData.object); - return this.document.update(updates); - } -} diff --git a/module/sheets/simple-sheet.mjs b/module/sheets/simple-sheet.mjs deleted file mode 100644 index 7e67cb2..0000000 --- a/module/sheets/simple-sheet.mjs +++ /dev/null @@ -1,65 +0,0 @@ -const { HandlebarsApplicationMixin } = foundry.applications.api -const { ActorSheetV2 } = foundry.applications.sheets - -/** - * A very basic Actor Sheet implementation using DocumentSheetV2 - */ -export class SimpleActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { - - /** @override */ - static DEFAULT_OPTIONS = { - ...ActorSheetV2.DEFAULT_OPTIONS, - classes: ["basicfantasyrpg", "sheet", "actor", "themed", "theme-light"], - position: { - width: 400, - height: 300 - }, - window: { - resizable: true, - title: "Just an actor!" - }, - form: { - handler: SimpleActorSheet.#onSubmitDocumentForm, - submitOnChange: true - } - }; - - /** @override */ - static PARTS = { - header: { - template: "systems/basicfantasyrpg/templates/actor/simple-actor-sheet.hbs" - } - }; - - /** @override */ - async _prepareContext(options) { - const context = await super._prepareContext(options); - - // Add the actor's basic data to the context - context.actor = this.document; - context.system = this.document.system; - context.name = this.document.name; - - return context; - } - - /** @override */ - _onRender(context, options) { - super._onRender(context, options); - - // Add any additional rendering logic here - console.log("Simple Actor Sheet rendered for:", context.name); - } - - /** - * Handle form submission for the actor sheet - * @param {SubmitEvent} event The form submission event - * @param {HTMLFormElement} form The submitted form - * @param {FormDataExtended} formData The form data - * @returns {Promise} - */ - static async #onSubmitDocumentForm(event, form, formData) { - const updates = foundry.utils.expandObject(formData.object); - return this.document.update(updates); - } -} diff --git a/templates/actor/actor-character-sheet.html b/templates/actor/actor-character-sheet.html index 0cc102e..00037ee 100644 --- a/templates/actor/actor-character-sheet.html +++ b/templates/actor/actor-character-sheet.html @@ -1,4 +1,4 @@ -
+
{{!-- Sheet Header --}}
@@ -105,5 +105,5 @@

- +

diff --git a/templates/actor/actor-monster-sheet.html b/templates/actor/actor-monster-sheet.html index b9887cf..68b547f 100644 --- a/templates/actor/actor-monster-sheet.html +++ b/templates/actor/actor-monster-sheet.html @@ -1,4 +1,4 @@ -
+
{{!-- Sheet Header --}}
@@ -104,4 +104,4 @@

- +

diff --git a/templates/actor/simple-actor-sheet.hbs b/templates/actor/header.hbs similarity index 59% rename from templates/actor/simple-actor-sheet.hbs rename to templates/actor/header.hbs index 07658bf..d0f8038 100644 --- a/templates/actor/simple-actor-sheet.hbs +++ b/templates/actor/header.hbs @@ -1,4 +1,4 @@ -
+
{{!-- Sheet Header --}}
@@ -7,16 +7,8 @@

- + {{> (lookup this "resourcesTemplate") }}
- {{!-- Sheet Tab Navigation (if any) --}} - {{#if tabs}} - - {{/if}}
\ No newline at end of file diff --git a/templates/actor/parts/character-resources.hbs b/templates/actor/parts/character-resources.hbs new file mode 100644 index 0000000..d68856f --- /dev/null +++ b/templates/actor/parts/character-resources.hbs @@ -0,0 +1,30 @@ +
+ +
+ +
+ +  /  + +
+
+ +
+ +
+ +   + +
+
+ +
+ +
+ +  /  + +
+
+ +
\ No newline at end of file From 730985a39e3450c5dc7c1299511464aa3c9194d4 Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Wed, 20 Aug 2025 18:57:13 +0200 Subject: [PATCH 05/17] WIP, i18n improvised --- module/sheets/base-actor-sheet.mjs | 7 ------ module/sheets/character-sheet.mjs | 4 +++- template.json | 18 ++++++++++----- templates/actor/{header.hbs => character.hbs} | 7 ++++-- templates/actor/simple-monster-sheet.hbs | 23 ------------------- 5 files changed, 20 insertions(+), 39 deletions(-) rename templates/actor/{header.hbs => character.hbs} (82%) delete mode 100644 templates/actor/simple-monster-sheet.hbs diff --git a/module/sheets/base-actor-sheet.mjs b/module/sheets/base-actor-sheet.mjs index b477f2e..0573db2 100644 --- a/module/sheets/base-actor-sheet.mjs +++ b/module/sheets/base-actor-sheet.mjs @@ -26,13 +26,6 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { }, }; - // /** @override */ - // static PARTS = { - // header: { - // template: "systems/basicfantasyrpg/templates/actor/header.hbs" - // } - // }; - /** @override */ async _prepareContext(options) { const context = await super._prepareContext(options); diff --git a/module/sheets/character-sheet.mjs b/module/sheets/character-sheet.mjs index bc2d78a..328e33a 100644 --- a/module/sheets/character-sheet.mjs +++ b/module/sheets/character-sheet.mjs @@ -19,7 +19,7 @@ export class CharacterSheet extends BaseActorSheet { static PARTS = { header: { - template: "systems/basicfantasyrpg/templates/actor/header.hbs" + template: "systems/basicfantasyrpg/templates/actor/character.hbs" }, resources: { template: "systems/basicfantasyrpg/templates/actor/parts/character-resources.hbs" @@ -45,6 +45,8 @@ export class CharacterSheet extends BaseActorSheet { const context = await super._prepareContext(options); context.resourcesTemplate = "systems/basicfantasyrpg/templates/actor/parts/character-resources.hbs"; + + console.log("Available context data:", context); return context; } } \ No newline at end of file diff --git a/template.json b/template.json index 6a6747d..f788540 100644 --- a/template.json +++ b/template.json @@ -51,22 +51,28 @@ "templates": ["base"], "abilities": { "str": { - "value": 10 + "value": 10, + "label": "BASICFANTASYRPG.AbilityStr" }, "int": { - "value": 10 + "value": 10, + "label": "BASICFANTASYRPG.AbilityInt" }, "wis": { - "value": 10 + "value": 10, + "label": "BASICFANTASYRPG.AbilityWis" }, "dex": { - "value": 10 + "value": 10, + "label": "BASICFANTASYRPG.AbilityDex" }, "con": { - "value": 10 + "value": 10, + "label": "BASICFANTASYRPG.AbilityCon" }, "cha": { - "value": 10 + "value": 10, + "label": "BASICFANTASYRPG.AbilityCha" } }, "age": { diff --git a/templates/actor/header.hbs b/templates/actor/character.hbs similarity index 82% rename from templates/actor/header.hbs rename to templates/actor/character.hbs index d0f8038..29d0bf5 100644 --- a/templates/actor/header.hbs +++ b/templates/actor/character.hbs @@ -1,4 +1,4 @@ -
+
{{!-- Sheet Header --}}
@@ -7,8 +7,11 @@

+ {{#if resourcesTemplate}} {{> (lookup this "resourcesTemplate") }} + {{/if}}
-
\ No newline at end of file + + \ No newline at end of file diff --git a/templates/actor/simple-monster-sheet.hbs b/templates/actor/simple-monster-sheet.hbs deleted file mode 100644 index 59e40f0..0000000 --- a/templates/actor/simple-monster-sheet.hbs +++ /dev/null @@ -1,23 +0,0 @@ -
-
-

- {{#if @root.editable}} - - {{else}} - {{name}} - {{/if}} -

-
- -
-

- Monster! p {{actor.type}} -

- {{#if @root.editable}} -
- - -
- {{/if}} -
-
From 4dc0209b2ce97073a9ec98d373fd917cde704f57 Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Wed, 20 Aug 2025 23:00:27 +0200 Subject: [PATCH 06/17] WIP make combat and items tab work on characters --- lang/en.json | 8 ++ lang/fr.json | 14 +- module/sheets/character-sheet.mjs | 59 ++++++--- styles/basicfantasyrpg.css | 4 + templates/actor/character.hbs | 42 +++++- templates/actor/parts/character-resources.hbs | 30 ----- templates/actor/parts/combat.hbs | 120 ++++++++++++++++++ templates/actor/parts/items.hbs | 74 +++++++++++ 8 files changed, 291 insertions(+), 60 deletions(-) delete mode 100644 templates/actor/parts/character-resources.hbs create mode 100644 templates/actor/parts/combat.hbs create mode 100644 templates/actor/parts/items.hbs diff --git a/lang/en.json b/lang/en.json index c68e18e..38e14e2 100644 --- a/lang/en.json +++ b/lang/en.json @@ -29,6 +29,14 @@ "TYPES.Item.floor": "Floor", "TYPES.Item.wall": "Wall", + "BASICFANTASYRPG.Tab.cargo": "Cargo", + "BASICFANTASYRPG.Tab.combat": "Combat", + "BASICFANTASYRPG.Tab.description": "Description", + "BASICFANTASYRPG.Tab.features": "Special Abilities", + "BASICFANTASYRPG.Tab.floors": "Floors & Walls", + "BASICFANTASYRPG.Tab.items": "Equipment", + "BASICFANTASYRPG.Tab.spells": "Spells", + "BASICFANTASYRPG.TabCargo": "Cargo", "BASICFANTASYRPG.TabCombat": "Combat", "BASICFANTASYRPG.TabDescription": "Description", diff --git a/lang/fr.json b/lang/fr.json index 2be7934..a55aef5 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -29,13 +29,13 @@ "TYPES.Item.floor": "Sol", "TYPES.Item.wall": "Mur", - "BASICFANTASYRPG.TabCargo": "Cargo", - "BASICFANTASYRPG.TabCombat": "Combat", - "BASICFANTASYRPG.TabDescription": "Description", - "BASICFANTASYRPG.TabFeatures": "Capacité spéciale", - "BASICFANTASYRPG.TabFloors": "Sols & Murs", - "BASICFANTASYRPG.TabItems": "Équipement", - "BASICFANTASYRPG.TabSpells": "Sortilèges", + "BASICFANTASYRPG.Tab.cargo": "Cargo", + "BASICFANTASYRPG.Tab.combat": "Combat", + "BASICFANTASYRPG.Tab.description": "Description", + "BASICFANTASYRPG.Tab.features": "Capacité spéciale", + "BASICFANTASYRPG.Tab.floors": "Sols & Murs", + "BASICFANTASYRPG.Tab.items": "Équipement", + "BASICFANTASYRPG.Tab.spells": "Sortilèges", "BASICFANTASYRPG.AbilityStr": "Force", "BASICFANTASYRPG.AbilityCon": "Constitution", diff --git a/module/sheets/character-sheet.mjs b/module/sheets/character-sheet.mjs index 328e33a..0171c2b 100644 --- a/module/sheets/character-sheet.mjs +++ b/module/sheets/character-sheet.mjs @@ -1,4 +1,4 @@ -import { BaseActorSheet } from './base-actor-sheet.mjs'; +import { BaseActorSheet } from "./base-actor-sheet.mjs"; /** * Character Sheet for Basic Fantasy RPG @@ -6,31 +6,39 @@ import { BaseActorSheet } from './base-actor-sheet.mjs'; * @extends {BaseActorSheet} */ export class CharacterSheet extends BaseActorSheet { - static DEFAULT_OPTIONS = { ...BaseActorSheet.DEFAULT_OPTIONS, classes: [...BaseActorSheet.DEFAULT_OPTIONS.classes, "character"], window: { ...BaseActorSheet.DEFAULT_OPTIONS.window, - title: "Character" + title: "Character", }, }; + static TABS = { + primary: { + tabs: [{ id: "combat" }, { id: "items" }], + labelPrefix: "BASICFANTASYRPG.Tab", + initial: "combat", + }, + }; static PARTS = { header: { - template: "systems/basicfantasyrpg/templates/actor/character.hbs" + template: "systems/basicfantasyrpg/templates/actor/character.hbs", + }, + tabs: { + // Foundry-provided generic template + template: 'templates/generic/tab-navigation.hbs', }, - resources: { - template: "systems/basicfantasyrpg/templates/actor/parts/character-resources.hbs" - } + combat: { + template: "systems/basicfantasyrpg/templates/actor/parts/combat.hbs", + }, + items: { + template: "systems/basicfantasyrpg/templates/actor/parts/items.hbs", + } }; - /** @override */ - _configureRenderOptions(options) { - super._configureRenderOptions(options); - options.parts = ['header'] - } /** @override */ _onRender(context, options) { @@ -41,12 +49,27 @@ export class CharacterSheet extends BaseActorSheet { } /** @override */ - async _prepareContext(options) { - const context = await super._prepareContext(options); + async _prepareContext(options) { + const context = await super._prepareContext(options); - context.resourcesTemplate = "systems/basicfantasyrpg/templates/actor/parts/character-resources.hbs"; + context.tabs = this._prepareTabs("primary"); - console.log("Available context data:", context); - return context; + // Handle saves. + for (let [k, v] of Object.entries(context.data.saves)) { + v.label = game.i18n.localize(CONFIG.BASICFANTASYRPG.saves[k]) ?? k; } -} \ No newline at end of file + + // Handle ability scores. + for (let [k, v] of Object.entries(context.data.abilities)) { + v.label = game.i18n.localize(CONFIG.BASICFANTASYRPG.abilities[k]) ?? k; + } + + // Handle money. + for (let [k, v] of Object.entries(context.data.money)) { + v.label = game.i18n.localize(CONFIG.BASICFANTASYRPG.money[k]) ?? k; + } + + console.log("Available context data:", context); + return context; + } +} diff --git a/styles/basicfantasyrpg.css b/styles/basicfantasyrpg.css index 360c8e7..fe5bda5 100644 --- a/styles/basicfantasyrpg.css +++ b/styles/basicfantasyrpg.css @@ -455,6 +455,10 @@ max-height: calc(100% - 53px); } +.basicfantasyrpg.sheet .tab-container { + display: flex; +} + .basicfantasyrpg .editor-content { font-family: "Soutane", "Century Gothic", "TeX Gyre Adventor", var(--font-primary); font-size: 18px; diff --git a/templates/actor/character.hbs b/templates/actor/character.hbs index 29d0bf5..a3e6d85 100644 --- a/templates/actor/character.hbs +++ b/templates/actor/character.hbs @@ -1,15 +1,47 @@ -
+
{{!-- Sheet Header --}}
+ +

- +

- {{#if resourcesTemplate}} - {{> (lookup this "resourcesTemplate") }} - {{/if}} +
+ + +
+ +
+ +  /  + +
+
+ + +
+ +
+ +   + +
+
+ + +
+ +
+ +  /  + +
+
+ +
diff --git a/templates/actor/parts/character-resources.hbs b/templates/actor/parts/character-resources.hbs deleted file mode 100644 index d68856f..0000000 --- a/templates/actor/parts/character-resources.hbs +++ /dev/null @@ -1,30 +0,0 @@ -
- -
- -
- -  /  - -
-
- -
- -
- -   - -
-
- -
- -
- -  /  - -
-
- -
\ No newline at end of file diff --git a/templates/actor/parts/combat.hbs b/templates/actor/parts/combat.hbs new file mode 100644 index 0000000..81da101 --- /dev/null +++ b/templates/actor/parts/combat.hbs @@ -0,0 +1,120 @@ +
+ +
+ + + + +
+ +
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
    +
  1. +
    {{localize 'ITEM.TypeWeapon'}}
    +
    {{localize 'BASICFANTASYRPG.Attack'}} / {{localize 'BASICFANTASYRPG.DamageAbbr'}}
    + +
  2. + {{#each weapons as |weapon id|}} +
  3. +
    +
    + +
    +

    {{weapon.name}}

    +
    + +
    + + +
    +
  4. + {{/each}} +
+ +
    +
  1. +
    {{localize 'ITEM.TypeArmor'}}
    +
    {{localize 'BASICFANTASYRPG.ArmorClass'}}
    + +
  2. + {{#each armors as |armor id|}} +
  3. +
    +
    + +
    +

    {{armor.name}}

    +
    +
    {{localize armor.system.armorClass.abbr}} {{armor.system.armorClass.value}}
    +
    + + +
    +
  4. + {{/each}} +
+
+
+
\ No newline at end of file diff --git a/templates/actor/parts/items.hbs b/templates/actor/parts/items.hbs new file mode 100644 index 0000000..a922480 --- /dev/null +++ b/templates/actor/parts/items.hbs @@ -0,0 +1,74 @@ +
+ + {{#if (eq actor.type 'character')}} +
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ {{/if}} + +
    +
  1. +
    {{localizeItemNameForActor actor.type}}
    +
    {{localize 'BASICFANTASYRPG.CarriedWeight'}}: {{carriedWeight}} {{localize 'BASICFANTASYRPG.PoundsAbbr'}}
    + +
  2. + {{#each gear as |item id|}} +
  3. +
    +
    + +
    +

    {{item.system.quantity.value}} {{item.name}}

    +
    +
    +
    {{item.system.weight.value}} {{localize 'BASICFANTASYRPG.PoundsAbbr'}}
    +
    {{item.system.price.value}}
    +
    +
    + + +
    +
  4. + {{/each}} +
+
\ No newline at end of file From 00903f291f1a084089a749677d8ad07ea34d03a9 Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Wed, 20 Aug 2025 23:25:11 +0200 Subject: [PATCH 07/17] WIP add more tabs --- module/sheets/base-actor-sheet.mjs | 8 +++++ module/sheets/character-sheet.mjs | 11 +++++- templates/actor/parts/description.hbs | 39 ++++++++++++++++++++++ templates/actor/parts/features.hbs | 28 ++++++++++++++++ templates/actor/parts/spells.hbs | 48 +++++++++++++++++++++++++++ 5 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 templates/actor/parts/description.hbs create mode 100644 templates/actor/parts/features.hbs create mode 100644 templates/actor/parts/spells.hbs diff --git a/module/sheets/base-actor-sheet.mjs b/module/sheets/base-actor-sheet.mjs index 0573db2..23a0bc8 100644 --- a/module/sheets/base-actor-sheet.mjs +++ b/module/sheets/base-actor-sheet.mjs @@ -36,6 +36,14 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { context.name = this.document.name; context.data = context.system; + // // biography editor + // context.enrichedBiography = await TextEditor.enrichHTML( + // this.document.system.biography, + // { + // async: true, + // } + // ); + return context; } diff --git a/module/sheets/character-sheet.mjs b/module/sheets/character-sheet.mjs index 0171c2b..84f0d68 100644 --- a/module/sheets/character-sheet.mjs +++ b/module/sheets/character-sheet.mjs @@ -17,7 +17,7 @@ export class CharacterSheet extends BaseActorSheet { static TABS = { primary: { - tabs: [{ id: "combat" }, { id: "items" }], + tabs: [{ id: "combat" }, { id: "description"}, { id: "items" }, { id: "spells"}, { id: "features" }], labelPrefix: "BASICFANTASYRPG.Tab", initial: "combat", }, @@ -34,8 +34,17 @@ export class CharacterSheet extends BaseActorSheet { combat: { template: "systems/basicfantasyrpg/templates/actor/parts/combat.hbs", }, + description: { + template: "systems/basicfantasyrpg/templates/actor/parts/description.hbs", + }, items: { template: "systems/basicfantasyrpg/templates/actor/parts/items.hbs", + }, + spells: { + template: "systems/basicfantasyrpg/templates/actor/parts/spells.hbs", + }, + features: { + template: "systems/basicfantasyrpg/templates/actor/parts/features.hbs", } }; diff --git a/templates/actor/parts/description.hbs b/templates/actor/parts/description.hbs new file mode 100644 index 0000000..af0d48d --- /dev/null +++ b/templates/actor/parts/description.hbs @@ -0,0 +1,39 @@ +
+ +
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ + + + + +
+ diff --git a/templates/actor/parts/features.hbs b/templates/actor/parts/features.hbs new file mode 100644 index 0000000..3c973fb --- /dev/null +++ b/templates/actor/parts/features.hbs @@ -0,0 +1,28 @@ +
+
    +
  1. +
    {{localize 'ITEM.TypeFeature'}}
    +
    {{localize 'BASICFANTASYRPG.Formula'}}
    + +
  2. + {{#each features as |item id|}} +
  3. +
    +
    + +
    +

    {{item.name}}

    +
    +
    {{item.system.formula.value}}{{#if item.system.targetNumber.value}} ({{#if item.system.rollUnder.value}}≤{{else}}≥{{/if}} {{item.system.targetNumber.value}}){{/if}}
    +
    + + +
    +
  4. + {{/each}} +
+
\ No newline at end of file diff --git a/templates/actor/parts/spells.hbs b/templates/actor/parts/spells.hbs new file mode 100644 index 0000000..07a4994 --- /dev/null +++ b/templates/actor/parts/spells.hbs @@ -0,0 +1,48 @@ +
+ +
+ {{#each data.spellsPerLevel.value as |spellsPerLevel level|}} +
+ +
+ +
+
+ {{/each}} + +
+ +
    + {{#each spells as |spells spellLevel|}} +
  1. +
    {{localize 'BASICFANTASYRPG.SpellLevel'}} {{spellLevel}} {{localize 'BASICFANTASYRPG.Spells'}}
    +
    {{localize 'BASICFANTASYRPG.Prepared'}}
    + +
  2. + {{#each spells as |item id|}} +
  3. +
    +
    + +
    +

    {{item.name}}

    +
    +
    +
    +
    {{item.system.prepared.value}}
    +
    +
    +
    + + +
    +
  4. + {{/each}} + {{/each}} +
+ +
\ No newline at end of file From 2252e0803e1b2c95b6e1b43cbaaaf8aa33647082 Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Thu, 21 Aug 2025 20:55:10 +0200 Subject: [PATCH 08/17] Biography editor working --- module/sheets/base-actor-sheet.mjs | 17 +++++++++-------- templates/actor/parts/description.hbs | 13 ++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/module/sheets/base-actor-sheet.mjs b/module/sheets/base-actor-sheet.mjs index 23a0bc8..1b5c811 100644 --- a/module/sheets/base-actor-sheet.mjs +++ b/module/sheets/base-actor-sheet.mjs @@ -29,6 +29,7 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { /** @override */ async _prepareContext(options) { const context = await super._prepareContext(options); + const TextEditor = foundry.applications.ux.TextEditor.implementation; // Add the actor's basic data to the context context.actor = this.document; @@ -36,15 +37,15 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { context.name = this.document.name; context.data = context.system; - // // biography editor - // context.enrichedBiography = await TextEditor.enrichHTML( - // this.document.system.biography, - // { - // async: true, - // } - // ); + // biography editor + context.enrichedBiography = await TextEditor.enrichHTML( + this.document.system.biography, + { + async: true, + } + ); - return context; + return context; } /** diff --git a/templates/actor/parts/description.hbs b/templates/actor/parts/description.hbs index af0d48d..be70ad6 100644 --- a/templates/actor/parts/description.hbs +++ b/templates/actor/parts/description.hbs @@ -27,13 +27,12 @@
- - - - + {{ uuid }} + +
From db6f3ae39309d4e2eb5ddaaa0bb53afeca16af20 Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Thu, 21 Aug 2025 22:26:04 +0200 Subject: [PATCH 09/17] Item data --- module/sheets/base-actor-sheet.mjs | 138 ++++++++++++++++++++++++----- 1 file changed, 117 insertions(+), 21 deletions(-) diff --git a/module/sheets/base-actor-sheet.mjs b/module/sheets/base-actor-sheet.mjs index 1b5c811..a440444 100644 --- a/module/sheets/base-actor-sheet.mjs +++ b/module/sheets/base-actor-sheet.mjs @@ -1,5 +1,5 @@ -import { successChatMessage } from '../helpers/chat.mjs'; -import { onManageActiveEffect, prepareActiveEffectCategories } from '../helpers/effects.mjs'; +import { successChatMessage } from "../helpers/chat.mjs"; +import { onManageActiveEffect, prepareActiveEffectCategories } from "../helpers/effects.mjs"; const { HandlebarsApplicationMixin } = foundry.applications.api; const { ActorSheetV2 } = foundry.applications.sheets; @@ -10,40 +10,39 @@ const { ActorSheetV2 } = foundry.applications.sheets; * @extends {ActorSheetV2} */ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { - static DEFAULT_OPTIONS = { classes: ["basicfantasyrpg", "sheet", "actor", "themed", "theme-light"], position: { width: 600, - height: 600 + height: 600, }, window: { - resizable: true + resizable: true, }, form: { handler: BaseActorSheet.#onSubmitDocumentForm, - submitOnChange: true + submitOnChange: true, }, }; /** @override */ async _prepareContext(options) { - const context = await super._prepareContext(options); + const context = await super._prepareContext(options); const TextEditor = foundry.applications.ux.TextEditor.implementation; - // Add the actor's basic data to the context - context.actor = this.document; - context.system = this.document.system; - context.name = this.document.name; - context.data = context.system; - - // biography editor - context.enrichedBiography = await TextEditor.enrichHTML( - this.document.system.biography, - { - async: true, - } - ); + // Add the actor's basic data to the context + context.actor = this.document; + context.system = this.document.system; + context.name = this.document.name; + context.data = context.system; + context.items = this.document.items.contents; + + // biography editor + context.enrichedBiography = await TextEditor.enrichHTML(this.document.system.biography, { + async: true, + }); + + this._prepareItems(context); return context; } @@ -59,4 +58,101 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { const updates = foundry.utils.expandObject(formData.object); return this.document.update(updates); } -} \ No newline at end of file + + /** + * Organize and classify Items for Actor sheets. + * + * @param {Object} actorData The actor to prepare. + * + * @return {undefined} + */ + _prepareItems(context) { + // Initialize containers. + const gear = []; + const weapons = []; + const armors = []; + const spells = { + 1: [], + 2: [], + 3: [], + 4: [], + 5: [], + 6: [], + }; + const features = []; + const floors = []; + const walls = []; + + // Define an object to store carried weight. + let carriedWeight = { + value: 0, + _addWeight(moreWeight, quantity) { + if (!quantity || quantity === "" || Number.isNaN(quantity) || quantity < 0) { + return; // check we have a valid quantity, and do nothing if we do not + } + let q = Math.floor(quantity / 20); + if (!Number.isNaN(parseFloat(moreWeight))) { + this.value += parseFloat(moreWeight) * quantity; + } else if (moreWeight === "*" && q > 0) { + // '*' is gold pieces + this.value += q; + } + }, + }; + + // Iterate through items, allocating to containers + for (let i of context.items) { + i.img = i.img || DEFAULT_TOKEN; + // Append to gear. + if (i.type === "item") { + gear.push(i); + carriedWeight._addWeight(i.system.weight.value, i.system.quantity.value); + } else if (i.type === "weapon") { + // Append to weapons. + weapons.push(i); + carriedWeight._addWeight(i.system.weight.value, 1); // Weapons are always quantity 1 + } else if (i.type === "armor") { + // Append to armors. + armors.push(i); + carriedWeight._addWeight(i.system.weight.value, 1); // Armor is always quantity 1 + } else if (i.type === "spell") { + // Append to spells. + if (i.system.spellLevel.value !== undefined) { + spells[i.system.spellLevel.value].push(i); + } + } else if (i.type === "feature") { + // Append to features. + features.push(i); + } else if (i.type === "floor") { + // Append to floors for strongholds. + floors.push(i); + } else if (i.type === "wall") { + // Append to walls for strongholds. + if (i.system.floor.value !== undefined) { + if (!walls[i.system.floor.value]) walls[i.system.floor.value] = []; + walls[i.system.floor.value].push(i); + } + } + } + + // Iterate through money, add to carried weight + if (context.data.money) { + let gp = Number(context.data.money.gp.value); + gp += context.data.money.pp.value; + gp += context.data.money.ep.value; + gp += context.data.money.sp.value; + gp += context.data.money.cp.value; + carriedWeight._addWeight("*", gp); // '*' will calculate GP weight + } + + // Assign and return + context.gear = gear; + context.weapons = weapons; + context.armors = armors; + context.spells = spells; + context.features = features; + context.floors = floors; + context.walls = walls; + context.carriedWeight = Math.floor(carriedWeight.value); // we discard fractions of weight when we update the sheet + } +} From cac93520f9ce68c6af0bf4cff0465e1af68edd7c Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Thu, 21 Aug 2025 22:57:43 +0200 Subject: [PATCH 10/17] WIP add items --- module/sheets/base-actor-sheet.mjs | 267 +++++++++++++++++++++++++++++ styles/basicfantasyrpg.css | 3 + 2 files changed, 270 insertions(+) diff --git a/module/sheets/base-actor-sheet.mjs b/module/sheets/base-actor-sheet.mjs index a440444..a85a1ea 100644 --- a/module/sheets/base-actor-sheet.mjs +++ b/module/sheets/base-actor-sheet.mjs @@ -155,4 +155,271 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { context.walls = walls; context.carriedWeight = Math.floor(carriedWeight.value); // we discard fractions of weight when we update the sheet } + + _onRender(context, options) { + super._onRender(context, options); + + // Item editing - always available + this.element.addEventListener('click', this._onItemEdit.bind(this)); + + // ------------------------------------------------------------- + // Everything below here is only needed if the sheet is editable + if (!this.isEditable) return; + + // Use event delegation for better performance + this.element.addEventListener('click', this._onSheetClick.bind(this)); + + // Drag and drop for macros + if (this.document.isOwner) { + this._setupDragAndDrop(); + } + } + + /** + * Handle click events using event delegation + * @param {Event} event The click event + */ + _onSheetClick(event) { + const target = event.target; + const control = target.closest('.item-control, .rollable, input[name="rangeBonus"]'); + + if (!control) return; + + // Item creation + if (control.classList.contains('item-create')) { + event.preventDefault(); + this._onItemCreate(event); + } + // Item deletion + else if (control.classList.contains('item-delete')) { + event.preventDefault(); + this._onItemDelete(event); + } + // Spell preparation + else if (control.classList.contains('spell-prepare')) { + event.preventDefault(); + this._onSpellPrepare(event); + } + // Quantity adjustment + else if (control.classList.contains('quantity')) { + event.preventDefault(); + this._onQuantityAdjust(event); + } + // Active effect management + else if (control.classList.contains('effect-control')) { + event.preventDefault(); + onManageActiveEffect(event, this.document); + } + // Rollable abilities + else if (control.classList.contains('rollable')) { + event.preventDefault(); + this._onRoll(event); + } + // Siege engine range bonus (specific to siege engines) + else if (control.matches('input[name="rangeBonus"]') && this.document.type === 'siegeEngine') { + this.document.update({'system.rangeBonus.value': Number(control.value)}); + } + } + + /** + * Handle item editing + * @param {Event} event The click event + */ + _onItemEdit(event) { + if (!event.target.closest('.item-edit')) return; + + event.preventDefault(); + const li = event.target.closest('.item'); + if (!li) return; + + const item = this.document.items.get(li.dataset.itemId); + if (item) { + item.sheet.render(true); + } + } + + /** + * Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset + * @param {Event} event The originating click event + */ + async _onItemCreate(event) { + const header = event.target.closest('.item-create'); + const type = header.dataset.type; + const data = foundry.utils.duplicate(header.dataset); + + if (type === 'spell') { + data.spellLevel = { + 'value': data.spellLevelValue + }; + delete data.spellLevelValue; + } else if (type === 'wall') { + data.floor = { + "value": data.floorNumber + }; + delete data.floorNumber; + } + + const name = `New ${type.capitalize()}`; + const itemData = { + name: name, + type: type, + system: data + }; + delete itemData.system['type']; + + return await Item.create(itemData, {parent: this.document}); + } + + /** + * Handle item deletion with animation + * @param {Event} event The click event + */ + _onItemDelete(event) { + const li = event.target.closest('.item'); + if (!li) return; + + const item = this.document.items.get(li.dataset.itemId); + if (!item) return; + + // Modern animation approach instead of jQuery slideUp + li.style.transition = 'opacity 0.2s ease-out, transform 0.2s ease-out'; + li.style.opacity = '0'; + li.style.transform = 'translateX(-100%)'; + + setTimeout(() => { + item.delete(); + this.render(false); + }, 200); + } + + /** + * Handle spell preparation adjustments + * @param {Event} event The click event + */ + _onSpellPrepare(event) { + const change = event.target.closest('.spell-prepare').dataset.change; + if (!parseInt(change)) return; + + const li = event.target.closest('.item'); + if (!li) return; + + const item = this.document.items.get(li.dataset.itemId); + if (!item) return; + + const newValue = item.system.prepared.value + parseInt(change); + item.update({'system.prepared.value': newValue}); + } + + /** + * Handle quantity adjustments + * @param {Event} event The click event + */ + _onQuantityAdjust(event) { + const change = event.target.closest('.quantity').dataset.change; + if (!parseInt(change)) return; + + const li = event.target.closest('.item'); + if (!li) return; + + const item = this.document.items.get(li.dataset.itemId); + if (!item) return; + + const newValue = item.system.quantity.value + parseInt(change); + item.update({'system.quantity.value': newValue}); + } + + /** + * Handle clickable rolls + * @param {Event} event The originating click event + */ + async _onRoll(event) { + const element = event.target.closest('.rollable'); + const dataset = element.dataset; + + if (dataset.rollType) { + // Handle weapon rolls + if (dataset.rollType === 'weapon') { + const itemId = element.closest('.item').dataset.itemId; + const item = this.document.items.get(itemId); + let label = dataset.label ? + `${game.i18n.localize('BASICFANTASYRPG.Roll')}: ${dataset.label}` : + `${game.i18n.localize('BASICFANTASYRPG.Roll')}: ${dataset.attack.capitalize()} attack with ${item.name}`; + + let rollFormula = 'd20+@ab'; + if (this.document.type === 'character') { + if (dataset.attack === 'melee') { + rollFormula += '+@str.bonus'; + } else if (dataset.attack === 'ranged') { + rollFormula += '+@dex.bonus'; + } + } + rollFormula += '+' + item.system.bonusAb.value; + + let roll = new Roll(rollFormula, this.document.getRollData()); + roll.toMessage({ + speaker: ChatMessage.getSpeaker({ actor: this.document }), + flavor: label, + rollMode: game.settings.get('core', 'rollMode'), + }); + return roll; + } + + // Handle item rolls + if (dataset.rollType === 'item') { + const itemId = element.closest('.item').dataset.itemId; + const item = this.document.items.get(itemId); + if (item) return item.roll(); + } + } + + // Handle rolls that supply the formula directly + if (dataset.roll) { + let label = dataset.label ? + `${game.i18n.localize('BASICFANTASYRPG.Roll')}: ${dataset.label}` : ''; + let roll = new Roll(dataset.roll, this.document.getRollData()); + await roll.roll(); + label += successChatMessage(roll.total, dataset.targetNumber, dataset.rollUnder); + roll.toMessage({ + speaker: ChatMessage.getSpeaker({ actor: this.document }), + flavor: label, + rollMode: game.settings.get('core', 'rollMode'), + }); + return roll; + } + } + + /** + * Setup drag and drop functionality for items + */ + _setupDragAndDrop() { + const handler = ev => this._onDragStart(ev); + this.element.querySelectorAll('li.item').forEach(li => { + if (li.classList.contains('inventory-header')) return; + li.setAttribute('draggable', true); + li.addEventListener('dragstart', handler, false); + }); + } + + /** + * Handle beginning of drag workflows + * @param {Event} event The drag start event + */ + _onDragStart(event) { + const li = event.currentTarget; + if (event.target.classList.contains('content-link')) return; + + let dragData = null; + + // Owned Items + if (li.dataset.itemId) { + const item = this.document.items.get(li.dataset.itemId); + dragData = item.toDragData(); + } + + if (!dragData) return; + + // Set data transfer + event.dataTransfer.setData("text/plain", JSON.stringify(dragData)); + } + } diff --git a/styles/basicfantasyrpg.css b/styles/basicfantasyrpg.css index fe5bda5..ce90138 100644 --- a/styles/basicfantasyrpg.css +++ b/styles/basicfantasyrpg.css @@ -558,6 +558,9 @@ margin: 0; white-space: nowrap; overflow-x: hidden; + font-size: 13px; + font-family: "Soutane", "Century Gothic", "TeX Gyre Adventor", var(--font-primary); + font-weight: 400; } .basicfantasyrpg .items-list .item-controls { From 442ada896440b2a682c52d9e777381de43d2b65c Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Thu, 21 Aug 2025 23:48:26 +0200 Subject: [PATCH 11/17] Inventory actions apparently working --- CLAUDE.md | 7 +- module/sheets/base-actor-sheet.mjs | 183 +++++++++++++++-------------- styles/basicfantasyrpg.css | 16 +++ 3 files changed, 118 insertions(+), 88 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 17ec148..52a4c20 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,8 +16,11 @@ Good sources can be found under the domains foundryvtt.com and foundryvtt.wiki In Foundry, there are top-level entities like "Actors" (which can be characters, monsters, vehicles, etc.), and "Items" (which can be equipment, spells, special abilities, etc.). -Usually, there is a single class for Actor and a single class for Items, and it also implements -subtype-specific behavior (like for monsters or characters in the case of Actor). +The old implementation (using Application v1) had a single class for Actor and a single class for Items, +and it also implements subtype-specific behavior (like for monsters or characters in the case of Actor). + +For the v2, I plan to have a base actor class and then inherit from it with specific classes for +subtypes. Besides the Actor and Item class, there are also corresponding Sheet classes, like an Actor Sheet and Item Sheet, responsible for rendering the relevant information about that entity on a sheet, which is diff --git a/module/sheets/base-actor-sheet.mjs b/module/sheets/base-actor-sheet.mjs index a85a1ea..369ff3b 100644 --- a/module/sheets/base-actor-sheet.mjs +++ b/module/sheets/base-actor-sheet.mjs @@ -159,17 +159,16 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { _onRender(context, options) { super._onRender(context, options); - // Item editing - always available - this.element.addEventListener('click', this._onItemEdit.bind(this)); - - // ------------------------------------------------------------- - // Everything below here is only needed if the sheet is editable - if (!this.isEditable) return; - - // Use event delegation for better performance - this.element.addEventListener('click', this._onSheetClick.bind(this)); + // Remove old listeners first to prevent duplicates + if (this._boundClickHandler) { + this.element.removeEventListener("click", this._boundClickHandler); + } - // Drag and drop for macros + // Add click listener with proper binding + this._boundClickHandler = this._onSheetClick.bind(this); + this.element.addEventListener("click", this._boundClickHandler); + + // Drag and drop for macros - always refresh since items may have changed if (this.document.isOwner) { this._setupDragAndDrop(); } @@ -181,43 +180,55 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { */ _onSheetClick(event) { const target = event.target; + // const control = target.closest('.item-control, .rollable, input[name="rangeBonus"]'); + // Handle item editing first (always available) + const editControl = target.closest(".item-edit"); + if (editControl) { + event.preventDefault(); + this._onItemEdit(event); + return; + } + + // Everything below here is only needed if the sheet is editable + if (!this.isEditable) return; + const control = target.closest('.item-control, .rollable, input[name="rangeBonus"]'); - + if (!control) return; // Item creation - if (control.classList.contains('item-create')) { + if (control.classList.contains("item-create")) { event.preventDefault(); this._onItemCreate(event); } - // Item deletion - else if (control.classList.contains('item-delete')) { + // Item deletion + else if (control.classList.contains("item-delete")) { event.preventDefault(); this._onItemDelete(event); } // Spell preparation - else if (control.classList.contains('spell-prepare')) { + else if (control.classList.contains("spell-prepare")) { event.preventDefault(); this._onSpellPrepare(event); } // Quantity adjustment - else if (control.classList.contains('quantity')) { + else if (control.classList.contains("quantity")) { event.preventDefault(); this._onQuantityAdjust(event); } // Active effect management - else if (control.classList.contains('effect-control')) { + else if (control.classList.contains("effect-control")) { event.preventDefault(); onManageActiveEffect(event, this.document); } // Rollable abilities - else if (control.classList.contains('rollable')) { + else if (control.classList.contains("rollable")) { event.preventDefault(); this._onRoll(event); } // Siege engine range bonus (specific to siege engines) - else if (control.matches('input[name="rangeBonus"]') && this.document.type === 'siegeEngine') { - this.document.update({'system.rangeBonus.value': Number(control.value)}); + else if (control.matches('input[name="rangeBonus"]') && this.document.type === "siegeEngine") { + this.document.update({ "system.rangeBonus.value": Number(control.value) }); } } @@ -226,11 +237,8 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { * @param {Event} event The click event */ _onItemEdit(event) { - if (!event.target.closest('.item-edit')) return; - - event.preventDefault(); - const li = event.target.closest('.item'); - if (!li) return; + const li = event.target.closest(".item"); + if (!li?.dataset.itemId) return; const item = this.document.items.get(li.dataset.itemId); if (item) { @@ -243,53 +251,55 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { * @param {Event} event The originating click event */ async _onItemCreate(event) { - const header = event.target.closest('.item-create'); + const header = event.target.closest(".item-create"); const type = header.dataset.type; const data = foundry.utils.duplicate(header.dataset); - - if (type === 'spell') { + + if (type === "spell") { data.spellLevel = { - 'value': data.spellLevelValue + value: data.spellLevelValue, }; delete data.spellLevelValue; - } else if (type === 'wall') { + } else if (type === "wall") { data.floor = { - "value": data.floorNumber + value: data.floorNumber, }; delete data.floorNumber; } - + const name = `New ${type.capitalize()}`; const itemData = { name: name, type: type, - system: data + system: data, }; - delete itemData.system['type']; + delete itemData.system["type"]; - return await Item.create(itemData, {parent: this.document}); + return await Item.create(itemData, { parent: this.document }); } /** * Handle item deletion with animation * @param {Event} event The click event */ - _onItemDelete(event) { - const li = event.target.closest('.item'); + async _onItemDelete(event) { + const li = event.target.closest(".item"); if (!li) return; - + const item = this.document.items.get(li.dataset.itemId); if (!item) return; - // Modern animation approach instead of jQuery slideUp - li.style.transition = 'opacity 0.2s ease-out, transform 0.2s ease-out'; - li.style.opacity = '0'; - li.style.transform = 'translateX(-100%)'; + // Add deletion animation class + li.classList.add('item-deleting'); + + // Listen for animation end + const handleAnimationEnd = async () => { + li.removeEventListener('animationend', handleAnimationEnd); + await item.delete(); + // Sheet will auto-render due to document change + }; - setTimeout(() => { - item.delete(); - this.render(false); - }, 200); + li.addEventListener('animationend', handleAnimationEnd); } /** @@ -297,17 +307,17 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { * @param {Event} event The click event */ _onSpellPrepare(event) { - const change = event.target.closest('.spell-prepare').dataset.change; + const change = event.target.closest(".spell-prepare").dataset.change; if (!parseInt(change)) return; - - const li = event.target.closest('.item'); + + const li = event.target.closest(".item"); if (!li) return; - + const item = this.document.items.get(li.dataset.itemId); if (!item) return; - + const newValue = item.system.prepared.value + parseInt(change); - item.update({'system.prepared.value': newValue}); + item.update({ "system.prepared.value": newValue }); } /** @@ -315,17 +325,17 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { * @param {Event} event The click event */ _onQuantityAdjust(event) { - const change = event.target.closest('.quantity').dataset.change; + const change = event.target.closest(".quantity").dataset.change; if (!parseInt(change)) return; - - const li = event.target.closest('.item'); + + const li = event.target.closest(".item"); if (!li) return; - + const item = this.document.items.get(li.dataset.itemId); if (!item) return; - + const newValue = item.system.quantity.value + parseInt(change); - item.update({'system.quantity.value': newValue}); + item.update({ "system.quantity.value": newValue }); } /** @@ -333,40 +343,40 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { * @param {Event} event The originating click event */ async _onRoll(event) { - const element = event.target.closest('.rollable'); + const element = event.target.closest(".rollable"); const dataset = element.dataset; if (dataset.rollType) { // Handle weapon rolls - if (dataset.rollType === 'weapon') { - const itemId = element.closest('.item').dataset.itemId; + if (dataset.rollType === "weapon") { + const itemId = element.closest(".item").dataset.itemId; const item = this.document.items.get(itemId); - let label = dataset.label ? - `${game.i18n.localize('BASICFANTASYRPG.Roll')}: ${dataset.label}` : - `${game.i18n.localize('BASICFANTASYRPG.Roll')}: ${dataset.attack.capitalize()} attack with ${item.name}`; - - let rollFormula = 'd20+@ab'; - if (this.document.type === 'character') { - if (dataset.attack === 'melee') { - rollFormula += '+@str.bonus'; - } else if (dataset.attack === 'ranged') { - rollFormula += '+@dex.bonus'; + let label = dataset.label + ? `${game.i18n.localize("BASICFANTASYRPG.Roll")}: ${dataset.label}` + : `${game.i18n.localize("BASICFANTASYRPG.Roll")}: ${dataset.attack.capitalize()} attack with ${item.name}`; + + let rollFormula = "d20+@ab"; + if (this.document.type === "character") { + if (dataset.attack === "melee") { + rollFormula += "+@str.bonus"; + } else if (dataset.attack === "ranged") { + rollFormula += "+@dex.bonus"; } } - rollFormula += '+' + item.system.bonusAb.value; - + rollFormula += "+" + item.system.bonusAb.value; + let roll = new Roll(rollFormula, this.document.getRollData()); roll.toMessage({ speaker: ChatMessage.getSpeaker({ actor: this.document }), flavor: label, - rollMode: game.settings.get('core', 'rollMode'), + rollMode: game.settings.get("core", "rollMode"), }); return roll; } // Handle item rolls - if (dataset.rollType === 'item') { - const itemId = element.closest('.item').dataset.itemId; + if (dataset.rollType === "item") { + const itemId = element.closest(".item").dataset.itemId; const item = this.document.items.get(itemId); if (item) return item.roll(); } @@ -374,15 +384,16 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { // Handle rolls that supply the formula directly if (dataset.roll) { - let label = dataset.label ? - `${game.i18n.localize('BASICFANTASYRPG.Roll')}: ${dataset.label}` : ''; + let label = dataset.label + ? `${game.i18n.localize("BASICFANTASYRPG.Roll")}: ${dataset.label}` + : ""; let roll = new Roll(dataset.roll, this.document.getRollData()); await roll.roll(); label += successChatMessage(roll.total, dataset.targetNumber, dataset.rollUnder); roll.toMessage({ speaker: ChatMessage.getSpeaker({ actor: this.document }), flavor: label, - rollMode: game.settings.get('core', 'rollMode'), + rollMode: game.settings.get("core", "rollMode"), }); return roll; } @@ -392,11 +403,12 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { * Setup drag and drop functionality for items */ _setupDragAndDrop() { - const handler = ev => this._onDragStart(ev); - this.element.querySelectorAll('li.item').forEach(li => { - if (li.classList.contains('inventory-header')) return; - li.setAttribute('draggable', true); - li.addEventListener('dragstart', handler, false); + const handler = (ev) => this._onDragStart(ev); + this.element.querySelectorAll("li.item").forEach((li) => { + if (li.classList.contains("inventory-header")) return; + if (li.getAttribute("draggable") === "true") return; + li.setAttribute("draggable", true); + li.addEventListener("dragstart", handler, false); }); } @@ -406,7 +418,7 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { */ _onDragStart(event) { const li = event.currentTarget; - if (event.target.classList.contains('content-link')) return; + if (event.target.classList.contains("content-link")) return; let dragData = null; @@ -421,5 +433,4 @@ export class BaseActorSheet extends HandlebarsApplicationMixin(ActorSheetV2) { // Set data transfer event.dataTransfer.setData("text/plain", JSON.stringify(dragData)); } - } diff --git a/styles/basicfantasyrpg.css b/styles/basicfantasyrpg.css index ce90138..6234219 100644 --- a/styles/basicfantasyrpg.css +++ b/styles/basicfantasyrpg.css @@ -662,3 +662,19 @@ .basicfantasyrpg .effects .item .effect-controls { border: none; } + +/* Item deletion animation */ +.basicfantasyrpg .items-list .item.item-deleting { + animation: itemDeleteFade 0.15s ease-out forwards; +} + +@keyframes itemDeleteFade { + 0% { + opacity: 1; + transform: translateX(0); + } + 100% { + opacity: 0; + transform: translateX(-30px); + } +} From 8078af13a15bc2bc534e1399f8214026a90ce4c2 Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Fri, 22 Aug 2025 22:02:20 +0200 Subject: [PATCH 12/17] Add back some i18n-fr stuff --- lang/fr.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lang/fr.json b/lang/fr.json index a55aef5..b5c4bc7 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -29,6 +29,14 @@ "TYPES.Item.floor": "Sol", "TYPES.Item.wall": "Mur", + "BASICFANTASYRPG.TabCargo": "Cargo", + "BASICFANTASYRPG.TabCombat": "Combat", + "BASICFANTASYRPG.TabDescription": "Description", + "BASICFANTASYRPG.TabFeatures": "Capacité spéciale", + "BASICFANTASYRPG.TabFloors": "Sols & Murs", + "BASICFANTASYRPG.TabItems": "Équipement", + "BASICFANTASYRPG.TabSpells": "Sortilèges", + "BASICFANTASYRPG.Tab.cargo": "Cargo", "BASICFANTASYRPG.Tab.combat": "Combat", "BASICFANTASYRPG.Tab.description": "Description", From c27caab90a237724ef63f55606573447b04221f8 Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Fri, 22 Aug 2025 22:02:46 +0200 Subject: [PATCH 13/17] Remove agent file --- CLAUDE.md | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 52a4c20..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,37 +0,0 @@ -# Purpose of the codebase - -This codebase is the implementation of the RPG system Basic Fantasy RPG to Foundry VTT. - -The current task is to port it to Foundry v13, which introduce a number of API changes, including the -usage of the Application v2. - -# Resources - -Look for online documentation whenever necessary to understand how to migrate to Application v2 and how to properly implement necessary methods. - -Good sources can be found under the domains foundryvtt.com and foundryvtt.wiki - -# Foundry Entities - -In Foundry, there are top-level entities like "Actors" (which can be characters, monsters, -vehicles, etc.), and "Items" (which can be equipment, spells, special abilities, etc.). - -The old implementation (using Application v1) had a single class for Actor and a single class for Items, -and it also implements subtype-specific behavior (like for monsters or characters in the case of Actor). - -For the v2, I plan to have a base actor class and then inherit from it with specific classes for -subtypes. - -Besides the Actor and Item class, there are also corresponding Sheet classes, like an Actor Sheet and -Item Sheet, responsible for rendering the relevant information about that entity on a sheet, which is -visible on the Foundry UI. - -## Entity Structure - -The structure of which types of entities exist can be found in the file `template.json`. It also defines -which attributes each type has. - -## Templates - -The folder `templates` contains HTML templates for rendering sheets. Notice that there are the higher -level actor sheet and item sheet, as well as more specific ones for monsters or vehicles. \ No newline at end of file From 56e56311431e180df5f156e9b708a48af2ba563d Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Fri, 22 Aug 2025 22:04:29 +0200 Subject: [PATCH 14/17] Remove incomplete monster sheet --- module/basicfantasyrpg.mjs | 7 +------ module/sheets/monster-sheet.mjs | 28 ---------------------------- 2 files changed, 1 insertion(+), 34 deletions(-) delete mode 100644 module/sheets/monster-sheet.mjs diff --git a/module/basicfantasyrpg.mjs b/module/basicfantasyrpg.mjs index 42eb93f..ddb1b66 100644 --- a/module/basicfantasyrpg.mjs +++ b/module/basicfantasyrpg.mjs @@ -5,7 +5,6 @@ import { BasicFantasyRPGItem } from './documents/item.mjs'; import { BasicFantasyRPGActorSheet } from './sheets/actor-sheet.mjs'; import { BasicFantasyRPGItemSheet } from './sheets/item-sheet.mjs'; import { CharacterSheet } from './sheets/character-sheet.mjs'; -import { MonsterSheet } from './sheets/monster-sheet.mjs'; // Import helper/utility classes and constants. import { preloadHandlebarsTemplates } from './helpers/templates.mjs'; @@ -44,16 +43,12 @@ Hooks.once('init', async function() { // Register sheet application classes Actors.unregisterSheet('core', ActorSheet); Actors.registerSheet('basicfantasyrpg', BasicFantasyRPGActorSheet, - { makeDefault: false } + { makeDefault: true } ); Actors.registerSheet('basicfantasyrpg', CharacterSheet, { types: ['character'], makeDefault: true, label: "Character Sheet V2"} ); - Actors.registerSheet('basicfantasyrpg', - MonsterSheet, - { types: ['monster'], makeDefault: true, label: "Monster Sheet V2"} - ); Items.unregisterSheet('core', ItemSheet); Items.registerSheet('basicfantasyrpg', BasicFantasyRPGItemSheet, { makeDefault: true }); diff --git a/module/sheets/monster-sheet.mjs b/module/sheets/monster-sheet.mjs deleted file mode 100644 index ef37409..0000000 --- a/module/sheets/monster-sheet.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import { BaseActorSheet } from './base-actor-sheet.mjs'; - -/** - * Monster Sheet for Basic Fantasy RPG - * Extends BaseActorSheet with monster-specific functionality - * @extends {BaseActorSheet} - */ -export class MonsterSheet extends BaseActorSheet { - - static DEFAULT_OPTIONS = { - ...BaseActorSheet.DEFAULT_OPTIONS, - classes: [...BaseActorSheet.DEFAULT_OPTIONS.classes, "monster"] - }; - - static PARTS = { - main: { - template: "systems/basicfantasyrpg/templates/actor/actor-monster-sheet.html" - } - }; - - /** @override */ - _onRender(context, options) { - super._onRender(context, options); - - // Add monster-specific rendering logic - console.log("Monster Sheet rendered for:", context.name); - } -} \ No newline at end of file From 34f0d07e1e124205a8c09554d5d94ea41e51c487 Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Fri, 22 Aug 2025 22:05:54 +0200 Subject: [PATCH 15/17] Remove unnecessary labels --- template.json | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/template.json b/template.json index f788540..6a6747d 100644 --- a/template.json +++ b/template.json @@ -51,28 +51,22 @@ "templates": ["base"], "abilities": { "str": { - "value": 10, - "label": "BASICFANTASYRPG.AbilityStr" + "value": 10 }, "int": { - "value": 10, - "label": "BASICFANTASYRPG.AbilityInt" + "value": 10 }, "wis": { - "value": 10, - "label": "BASICFANTASYRPG.AbilityWis" + "value": 10 }, "dex": { - "value": 10, - "label": "BASICFANTASYRPG.AbilityDex" + "value": 10 }, "con": { - "value": 10, - "label": "BASICFANTASYRPG.AbilityCon" + "value": 10 }, "cha": { - "value": 10, - "label": "BASICFANTASYRPG.AbilityCha" + "value": 10 } }, "age": { From 16ce884aff3fdcd133b100aa9591f18c635b11ac Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Sat, 23 Aug 2025 12:42:19 +0200 Subject: [PATCH 16/17] Fix title size --- styles/basicfantasyrpg.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/styles/basicfantasyrpg.css b/styles/basicfantasyrpg.css index 6234219..858aceb 100644 --- a/styles/basicfantasyrpg.css +++ b/styles/basicfantasyrpg.css @@ -428,6 +428,9 @@ padding: 0px; margin: 5px 0; border-bottom: 0; + line-height: 48px; + font-size: 28px; + font-weight: 400; } .basicfantasyrpg .sheet-header h1.charname input { From 8d7508bae96e7da1a36c9175dfcc841471a50f8b Mon Sep 17 00:00:00 2001 From: Erick Fonseca Date: Sat, 20 Sep 2025 16:55:43 +0200 Subject: [PATCH 17/17] Prioritize century gothic for sheets --- styles/basicfantasyrpg.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles/basicfantasyrpg.css b/styles/basicfantasyrpg.css index 858aceb..a92027b 100644 --- a/styles/basicfantasyrpg.css +++ b/styles/basicfantasyrpg.css @@ -386,7 +386,7 @@ /* Styles limited to basicfantasyrpg sheets */ .basicfantasyrpg.sheet.actor { - font-family: "Soutane", "Century Gothic", "TeX Gyre Adventor", var(--font-primary); + font-family: "Century Gothic", "TeX Gyre Adventor", "Soutane", var(--font-primary); } .basicfantasyrpg .sheet-header {