Files
fvtt-celestopol/module/applications/sheets/base-actor-sheet.mjs
LeRatierBretonnier 9dbd614c5a feat: gestion de l'expérience (XP)
- Schéma xp dans CelestopolCharacter : actuel (éditable), log[] ({montant, raison, date}), depense (calculé dans prepareDerivedData)
- Bouton 'Dépenser XP' → DialogV2 (montant + raison) : décrémente actuel, logge l'entrée
- Suppression d'entrée de log avec remboursement des points (mode édition)
- Section XP en haut de l'onglet Biographie : compteurs, tableau du log, référentiel des coûts
- i18n : section CELESTOPOL.XP.* complète
- CSS : .xp-section avec compteurs, tableau de log et accordéon de référence

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 00:33:59 +02:00

185 lines
6.2 KiB
JavaScript

import { SYSTEM } from "../../config/system.mjs"
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class CelestopolActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-celestopol", "actor"],
position: { width: 900, height: "auto" },
form: { submitOnChange: true },
window: { resizable: true },
dragDrop: [{ dragSelector: '[data-drag="true"], .rollable', dropSelector: null }],
actions: {
editImage: CelestopolActorSheet.#onEditImage,
toggleSheet: CelestopolActorSheet.#onToggleSheet,
edit: CelestopolActorSheet.#onItemEdit,
delete: CelestopolActorSheet.#onItemDelete,
attack: CelestopolActorSheet.#onAttack,
rangedDefense: CelestopolActorSheet.#onRangedDefense,
trackBox: CelestopolActorSheet.#onTrackBox,
skillLevel: CelestopolActorSheet.#onSkillLevel,
factionLevel: CelestopolActorSheet.#onFactionLevel,
},
}
_sheetMode = this.constructor.SHEET_MODES.PLAY
get isPlayMode() { return this._sheetMode === this.constructor.SHEET_MODES.PLAY }
get isEditMode() { return this._sheetMode === this.constructor.SHEET_MODES.EDIT }
/** @override */
async _prepareContext() {
return {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
actor: this.document,
system: this.document.system,
source: this.document.toObject(),
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isEditable: this.isEditable,
woundLevels: SYSTEM.WOUND_LEVELS,
}
}
/** @override */
_onRender(context, options) {
this.#dragDrop.forEach(d => d.bind(this.element))
this.element.querySelectorAll(".rollable").forEach(el => {
el.addEventListener("click", this._onRoll.bind(this))
})
}
async _onRoll(event) {
if (!this.isPlayMode) return
const el = event.currentTarget
const statId = el.dataset.statId
const skillId = el.dataset.skillId
if (!statId) return
if (!skillId) {
// Test de résistance (clic sur la zone TR de la stat)
await this.document.system.rollResistance(statId)
return
}
await this.document.system.roll(statId, skillId)
}
#createDragDropHandlers() {
return this.options.dragDrop.map(d => {
d.permissions = {
dragstart: this._canDragStart.bind(this),
drop: this._canDragDrop.bind(this),
}
d.callbacks = {
dragover: this._onDragOver.bind(this),
drop: this._onDrop.bind(this),
}
return new foundry.applications.ux.DragDrop.implementation(d)
})
}
_canDragStart() { return this.isEditable }
_canDragDrop() { return true }
_onDragOver() {}
async _onDrop(event) {
if (!this.isEditable) return
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
if (data.type === "Item") {
const item = await fromUuid(data.uuid)
if (item) return this._onDropItem(item)
}
}
async _onDropItem(item) {
if (item.type === "anomaly" && this.document.itemTypes.anomaly.length > 0) {
ui.notifications.warn(game.i18n.localize("CELESTOPOL.Anomaly.maxAnomaly"))
return
}
await this.document.createEmbeddedDocuments("Item", [item.toObject()], { renderSheet: false })
}
static async #onEditImage(event, _target) {
const current = this.document.img
const fp = new FilePicker({
current,
type: "image",
callback: (path) => this.document.update({ img: path }),
top: this.position.top + 40,
left: this.position.left + 10,
})
return fp.browse()
}
static #onToggleSheet() {
const modes = this.constructor.SHEET_MODES
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
this.render()
}
static async #onItemEdit(event, target) {
const uuid = target.getAttribute("data-item-uuid")
const id = target.getAttribute("data-item-id")
const item = uuid ? await fromUuid(uuid) : this.document.items.get(id)
item?.sheet.render(true)
}
static async #onItemDelete(event, target) {
const uuid = target.getAttribute("data-item-uuid")
const item = await fromUuid(uuid)
await item?.deleteDialog()
}
static async #onAttack(_event, target) {
const itemId = target.getAttribute("data-item-id")
if (!itemId) return
await this.document.system.rollAttack(itemId)
}
static async #onRangedDefense(_event, target) {
const itemId = target.getAttribute("data-item-id")
if (!itemId) return
await this.document.system.rollRangedDefense(itemId)
}
/** Met à jour une jauge de piste (blessures/destin/spleen) par clic sur une case. */
static #onTrackBox(_event, target) {
if (!this.isEditable) return
const path = target.dataset.path
const index = parseInt(target.dataset.index)
const current = foundry.utils.getProperty(this.document, path) ?? 0
const newValue = (index <= current) ? index - 1 : index
this.document.update({ [path]: Math.max(0, newValue) })
}
/** Met à jour la valeur d'un domaine par clic sur un point de niveau. */
static #onSkillLevel(_event, target) {
if (!this.isEditable) return
const { statId, skillId } = target.dataset
const index = parseInt(target.dataset.index)
const current = this.document.system.stats[statId]?.[skillId]?.value ?? 0
const newValue = (index <= current) ? index - 1 : index
this.document.update({ [`system.stats.${statId}.${skillId}.value`]: Math.max(0, newValue) })
}
/** Met à jour le score d'une faction par clic sur un point. */
static #onFactionLevel(_event, target) {
if (!this.isEditable) return
const factionId = target.dataset.faction
const index = parseInt(target.dataset.index)
const current = this.document.system.factions[factionId]?.value ?? 0
const newValue = (index <= current) ? index - 1 : index
this.document.update({ [`system.factions.${factionId}.value`]: Math.max(0, newValue) })
}
}