Migration vers DataModels et appv2

This commit is contained in:
2026-01-10 15:08:28 +01:00
parent 939cfb1e86
commit b113f630bf
75 changed files with 8232 additions and 11 deletions

View File

@@ -0,0 +1,311 @@
import { HeritiersUtility } from "../heritiers-utility.js"
/**
* Dialogue de jet de dé pour Les Héritiers - Version AppV2
*/
export class HeritiersRollDialog {
/**
* Create and display the roll dialog
* @param {HeritiersActor} actor - The actor making the roll
* @param {Object} rollData - Data for the roll
* @returns {Promise<Object>} - Returns a dialog-like object for compatibility
*/
static async create(actor, rollData) {
// Préparer le contexte pour le template
const context = {
...rollData,
img: actor.img,
name: actor.name,
config: game.system.lesheritiers.config,
}
// Rendre le template en HTML
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-les-heritiers/templates/roll-dialog-generic.hbs",
context
)
// Préparer les boutons selon le mode et le niveau
const buttons = this._prepareButtons(rollData)
// Lancer le dialog de manière asynchrone (sans attendre)
setTimeout(() => {
foundry.applications.api.DialogV2.wait({
window: { title: "Test de Capacité", icon: "fa-solid fa-dice" },
classes: ["heritiers-roll-dialog"],
position: { width: 420, height: 'fit-content' },
modal: false,
content,
buttons,
rejectClose: false,
render: (event, html) => {
this._activateListeners(html, rollData)
}
})
}, 0)
// Retourner un objet avec une méthode render() vide pour compatibilité
return {
render: () => {} // No-op for compatibility with old code
}
}
/**
* Préparer les boutons selon le mode et le niveau de compétence
* @param {Object} rollData - Data for the roll
* @returns {Array} - Array of button configurations
* @private
*/
static _prepareButtons(rollData) {
const buttons = []
// Bouton d8 toujours disponible
buttons.push({
action: "rolld8",
label: "Lancer 1d8",
icon: "fa-solid fa-dice-d8",
default: true,
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements)
this._executeRoll(rollData, "d8")
}
})
// Bouton d10 si niveau > 0 ou pouvoir
const enableD10 = rollData.mode === "pouvoir" || rollData.competence?.system.niveau > 0
if (enableD10) {
buttons.push({
action: "rolld10",
label: "Lancer 1d10",
icon: "fa-solid fa-dice-d10",
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements)
this._executeRoll(rollData, "d10")
}
})
}
// Bouton d12 si niveau > 1 ou pouvoir
const enableD12 = rollData.mode === "pouvoir" || rollData.competence?.system.niveau > 1
if (enableD12) {
buttons.push({
action: "rolld12",
label: "Lancer 1d12",
icon: "fa-solid fa-dice-d12",
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements)
this._executeRoll(rollData, "d12")
}
})
}
// Bouton Tricherie si disponible
if (rollData.tricherie) {
buttons.push({
action: "rollTricherie",
label: "Lancer avec 1 Point de Tricherie",
icon: "fa-solid fa-mask",
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements)
this._executeRoll(rollData, "tricherie")
}
})
}
// Bouton Héritage si disponible
if (rollData.heritage) {
buttons.push({
action: "rollHeritage",
label: "Lancer avec 1 Point d'Héritage",
icon: "fa-solid fa-crown",
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements)
this._executeRoll(rollData, "heritage")
}
})
}
// Si mode carac uniquement, on ne garde que d8
if (rollData.mode === "carac") {
return [
{
action: "rolld8",
label: "Lancer 1d8",
icon: "fa-solid fa-dice-d8",
default: true,
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements)
this._executeRoll(rollData, "d8")
}
}
]
}
return buttons
}
/**
* Activer les listeners sur le formulaire
* @param {HTMLElement} html - L'élément HTML du dialog
* @param {Object} rollData - Data for the roll
* @private
*/
static _activateListeners(html, rollData) {
// Seuil de Difficulté
const sdValue = html.querySelector('#sdValue')
if (sdValue) {
sdValue.addEventListener('change', (event) => {
rollData.sdValue = Number(event.currentTarget.value)
})
}
// Caractéristique
const caracKey = html.querySelector('#caracKey')
if (caracKey) {
caracKey.addEventListener('change', (event) => {
rollData.caracKey = String(event.currentTarget.value)
})
}
// Bonus/Malus contextuel
const bonusMalusContext = html.querySelector('#bonus-malus-context')
if (bonusMalusContext) {
bonusMalusContext.addEventListener('change', (event) => {
rollData.bonusMalusContext = Number(event.currentTarget.value)
})
}
// Attaque à plusieurs
const bonusAttaquePlusieurs = html.querySelector('#bonus-attaque-plusieurs')
if (bonusAttaquePlusieurs) {
bonusAttaquePlusieurs.addEventListener('change', (event) => {
rollData.bonusAttaquePlusieurs = Number(event.currentTarget.value)
})
}
// Spécialité
const useSpecialite = html.querySelector('#useSpecialite')
if (useSpecialite) {
useSpecialite.addEventListener('change', (event) => {
rollData.useSpecialite = event.currentTarget.checked
})
}
// Points d'usage du pouvoir
const pouvoirPointsUsage = html.querySelector('#pouvoirPointsUsage')
if (pouvoirPointsUsage) {
pouvoirPointsUsage.addEventListener('change', (event) => {
rollData.pouvoirPointsUsage = Number(event.currentTarget.value)
})
}
// Attaque dans le dos
const attaqueDos = html.querySelector('#attaqueDos')
if (attaqueDos) {
attaqueDos.addEventListener('change', (event) => {
rollData.attaqueDos = event.currentTarget.checked
})
}
// Seconde arme
const secondeArme = html.querySelector('#bonus-attaque-seconde-arme')
if (secondeArme) {
secondeArme.addEventListener('change', (event) => {
rollData.secondeArme = String(event.currentTarget.value)
})
}
// Attaque ciblée
const attaqueCible = html.querySelector('#attaque-cible')
if (attaqueCible) {
attaqueCible.addEventListener('change', (event) => {
rollData.attaqueCible = String(event.currentTarget.value)
})
}
// Attaque à deux armes
const attaqueDeuxArmes = html.querySelector('#bonus-attaque-deux-armes')
if (attaqueDeuxArmes) {
attaqueDeuxArmes.addEventListener('change', (event) => {
rollData.attaqueDeuxArmes = Number(event.currentTarget.value)
})
}
}
/**
* Mettre à jour rollData avec les valeurs du formulaire
* @param {Object} rollData - L'objet rollData à mettre à jour
* @param {HTMLFormControlsCollection} formElements - Les éléments du formulaire
* @private
*/
static _updateRollDataFromForm(rollData, formElements) {
// Seuil de Difficulté
if (formElements.sdValue) {
rollData.sdValue = Number(formElements.sdValue.value)
}
// Caractéristique
if (formElements.caracKey) {
rollData.caracKey = String(formElements.caracKey.value)
}
// Bonus/Malus contextuel
if (formElements['bonus-malus-context']) {
rollData.bonusMalusContext = Number(formElements['bonus-malus-context'].value)
}
// Attaque à plusieurs
if (formElements['bonus-attaque-plusieurs']) {
rollData.bonusAttaquePlusieurs = Number(formElements['bonus-attaque-plusieurs'].value)
}
// Spécialité
if (formElements.useSpecialite !== undefined) {
rollData.useSpecialite = formElements.useSpecialite.checked
}
// Points d'usage du pouvoir
if (formElements.pouvoirPointsUsage) {
rollData.pouvoirPointsUsage = Number(formElements.pouvoirPointsUsage.value)
}
// Attaque dans le dos
if (formElements.attaqueDos !== undefined) {
rollData.attaqueDos = formElements.attaqueDos.checked
}
// Seconde arme
if (formElements['bonus-attaque-seconde-arme']) {
rollData.secondeArme = String(formElements['bonus-attaque-seconde-arme'].value)
}
// Attaque ciblée
if (formElements['attaque-cible']) {
rollData.attaqueCible = String(formElements['attaque-cible'].value)
}
// Attaque à deux armes
if (formElements['bonus-attaque-deux-armes']) {
rollData.attaqueDeuxArmes = Number(formElements['bonus-attaque-deux-armes'].value)
}
}
/**
* Exécuter le jet de dés
* @param {Object} rollData - Data for the roll
* @param {String} dice - Type de dé (d8, d10, d12, tricherie, heritage)
* @private
*/
static _executeRoll(rollData, dice) {
if (dice === "heritage") {
rollData.useHeritage = true
} else if (dice === "tricherie") {
rollData.useTricherie = true
} else {
rollData.mainDice = dice
}
HeritiersUtility.rollHeritiers(rollData)
}
}

View File

@@ -0,0 +1,23 @@
/**
* Export all application sheets for Les Héritiers
*/
// Actor Sheets
export { default as HeritiersPersonnageSheet } from './personnage-sheet.mjs';
export { default as HeritiersPnjSheet } from './pnj-sheet.mjs';
// Item Sheets
export { default as HeritiersAccessoireSheet } from './accessoire-sheet.mjs';
export { default as HeritiersArmeSheet } from './arme-sheet.mjs';
export { default as HeritiersAtoutFeeriqueSheet } from './atoutfeerique-sheet.mjs';
export { default as HeritiersAvantageSheet } from './avantage-sheet.mjs';
export { default as HeritiersCapaciteNaturelleSheet } from './capacitenaturelle-sheet.mjs';
export { default as HeritiersCompetenceSheet } from './competence-sheet.mjs';
export { default as HeritiersContactSheet } from './contact-sheet.mjs';
export { default as HeritiersDesavantageSheet } from './desavantage-sheet.mjs';
export { default as HeritiersEquipementSheet } from './equipement-sheet.mjs';
export { default as HeritiersFeeSheet } from './fee-sheet.mjs';
export { default as HeritiersPouvoirSheet } from './pouvoir-sheet.mjs';
export { default as HeritiersProfilSheet } from './profil-sheet.mjs';
export { default as HeritiersProtectionSheet } from './protection-sheet.mjs';
export { default as HeritiersSortSheet } from './sort-sheet.mjs';

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersAccessoireSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.accessoire",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-accessoire-sheet.html",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersArmeSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.arme",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-arme-sheet.html",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersAtoutFeeriqueSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.atoutfeerique",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-atoutfeerique-sheet.html",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersAvantageSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.avantage",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-avantage-sheet.html",
},
}
}

View File

@@ -0,0 +1,618 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
import { HeritiersUtility } from "../../heritiers-utility.js"
export default class HeritiersActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
/**
* Different sheet modes.
* @enum {number}
*/
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
this._sheetMode = this.constructor.SHEET_MODES.PLAY
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-les-heritiers", "sheet", "actor"],
position: {
width: 780,
height: 840,
},
window: {
resizable: true,
},
form: {
submitOnChange: true,
closeOnSubmit: false,
},
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: "form" }],
actions: {
editImage: HeritiersActorSheet.#onEditImage,
toggleSheet: HeritiersActorSheet.#onToggleSheet,
editItem: HeritiersActorSheet.#onEditItem,
deleteItem: HeritiersActorSheet.#onDeleteItem,
createItem: HeritiersActorSheet.#onCreateItem,
equipItem: HeritiersActorSheet.#onEquipItem,
modifyQuantity: HeritiersActorSheet.#onModifyQuantity,
rollInitiative: HeritiersActorSheet.#onRollInitiative,
rollCarac: HeritiersActorSheet.#onRollCarac,
rollRang: HeritiersActorSheet.#onRollRang,
rollRootCompetence: HeritiersActorSheet.#onRollRootCompetence,
rollCompetence: HeritiersActorSheet.#onRollCompetence,
rollSort: HeritiersActorSheet.#onRollSort,
rollAttaqueArme: HeritiersActorSheet.#onRollAttaqueArme,
rollAttaqueBrutaleArme: HeritiersActorSheet.#onRollAttaqueBrutaleArme,
rollAttaqueChargeArme: HeritiersActorSheet.#onRollAttaqueChargeArme,
rollAssomerArme: HeritiersActorSheet.#onRollAssomerArme,
rollPouvoir: HeritiersActorSheet.#onRollPouvoir,
toggleMasque: HeritiersActorSheet.#onToggleMasque,
dialogRecupUsage: HeritiersActorSheet.#onDialogRecupUsage,
},
}
/**
* Is the sheet currently in 'Play' mode?
* @type {boolean}
*/
get isPlayMode() {
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY
return this._sheetMode === this.constructor.SHEET_MODES.PLAY
}
/**
* Is the sheet currently in 'Edit' mode?
* @type {boolean}
*/
get isEditMode() {
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY
return this._sheetMode === this.constructor.SHEET_MODES.EDIT
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = { primary: "stats" }
/** @override */
async _prepareContext() {
const actor = this.document
const context = {
actor: actor,
system: actor.system,
source: actor.toObject(),
fields: actor.schema.fields,
systemFields: actor.system.schema.fields,
isEditable: this.isEditable,
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isGM: game.user.isGM,
config: CONFIG.HERITIERS,
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.description || "", { async: true }),
enrichedHabitat: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.habitat || "", { async: true }),
enrichedRevesetranges: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.revesetranges || "", { async: true }),
enrichedSecretsdecouverts: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.secretsdecouverts || "", { async: true }),
enrichedQuestions: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.questions || "", { async: true }),
enrichedPlayernotes: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.playernotes || "", { async: true }),
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
// Activate drag & drop handlers
this.#dragDrop.forEach(d => d.bind(this.element))
// Manual tab navigation
const html = this.element
const tabLinks = html.querySelectorAll('.sheet-tabs a.item[data-tab]')
const tabContents = html.querySelectorAll('.sheet-body .tab[data-group="primary"]')
// Hide all tabs initially
tabContents.forEach(tab => {
tab.classList.remove('active')
tab.style.display = 'none'
})
// Show active tab
const activeTab = this.tabGroups.primary
const activeTabContent = html.querySelector(`.tab[data-group="primary"][data-tab="${activeTab}"]`)
if (activeTabContent) {
activeTabContent.classList.add('active')
activeTabContent.style.display = 'block'
}
// Activate the corresponding nav link
tabLinks.forEach(link => {
if (link.dataset.tab === activeTab) {
link.classList.add('active')
} else {
link.classList.remove('active')
}
})
// Tab click handler
tabLinks.forEach(link => {
link.addEventListener('click', (event) => {
event.preventDefault()
const tab = link.dataset.tab
// Update state
this.tabGroups.primary = tab
// Hide all tabs
tabContents.forEach(t => {
t.classList.remove('active')
t.style.display = 'none'
})
// Show selected tab
const selectedTab = html.querySelector(`.tab[data-group="primary"][data-tab="${tab}"]`)
if (selectedTab) {
selectedTab.classList.add('active')
selectedTab.style.display = 'block'
}
// Update nav links
tabLinks.forEach(l => {
if (l.dataset.tab === tab) {
l.classList.add('active')
} else {
l.classList.remove('active')
}
})
})
})
// Inline item editing
html.querySelectorAll('.edit-item-data').forEach(input => {
input.addEventListener('change', (event) => {
const li = event.target.closest('.item')
const itemId = li?.dataset.itemId
const itemType = li?.dataset.itemType
const itemField = event.target.dataset.itemField
const dataType = event.target.dataset.dtype
const value = event.target.value
if (itemId && itemType && itemField) {
this.actor.editItemField(itemId, itemType, itemField, dataType, value)
}
})
})
}
// #region Drag & Drop
/**
* Create drag-and-drop workflow handlers for this Application
* @returns {DragDrop[]} An array of DragDrop handlers
* @private
*/
#createDragDropHandlers() {
return this.options.dragDrop.map((d) => {
d.permissions = {
dragstart: this._canDragStart.bind(this),
drop: this._canDragDrop.bind(this),
}
d.callbacks = {
dragstart: this._onDragStart.bind(this),
drop: this._onDrop.bind(this),
}
return new foundry.applications.ux.DragDrop(d)
})
}
/**
* Define whether a user is able to begin a dragstart workflow for a given drag selector
* @param {string} selector The candidate HTML selector for dragging
* @returns {boolean} Can the current user drag this selector?
* @protected
*/
_canDragStart(selector) {
return this.isEditable
}
/**
* Define whether a user is able to conclude a drag-and-drop workflow for a given drop selector
* @param {string} selector The candidate HTML selector for the drop target
* @returns {boolean} Can the current user drop on this selector?
* @protected
*/
_canDragDrop(selector) {
return this.isEditable
}
/**
* Callback actions which occur at the beginning of a drag start workflow.
* @param {DragEvent} event The originating DragEvent
* @protected
*/
_onDragStart(event) {
const li = event.currentTarget.closest(".item")
if (!li?.dataset.itemId) return
const item = this.actor.items.get(li.dataset.itemId)
if (!item) return
const dragData = item.toDragData()
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
}
/**
* Callback actions which occur when a dragged element is dropped on a target.
* @param {DragEvent} event The originating DragEvent
* @protected
*/
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
const actor = this.actor
// Handle different data types
switch (data.type) {
case "Item":
return this._onDropItem(event, data)
case "Actor":
return this._onDropActor(event, data)
case "ActiveEffect":
return this._onDropActiveEffect(event, data)
}
}
/**
* Handle dropping an Item on the actor sheet
* @param {DragEvent} event
* @param {object} data
* @private
*/
async _onDropItem(event, data) {
if (!this.actor.isOwner) return false
let item = await fromUuid(data.uuid)
if (item.pack) {
item = await HeritiersUtility.searchItem(item)
}
const itemData = item.toObject ? item.toObject() : item
return this.actor.createEmbeddedDocuments("Item", [itemData])
}
/**
* Handle dropping an Actor on the sheet
* @param {DragEvent} event
* @param {object} data
* @private
*/
async _onDropActor(event, data) {
return false
}
/**
* Handle dropping an ActiveEffect on the sheet
* @param {DragEvent} event
* @param {object} data
* @private
*/
async _onDropActiveEffect(event, data) {
return false
}
// #endregion
// #region Action Handlers
/**
* Toggle between edit and play mode
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static #onToggleSheet(event, target) {
const wasEditMode = this.isEditMode
this._sheetMode = wasEditMode ? this.constructor.SHEET_MODES.PLAY : this.constructor.SHEET_MODES.EDIT
this.render({ force: true })
}
/**
* Edit the actor image
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onEditImage(event, target) {
const fp = new FilePicker({
type: "image",
current: this.actor.img,
callback: (path) => {
this.actor.update({ img: path })
},
})
return fp.browse()
}
/**
* Edit an item
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onEditItem(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
const item = this.actor.items.get(itemId)
if (item) item.sheet.render(true)
}
/**
* Delete an item
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onDeleteItem(event, target) {
const li = target.closest(".item")
await HeritiersUtility.confirmDelete(this, li)
}
/**
* Create a new item
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onCreateItem(event, target) {
const itemType = target.dataset.type
// Cas spécial pour les sorts avec une compétence spécifique
if (itemType === "sort" && target.dataset.sortCompetence) {
const sortCompetence = target.dataset.sortCompetence
await this.actor.createEmbeddedDocuments('Item', [{
name: `Nouveau ${itemType} de ${sortCompetence}`,
type: itemType,
system: { competence: sortCompetence }
}], { renderSheet: true })
return
}
await this.actor.createEmbeddedDocuments("Item", [{ name: `Nouveau ${itemType}`, type: itemType }], { renderSheet: true })
}
/**
* Equip/unequip an item
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onEquipItem(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (itemId) {
await this.actor.equipItem(itemId)
this.render()
}
}
/**
* Modify item quantity
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onModifyQuantity(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
const value = Number(target.dataset.quantiteValue)
if (itemId) {
await this.actor.incDecQuantity(itemId, value)
}
}
/**
* Roll initiative
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollInitiative(event, target) {
await this.actor.rollInitiative()
}
/**
* Roll caractéristique
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollCarac(event, target) {
const key = target.dataset.key
if (key) {
await this.actor.rollCarac(key, false)
}
}
/**
* Roll rang
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollRang(event, target) {
const key = target.dataset.rangKey
if (key) {
await this.actor.rollRang(key)
}
}
/**
* Roll root competence
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollRootCompetence(event, target) {
const compKey = target.dataset.attrKey
if (compKey) {
await this.actor.rollRootCompetence(compKey)
}
}
/**
* Roll competence
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollCompetence(event, target) {
const li = target.closest(".item")
const compId = li?.dataset.itemId
if (compId) {
await this.actor.rollCompetence(compId)
}
}
/**
* Roll sort
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollSort(event, target) {
const li = target.closest(".item")
const sortId = li?.dataset.itemId
if (sortId) {
await this.actor.rollSort(sortId)
}
}
/**
* Roll attaque arme
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollAttaqueArme(event, target) {
const li = target.closest(".item")
const armeId = li?.dataset.itemId
if (armeId) {
await this.actor.rollAttaqueArme(armeId)
}
}
/**
* Roll attaque brutale arme
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollAttaqueBrutaleArme(event, target) {
const li = target.closest(".item")
const armeId = li?.dataset.itemId
if (armeId) {
await this.actor.rollAttaqueBrutaleArme(armeId)
}
}
/**
* Roll attaque charge arme
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollAttaqueChargeArme(event, target) {
const li = target.closest(".item")
const armeId = li?.dataset.itemId
if (armeId) {
await this.actor.rollAttaqueChargeArme(armeId)
}
}
/**
* Roll assomer arme
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollAssomerArme(event, target) {
const li = target.closest(".item")
const armeId = li?.dataset.itemId
if (armeId) {
await this.actor.rollAssomerArme(armeId)
}
}
/**
* Roll pouvoir
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollPouvoir(event, target) {
const li = target.closest(".item")
const pouvoirId = li?.dataset.itemId
if (pouvoirId) {
await this.actor.rollPouvoir(pouvoirId)
}
}
/**
* Toggle masque
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onToggleMasque(event, target) {
await this.actor.toggleMasqueStatut()
this.render()
}
/**
* Dialog récupération usage
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onDialogRecupUsage(event, target) {
new Dialog({
title: "Récupération des Points d'Usage",
content: "<p>Combien de Points d'Usage souhaitez-vous récupérer ?</p>",
buttons: {
one: {
icon: '<i class="fas fa-check"></i>',
label: "1 Point",
callback: () => {
this.actor.recupUsage(1)
}
},
two: {
icon: '<i class="fas fa-check"></i>',
label: "2 Points",
callback: () => {
this.actor.recupUsage(2)
}
},
three: {
icon: '<i class="fas fa-check"></i>',
label: "3 Points",
callback: () => {
this.actor.recupUsage(3)
}
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Annuler"
}
},
default: "one"
}).render(true)
}
// #endregion
}

View File

@@ -0,0 +1,212 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class HeritiersItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-les-heritiers", "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: HeritiersItemSheet.#onEditImage,
postItem: HeritiersItemSheet.#onPostItem,
},
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = { primary: "description" }
/** @override */
async _prepareContext() {
const context = {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
source: this.document.toObject(),
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true }),
isEditMode: true,
isEditable: this.isEditable,
isGM: game.user.isGM,
config: CONFIG.HERITIERS,
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
this.#dragDrop.forEach((d) => d.bind(this.element))
// Activate tab navigation manually
const nav = this.element.querySelector('nav.tabs[data-group]')
if (nav) {
const group = nav.dataset.group
// Activate the current tab
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()
})
})
// Show/hide tab content
this.element.querySelectorAll('[data-group="' + group + '"][data-tab]').forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab)
})
}
}
// #region Drag-and-Drop Workflow
/**
* Create drag-and-drop workflow handlers for this Application
* @returns {DragDrop[]} An array of DragDrop handlers
* @private
*/
#createDragDropHandlers() {
return this.options.dragDrop.map((d) => {
d.permissions = {
dragstart: this._canDragStart.bind(this),
drop: this._canDragDrop.bind(this),
}
d.callbacks = {
dragstart: this._onDragStart.bind(this),
dragover: this._onDragOver.bind(this),
drop: this._onDrop.bind(this),
}
return new foundry.applications.ux.DragDrop(d)
})
}
/**
* Can the User start a drag workflow for a given drag selector?
* @param {string} selector The candidate HTML selector for the drag event
* @returns {boolean} Can the current user drag this selector?
* @protected
*/
_canDragStart(selector) {
return this.isEditable
}
/**
* Can the User drop an entry at a given drop selector?
* @param {string} selector The candidate HTML selector for the drop event
* @returns {boolean} Can the current user drop on this selector?
* @protected
*/
_canDragDrop(selector) {
return this.isEditable
}
/**
* Callback for dragstart events.
* @param {DragEvent} event The drag start event
* @protected
*/
_onDragStart(event) {
const target = event.currentTarget
const dragData = { type: "Item", uuid: this.document.uuid }
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
}
/**
* Callback for dragover events.
* @param {DragEvent} event The drag over event
* @protected
*/
_onDragOver(event) {
// Default behavior is fine
}
/**
* Callback for drop events.
* @param {DragEvent} event The drop event
* @protected
*/
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
const item = await fromUuid(data.uuid)
if (!item) return
console.log("Item dropped:", item)
}
// #endregion
// #region Action Handlers
/**
* Edit the item image
* @param {Event} event The triggering event
* @param {HTMLElement} target The target element
* @private
*/
static async #onEditImage(event, target) {
const fp = new FilePicker({
type: "image",
current: this.document.img,
callback: (path) => {
this.document.update({ img: path })
},
})
return fp.browse()
}
/**
* Post item to chat
* @param {Event} event The triggering event
* @param {HTMLElement} target The target element
* @private
*/
static async #onPostItem(event, target) {
let chatData = foundry.utils.duplicate(this.document)
if (this.document.actor) {
chatData.actor = { id: this.document.actor.id }
}
// Don't post any image for the item if the default image is used
if (chatData.img.includes("/blank.png") || chatData.img.includes("/mystery-man")) {
chatData.img = null
}
// JSON object for easy creation
chatData.jsondata = JSON.stringify({
compendium: "postedItem",
payload: chatData,
})
const html = await renderTemplate('systems/fvtt-les-heritiers/templates/post-item.html', chatData)
const chatOptions = {
user: game.user.id,
content: html,
}
ChatMessage.create(chatOptions)
}
// #endregion
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersCapaciteNaturelleSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.capacitenaturelle",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-capacitenaturelle-sheet.html",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersCompetenceSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.competence",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-competence-sheet.html",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersContactSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.contact",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-contact-sheet.html",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersDesavantageSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.desavantage",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-desavantage-sheet.html",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersEquipementSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.equipement",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-equipement-sheet.html",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersFeeSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.fee",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-fee-sheet.html",
},
}
}

View File

@@ -0,0 +1,59 @@
import HeritiersActorSheet from "./base-actor-sheet.mjs"
export default class HeritiersPersonnageSheet extends HeritiersActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Actor.personnage",
},
actions: {
...super.DEFAULT_OPTIONS.actions,
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/actor-sheet.html",
},
}
/** @override */
tabGroups = { primary: "stats" }
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
// Add personnage-specific data
context.skills = actor.getSkills()
context.utileSkillsMental = actor.organizeUtileSkills("mental")
context.utileSkillsPhysical = actor.organizeUtileSkills("physical")
context.competencesMagie = game.system.lesheritiers.config.competencesMagie || []
context.futileSkills = actor.organizeFutileSkills()
context.contacts = actor.organizeContacts()
context.armes = foundry.utils.duplicate(actor.getWeapons())
context.monnaies = foundry.utils.duplicate(actor.getMonnaies())
context.pouvoirs = foundry.utils.duplicate(actor.getPouvoirs())
context.fee = foundry.utils.duplicate(actor.getFee() || {})
context.protections = foundry.utils.duplicate(actor.getArmors())
context.combat = actor.getCombatValues()
context.equipements = foundry.utils.duplicate(actor.getEquipments())
context.avantages = foundry.utils.duplicate(actor.getAvantages())
context.atouts = foundry.utils.duplicate(actor.getAtouts())
context.capacites = foundry.utils.duplicate(actor.getCapacites())
context.desavantages = foundry.utils.duplicate(actor.getDesavantages())
context.profils = foundry.utils.duplicate(actor.getProfils())
context.pvMalus = actor.getPvMalus()
context.heritage = game.settings.get("fvtt-les-heritiers", "heritiers-heritage")
context.initiative = actor.getFlag("world", "last-initiative") || -1
context.magieList = actor.prepareMagie()
context.isPNJ = false
return context
}
}

View File

@@ -0,0 +1,59 @@
import HeritiersActorSheet from "./base-actor-sheet.mjs"
export default class HeritiersPnjSheet extends HeritiersActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Actor.pnj",
},
actions: {
...super.DEFAULT_OPTIONS.actions,
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/actor-pnj-sheet.html",
},
}
/** @override */
tabGroups = { primary: "stats" }
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
// Add PNJ-specific data
context.skills = actor.getSkills()
context.utileSkillsMental = actor.organizeUtileSkills("mental")
context.utileSkillsPhysical = actor.organizeUtileSkills("physical")
context.competencesMagie = game.system.lesheritiers.config.competencesMagie || []
context.futileSkills = actor.organizeFutileSkills()
context.contacts = actor.organizeContacts()
context.armes = foundry.utils.duplicate(actor.getWeapons())
context.monnaies = foundry.utils.duplicate(actor.getMonnaies())
context.pouvoirs = foundry.utils.duplicate(actor.getPouvoirs())
context.fee = foundry.utils.duplicate(actor.getFee() || {})
context.protections = foundry.utils.duplicate(actor.getArmors())
context.combat = actor.getCombatValues()
context.equipements = foundry.utils.duplicate(actor.getEquipments())
context.avantages = foundry.utils.duplicate(actor.getAvantages())
context.atouts = foundry.utils.duplicate(actor.getAtouts())
context.capacites = foundry.utils.duplicate(actor.getCapacites())
context.desavantages = foundry.utils.duplicate(actor.getDesavantages())
context.profils = foundry.utils.duplicate(actor.getProfils())
context.pvMalus = actor.getPvMalus()
context.heritage = game.settings.get("fvtt-les-heritiers", "heritiers-heritage")
context.initiative = actor.getFlag("world", "last-initiative") || -1
context.magieList = actor.prepareMagie()
context.isPNJ = true
return context
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersPouvoirSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.pouvoir",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-pouvoir-sheet.html",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersProfilSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.profil",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-profil-sheet.html",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersProtectionSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.protection",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-protection-sheet.html",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersSortSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.sort",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-sort-sheet.html",
},
}
}

View File

@@ -1,6 +1,6 @@
/* -------------------------------------------- */
import { HeritiersUtility } from "./heritiers-utility.js";
import { HeritiersRollDialog } from "./heritiers-roll-dialog.js";
import { HeritiersRollDialog } from "./applications/heritiers-roll-dialog.mjs";
/* -------------------------------------------- */
const __degatsBonus = [-2, -2, -1, -1, 0, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 8, 8, 9, 9, 10, 10]

View File

@@ -1,7 +1,7 @@
/* -------------------------------------------- */
import { HeritiersUtility } from "./heritiers-utility.js";
import { HeritiersRollDialog } from "./heritiers-roll-dialog.js";
import { HeritiersRollDialog } from "./applications/heritiers-roll-dialog.mjs";
/* -------------------------------------------- */
export class HeritiersCommands {

View File

@@ -9,14 +9,17 @@
/* -------------------------------------------- */
// Import Modules
import { HeritiersActor } from "./heritiers-actor.js";
import { HeritiersItemSheet } from "./heritiers-item-sheet.js";
import { HeritiersActorSheet } from "./heritiers-actor-sheet.js";
import { HeritiersActorPNJSheet } from "./heritiers-actor-pnj-sheet.js";
import { HeritiersItem } from "./heritiers-item.js";
import { HeritiersUtility } from "./heritiers-utility.js";
import { HeritiersCombat } from "./heritiers-combat.js";
import { HeritiersItem } from "./heritiers-item.js";
import { HERITIERS_CONFIG } from "./heritiers-config.js";
// Import DataModels
import * as models from "./models/index.mjs";
// Import AppV2 Sheets
import * as sheets from "./applications/sheets/_module.mjs";
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
@@ -45,22 +48,62 @@ Hooks.once("init", async function () {
// Define custom Entity classes
CONFIG.Combat.documentClass = HeritiersCombat
CONFIG.Actor.documentClass = HeritiersActor
CONFIG.Actor.dataModels = {
personnage: models.PersonnageDataModel,
pnj: models.PnjDataModel
}
CONFIG.Item.documentClass = HeritiersItem
CONFIG.Item.dataModels = {
accessoire: models.AccessoireDataModel,
arme: models.ArmeDataModel,
atoutfeerique: models.AtoutFeeriqueDataModel,
avantage: models.AvantageDataModel,
capacitenaturelle: models.CapaciteNaturelleDataModel,
competence: models.CompetenceDataModel,
contact: models.ContactDataModel,
desavantage: models.DesavantageDataModel,
equipement: models.EquipementDataModel,
fee: models.FeeDataModel,
pouvoir: models.PouvoirDataModel,
profil: models.ProfilDataModel,
protection: models.ProtectionDataModel,
sort: models.SortDataModel
}
// Create an object of bonus/malus from -6 to +6 signed
HERITIERS_CONFIG.bonusMalus = Array.from({ length: 7 }, (v, k) => toString(k - 6))
CONFIG.HERITIERS = HERITIERS_CONFIG
game.system.lesheritiers = {
HeritiersUtility,
config: HERITIERS_CONFIG
config: HERITIERS_CONFIG,
models,
sheets
}
/* -------------------------------------------- */
// Register sheet application classes
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
foundry.documents.collections.Actors.registerSheet("fvtt-les-heritiers", HeritiersActorSheet, { types: ["personnage"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("fvtt-les-heritiers", HeritiersActorPNJSheet, { types: ["pnj"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("fvtt-les-heritiers", sheets.HeritiersPersonnageSheet, { types: ["personnage"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("fvtt-les-heritiers", sheets.HeritiersPnjSheet, { types: ["pnj"], makeDefault: true })
// Register AppV2 Item Sheets
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", HeritiersItemSheet, { makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersAccessoireSheet, { types: ["accessoire"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersArmeSheet, { types: ["arme"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersAtoutFeeriqueSheet, { types: ["atoutfeerique"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersAvantageSheet, { types: ["avantage"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersCapaciteNaturelleSheet, { types: ["capacitenaturelle"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersCompetenceSheet, { types: ["competence"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersContactSheet, { types: ["contact"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersDesavantageSheet, { types: ["desavantage"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersEquipementSheet, { types: ["equipement"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersFeeSheet, { types: ["fee"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersPouvoirSheet, { types: ["pouvoir"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersProfilSheet, { types: ["profil"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersProtectionSheet, { types: ["protection"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersSortSheet, { types: ["sort"], makeDefault: true })
HeritiersUtility.init()

View File

@@ -0,0 +1,16 @@
/**
* Data model pour les accessoires
*/
export default class AccessoireDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
quantite: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true }),
equipped: new fields.BooleanField({ initial: false }),
lieu: new fields.NumberField({ initial: 0, integer: true })
};
}
}

30
modules/models/arme.mjs Normal file
View File

@@ -0,0 +1,30 @@
/**
* Data model pour les armes
*/
export default class ArmeDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
quantite: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true }),
equipped: new fields.BooleanField({ initial: false }),
categorie: new fields.StringField({ initial: "" }),
armetype: new fields.StringField({ initial: "" }),
degats: new fields.NumberField({ initial: 0, integer: true }),
precision: new fields.NumberField({ initial: 0, integer: true }),
cadence: new fields.StringField({ initial: "" }),
enraiement: new fields.StringField({ initial: "" }),
magasin: new fields.NumberField({ initial: 0, integer: true }),
charge: new fields.NumberField({ initial: 0, integer: true }),
portee: new fields.StringField({ initial: "" }),
legalite: new fields.StringField({ initial: "" }),
dissimulation: new fields.StringField({ initial: "" }),
zone: new fields.NumberField({ initial: 0, integer: true }),
temps: new fields.StringField({ initial: "" }),
allumage: new fields.StringField({ initial: "" }),
special: new fields.StringField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,11 @@
/**
* Data model pour les atouts féériques
*/
export default class AtoutFeeriqueDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,11 @@
/**
* Data model pour les avantages
*/
export default class AvantageDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,11 @@
/**
* Base data model pour les items
*/
export default class BaseItemDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,21 @@
/**
* Data model pour les capacités naturelles
*/
export default class CapaciteNaturelleDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
pouvoirtype: new fields.StringField({ initial: "" }),
activation: new fields.StringField({ initial: "" }),
cibles: new fields.StringField({ initial: "" }),
effet: new fields.StringField({ initial: "" }),
duree: new fields.StringField({ initial: "" }),
portee: new fields.StringField({ initial: "" }),
resistance: new fields.StringField({ initial: "" }),
resistanceautre: new fields.StringField({ initial: "" }),
isvirulence: new fields.BooleanField({ initial: false }),
virulence: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,43 @@
/**
* Data model pour les compétences
*/
export default class CompetenceDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
profil: new fields.StringField({ initial: "" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
nomniveau: new fields.SchemaField({
1: new fields.StringField({ initial: "" }),
2: new fields.StringField({ initial: "" }),
3: new fields.StringField({ initial: "" }),
4: new fields.StringField({ initial: "" })
}),
nomniveausouffle: new fields.SchemaField({
soufflecombat: new fields.SchemaField({
1: new fields.StringField({ initial: "" }),
2: new fields.StringField({ initial: "" }),
3: new fields.StringField({ initial: "" }),
4: new fields.StringField({ initial: "" })
}),
soufflemouvement: new fields.SchemaField({
1: new fields.StringField({ initial: "" }),
2: new fields.NumberField({ initial: 0, integer: true }),
3: new fields.StringField({ initial: "" }),
4: new fields.StringField({ initial: "" })
}),
souffleesprit: new fields.SchemaField({
1: new fields.StringField({ initial: "" }),
2: new fields.StringField({ initial: "" }),
3: new fields.StringField({ initial: "" }),
4: new fields.StringField({ initial: "" })
})
}),
predilection: new fields.BooleanField({ initial: false }),
specialites: new fields.ArrayField(new fields.ObjectField(), { initial: [] }),
ismagie: new fields.BooleanField({ initial: false }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,12 @@
/**
* Data model pour les contacts
*/
export default class ContactDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
contacttype: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,11 @@
/**
* Data model pour les désavantages
*/
export default class DesavantageDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,15 @@
/**
* Data model pour les équipements
*/
export default class EquipementDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
quantite: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true }),
equipped: new fields.BooleanField({ initial: false })
};
}
}

19
modules/models/fee.mjs Normal file
View File

@@ -0,0 +1,19 @@
/**
* Data model pour les fées
*/
export default class FeeDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
feetype: new fields.NumberField({ initial: 0, integer: true }),
avantages: new fields.StringField({ initial: "" }),
desavantages: new fields.StringField({ initial: "" }),
pouvoirsfeeriquesmasque: new fields.StringField({ initial: "" }),
pouvoirsfeeriquesdemasque: new fields.StringField({ initial: "" }),
atoutsfeeriques: new fields.StringField({ initial: "" }),
competences: new fields.StringField({ initial: "" }),
capacitenaturelles: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
};
}
}

24
modules/models/index.mjs Normal file
View File

@@ -0,0 +1,24 @@
/**
* Index des DataModels pour Les Héritiers
* Ce fichier centralise tous les exports des modèles de données
*/
// Modèles d'acteurs
export { default as PersonnageDataModel } from './personnage.mjs';
export { default as PnjDataModel } from './pnj.mjs';
// Modèles d'items
export { default as AccessoireDataModel } from './accessoire.mjs';
export { default as ArmeDataModel } from './arme.mjs';
export { default as AtoutFeeriqueDataModel } from './atoutfeerique.mjs';
export { default as AvantageDataModel } from './avantage.mjs';
export { default as CapaciteNaturelleDataModel } from './capacitenaturelle.mjs';
export { default as CompetenceDataModel } from './competence.mjs';
export { default as ContactDataModel } from './contact.mjs';
export { default as DesavantageDataModel } from './desavantage.mjs';
export { default as EquipementDataModel } from './equipement.mjs';
export { default as FeeDataModel } from './fee.mjs';
export { default as PouvoirDataModel } from './pouvoir.mjs';
export { default as ProfilDataModel } from './profil.mjs';
export { default as ProtectionDataModel } from './protection.mjs';
export { default as SortDataModel } from './sort.mjs';

View File

@@ -0,0 +1,234 @@
/**
* Data model pour les personnages
*/
export default class PersonnageDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
// Template biodata
biodata: new fields.SchemaField({
name: new fields.StringField({ initial: "" }),
activite: new fields.StringField({ initial: "" }),
nomhumain: new fields.StringField({ initial: "" }),
activites: new fields.StringField({ initial: "" }),
fortune: new fields.NumberField({ initial: 0, integer: true }),
traitscaracteres: new fields.StringField({ initial: "" }),
tailledemasquee: new fields.StringField({ initial: "" }),
taillemasquee: new fields.StringField({ initial: "" }),
poidsmasquee: new fields.StringField({ initial: "" }),
poidsdemasquee: new fields.StringField({ initial: "" }),
apparencemasquee: new fields.StringField({ initial: "" }),
apparencedemasquee: new fields.StringField({ initial: "" }),
titrefamille: new fields.StringField({ initial: "" }),
langues: new fields.StringField({ initial: "" }),
factionfeerique: new fields.StringField({ initial: "" }),
typetaille: new fields.StringField({ initial: "" }),
age: new fields.NumberField({ initial: 0, integer: true }),
poids: new fields.StringField({ initial: "" }),
taille: new fields.StringField({ initial: "" }),
cheveux: new fields.StringField({ initial: "" }),
sexe: new fields.StringField({ initial: "" }),
yeux: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
revesetranges: new fields.HTMLField({ initial: "" }),
secretsdecouverts: new fields.HTMLField({ initial: "" }),
questions: new fields.HTMLField({ initial: "" }),
habitat: new fields.HTMLField({ initial: "" }),
notes: new fields.HTMLField({ initial: "" }),
statut: new fields.StringField({ initial: "" }),
playernotes: new fields.HTMLField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" }),
magie: new fields.BooleanField({ initial: false })
}),
// Template core
subactors: new fields.ArrayField(new fields.StringField(), { initial: [] }),
caracteristiques: new fields.SchemaField({
agi: new fields.SchemaField({
label: new fields.StringField({ initial: "Agilité" }),
labelnorm: new fields.StringField({ initial: "agilite" }),
abbrev: new fields.StringField({ initial: "agi" }),
kind: new fields.StringField({ initial: "physical" }),
value: new fields.NumberField({ initial: 1, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true })
}),
con: new fields.SchemaField({
label: new fields.StringField({ initial: "Constitution" }),
labelnorm: new fields.StringField({ initial: "constitution" }),
abbrev: new fields.StringField({ initial: "con" }),
kind: new fields.StringField({ initial: "physical" }),
value: new fields.NumberField({ initial: 1, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true })
}),
for: new fields.SchemaField({
label: new fields.StringField({ initial: "Force" }),
labelnorm: new fields.StringField({ initial: "force" }),
abbrev: new fields.StringField({ initial: "for" }),
kind: new fields.StringField({ initial: "physical" }),
value: new fields.NumberField({ initial: 1, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true })
}),
prec: new fields.SchemaField({
label: new fields.StringField({ initial: "Précision" }),
labelnorm: new fields.StringField({ initial: "precision" }),
abbrev: new fields.StringField({ initial: "prec" }),
kind: new fields.StringField({ initial: "physical" }),
value: new fields.NumberField({ initial: 1, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true })
}),
esp: new fields.SchemaField({
label: new fields.StringField({ initial: "Esprit" }),
labelnorm: new fields.StringField({ initial: "esprit" }),
abbrev: new fields.StringField({ initial: "esp" }),
kind: new fields.StringField({ initial: "mental" }),
value: new fields.NumberField({ initial: 1, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true })
}),
per: new fields.SchemaField({
label: new fields.StringField({ initial: "Perception" }),
labelnorm: new fields.StringField({ initial: "perception" }),
abbrev: new fields.StringField({ initial: "per" }),
kind: new fields.StringField({ initial: "mental" }),
value: new fields.NumberField({ initial: 1, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true })
}),
pres: new fields.SchemaField({
label: new fields.StringField({ initial: "Prestance" }),
labelnorm: new fields.StringField({ initial: "pres" }),
abbrev: new fields.StringField({ initial: "pres" }),
kind: new fields.StringField({ initial: "mental" }),
value: new fields.NumberField({ initial: 1, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true })
}),
san: new fields.SchemaField({
label: new fields.StringField({ initial: "Sang-Froid" }),
labelnorm: new fields.StringField({ initial: "sangfroid" }),
abbrev: new fields.StringField({ initial: "san" }),
kind: new fields.StringField({ initial: "mental" }),
value: new fields.NumberField({ initial: 1, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true })
})
}),
statutmasque: new fields.StringField({ initial: "masque" }),
rang: new fields.SchemaField({
tricherie: new fields.SchemaField({
label: new fields.StringField({ initial: "Tricherie" }),
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
feerie: new fields.SchemaField({
label: new fields.StringField({ initial: "Féerie" }),
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
masque: new fields.SchemaField({
label: new fields.StringField({ initial: "Masque" }),
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
heritage: new fields.SchemaField({
label: new fields.StringField({ initial: "Héritage" }),
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
scenarios: new fields.NumberField({ initial: 0, integer: true })
})
}),
pv: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
mod: new fields.NumberField({ initial: 0, integer: true })
}),
competences: new fields.SchemaField({
aventurier: new fields.SchemaField({
label: new fields.StringField({ initial: "Aventurier" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
pp: new fields.NumberField({ initial: 0, integer: true })
}),
combattant: new fields.SchemaField({
label: new fields.StringField({ initial: "Combattant" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
pp: new fields.NumberField({ initial: 0, integer: true })
}),
erudit: new fields.SchemaField({
label: new fields.StringField({ initial: "Erudit" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
pp: new fields.NumberField({ initial: 0, integer: true })
}),
gentleman: new fields.SchemaField({
label: new fields.StringField({ initial: "Gentleman" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
pp: new fields.NumberField({ initial: 0, integer: true })
}),
roublard: new fields.SchemaField({
label: new fields.StringField({ initial: "Roublard" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
pp: new fields.NumberField({ initial: 0, integer: true })
}),
savant: new fields.SchemaField({
label: new fields.StringField({ initial: "Savant" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
pp: new fields.NumberField({ initial: 0, integer: true })
})
}),
magie: new fields.SchemaField({
pointsame: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true })
})
}),
experience: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
pourtricher: new fields.NumberField({ initial: 0, integer: true })
}),
combat: new fields.SchemaField({
esquive: new fields.SchemaField({
masquee: new fields.NumberField({ initial: 0, integer: true }),
demasquee: new fields.NumberField({ initial: 0, integer: true })
}),
parade: new fields.SchemaField({
masquee: new fields.NumberField({ initial: 0, integer: true }),
demasquee: new fields.NumberField({ initial: 0, integer: true }),
value: new fields.NumberField({ initial: 0, integer: true })
}),
resistancephysique: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
resistancepsychique: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
protection: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
effetssecondaires: new fields.StringField({ initial: "" }),
dissimulation: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
initiative: new fields.SchemaField({
masquee: new fields.NumberField({ initial: 0, integer: true }),
demasquee: new fields.NumberField({ initial: 0, integer: true })
}),
corpsacorps: new fields.SchemaField({
masquee: new fields.NumberField({ initial: 0, integer: true }),
demasquee: new fields.NumberField({ initial: 0, integer: true })
}),
tir: new fields.SchemaField({
masquee: new fields.NumberField({ initial: 0, integer: true }),
demasquee: new fields.NumberField({ initial: 0, integer: true })
})
})
};
}
}

10
modules/models/pnj.mjs Normal file
View File

@@ -0,0 +1,10 @@
/**
* Data model pour les PNJ
* Utilise le même schéma que les personnages
*/
import PersonnageDataModel from './personnage.mjs';
export default class PnjDataModel extends PersonnageDataModel {
// Les PNJ utilisent exactement le même schéma que les personnages
// On hérite simplement de PersonnageDataModel
}

View File

@@ -0,0 +1,29 @@
/**
* Data model pour les pouvoirs
*/
export default class PouvoirDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
pouvoirtype: new fields.StringField({ initial: "" }),
masquetype: new fields.StringField({ initial: "" }),
niveau: new fields.StringField({ initial: "" }),
activation: new fields.StringField({ initial: "" }),
istest: new fields.BooleanField({ initial: false }),
feeriemasque: new fields.StringField({ initial: "feerie" }),
zoneffet: new fields.StringField({ initial: "" }),
testautre: new fields.StringField({ initial: "" }),
carac: new fields.StringField({ initial: "pre" }),
duree: new fields.StringField({ initial: "" }),
cibles: new fields.StringField({ initial: "" }),
effet: new fields.StringField({ initial: "" }),
portee: new fields.StringField({ initial: "" }),
resistance: new fields.StringField({ initial: "" }),
resistanceautre: new fields.StringField({ initial: "" }),
pointsusagecourant: new fields.NumberField({ initial: -1, integer: true }),
isvirulence: new fields.BooleanField({ initial: false }),
virulence: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
};
}
}

12
modules/models/profil.mjs Normal file
View File

@@ -0,0 +1,12 @@
/**
* Data model pour les profils
*/
export default class ProfilDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
profiltype: new fields.StringField({ initial: "majeur" }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,20 @@
/**
* Data model pour les protections
*/
export default class ProtectionDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
quantite: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true }),
equipped: new fields.BooleanField({ initial: false }),
points: new fields.NumberField({ initial: 0, integer: true }),
protectiontype: new fields.StringField({ initial: "" }),
effetsecondaire: new fields.StringField({ initial: "" }),
malusagilite: new fields.NumberField({ initial: 0, integer: true }),
dissimulation: new fields.StringField({ initial: "" })
};
}
}

27
modules/models/sort.mjs Normal file
View File

@@ -0,0 +1,27 @@
/**
* Data model pour les sorts
*/
export default class SortDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
niveau: new fields.StringField({ initial: "1" }),
rang: new fields.StringField({ initial: "1" }),
competence: new fields.StringField({ initial: "Druidisme" }),
carac1: new fields.StringField({ initial: "esp" }),
carac2: new fields.StringField({ initial: "none" }),
sdspecial: new fields.StringField({ initial: "" }),
duree: new fields.StringField({ initial: "" }),
portee: new fields.StringField({ initial: "" }),
concentration: new fields.StringField({ initial: "" }),
informatif: new fields.BooleanField({ initial: false }),
texteinformatif: new fields.StringField({ initial: "" }),
critique: new fields.StringField({ initial: "" }),
ingredients: new fields.StringField({ initial: "" }),
resistance: new fields.StringField({ initial: "" }),
coutactivation: new fields.StringField({ initial: "" }),
souffle: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
};
}
}