Files
fvtt-mournblade-cyd-2-0/modules/applications/sheets/base-item-sheet.mjs
T
uberwald 156672d853 ActiveEffects: Add complete ActiveEffects management system
- New: modules/mournblade-cyd2-effects.js with utility methods for creating, applying, and managing effects
- New: templates/partial-active-effects.hbs for displaying actor effects
- New: templates/partial-item-effects.hbs for displaying item effects
- Update: modules/mournblade-cyd2-config.js with effect configuration (types, attribute keys, categories)
- Update: templates/actor-sheet.hbs and creature-sheet.hbs with Effects tab
- Update: templates/partial-item-nav.hbs with conditional Effects tab
- Update: templates/item-talent-sheet.hbs with Effects tab content
- Update: base-actor-sheet.mjs with effect action handlers (create, edit, delete, toggle)
- Update: base-item-sheet.mjs with effect action handlers and context
- Update: modules/mournblade-cyd2-main.js to import and expose MournbladeCYD2Effects
- Update: lang/fr.json with effect-related translations

Features:
- Support for creating permanent and temporary effects
- Support for attribute modifications (ADR, PUI, CLA, PRE, TRE, etc.)
- Support for health, soul, combat, and adversity modifications
- Support for status effects
- Support for runes (pronounced and traced) effects
- Toggle to enable/disable effects
- Duration tracking (rounds, turns, seconds, combat, scene)
- Display of all active modifications summary

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-06 22:51:31 +02:00

296 lines
9.1 KiB
JavaScript

const { HandlebarsApplicationMixin } = foundry.applications.api;
import { MournbladeCYD2Utility } from "../../mournblade-cyd2-utility.js";
export default class MournbladeCYD2ItemSheetV2 extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options);
this.#dragDrop = this.#createDragDropHandlers();
}
#dragDrop;
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-mournblade-cyd-2-0", "item"],
position: {
width: 620,
height: 600,
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
tabs: [
{
navSelector: 'nav[data-group="primary"]',
contentSelector: "section.sheet-body",
initial: "description",
},
],
dragDrop: [{ dragSelector: "[data-drag]", dropSelector: null }],
actions: {
editImage: MournbladeCYD2ItemSheetV2.#onEditImage,
postItem: MournbladeCYD2ItemSheetV2.#onPostItem,
addPredilection: MournbladeCYD2ItemSheetV2.#onAddPredilection,
deletePredilection: MournbladeCYD2ItemSheetV2.#onDeletePredilection,
addAutomation: MournbladeCYD2ItemSheetV2.#onAddAutomation,
deleteAutomation: MournbladeCYD2ItemSheetV2.#onDeleteAutomation,
// Actions pour les ActiveEffects
createEffect: MournbladeCYD2ItemSheetV2.#onCreateEffect,
editEffect: MournbladeCYD2ItemSheetV2.#onEditEffect,
deleteEffect: MournbladeCYD2ItemSheetV2.#onDeleteEffect,
toggleEffect: MournbladeCYD2ItemSheetV2.#onToggleEffect,
applyEffect: MournbladeCYD2ItemSheetV2.#onApplyEffect,
},
};
tabGroups = { primary: "description" };
/** @override */
async _prepareContext() {
return {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
source: this.document.toObject(),
config: game.system.mournbladecyd2.config,
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(
this.document.system.description || "", { async: true }
),
isEditMode: true,
isEditable: this.isEditable,
isGM: game.user.isGM,
config: game.system.mournbladecyd2.config,
};
}
/** @override */
_onRender(context, options) {
super._onRender(context, options);
this.#dragDrop.forEach((d) => d.bind(this.element));
// Tab navigation
const nav = this.element.querySelector('nav.tabs[data-group]');
if (nav) {
const group = nav.dataset.group;
const activeTab = this.tabGroups[group] || "description";
nav.querySelectorAll('[data-tab]').forEach(link => {
const tab = link.dataset.tab;
link.classList.toggle('active', tab === activeTab);
link.addEventListener('click', (event) => {
event.preventDefault();
this.tabGroups[group] = tab;
this.render();
});
});
this.element.querySelectorAll(`[data-group="${group}"][data-tab]`).forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab);
});
}
}
#createDragDropHandlers() {
return this.options.dragDrop.map(d => {
d.permissions = { dragstart: () => this.isEditable };
d.callbacks = { dragstart: this._onDragStart.bind(this) };
return new foundry.applications.ux.DragDrop.implementation(d);
});
}
_onDragStart(event) {}
// #region Actions
static async #onEditImage(event) {
const fp = new FilePicker({
type: "image",
current: this.document.img,
callback: (path) => this.document.update({ img: path })
});
fp.browse();
}
static async #onPostItem(event) {
event.preventDefault();
const item = this.document;
const chatData = foundry.utils.duplicate(item);
if (item.actor) {
chatData.actor = { id: item.actor.id };
}
if (chatData.img?.includes("/blank.png")) {
chatData.img = null;
}
chatData.jsondata = JSON.stringify({
compendium: "postedItem",
payload: chatData,
});
const html = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-mournblade-cyd-2-0/templates/post-item.hbs",
chatData
);
ChatMessage.create({ user: game.user.id, content: html });
}
static async #onAddPredilection(event) {
const preds = foundry.utils.duplicate(this.document.system.predilections || []);
preds.push({ id: foundry.utils.randomID(), name: "Nouvelle prédilection", description: "", acquise: false, maitrise: false, used: false });
await this.document.update({ "system.predilections": preds });
}
static async #onDeletePredilection(event, target) {
const idx = Number(target.dataset.predilectionIndex);
const preds = foundry.utils.duplicate(this.document.system.predilections || []);
preds.splice(idx, 1);
await this.document.update({ "system.predilections": preds });
}
/* -------------------------------------------- */
static async #onAddAutomation(event) {
const automations = foundry.utils.duplicate(this.document.system.automations || []);
automations.push({
id: foundry.utils.randomID(),
eventtype: "on-drop",
name: "",
bonusname: "vigueur",
bonus: 0,
competence: "",
minLevel: 0,
baCost: 0
});
await this.document.update({
"system.automations": automations,
"system.isautomated": true
});
}
/* -------------------------------------------- */
static async #onDeleteAutomation(event, target) {
const idx = Number(target.dataset.automationIndex);
const automations = foundry.utils.duplicate(this.document.system.automations || []);
automations.splice(idx, 1);
await this.document.update({ "system.automations": automations });
if (automations.length === 0) {
await this.document.update({ "system.isautomated": false });
}
}
// #region ActiveEffects Management
/**
* Crée un nouvel effet actif sur l'item
* @param {Event} event - Événement
* @param {HTMLElement} target - Éléments cible
* @private
*/
static async #onCreateEffect(event, target) {
event.preventDefault();
// Créer les données par défaut pour un nouvel effet
const defaultEffectData = {
name: game.i18n.localize("MOURNBLADECYD2.EFFECT.new") || "Nouvel Effet",
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/effect.webp",
description: "",
changes: [],
disabled: false,
duration: {},
origin: this.document.uuid,
tint: "",
transfer: false,
flags: {}
};
// Utiliser la dialog native FoundryVTT pour créer l'effet
const effect = await foundry.applications.api.ActiveEffectDialog.create({
document: this.document,
effect: defaultEffectData
});
if (effect) {
await this.document.createEmbeddedDocuments("ActiveEffect", [effect.toObject()]);
}
}
/**
* Édite un effet actif existant sur l'item
* @param {Event} event - Événement
* @param {HTMLElement} target - Éléments cible
* @private
*/
static async #onEditEffect(event, target) {
event.preventDefault();
const effectId = target?.dataset?.effectId;
if (!effectId) return;
const effect = this.document.effects.get(effectId);
if (effect) {
// Ouvrir la sheet de l'effet pour édition
effect.sheet.render(true);
}
}
/**
* Supprime un effet actif de l'item
* @param {Event} event - Événement
* @param {HTMLElement} target - Éléments cible
* @private
*/
static async #onDeleteEffect(event, target) {
event.preventDefault();
const effectId = target?.dataset?.effectId;
if (!effectId) return;
const effect = this.document.effects.get(effectId);
if (effect) {
const confirmed = await foundry.applications.api.DialogV2.confirm({
title: game.i18n.localize("MOURNBLADECYD2.EFFECT.deleteConfirm") || "Supprimer l'effet",
content: game.i18n.localize("MOURNBLADECYD2.EFFECT.deleteConfirmText") || `Êtes-vous sûr de vouloir supprimer l'effet "${effect.name}" ?`
});
if (confirmed) {
await this.document.deleteEmbeddedDocuments("ActiveEffect", [effectId]);
}
}
}
/**
* Toggle l'état actif/désactivé d'un effet sur l'item
* @param {Event} event - Événement
* @param {HTMLElement} target - Éléments cible
* @private
*/
static async #onToggleEffect(event, target) {
event.preventDefault();
const effectId = target?.dataset?.effectId;
if (!effectId) return;
const effect = this.document.effects.get(effectId);
if (effect) {
await effect.update({ disabled: !effect.disabled });
}
}
/**
* Applique un effet à partir de l'item
* @param {Event} event - Événement
* @param {HTMLElement} target - Éléments cible
* @private
*/
static async #onApplyEffect(event, target) {
event.preventDefault();
const effectId = target?.dataset?.effectId;
if (!effectId) return;
const effect = this.document.effects.get(effectId);
if (effect) {
await effect.apply();
}
}
// #endregion
}