Finalize aappv2 data models migration

This commit is contained in:
2026-02-27 14:36:54 +01:00
parent 8735b7e4a4
commit c45837ea31
87 changed files with 10701 additions and 1225 deletions

View File

@@ -0,0 +1,13 @@
/**
* Index des fiches AppV2 pour Maléfices
*/
// Actor sheets
export { default as MaleficesPersonnageSheet } from './malefices-personnage-sheet.mjs';
export { default as MaleficesNPCActorSheet } from './malefices-npc-actor-sheet.mjs';
// Item sheets
export { default as MaleficesArmeSheet } from './malefices-arme-sheet.mjs';
export { default as MaleficesEquipementSheet } from './malefices-equipement-sheet.mjs';
export { default as MaleficesArchetypeSheet } from './malefices-archetype-sheet.mjs';
export { default as MaleficesTarotSheet } from './malefices-tarot-sheet.mjs';
export { default as MaleficesSortilegeSheet } from './malefices-sortilege-sheet.mjs';
export { default as MaleficesElementbioSheet } from './malefices-elementbio-sheet.mjs';

View File

@@ -0,0 +1,132 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class MaleficesItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-malefices", "item"],
position: {
width: 620,
height: 600,
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
dragDrop: [{ dragSelector: "[data-drag]", dropSelector: null }],
actions: {
editImage: MaleficesItemSheet.#onEditImage,
postItem: MaleficesItemSheet.#onPostItem,
},
}
/** @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 }
),
isEditable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
isGM: game.user.isGM,
config: game.system.malefices.config,
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
this.#dragDrop.forEach((d) => d.bind(this.element))
// Manual 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)
})
}
}
// #region Drag-and-Drop
#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.implementation(d)
})
}
_canDragStart(selector) { return this.isEditable }
_canDragDrop(selector) { return this.isEditable }
_onDragStart(event) {
const dragData = { type: "Item", uuid: this.document.uuid }
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
}
_onDragOver(event) {}
async _onDrop(event) {}
// #endregion
// #region Actions
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()
}
static async #onPostItem(event, target) {
let chatData = foundry.utils.duplicate(this.document)
if (this.document.actor) {
chatData.actor = { id: this.document.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-malefices/templates/post-item.hbs', chatData
)
ChatMessage.create({ user: game.user.id, content: html })
}
// #endregion
}

View File

@@ -0,0 +1,35 @@
import MaleficesItemSheet from "./base-item-sheet.mjs"
import { MaleficesUtility } from "../../malefices-utility.js"
export default class MaleficesArchetypeSheet extends MaleficesItemSheet {
static DEFAULT_OPTIONS = {
classes: ["archetype"],
position: { width: 620 },
window: { contentClasses: ["archetype-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-malefices/templates/items/item-archetype-sheet.hbs" },
}
tabGroups = { primary: "details" }
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" },
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
context.tarots = MaleficesUtility.getTarots()
return context
}
}

View File

@@ -0,0 +1,33 @@
import MaleficesItemSheet from "./base-item-sheet.mjs"
export default class MaleficesArmeSheet extends MaleficesItemSheet {
static DEFAULT_OPTIONS = {
classes: ["arme"],
position: { width: 640 },
window: { contentClasses: ["arme-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-malefices/templates/items/item-arme-sheet.hbs" },
}
tabGroups = { primary: "details" }
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" },
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,225 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
import { MaleficesUtility } from "../../malefices-utility.js"
export default class MaleficesActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
this._editScore = true
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-malefices", "actor"],
position: {
width: 640,
height: 680,
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
actions: {
editImage: MaleficesActorSheet.#onEditImage,
toggleSheet: MaleficesActorSheet.#onToggleSheet,
editItem: MaleficesActorSheet.#onEditItem,
deleteItem: MaleficesActorSheet.#onDeleteItem,
createItem: MaleficesActorSheet.#onCreateItem,
equipItem: MaleficesActorSheet.#onEquipItem,
modifyQuantity: MaleficesActorSheet.#onModifyQuantity,
modifyAmmo: MaleficesActorSheet.#onModifyAmmo,
rollAttribut: MaleficesActorSheet.#onRollAttribut,
rollArme: MaleficesActorSheet.#onRollArme,
editSubActor: MaleficesActorSheet.#onEditSubActor,
deleteSubActor: MaleficesActorSheet.#onDeleteSubActor,
},
}
/** @type {object} */
tabGroups = { primary: "main" }
/** @override */
async _prepareContext() {
const actor = this.document
return {
actor,
system: actor.system,
source: actor.toObject(),
fields: actor.schema.fields,
systemFields: actor.system.schema.fields,
isEditable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
isGM: game.user.isGM,
config: game.system.malefices.config,
editScore: this._editScore,
}
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
this.#dragDrop.forEach((d) => d.bind(this.element))
// Ignore Enter key in text inputs (not textarea)
this.element.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && e.target.tagName !== 'TEXTAREA') e.preventDefault()
})
// Manual tab navigation
const nav = this.element.querySelector('nav.tabs[data-group]')
if (nav) {
const group = nav.dataset.group
const activeTab = this.tabGroups[group] || "main"
nav.querySelectorAll('[data-tab]').forEach(link => {
link.classList.toggle('active', link.dataset.tab === activeTab)
link.addEventListener('click', (event) => {
event.preventDefault()
this.tabGroups[group] = link.dataset.tab
this.render()
})
})
this.element.querySelectorAll(`[data-group="${group}"][data-tab]`).forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab)
})
}
// Handle .update-field change events (legacy support)
this.element.querySelectorAll('.update-field').forEach(el => {
el.addEventListener('change', (ev) => {
const fieldName = ev.currentTarget.dataset.fieldName
const value = Number(ev.currentTarget.value)
this.actor.update({ [fieldName]: value })
})
})
}
// #region Drag-and-Drop
#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.implementation(d)
})
}
_canDragStart(selector) { return this.isEditable }
_canDragDrop(selector) { return this.isEditable }
_onDragStart(event) {
const li = event.currentTarget.closest('.item')
if (!li) return
const itemId = li.dataset.itemId
const item = this.actor.items.get(itemId)
if (item) {
event.dataTransfer.setData("text/plain", JSON.stringify({ type: "Item", uuid: item.uuid }))
}
}
_onDragOver(event) {}
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
if (data?.type === "Actor") {
const actor = await fromUuid(data.uuid)
if (actor) this.actor.addSubActor(actor.id)
} else {
super._onDrop(event)
}
}
// #endregion
// #region Actions
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()
}
static async #onToggleSheet(event, target) {
this._editScore = !this._editScore
this.render()
}
static async #onEditItem(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
this.actor.items.get(itemId)?.sheet.render(true)
}
static async #onDeleteItem(event, target) {
const li = target.closest(".item")
MaleficesUtility.confirmDelete(this, li)
}
static async #onCreateItem(event, target) {
const dataType = target.dataset.type
this.actor.createEmbeddedDocuments('Item', [{ name: "NewItem", type: dataType }], { renderSheet: true })
}
static async #onEquipItem(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
await this.actor.equipItem(itemId)
this.render()
}
static async #onModifyQuantity(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
const delta = parseInt(target.dataset.delta) || 0
this.actor.incDecQuantity(itemId, delta)
}
static async #onModifyAmmo(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
const delta = parseInt(target.dataset.delta) || 0
this.actor.incDecAmmo(itemId, delta)
}
static async #onRollAttribut(event, target) {
const attrKey = target.dataset.attrKey
this.actor.rollAttribut(attrKey)
}
static async #onRollArme(event, target) {
const armeId = target.dataset.armeId
this.actor.rollArme(armeId)
}
static async #onEditSubActor(event, target) {
const li = target.closest(".item")
const actorId = li?.dataset.actorId
if (!actorId) return
game.actors.get(actorId)?.sheet.render(true)
}
static async #onDeleteSubActor(event, target) {
const li = target.closest(".item")
const actorId = li?.dataset.actorId
if (!actorId) return
this.actor.delSubActor(actorId)
}
// #endregion
}

View File

@@ -0,0 +1,32 @@
import MaleficesItemSheet from "./base-item-sheet.mjs"
export default class MaleficesElementbioSheet extends MaleficesItemSheet {
static DEFAULT_OPTIONS = {
classes: ["elementbio"],
position: { width: 620 },
window: { contentClasses: ["elementbio-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-malefices/templates/items/item-elementbio-sheet.hbs" },
}
tabGroups = { primary: "description" }
#getTabs() {
const tabs = {
description: { id: "description", group: "primary", label: "Description" },
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,32 @@
import MaleficesItemSheet from "./base-item-sheet.mjs"
export default class MaleficesEquipementSheet extends MaleficesItemSheet {
static DEFAULT_OPTIONS = {
classes: ["equipement"],
position: { width: 620 },
window: { contentClasses: ["equipement-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-malefices/templates/items/item-equipement-sheet.hbs" },
}
tabGroups = { primary: "description" }
#getTabs() {
const tabs = {
description: { id: "description", group: "primary", label: "Description" },
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,27 @@
import MaleficesActorSheet from "./malefices-base-actor-sheet.mjs"
export default class MaleficesNPCActorSheet extends MaleficesActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["pnj"],
position: { width: 560, height: 460 },
}
/** @override */
static PARTS = {
main: { template: "systems/fvtt-malefices/templates/actors/npc-sheet.hbs" },
}
/** @override */
tabGroups = { primary: "main" }
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
this.document.system.description ?? "", { async: true }
)
return context
}
}

View File

@@ -0,0 +1,48 @@
import MaleficesActorSheet from "./malefices-base-actor-sheet.mjs"
export default class MaleficesPersonnageSheet extends MaleficesActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["personnage"],
position: { width: 640, height: 680 },
}
/** @override */
static PARTS = {
main: { template: "systems/fvtt-malefices/templates/actors/actor-sheet.hbs" },
}
/** @override */
tabGroups = { primary: "main" }
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
context.armes = foundry.utils.duplicate(actor.getArmes())
context.tarots = foundry.utils.duplicate(actor.getTarots())
context.tarotsCache = foundry.utils.duplicate(actor.getHiddenTarots())
context.archetype = foundry.utils.duplicate(actor.getArchetype())
context.equipements = foundry.utils.duplicate(actor.getEquipements())
context.elementsbio = actor.getElementsBio()
context.sorts = actor.getSorts()
context.phyMalus = actor.getPhysiqueMalus()
context.subActors = foundry.utils.duplicate(actor.getSubActors())
// Expose nested biodata schema fields for {{formInput}} helper
context.biodataFields = actor.system.schema.fields.biodata.fields
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
actor.system.biodata?.description ?? "", { async: true }
)
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
actor.system.biodata?.notes ?? "", { async: true }
)
context.enrichedEquipementlibre = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
actor.system.equipementlibre ?? "", { async: true }
)
return context
}
}

View File

@@ -0,0 +1,33 @@
import MaleficesItemSheet from "./base-item-sheet.mjs"
export default class MaleficesSortilegeSheet extends MaleficesItemSheet {
static DEFAULT_OPTIONS = {
classes: ["sortilege"],
position: { width: 620 },
window: { contentClasses: ["sortilege-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-malefices/templates/items/item-sortilege-sheet.hbs" },
}
tabGroups = { primary: "details" }
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" },
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,33 @@
import MaleficesItemSheet from "./base-item-sheet.mjs"
export default class MaleficesTarotSheet extends MaleficesItemSheet {
static DEFAULT_OPTIONS = {
classes: ["tarot"],
position: { width: 660, height: 640 },
window: { contentClasses: ["tarot-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-malefices/templates/items/item-tarot-sheet.hbs" },
}
tabGroups = { primary: "details" }
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" },
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -389,8 +389,7 @@ export class MaleficesActor extends Actor {
/* -------------------------------------------- */
async startRoll(rollData) {
let rollDialog = await MaleficesRollDialog.create(this, rollData)
rollDialog.render(true)
await MaleficesRollDialog.create(this, rollData)
}
}

View File

@@ -125,7 +125,6 @@ export class MaleficesCommands {
tirageData.secretCards.push({ name: "???", img: "systems/fvtt-malefices/images/tarots/background.webp" })
let tirageDialog = await MaleficesTirageTarotDialog.create(this, tirageData)
tirageDialog.render(true)
}
}
/* --------------------------------------------- */

View File

@@ -80,20 +80,14 @@ export class MaleficesItemSheet extends foundry.appv1.sheets.ItemSheet {
if (this.actor) {
chatData.actor = { id: this.actor.id };
}
// Don't post any image for the item (which would leave a large gap) if the default image is used
if (chatData.img.includes("/blank.png")) {
if (chatData.img?.includes("/blank.png")) {
chatData.img = null;
}
// JSON object for easy creation
chatData.jsondata = JSON.stringify(
{
compendium: "postedItem",
payload: chatData,
});
chatData.config = game.system.malefices.config
chatData.jsondata = JSON.stringify({ compendium: "postedItem", payload: chatData })
renderTemplate('systems/Malefices/templates/post-item.html', chatData).then(html => {
let chatOptions = MaleficesUtility.chatDataSetup(html);
ChatMessage.create(chatOptions)
foundry.applications.handlebars.renderTemplate('systems/fvtt-malefices/templates/post-item.hbs', chatData).then(html => {
ChatMessage.create(MaleficesUtility.chatDataSetup(html))
});
}

View File

@@ -1,7 +1,7 @@
import { MaleficesUtility } from "./malefices-utility.js";
export const defaultItemImg = {
arme: "systems/fvtt-malefices/images/icons/arme.webp",
arme: "systems/fvtt-malefices/images/icons/epee.webp",
equipement: "systems/fvtt-malefices/images/icons/equipement.webp",
elementbio: "systems/fvtt-malefices/images/icons/wisdom.webp",
archetype: "systems/fvtt-malefices/images/icons/archetype.webp",

View File

@@ -9,9 +9,6 @@
/* -------------------------------------------- */
// Import Modules
import { MaleficesActor } from "./malefices-actor.js";
import { MaleficesItemSheet } from "./malefices-item-sheet.js";
import { MaleficesActorSheet } from "./malefices-actor-sheet.js";
import { MaleficesNPCSheet } from "./malefices-npc-sheet.js";
import { MaleficesUtility } from "./malefices-utility.js";
import { MaleficesCombat } from "./malefices-combat.js";
import { MaleficesItem } from "./malefices-item.js";
@@ -20,6 +17,12 @@ import { MaleficesCharacterSummary } from "./malefices-summary-app.js"
import { MALEFICES_CONFIG } from "./malefices-config.js"
import { ClassCounter} from "https://www.uberwald.me/fvtt_appcount/count-class-ready.js"
// Import DataModels
import * as models from "./models/index.mjs"
// Import AppV2 Sheets
import * as sheets from "./applications/sheets/_module.mjs"
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
@@ -54,33 +57,51 @@ Hooks.once("init", async function () {
// Define custom Entity classes
CONFIG.Combat.documentClass = MaleficesCombat
CONFIG.Actor.documentClass = MaleficesActor
CONFIG.Actor.dataModels = {
personnage: models.PersonnageDataModel,
pnj: models.PnjDataModel
}
CONFIG.Item.documentClass = MaleficesItem
CONFIG.Item.dataModels = {
arme: models.ArmeDataModel,
equipement: models.EquipementDataModel,
archetype: models.ArchetypeDataModel,
tarot: models.TarotDataModel,
sortilege: models.SortilegeDataModel,
elementbio: models.ElementbioDataModel
}
/* -------------------------------------------- */
// Register sheet application classes
// Register AppV2 Actor Sheets
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
foundry.documents.collections.Actors.registerSheet("fvtt-malefices", MaleficesActorSheet, { types: ["personnage"], makeDefault: true });
foundry.documents.collections.Actors.registerSheet("fvtt-malefices", MaleficesNPCSheet, { types: ["pnj"], makeDefault: false });
foundry.documents.collections.Actors.registerSheet("fvtt-malefices", sheets.MaleficesPersonnageSheet, { types: ["personnage"], makeDefault: true });
foundry.documents.collections.Actors.registerSheet("fvtt-malefices", sheets.MaleficesNPCActorSheet, { types: ["pnj"], makeDefault: true });
// Register AppV2 Item Sheets
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("fvtt-malefices", MaleficesItemSheet, { makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-malefices", sheets.MaleficesArmeSheet, { types: ["arme"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-malefices", sheets.MaleficesEquipementSheet, { types: ["equipement"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-malefices", sheets.MaleficesArchetypeSheet, { types: ["archetype"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-malefices", sheets.MaleficesTarotSheet, { types: ["tarot"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-malefices", sheets.MaleficesSortilegeSheet, { types: ["sortilege"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-malefices", sheets.MaleficesElementbioSheet, { types: ["elementbio"], makeDefault: true });
MaleficesUtility.init()
});
/* -------------------------------------------- */
function welcomeMessage() {
async function welcomeMessage() {
if (game.user.isGM) {
const content = await foundry.applications.handlebars.renderTemplate(
'systems/fvtt-malefices/templates/chat/welcome-message.hbs', {}
)
ChatMessage.create({
user: game.user.id,
whisper: [game.user.id],
content: `<div id="welcome-message-malefices"><span class="rdd-roll-part">
<strong>Bienvenu dans Malefices, le JDR qui sent le souffre !</strong>
<p>Le Livre de Base de Maléfices v4 est nécessaire pour jouer : https://arkhane-asylum.fr/en/malefices/</p>
<p>Maléfices et un jeu de rôle publié par Arkhane Asylum Publishing, tout les droits leur appartiennent.</p>
<p>Système développé par LeRatierBretonnien avec l'aide de la Dame du Lac et Malik, support sur le <a href="https://discord.gg/pPSDNJk">Discord FR de Foundry</a>.</p>
<p>Commandes : /tirage pour le tirage des tarots, /carte pour tirer une simple carte et /resume pour le résumé des PJs (MJ seulement)` });
content
});
}
}

View File

@@ -1,91 +1,57 @@
import { MaleficesUtility } from "./malefices-utility.js";
export class MaleficesRollDialog extends Dialog {
export class MaleficesRollDialog {
/* -------------------------------------------- */
static async create(actor, rollData) {
const isCard = rollData?.attr?.iscard
const template = isCard
? 'systems/fvtt-malefices/templates/dialogs/confrontation-dialog.hbs'
: 'systems/fvtt-malefices/templates/dialogs/roll-dialog-generic.hbs'
let options = { classes: ["MaleficesDialog"], width: 540, height: 'fit-content', 'z-index': 99999 }
let html
if (rollData?.attr?.iscard) {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-malefices/templates/dialogs/confrontation-dialog.hbs', rollData);
} else {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-malefices/templates/dialogs/roll-dialog-generic.hbs', rollData);
}
const content = await foundry.applications.handlebars.renderTemplate(template, rollData)
return new MaleficesRollDialog(actor, rollData, html, options);
}
/* -------------------------------------------- */
constructor(actor, rollData, html, options, close = undefined) {
let isCard = rollData?.attr?.iscard
let conf = {
title: (isCard) ? "Jet" : "Tirage",
content: html,
buttons: {
roll: {
icon: '<i class="fas fa-check"></i>',
label: (isCard) ? "Tirer une carte" : "Lancer le dé",
callback: () => { this.roll() }
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Annuler",
callback: () => { this.close() }
}
return foundry.applications.api.DialogV2.wait({
window: {
title: isCard ? "Tirage" : "Jet de dé",
icon: isCard ? "fa-solid fa-layer-group" : "fa-solid fa-dice-d20",
},
close: close
}
super(conf, options);
this.actor = actor;
this.rollData = rollData;
classes: ["malefices-roll-dialog"],
position: { width: 540 },
modal: false,
rejectClose: false,
content,
buttons: [
{
action: "roll",
label: isCard ? "Tirer une carte" : "Lancer le dé",
icon: isCard ? "fa-solid fa-layer-group" : "fa-solid fa-check",
default: true,
callback: (event, button, dialog) => {
MaleficesRollDialog._updateRollDataFromForm(rollData, button.form.elements)
if (isCard) {
MaleficesUtility.tirageConfrontationMalefices(rollData)
} else {
MaleficesUtility.rollMalefices(rollData)
}
}
},
{
action: "cancel",
label: "Annuler",
icon: "fa-solid fa-times",
}
],
})
}
/* -------------------------------------------- */
roll() {
let isCard = this.rollData?.attr?.iscard
if (isCard) {
MaleficesUtility.tirageConfrontationMalefices(this.rollData)
} else {
MaleficesUtility.rollMalefices(this.rollData)
}
}
/* -------------------------------------------- */
async refreshDialog() {
const content = await renderTemplate("systems/fvtt-malefices/templates/dialogs/roll-dialog-generic.hbs", this.rollData)
this.data.content = content
this.render(true)
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
function onLoad() {
}
$(function () { onLoad(); });
html.find('#bonusMalusSituation').change((event) => {
this.rollData.bonusMalusSituation = Number(event.currentTarget.value)
})
html.find('#bonusMalusPerso').change((event) => {
this.rollData.bonusMalusPerso = Number(event.currentTarget.value)
})
html.find('#bonusMalusDef').change((event) => {
this.rollData.bonusMalusDef = Number(event.currentTarget.value)
})
html.find('#bonusMalusPortee').change((event) => {
this.rollData.bonusMalusPortee = Number(event.currentTarget.value)
})
html.find('#confrontationDegre').change((event) => {
this.rollData.confrontationDegre = Number(event.currentTarget.value)
})
html.find('#confrontationModif').change((event) => {
this.rollData.confrontationModif = Number(event.currentTarget.value)
})
static _updateRollDataFromForm(rollData, elements) {
if (elements.bonusMalusPerso) rollData.bonusMalusPerso = Number(elements.bonusMalusPerso.value)
if (elements.bonusMalusSituation) rollData.bonusMalusSituation = Number(elements.bonusMalusSituation.value)
if (elements.bonusMalusDef) rollData.bonusMalusDef = Number(elements.bonusMalusDef.value)
if (elements.bonusMalusPortee) rollData.bonusMalusPortee = Number(elements.bonusMalusPortee.value)
if (elements.confrontationDegre) rollData.confrontationDegre = Number(elements.confrontationDegre.value)
if (elements.confrontationModif) rollData.confrontationModif = Number(elements.confrontationModif.value)
}
}

View File

@@ -1,134 +1,133 @@
/* -------------------------------------------- */
import { MaleficesUtility } from "./malefices-utility.js";
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api
/* -------------------------------------------- */
export class MaleficesCharacterSummary extends Application {
export class MaleficesCharacterSummary extends HandlebarsApplicationMixin(ApplicationV2) {
/* -------------------------------------------- */
static DEFAULT_OPTIONS = {
id: "malefices-character-summary",
classes: ["MaleficesDialog"],
window: { title: "Résumé des Personnages", resizable: true },
position: { width: 960, height: "auto" },
dragDrop: [{ dragSelector: null, dropSelector: ".character-summary-container" }],
}
static PARTS = {
form: { template: "systems/fvtt-malefices/templates/dialogs/character-summary.hbs" }
}
/* -------------------------------------------- */
static displayPCSummary() {
if (game.user.isGM) {
game.system.malefices.charSummary.render(true)
game.system.malefices.charSummary.render({ force: true })
} else {
ui.notifications.info("Commande /tirage réservée au MJ !")
ui.notifications.info("Commande /resume réservée au MJ !")
}
}
/* -------------------------------------------- */
updatePCSummary() {
if (this.rendered) {
this.render(true)
if (this.element?.isConnected) {
this.render({ force: true })
}
}
/* -------------------------------------------- */
static createSummaryPos() {
return { top: 200, left: 200 };
}
/* -------------------------------------------- */
static ready() {
if (!game.user.isGM) { // Uniquement si GM
if (!game.user.isGM) {
return
}
let charSummary = new MaleficesCharacterSummary()
game.system.malefices.charSummary = charSummary
game.system.malefices.charSummary = new MaleficesCharacterSummary()
}
/* -------------------------------------------- */
constructor() {
super();
//game.settings.set("world", "character-summary-data", {npcList: [], x:0, y:0})
this.settings = game.settings.get("world", "character-summary-data")
constructor(options = {}) {
super(options)
const saved = game.settings.get("world", "character-summary-data")
this.extraList = saved.extraList ?? saved.npcList ?? []
}
/* -------------------------------------------- */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
template: "systems/fvtt-malefices/templates/dialogs/character-summary.hbs",
popOut: true,
resizable: true,
dragDrop: [{ dragSelector: ".items-list .item", dropSelector: null }],
classes: ["bol", "dialog"], width: 920, height: 'fit-content'
async _prepareContext(_options) {
const pcs = game.actors.filter(ac => ac.type === "personnage" && ac.hasPlayerOwner)
const extras = []
const validList = []
for (const actorId of this.extraList) {
const actor = game.actors.get(actorId)
if (actor) { extras.push(actor); validList.push(actorId) }
}
if (validList.length !== this.extraList.length) {
this.extraList = validList
this._persist()
}
return { pcs, extras, config: game.system.malefices.config }
}
/* -------------------------------------------- */
_onRender(_context, _options) {
const el = this.element
el.querySelectorAll('.actor-open').forEach(a => {
a.addEventListener('click', event => {
const li = event.currentTarget.closest('.item')
const actor = game.actors.get(li.dataset.actorId)
actor?.sheet.render(true)
})
})
el.querySelectorAll('.summary-roll').forEach(a => {
a.addEventListener('click', event => {
const li = event.currentTarget.closest('.item')
const actor = game.actors.get(li.dataset.actorId)
const key = event.currentTarget.dataset.key
actor?.rollAttribut(key)
})
})
el.querySelectorAll('.actor-delete').forEach(a => {
a.addEventListener('click', event => {
const li = event.currentTarget.closest('.item')
this.extraList = this.extraList.filter(id => id !== li.dataset.actorId)
this._persist()
this.render({ force: true })
})
})
const dropZone = el.querySelector('.character-summary-container')
if (dropZone) {
dropZone.addEventListener('dragover', ev => ev.preventDefault())
dropZone.addEventListener('drop', ev => { ev.stopPropagation(); this._onDrop(ev) })
}
}
/* -------------------------------------------- */
getData() {
let formData = super.getData();
_canDragDrop(_selector) { return true }
formData.pcs = game.actors.filter(ac => ac.type == "personnage" && ac.hasPlayerOwner)
formData.npcs = []
let newList = []
let toUpdate = false
for (let actorId of this.settings.npcList) {
let actor = game.actors.get(actorId)
if (actor) {
formData.npcs.push(actor)
newList.push(actorId)
} else {
toUpdate = true
}
}
formData.config = game.system.malefices.config
if (toUpdate) {
this.settings.npcList = newList
//console.log("Going to update ...", this.settings)
game.settings.set("world", "character-summary-data", this.settings)
}
return formData
/* -------------------------------------------- */
_persist() {
const saved = game.settings.get("world", "character-summary-data")
game.settings.set("world", "character-summary-data", { ...saved, extraList: this.extraList })
}
/* -------------------------------------------- */
updateNPC() {
game.settings.set("world", "character-summary-data", game.system.malefices.charSummary.settings)
game.system.malefices.charSummary.close()
setTimeout(function () { game.system.malefices.charSummary.render(true) }, 500)
_saveAndRefresh() {
this.render({ force: true })
}
/* -------------------------------------------- */
async _onDrop(event) {
//console.log("Dragged data are : ", dragData)
let data = event.dataTransfer.getData('text/plain')
let dataItem = JSON.parse(data)
let actor = fromUuidSync(dataItem.uuid)
if (actor) {
game.system.malefices.charSummary.settings.npcList.push(actor.id)
game.system.malefices.charSummary.updateNPC()
} else {
ui.notifications.warn("Pas d'acteur trouvé")
}
}
/* -------------------------------------------- */
/** @override */
async activateListeners(html) {
super.activateListeners(html);
html.find('.actor-open').click((event) => {
const li = $(event.currentTarget).parents(".item")
const actor = game.actors.get(li.data("actor-id"))
actor.sheet.render(true)
})
html.find('.summary-roll').click((event) => {
const li = $(event.currentTarget).parents(".item")
const actor = game.actors.get(li.data("actor-id"))
let type = $(event.currentTarget).data("type")
let key = $(event.currentTarget).data("key")
actor.rollAttribut(key)
})
html.find('.actor-delete').click(event => {
const li = $(event.currentTarget).parents(".item");
let actorId = li.data("actor-id")
let newList = game.system.malefices.charSummary.settings.npcList.filter(id => id != actorId)
game.system.malefices.charSummary.settings.npcList = newList
game.system.malefices.charSummary.updateNPC()
})
try {
const dataItem = JSON.parse(event.dataTransfer.getData('text/plain'))
const actor = fromUuidSync(dataItem.uuid)
if (actor && !this.extraList.includes(actor.id)) {
this.extraList.push(actor.id)
this._persist()
this.render({ force: true })
}
} catch(e) { /* not a valid drag payload */ }
}
}

View File

@@ -1,155 +1,132 @@
import { MaleficesUtility } from "./malefices-utility.js";
export class MaleficesTirageTarotDialog extends Dialog {
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api
export class MaleficesTirageTarotDialog extends HandlebarsApplicationMixin(ApplicationV2) {
/* -------------------------------------------- */
static async create(actor, tirageData) {
static DEFAULT_OPTIONS = {
id: "malefices-tirage-tarot",
classes: ["MaleficesDialog"],
window: { title: "Tirage des Tarots", resizable: true },
position: { width: 720, height: 740 },
}
let options = { classes: ["MaleficesDialog"], width: 720, height: 740, 'z-index': 99999 };
let html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-malefices/templates/dialogs/tirage-tarot-dialog.hbs', tirageData);
return new MaleficesTirageTarotDialog(actor, tirageData, html, options);
static PARTS = {
form: { template: 'systems/fvtt-malefices/templates/dialogs/tirage-tarot-dialog.hbs' }
}
/* -------------------------------------------- */
constructor(actor, tirageData, html, options, close = undefined) {
let conf = {
title: "Tirage des tarots",
content: html,
buttons: {
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Fermer/Annuler",
callback: () => { this.close() }
}
},
close: close
}
constructor(actor, tirageData, options = {}) {
super(options)
this.actor = actor
this.tirageData = tirageData
}
super(conf, options);
/* -------------------------------------------- */
static async create(actor, tirageData) {
const app = new MaleficesTirageTarotDialog(actor, tirageData)
app.render({ force: true })
return app
}
this.actor = actor;
this.tirageData = tirageData;
/* -------------------------------------------- */
async _prepareContext(_options) {
return { ...this.tirageData }
}
/* -------------------------------------------- */
_onRender(_context, _options) {
const el = this.element
el.querySelector('#playerId')?.addEventListener('change', (event) => {
if (event.currentTarget.value !== "none") {
this.tirageData.playerId = event.currentTarget.value
this.processSelectedPlayer()
}
})
el.querySelector('#actorId')?.addEventListener('change', (event) => {
if (event.currentTarget.value !== "none") {
this.attributeToActor(event.currentTarget.value)
}
})
el.querySelector('.tirage-close-btn')?.addEventListener('click', () => this.close())
}
/* -------------------------------------------- */
async sendCardRequest() {
this.tirageData.state = 'waiting-user-card'
let msg = await MaleficesUtility.createChatMessage(this.tirageData.user.name, "useronly", {
content: await renderTemplate(`systems/fvtt-malefices/templates/chat/request-tarot-card.hbs`, this.tirageData)
await MaleficesUtility.createChatMessage(this.tirageData.user.name, "useronly", {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/request-tarot-card.hbs`, this.tirageData)
})
//msg.setFlag("world", "tirage-data", this.tirageData)
console.log("MSG IS", msg)
}
/* -------------------------------------------- */
drawCard() {
let index = Math.round(Math.random() * (this.tirageData.deck.length-1))
let index = Math.round(Math.random() * (this.tirageData.deck.length - 1))
let selectedCard = this.tirageData.deck[index]
selectedCard.system.ispositif = true
if ( selectedCard.system.isdualside) { // Cas des cartes pouvant avoir 2 sens
if (selectedCard.system.isdualside) {
selectedCard.system.ispositif = (Math.random() > 0.5)
}
console.log("CARD SELECTED:", selectedCard)
// Cas spécial de la Roue de la Fortune
if ( selectedCard.name.toLowerCase().includes("fortune")) {
if (selectedCard.name.toLowerCase().includes("fortune")) {
this.tirageData.maxPlayerCard += 1
this.tirageData.maxSecretCard += 1
}
let newList = []
for(let card of this.tirageData.deck) {
if (card.name != selectedCard.name) {
newList.push(card)
}
}
this.tirageData.deck = newList
this.tirageData.deck = this.tirageData.deck.filter(c => c.name !== selectedCard.name)
return selectedCard
}
/* -------------------------------------------- */
async addCard( msgId ) {
async addCard(msgId) {
MaleficesUtility.removeChatMessageId(msgId)
let selectedCard = this.drawCard()
selectedCard.system.isgm = false
await MaleficesUtility.createChatMessage(this.tirageData.user.name, "gmroll", {
content: await renderTemplate(`systems/fvtt-malefices/templates/chat/display-tarot-card.hbs`, selectedCard)
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/display-tarot-card.hbs`, selectedCard)
})
if (this.tirageData.cards[0].name == "???") {
this.tirageData.cards.shift()
}
if (this.tirageData.cards[0].name == "???") this.tirageData.cards.shift()
this.tirageData.cards.push(selectedCard)
this.tirageData.nbCard++
if (this.tirageData.nbCard == this.tirageData.maxPlayerCard) {
for (let i=0; i<this.tirageData.maxSecretCard; i++) {
let selectedCard = this.drawCard()
selectedCard.system.isgm = true
for (let i = 0; i < this.tirageData.maxSecretCard; i++) {
let secretCard = this.drawCard()
secretCard.system.isgm = true
await MaleficesUtility.createChatMessage(this.tirageData.user.name, "blindroll", {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/display-tarot-card.hbs`, selectedCard)
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/display-tarot-card.hbs`, secretCard)
})
if (this.tirageData.secretCards[0].name == "???") {
this.tirageData.secretCards.shift()
}
this.tirageData.secretCards.push(selectedCard)
if (this.tirageData.secretCards[0].name == "???") this.tirageData.secretCards.shift()
this.tirageData.secretCards.push(secretCard)
}
this.tirageData.actors = foundry.utils.duplicate(game.actors)
this.tirageData.state = 'attribute-to-actor'
}else {
} else {
this.sendCardRequest()
}
this.refreshDialog()
this.render({ force: true })
}
/* -------------------------------------------- */
async processSelectedPlayer() {
let user = game.users.get(this.tirageData.playerId)
this.tirageData.user = user
this.tirageData.user = game.users.get(this.tirageData.playerId)
this.tirageData.players = null
console.log("Going to work with ", user.name)
game.system.malefices.currentTirage = this
this.refreshDialog()
this.render({ force: true })
this.sendCardRequest()
}
/* -------------------------------------------- */
attributeToActor(actorId) {
let actor = game.actors.get(actorId)
const actor = game.actors.get(actorId)
if (actor) {
actor.createEmbeddedDocuments('Item', this.tirageData.cards)
actor.createEmbeddedDocuments('Item', this.tirageData.secretCards)
ui.notifications.info("Les cartes ont été attribuées à " + actor.name)
}
}
/* -------------------------------------------- */
async refreshDialog() {
const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-malefices/templates/dialogs/tirage-tarot-dialog.hbs", this.tirageData)
this.data.content = content
this.render(true)
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
let dialog = this;
function onLoad() {
}
$(function () { onLoad(); });
html.find('#playerId').change((event) => {
if ( event.currentTarget.value != "none") {
dialog.tirageData.playerId = event.currentTarget.value
dialog.processSelectedPlayer()
}
})
html.find('#actorId').change((event) => {
if ( event.currentTarget.value != "none") {
let actorId = event.currentTarget.value
dialog.attributeToActor(actorId)
}
})
}
}

View File

@@ -56,7 +56,7 @@ export class MaleficesUtility {
name: "character-summary-data",
scope: "world",
config: false,
default: { npcList: [], x: 200, y: 200 },
default: { extraList: [], x: 200, y: 200 },
type: Object
})
@@ -135,8 +135,13 @@ export class MaleficesUtility {
const templatePaths = [
'systems/fvtt-malefices/templates/actors/editor-notes-gm.hbs',
'systems/fvtt-malefices/templates/items/partial-item-header.hbs',
'systems/fvtt-malefices/templates/items/partial-item-nav.hbs',
'systems/fvtt-malefices/templates/items/partial-item-description.hbs'
'systems/fvtt-malefices/templates/items/partial-item-description.hbs',
'systems/fvtt-malefices/templates/post-item.hbs',
'systems/fvtt-malefices/templates/actors/npc-sheet.hbs',
'systems/fvtt-malefices/templates/chat/welcome-message.hbs',
'systems/fvtt-malefices/templates/dialogs/character-summary.hbs'
]
return foundry.applications.handlebars.loadTemplates(templatePaths);
}
@@ -385,7 +390,7 @@ export class MaleficesUtility {
rollData.total = selectedCard.value
rollData.selectedCard = selectedCard
await MaleficesUtility.createChatMessage(actor.name, "gmroll", {
content: await renderTemplate(`systems/fvtt-malefices/templates/chat/display-tarot-card.hbs`, selectedCard)
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/display-tarot-card.hbs`, selectedCard)
})
this.computeResults(rollData)
@@ -399,7 +404,7 @@ export class MaleficesUtility {
}
await MaleficesUtility.createChatMessage(actor.name, "gmroll", {
content: await renderTemplate(`systems/fvtt-malefices/templates/chat/chat-confrontation-result.hbs`, rollData)
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/chat-confrontation-result.hbs`, rollData)
})
this.processSpecialCard(actor, rollData)
}
@@ -554,30 +559,16 @@ export class MaleficesUtility {
/* -------------------------------------------- */
static async confirmDelete(actorSheet, li) {
let itemId = li.data("item-id");
let msgTxt = "<p>Are you sure to remove this Item ?";
let buttons = {
delete: {
icon: '<i class="fas fa-check"></i>',
label: "Yes, remove it",
callback: () => {
actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]);
li.slideUp(200, () => actorSheet.render(false));
}
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Cancel"
}
}
msgTxt += "</p>";
let d = new Dialog({
title: "Confirm removal",
content: msgTxt,
buttons: buttons,
default: "cancel"
const itemId = li.dataset.itemId;
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: { title: "Confirmer la suppression" },
content: "<p>Supprimer cet objet ?</p>",
yes: { label: "Supprimer", icon: "fas fa-trash" },
no: { label: "Annuler", icon: "fas fa-times" },
});
d.render(true);
if (confirmed) {
actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]);
}
}
}

View File

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

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

@@ -0,0 +1,19 @@
/**
* Data model pour les armes
*/
export default class ArmeDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
armetype: new fields.StringField({ initial: "" }),
porteecourte: new fields.StringField({ initial: "" }),
porteemoyenne: new fields.StringField({ initial: "" }),
dommagenormale: new fields.NumberField({ initial: 0, integer: true }),
dommagepart: new fields.NumberField({ initial: 0, integer: true }),
dommagecritique: new fields.NumberField({ initial: 0, integer: true }),
dommagecritiqueKO: new fields.BooleanField({ initial: false }),
dommagecritiquemort: new fields.BooleanField({ initial: false }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,11 @@
/**
* Data model pour les éléments biographiques
*/
export default class ElementbioDataModel 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 équipements
*/
export default class EquipementDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

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

@@ -0,0 +1,15 @@
/**
* Index des DataModels pour Maléfices
*/
// 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 ArmeDataModel } from './arme.mjs';
export { default as EquipementDataModel } from './equipement.mjs';
export { default as ArchetypeDataModel } from './archetype.mjs';
export { default as TarotDataModel } from './tarot.mjs';
export { default as SortilegeDataModel } from './sortilege.mjs';
export { default as ElementbioDataModel } from './elementbio.mjs';

View File

@@ -0,0 +1,95 @@
/**
* Data model pour les personnages joueurs (type "personnage")
*/
export default class PersonnageDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
// Template biodata
biodata: new fields.SchemaField({
age: new fields.NumberField({ initial: 0, integer: true }),
size: new fields.StringField({ initial: "" }),
lieunaissance: new fields.StringField({ initial: "" }),
nationalite: new fields.StringField({ initial: "" }),
profession: new fields.StringField({ initial: "" }),
residence: new fields.StringField({ initial: "" }),
milieusocial: new fields.StringField({ initial: "" }),
poids: new fields.StringField({ initial: "" }),
cheveux: new fields.StringField({ initial: "" }),
sexe: new fields.StringField({ initial: "" }),
yeux: new fields.StringField({ initial: "" }),
enfance: new fields.StringField({ initial: "" }),
adulte: new fields.StringField({ initial: "" }),
loisirs: new fields.StringField({ initial: "" }),
singularite: new fields.StringField({ initial: "" }),
politique: new fields.StringField({ initial: "" }),
religion: new fields.StringField({ initial: "" }),
fantastique: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
notes: new fields.HTMLField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" })
}),
// Template core
subactors: new fields.ArrayField(new fields.StringField(), { initial: [] }),
lamesdestin: new fields.ArrayField(new fields.StringField(), { initial: [] }),
pointdestin: new fields.NumberField({ initial: 1, integer: true }),
fluide: new fields.NumberField({ initial: 5, integer: true }),
mpmb: new fields.NumberField({ initial: 0, integer: true }),
mpmn: new fields.NumberField({ initial: 0, integer: true }),
equipementlibre: new fields.HTMLField({ initial: "" }),
attributs: new fields.SchemaField({
constitution: new fields.SchemaField({
label: new fields.StringField({ initial: "Constitution" }),
abbrev: new fields.StringField({ initial: "constitution" }),
value: new fields.NumberField({ initial: 0, integer: true }),
hasmax: new fields.BooleanField({ initial: true }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
physique: new fields.SchemaField({
label: new fields.StringField({ initial: "Aptitudes Physiques" }),
abbrev: new fields.StringField({ initial: "physique" }),
value: new fields.NumberField({ initial: 0, integer: true }),
hasmax: new fields.BooleanField({ initial: false }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
culturegenerale: new fields.SchemaField({
label: new fields.StringField({ initial: "Culture Générale" }),
abbrev: new fields.StringField({ initial: "culturegenerale" }),
value: new fields.NumberField({ initial: 0, integer: true }),
hasmax: new fields.BooleanField({ initial: false }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
habilite: new fields.SchemaField({
label: new fields.StringField({ initial: "Habilité" }),
abbrev: new fields.StringField({ initial: "habilite" }),
value: new fields.NumberField({ initial: 0, integer: true }),
hasmax: new fields.BooleanField({ initial: false }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
perception: new fields.SchemaField({
label: new fields.StringField({ initial: "Perception" }),
abbrev: new fields.StringField({ initial: "perception" }),
value: new fields.NumberField({ initial: 0, integer: true }),
hasmax: new fields.BooleanField({ initial: false }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
spiritualite: new fields.SchemaField({
label: new fields.StringField({ initial: "Spiritualite" }),
abbrev: new fields.StringField({ initial: "spiritualite" }),
value: new fields.NumberField({ initial: 0, integer: true }),
hasmax: new fields.BooleanField({ initial: false }),
iscard: new fields.BooleanField({ initial: true }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
rationnalite: new fields.SchemaField({
label: new fields.StringField({ initial: "Rationnalite" }),
abbrev: new fields.StringField({ initial: "rationnalite" }),
value: new fields.NumberField({ initial: 0, integer: true }),
hasmax: new fields.BooleanField({ initial: false }),
iscard: new fields.BooleanField({ initial: true }),
max: new fields.NumberField({ initial: 0, integer: true })
})
})
};
}
}

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

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

View File

@@ -0,0 +1,12 @@
/**
* Data model pour les sortilèges
*/
export default class SortilegeDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
seuil: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

17
modules/models/tarot.mjs Normal file
View File

@@ -0,0 +1,17 @@
/**
* Data model pour les lames de tarot
*/
export default class TarotDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
tarottype: new fields.StringField({ initial: "" }),
numericvalueup: new fields.NumberField({ initial: 0, integer: true }),
numericvaluedown: new fields.NumberField({ initial: 0, integer: true }),
isdualside: new fields.BooleanField({ initial: false }),
ispositif: new fields.BooleanField({ initial: true }),
isgm: new fields.BooleanField({ initial: false }),
description: new fields.HTMLField({ initial: "" })
};
}
}