Migration datamodels !

This commit is contained in:
2026-01-11 22:40:06 +01:00
parent 8d3fdbd009
commit fc7c51e369
238 changed files with 16947 additions and 2539 deletions

View File

@@ -0,0 +1,27 @@
/**
* Index des applications AppV2 pour Yggdrasill
* Ce fichier centralise tous les exports des applications
*/
// Applications de feuilles d'acteurs
export { default as YggdrasillPersonnageSheet } from './yggdrasill-personnage-sheet.mjs';
export { default as YggdrasillFigurantSheet } from './yggdrasill-figurant-sheet.mjs';
// Applications de feuilles d'items
export { default as YggdrasillCompetenceSheet } from './yggdrasill-competence-sheet.mjs';
export { default as YggdrasillDonSheet } from './yggdrasill-don-sheet.mjs';
export { default as YggdrasillFaiblesseSheet } from './yggdrasill-faiblesse-sheet.mjs';
export { default as YggdrasillBlessureSheet } from './yggdrasill-blessure-sheet.mjs';
export { default as YggdrasillMaladieSheet } from './yggdrasill-maladie-sheet.mjs';
export { default as YggdrasillPoisonSheet } from './yggdrasill-poison-sheet.mjs';
export { default as YggdrasillProuesseSheet } from './yggdrasill-prouesse-sheet.mjs';
export { default as YggdrasillSortsejdrSheet } from './yggdrasill-sortsejdr-sheet.mjs';
export { default as YggdrasillSortgaldrSheet } from './yggdrasill-sortgaldr-sheet.mjs';
export { default as YggdrasillRuneSheet } from './yggdrasill-rune-sheet.mjs';
export { default as YggdrasillArmeccSheet } from './yggdrasill-armecc-sheet.mjs';
export { default as YggdrasillArmedistSheet } from './yggdrasill-armedist-sheet.mjs';
export { default as YggdrasillArmureSheet } from './yggdrasill-armure-sheet.mjs';
export { default as YggdrasillBouclierSheet } from './yggdrasill-bouclier-sheet.mjs';
export { default as YggdrasillEquipementSheet } from './yggdrasill-equipement-sheet.mjs';
export { default as YggdrasillMonnaieSheet } from './yggdrasill-monnaie-sheet.mjs';
export { default as YggdrasillEffetmagiqueSheet } from './yggdrasill-effetmagique-sheet.mjs';

View File

@@ -0,0 +1,449 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
import { YggdrasillUtility } from "../../yggdrasill-utility.js"
export default class YggdrasillActorSheet 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-yggdrasill", "sheet", "actor"],
position: {
width: 750,
height: 720,
},
form: {
submitOnChange: true,
closeOnSubmit: false,
},
window: {
resizable: true,
},
tabs: [
{
navSelector: 'nav[data-group="primary"]',
contentSelector: "section.sheet-body",
initial: "principal",
},
],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
actions: {
editImage: YggdrasillActorSheet.#onEditImage,
toggleSheet: YggdrasillActorSheet.#onToggleSheet,
editItem: YggdrasillActorSheet.#onEditItem,
deleteItem: YggdrasillActorSheet.#onDeleteItem,
createItem: YggdrasillActorSheet.#onCreateItem,
equipItem: YggdrasillActorSheet.#onEquipItem,
rollCarac: YggdrasillActorSheet.#onRollCarac,
rollCompetence: YggdrasillActorSheet.#onRollCompetence,
rollArme: YggdrasillActorSheet.#onRollArme,
rollSort: YggdrasillActorSheet.#onRollSort,
rollProuesse: YggdrasillActorSheet.#onRollProuesse,
rollDamage: YggdrasillActorSheet.#onRollDamage,
lockUnlock: YggdrasillActorSheet.#onLockUnlock,
incrementPV: YggdrasillActorSheet.#onIncrementPV,
decrementPV: YggdrasillActorSheet.#onDecrementPV,
updateCompetence: YggdrasillActorSheet.#onUpdateCompetence,
},
}
/**
* 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: "principal",
}
/** @override */
async _prepareContext() {
const context = {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
actor: this.document,
system: this.document.system,
source: this.document.toObject(),
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isEditable: this.isEditable,
isGM: game.user.isGM,
config: game.system.yggdrasill.config,
editScore: this.isEditMode,
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
// Activate tab navigation manually
const nav = this.element.querySelector('nav.tabs[data-group], nav.sheet-tabs[data-group]')
if (nav) {
const group = nav.dataset.group
// Activate the current tab
const activeTab = this.tabGroups[group] || "principal"
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)
})
}
// Add change listener for competence niveau selects
this.element.querySelectorAll('select.competence-niveau').forEach(select => {
select.addEventListener('change', async (event) => {
const itemId = event.target.dataset.itemId
const item = this.document.items.get(itemId)
if (item) {
const newNiveau = parseInt(event.target.value)
await item.update({ "system.niveau": newNiveau })
}
})
})
}
/**
* Creates drag-and-drop handlers for this application
* @returns {DragDrop[]} An array of DragDrop handlers
* @private
*/
#createDragDropHandlers() {
return []
}
/**
* Handle changing a Document's image
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onEditImage(event, target) {
const attr = target.dataset.edit
const current = foundry.utils.getProperty(this.document, attr)
const fp = new FilePicker({
current,
type: "image",
callback: (path) => {
this.document.update({ [attr]: path })
},
})
return fp.browse()
}
/**
* Toggle sheet mode between Edit and Play
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onToggleSheet(event, target) {
this._sheetMode = this.isEditMode
? this.constructor.SHEET_MODES.PLAY
: this.constructor.SHEET_MODES.EDIT
this.render()
}
/**
* Handle item editing
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onEditItem(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
const item = this.document.items.get(itemId)
if (item) item.sheet.render(true)
}
/**
* Handle item deletion
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onDeleteItem(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
const item = this.document.items.get(itemId)
if (item) {
await item.delete()
}
}
/**
* Handle item creation
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onCreateItem(event, target) {
const itemType = target.dataset.itemType
const itemData = {
name: `Nouveau ${itemType}`,
type: itemType,
}
await this.document.createEmbeddedDocuments("Item", [itemData])
}
/**
* Handle item equip toggle
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onEquipItem(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
const item = this.document.items.get(itemId)
if (item && item.system.equipe !== undefined) {
await item.update({ "system.equipe": !item.system.equipe })
}
}
/**
* Handle characteristic roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollCarac(event, target) {
const caracCateg = target.dataset.caracCateg
const caracKey = target.dataset.caracKey
this.document.rollCarac(caracCateg, caracKey)
}
/**
* Handle competence roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollCompetence(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
this.document.rollCompetence(itemId)
}
/**
* Handle weapon roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollArme(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
this.document.rollArme(itemId)
}
/**
* Handle lock/unlock toggle
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onLockUnlock(event, target) {
this._sheetMode = this.isEditMode
? this.constructor.SHEET_MODES.PLAY
: this.constructor.SHEET_MODES.EDIT
this.render()
}
/**
* Handle incrementing PV
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onIncrementPV(event, target) {
const currentPV = this.document.system.caracsecondaire.pv.value || 0
const maxPV = this.document.system.caracsecondaire.pv.max || 0
const newPV = Math.min(currentPV + 1, maxPV)
await this.document.update({ "system.caracsecondaire.pv.value": newPV })
}
/**
* Handle decrementing PV
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onDecrementPV(event, target) {
const currentPV = this.document.system.caracsecondaire.pv.value || 0
const newPV = Math.max(currentPV - 1, 0)
await this.document.update({ "system.caracsecondaire.pv.value": newPV })
}
/**
* Handle competence niveau update
* @this {YggdrasillActorSheet}
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The select element
*/
static async #onUpdateCompetence(event, target) {
const itemId = target.dataset.itemId
const item = this.document.items.get(itemId)
if (!item) return
const newNiveau = parseInt(target.value)
await item.update({ "system.niveau": newNiveau })
}
/**
* Handle sort roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollSort(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
const sortType = target.dataset.sortType || "sejdr"
this.document.rollSort(itemId, sortType)
}
/**
* Handle prouesse roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollProuesse(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
this.document.rollProuesse(itemId)
}
/**
* Handle damage roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollDamage(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
const weapon = this.document.items.get(itemId)
if (weapon) {
this.document.rollDamage(weapon, 'damage')
}
}
/**
* Handle beginning of a drag operation
* @param {DragEvent} event - The originating drag event
* @protected
*/
_onDragStart(event) {
const li = event.currentTarget
const itemId = li.dataset.itemId
const item = this.document.items.get(itemId)
if (!item) return
const dragData = item.toDragData()
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
}
/**
* Handle a drop event
* @param {DragEvent} event - The originating drop event
* @protected
*/
async _onDrop(event) {
const data = TextEditor.getDragEventData(event)
const actor = this.document
// Handle different data types
switch (data.type) {
case "Item":
return this._onDropItem(event, data)
case "ActiveEffect":
return this._onDropActiveEffect(event, data)
}
}
/**
* Handle dropping an Item on the sheet
* @param {DragEvent} event - The originating drop event
* @param {object} data - The dropped data
* @protected
*/
async _onDropItem(event, data) {
if (!this.isEditable) return false
const item = await Item.implementation.fromDropData(data)
const itemData = item.toObject()
// Handle item from same actor
if (this.document.uuid === item.parent?.uuid) return this._onSortItem(event, itemData)
// Create the item
return this._onDropItemCreate(itemData)
}
/**
* Handle creating an owned item from drop data
* @param {object} itemData - The item data to create
* @protected
*/
async _onDropItemCreate(itemData) {
itemData = itemData instanceof Array ? itemData : [itemData]
return this.document.createEmbeddedDocuments("Item", itemData)
}
/**
* Handle sorting items
* @param {DragEvent} event - The originating drop event
* @param {object} itemData - The item data being sorted
* @protected
*/
_onSortItem(event, itemData) {
// Implement sorting logic if needed
return Promise.resolve()
}
/**
* Handle dropping an ActiveEffect on the sheet
* @param {DragEvent} event - The originating drop event
* @param {object} data - The dropped data
* @protected
*/
async _onDropActiveEffect(event, data) {
const effect = await ActiveEffect.implementation.fromDropData(data)
if (!this.isEditable || !effect) return false
if (this.document.uuid === effect.parent?.uuid) return false
return ActiveEffect.create(effect.toObject(), { parent: this.document })
}
}

View File

@@ -0,0 +1,178 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class YggdrasillItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-yggdrasill", "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: YggdrasillItemSheet.#onEditImage,
postItem: YggdrasillItemSheet.#onPostItem,
},
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = { primary: "description" }
/** @override */
async _prepareContext() {
// Import config
const YGGDRASILL_CONFIG = game.system.yggdrasill?.config || game.system.config || {};
// Create options for niveau 0-5
const optionsNiveaux4 = {};
for (let i = 0; i <= 5; i++) {
optionsNiveaux4[`${i}`] = `${i}`;
}
// Create options for base (0-20)
const optionsBase = {};
for (let i = 0; i <= 20; i++) {
optionsBase[`${i}`] = `${i}`;
}
const context = {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
data: this.document.system,
source: this.document.toObject(),
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description || "", { async: true }),
enrichedEffet: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.effet || "", { async: true }),
isEditMode: true,
isEditable: this.isEditable,
editable: this.isEditable,
isGM: game.user.isGM,
config: YGGDRASILL_CONFIG,
optionsBase: optionsBase,
optionsNiveaux4: optionsNiveaux4,
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
// 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
const body = this.element.querySelector('section.sheet-body')
if (body) {
body.querySelectorAll('[data-tab]').forEach(content => {
const tab = content.dataset.tab
content.style.display = tab === activeTab ? 'block' : 'none'
})
}
}
}
/**
* Creates drag-and-drop handlers for this application
* @returns {DragDrop[]} An array of DragDrop handlers
* @private
*/
#createDragDropHandlers() {
return []
}
/**
* Handle changing a Document's image
* @this {YggdrasillItemSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onEditImage(event, target) {
const attr = target.dataset.edit || "img"
const current = foundry.utils.getProperty(this.document, attr)
const fp = new FilePicker({
current,
type: "image",
callback: (path) => {
this.document.update({ [attr]: path })
},
})
return fp.browse()
}
/**
* Handle posting item to chat
* @this {YggdrasillItemSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onPostItem(event, target) {
const item = this.document
const chatData = {
user: game.user.id,
speaker: ChatMessage.getSpeaker(),
content: await foundry.applications.handlebars.renderTemplate("systems/fvtt-yggdrasill/templates/chat-item-card.hbs", {
item: item,
system: item.system,
description: await TextEditor.enrichHTML(item.system.description || "", { async: true })
})
}
ChatMessage.create(chatData)
}
/**
* Handle beginning of a drag operation
* @param {DragEvent} event - The originating drag event
* @protected
*/
_onDragStart(event) {
const dragData = this.document.toDragData()
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
}
/**
* Handle a drop event
* @param {DragEvent} event - The originating drop event
* @protected
*/
async _onDrop(event) {
// Items generally don't handle drops, but method exists for extensibility
return false
}
}

View File

@@ -0,0 +1,44 @@
#!/bin/bash
# Liste des types d'items restants à créer
items=(
"poison:Poison"
"prouesse:Prouesse"
"sortsejdr:Sortsejdr"
"sortgaldr:Sortgaldr"
"rune:Rune"
"armecc:Armecc"
"armedist:Armedist"
"armure:Armure"
"bouclier:Bouclier"
"equipement:Equipement"
"monnaie:Monnaie"
"effetmagique:Effetmagique"
)
for item in "${items[@]}"; do
type="${item%%:*}"
class="${item##*:}"
cat > "yggdrasill-${type}-sheet.mjs" << EOF
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class Yggdrasill${class}Sheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "${type}"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-${type}-sheet.hbs",
},
}
}
EOF
done
echo "Created all item sheets"
ls -1 yggdrasill-*-sheet.mjs | wc -l

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillArmeccSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "armecc"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-armecc-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillArmedistSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "armedist"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-armedist-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillArmureSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "armure"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-armure-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillBlessureSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "blessure"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-blessure-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillBouclierSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "bouclier"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-bouclier-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,21 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillCompetenceSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "competence"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-competence-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "details",
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillDonSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "don"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-don-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillEffetmagiqueSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "effetmagique"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-effetmagique-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillEquipementSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "equipement"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-equipement-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillFaiblesseSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "faiblesse"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-faiblesse-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,40 @@
import YggdrasillActorSheet from "./base-actor-sheet.mjs"
export default class YggdrasillFigurantSheet extends YggdrasillActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "figurant"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "Feuille de Figurant",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-yggdrasill/templates/actor-figurant-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "principal",
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
// System fields for formInput helpers
context.systemFields = actor.system.schema.fields
// Enrich HTML 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 })
return context
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillMaladieSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "maladie"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-maladie-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillMonnaieSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "monnaie"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-monnaie-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,99 @@
import YggdrasillActorSheet from "./base-actor-sheet.mjs"
export default class YggdrasillPersonnageSheet extends YggdrasillActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "personnage"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "Feuille de Personnage",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-yggdrasill/templates/actor-personnage-sheet-new.hbs",
},
}
/** @override */
tabGroups = {
primary: "principal",
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
// Prepare items by type
context.competencesGenerales = actor.items.filter(i => i.type === 'competence' && i.system.categorie === 'generale')
context.competencesMartiales = actor.items.filter(i => i.type === 'competence' && i.system.categorie === 'martiale')
context.competencesMagiques = actor.items.filter(i => i.type === 'competence' && i.system.categorie === 'magique')
context.dons = actor.items.filter(i => i.type === 'don')
context.faiblesses = actor.items.filter(i => i.type === 'faiblesse')
context.blessures = actor.items.filter(i => i.type === 'blessure')
context.maladies = actor.items.filter(i => i.type === 'maladie')
context.poisons = actor.items.filter(i => i.type === 'poison')
context.prouesses = actor.items.filter(i => i.type === 'prouesse')
context.sortsSejdr = actor.items.filter(i => i.type === 'sortsejdr')
context.sortsGaldr = actor.items.filter(i => i.type === 'sortgaldr')
context.runes = actor.items.filter(i => i.type === 'rune')
context.armesCC = actor.items.filter(i => i.type === 'armecc')
context.armesDist = actor.items.filter(i => i.type === 'armedist')
context.armures = actor.items.filter(i => i.type === 'armure')
context.boucliers = actor.items.filter(i => i.type === 'bouclier')
context.equipements = actor.items.filter(i => i.type === 'equipement')
context.monnaies = actor.items.filter(i => i.type === 'monnaie')
context.effetsMagiques = actor.items.filter(i => i.type === 'effetmagique')
// Prepare equipped items
context.armesEquipees = actor.items.filter(i => (i.type === 'armecc' || i.type === 'armedist') && i.system.equipe)
context.armureEquipee = actor.items.find(i => i.type === 'armure' && i.system.equipe)
context.bouclierEquipe = actor.items.find(i => i.type === 'bouclier' && i.system.equipe)
// Calculate total protection from equipped armors
context.protectionTotal = context.armures
.filter(a => a.system.equipe)
.reduce((sum, a) => sum + (parseInt(a.system.protection) || 0), 0)
// Calculate shield defense bonus from equipped shield
context.dpBouclier = context.boucliers
.filter(b => b.system.equipe)
.reduce((sum, b) => sum + (parseInt(b.system.defensebonus) || 0), 0)
// Calculate total encumbrance from equipements
context.encTotal = context.equipements
.reduce((sum, e) => sum + ((parseInt(e.system.quantite) || 0) * (parseInt(e.system.enc) || 0)), 0)
// Options for selects
context.optionsCarac = {}
for (let i = 0; i <= 10; i++) {
context.optionsCarac[i] = i.toString()
}
context.optionsBase = {}
for (let i = 0; i <= 5; i++) {
context.optionsBase[i] = i.toString()
}
// Options for bonus/malus (-10 to +10)
context.optionsDMDP = []
for (let i = -10; i <= 10; i++) {
context.optionsDMDP.push({ value: i, text: i >= 0 ? `+${i}` : i.toString() })
}
// System fields for formInput helpers
context.systemFields = actor.system.schema.fields
// Enrich HTML 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.enrichedGMNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.gmnotes || "", { async: true })
context.enrichedTirageRunes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.tiragerunes || "", { async: true })
return context
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillPoisonSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "poison"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-poison-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillProuesseSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "prouesse"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-prouesse-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillRuneSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "rune"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-rune-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillSortgaldrSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "sortgaldr"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-sortgaldr-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillSortsejdrSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "sortsejdr"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-sortsejdr-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,334 @@
import { YggdrasillUtility } from "../yggdrasill-utility.js"
const dureeGaldrSD = { "1d5a": 3, "1d10t": 6, "1d10m": 9, "1d10h": 12, "1d5j": 15}
const ciblesGaldrSD = { "1": 3, "2_4": 6, "5_9": 9, "10_49": 12, "50plus": 15}
const zonesciblesGaldrSD = { "INS10cm3": 3, "INS50cm3": 6, "INS1m3": 9, "INS5m3": 12, "INS10m3": 15}
/**
* Dialogue de jet de dé pour Yggdrasill - Version DialogV2
*/
export class YggdrasillRollDialog {
/**
* Create and display the roll dialog
* @param {YggdrasillActor} actor - The actor making the roll
* @param {Object} rollData - Data for the roll
* @returns {Promise}
*/
static async create(actor, rollData) {
// Initialiser attackData pour les armes si nécessaire
if ((rollData.mode === "armecc" || rollData.mode === "armetir" || rollData.mode === "armedist") && rollData.attackDef) {
// Les données sont déjà calculées dans attackDef par getAttaqueData/getTirData
// On les copie simplement dans attackData pour le template
rollData.attackData = { ...rollData.attackDef }
}
// Calculer srTotal pour les sorts
if (rollData.mode === "galdr") {
this._updateGaldrSD(rollData)
rollData.srTotal = 14 + (rollData.sdGaldr || 0)
} else if (rollData.mode === "sejdr") {
rollData.srTotal = 14 + (rollData.bonusdefense || 0)
} else if (rollData.mode === "rune") {
this._updateRuneData(rollData, actor)
}
// Préparer le contexte pour le template
const context = {
...rollData,
img: actor.img,
name: actor.name,
config: game.system.yggdrasill.config,
}
// Titre selon le mode
let title = "Test"
if (rollData.mode === "competence") title = "Test de Compétence"
else if (rollData.mode === "carac") title = "Test de Caractéristique"
else if (rollData.mode === "attribut") title = "Test d'Attribut"
else if (rollData.mode === "armecc") title = "Attaque au Corps à Corps"
else if (rollData.mode === "armetir" || rollData.mode === "armedist") title = "Attaque à Distance"
else if (rollData.mode === "sejdr") title = "Sort Sejdr"
else if (rollData.mode === "galdr") title = "Sort Galdr"
else if (rollData.mode === "rune") title = "Rune"
// Rendre le template en HTML
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-yggdrasill/templates/roll-dialog-generic-new.hbs",
context
)
// Utiliser DialogV2.wait avec le HTML rendu
return foundry.applications.api.DialogV2.wait({
window: {
title: title,
icon: "fa-solid fa-dice-d20"
},
classes: ["yggdrasill-roll-dialog"],
position: { width: 600 },
modal: false,
content,
buttons: [
{
action: "roll",
label: "Lancer le Test",
icon: "fa-solid fa-dice",
default: true,
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements, actor)
this._performRoll(rollData)
}
},
],
actions: {},
render: (event, dialog) => {
// Pour Galdr: recalculer srTotal quand durée, cibles ou zone changent
if (rollData.mode === "galdr") {
$("#dureeGaldr, #nbCibles, #zoneGaldr").on("change", () => {
rollData.dureeGaldr = $("#dureeGaldr").val()
rollData.nbCibles = $("#nbCibles").val()
rollData.zoneGaldr = $("#zoneGaldr").val()
this._updateGaldrSD(rollData)
rollData.srTotal = 14 + (rollData.sdGaldr || 0)
$("#srTotal").text(rollData.srTotal)
})
}
// Pour Sejdr: recalculer srTotal quand DM change
if (rollData.mode === "sejdr") {
$("#bonusdefense").on("change", () => {
rollData.bonusdefense = parseInt($("#bonusdefense").val()) || 0
rollData.srTotal = 14 + rollData.bonusdefense
$("#srTotal").text(rollData.srTotal)
})
}
// Pour Rune: recalculer srTotal quand support ou puissance changent
if (rollData.mode === "rune") {
$("#supportRune, #puissanceRune").on("change", () => {
rollData.supportRune = $("#supportRune").val()
rollData.puissanceRune = parseInt($("#puissanceRune").val()) || 1
this._updateRuneData(rollData, actor)
$("#runeDuree").text(rollData.runeDuree)
$("#runeDureeVie").text(rollData.runeDureeVie)
$("#srTotal").text(rollData.srTotal)
})
}
},
rejectClose: false,
})
}
/**
* Perform the roll based on mode
* @param {Object} rollData - The roll data
* @private
*/
static _performRoll(rollData) {
if (rollData.mode === "attribut") {
YggdrasillUtility.rollAttribute(rollData)
} else {
YggdrasillUtility.rollYggdrasill(rollData)
}
}
/**
* Mettre à jour rollData avec les valeurs du formulaire
* @param {Object} rollData - L'objet rollData à mettre à jour
* @param {HTMLFormControlsCollection} formElements - Les éléments du formulaire
* @param {YggdrasillActor} actor - L'acteur pour récupérer les données
* @private
*/
static _updateRollDataFromForm(rollData, formElements, actor) {
// Caractéristique (pour compétences)
if (formElements.caracName) {
const caracKey = formElements.caracName.value
rollData.caracName = caracKey
rollData.selectedCarac = this._findCaracByKey(actor, caracKey)
}
// Type d'attaque (pour armes)
if (formElements.typeAttack) {
rollData.attackDef = rollData.attackDef || {}
rollData.attackDef.typeAttack = formElements.typeAttack.value
this._updateAttackData(rollData, actor)
}
// Bonus/Malus
if (formElements.bonusMalus) {
rollData.bonusMalus = parseInt(formElements.bonusMalus.value) || 0
}
// Furor
if (formElements.furorUsage) {
rollData.furorUsage = parseInt(formElements.furorUsage.value) || 0
}
// Seuil de Réussite
if (formElements.sr) {
rollData.sr = parseInt(formElements.sr.value) || 9
}
// Galdr options
if (formElements.dureeGaldr) {
rollData.dureeGaldr = formElements.dureeGaldr.value
}
if (formElements.nbCibles) {
rollData.nbCibles = formElements.nbCibles.value
}
if (formElements.zoneGaldr) {
rollData.zoneGaldr = formElements.zoneGaldr.value
}
if (rollData.mode === "galdr") {
this._updateGaldrSD(rollData)
rollData.srTotal = 14 + (rollData.sdGaldr || 0)
}
// Sejdr options
if (formElements.bonusdefense) {
rollData.bonusdefense = parseInt(formElements.bonusdefense.value) || 0
}
if (rollData.mode === "sejdr") {
rollData.srTotal = 14 + (rollData.bonusdefense || 0)
}
// Rune options
if (formElements.runeDuree) {
rollData.runeDuree = formElements.runeDuree.value
}
}
/**
* Find characteristic by key in actor
* @param {YggdrasillActor} actor
* @param {String} caracKey
* @returns {Object}
* @private
*/
static _findCaracByKey(actor, caracKey) {
for (let [categKey, categ] of Object.entries(actor.system.carac)) {
for (let [key, carac] of Object.entries(categ.carac)) {
if (key === caracKey) {
return { ...carac, categName: categKey, caracName: key }
}
}
}
return null
}
/**
* Update attack data based on attack type
* @param {Object} rollData
* @param {YggdrasillActor} actor
* @private
*/
static _updateAttackData(rollData, actor) {
const config = game.system.yggdrasill.config
const attackType = rollData.attackDef.typeAttack
const attackMode = config.attackMode?.[attackType]
if (attackMode) {
rollData.attackData = rollData.attackData || {}
rollData.attackData.categName = attackMode.categName
rollData.attackData.caracName = attackMode.caracName
rollData.attackData.malus = this._computeValue(attackMode.malus, actor)
rollData.attackData.bonusdegats = this._computeValue(attackMode.bonusdegats, actor)
rollData.attackData.protection = this._computeValue(attackMode.protection, actor)
rollData.attackData.label = attackMode.label
rollData.attackData.description = attackMode.description
}
}
/**
* Compute value from string formula like "puissance;3"
* @param {String|Number} value
* @param {YggdrasillActor} actor
* @returns {Number}
* @private
*/
static _computeValue(value, actor) {
if (typeof value === 'number') return value
if (!value || value === "0") return 0
const parts = String(value).split(';')
if (parts.length === 2) {
const caracName = parts[0]
const multiplier = parseInt(parts[1]) || 1
const caracValue = this._getCaracValue(actor, caracName)
return caracValue * multiplier
}
return 0
}
/**
* Get characteristic value by name
* @param {YggdrasillActor} actor
* @param {String} caracName
* @returns {Number}
* @private
*/
static _getCaracValue(actor, caracName) {
for (let categ of Object.values(actor.system.carac)) {
if (categ.carac && categ.carac[caracName]) {
return categ.carac[caracName].value || 0
}
}
return 0
}
/**
* Update rune data based on support and power
* @param {Object} rollData
* @param {YggdrasillActor} actor
* @private
*/
static _updateRuneData(rollData, actor) {
let support = 0
rollData.dureeRune = 6 - (rollData.agiliteCarac?.value || 0)
if (rollData.supportRune === "peau") {
support = 3
rollData.echelleDuree = "Actions"
rollData.echelleDureeVie = "Heures"
} else if (rollData.supportRune === "tissu") {
support = 6
rollData.echelleDuree = "Tours"
rollData.echelleDureeVie = "Jours"
} else if (rollData.supportRune === "cuir") {
support = 9
rollData.echelleDuree = "Minutes"
rollData.echelleDureeVie = "Semaines"
} else if (rollData.supportRune === "bois") {
support = 12
rollData.echelleDuree = "Heures"
rollData.echelleDureeVie = "Mois"
} else if (rollData.supportRune === "pierremetal") {
support = 15
rollData.echelleDuree = "Jours"
rollData.echelleDureeVie = "Années"
}
rollData.runeDuree = `${rollData.dureeRune} ${rollData.echelleDuree}`
rollData.runeDureeVie = `${rollData.competence?.system.niveau || 1} ${rollData.echelleDureeVie}`
rollData.srTotal = (rollData.puissanceRune || 1) + (Number(rollData.sort?.system.niveau || 0) * 3) + support
}
/**
* Update Galdr SD based on duration and targets
* @param {Object} rollData
* @private
*/
static _updateGaldrSD(rollData) {
let sdDuree = Number(dureeGaldrSD[rollData.dureeGaldr]) || 0
let sdVar = 0
if (rollData.sort?.system.voie === "illusion") {
sdVar = Number(zonesciblesGaldrSD[rollData.zoneGaldr]) || 0
} else {
sdVar = Number(ciblesGaldrSD[rollData.nbCibles]) || 0
}
rollData.sdGaldr = sdDuree + sdVar
}
}

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

@@ -0,0 +1,17 @@
/**
* Data model pour les armes de corps à corps
*/
export default class ArmeccDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
equipe: new fields.BooleanField({ initial: false }),
degat: new fields.NumberField({ initial: 0, integer: true }),
solidite: new fields.NumberField({ initial: 0, integer: true }),
enc: new fields.NumberField({ initial: 0, integer: true }),
valeur: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,18 @@
/**
* Data model pour les armes à distance
*/
export default class ArmedistDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
equipe: new fields.BooleanField({ initial: false }),
degat: new fields.NumberField({ initial: 0, integer: true }),
solidite: new fields.NumberField({ initial: 0, integer: true }),
enc: new fields.NumberField({ initial: 0, integer: true }),
portee: new fields.StringField({ initial: "" }),
valeur: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

16
modules/models/armure.mjs Normal file
View File

@@ -0,0 +1,16 @@
/**
* Data model pour les armures
*/
export default class ArmureDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
equipe: new fields.BooleanField({ initial: false }),
protection: new fields.StringField({ initial: "" }),
enc: new fields.NumberField({ initial: 0, integer: true }),
valeur: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

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

View File

@@ -0,0 +1,18 @@
/**
* Data model pour les boucliers
*/
export default class BouclierDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
equipe: new fields.BooleanField({ initial: false }),
defensebonus: new fields.NumberField({ initial: 0, integer: true }),
enc: new fields.NumberField({ initial: 0, integer: true }),
enccomb: new fields.NumberField({ initial: 0, integer: true }),
solidite: new fields.NumberField({ initial: 0, integer: true }),
valeur: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,16 @@
/**
* Data model pour les compétences
*/
export default class CompetenceDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
isspecialisation: new fields.BooleanField({ initial: false }),
categorie: new fields.StringField({ initial: "" }),
specialisation: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
niveauunrequis: new fields.BooleanField({ initial: false })
};
}
}

11
modules/models/don.mjs Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,87 @@
/**
* Data model pour les figurants
*/
export default class FigurantDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
biodata: new fields.SchemaField({
age: new fields.NumberField({ initial: 0, integer: true }),
taille: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
poids: new fields.NumberField({ initial: 0, integer: true }),
notes: new fields.HTMLField({ initial: "" })
}),
attributs: new fields.SchemaField({
conflit: new fields.SchemaField({
values: new fields.SchemaField({
offensif: new fields.SchemaField({
label: new fields.StringField({ initial: "Offensif" }),
value: new fields.NumberField({ initial: 0, integer: true }),
degats: new fields.BooleanField({ initial: true })
}),
defensif: new fields.SchemaField({
label: new fields.StringField({ initial: "Défensif" }),
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: false }),
label: new fields.StringField({ initial: "Conflit" })
}),
relationnel: new fields.SchemaField({
values: new fields.SchemaField({
defaut: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: true }),
label: new fields.StringField({ initial: "Relationnel" })
}),
physique: new fields.SchemaField({
values: new fields.SchemaField({
defaut: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: true }),
label: new fields.StringField({ initial: "Physique" })
}),
mental: new fields.SchemaField({
values: new fields.SchemaField({
defaut: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: true }),
label: new fields.StringField({ initial: "Mental" })
}),
mystique: new fields.SchemaField({
values: new fields.SchemaField({
actif: new fields.SchemaField({
label: new fields.StringField({ initial: "Actif" }),
value: new fields.NumberField({ initial: 0, integer: true })
}),
passif: new fields.SchemaField({
label: new fields.StringField({ initial: "Passif" }),
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: false }),
label: new fields.StringField({ initial: "Mystique" })
}),
vitalite: new fields.SchemaField({
values: new fields.SchemaField({
defaut: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: false }),
label: new fields.StringField({ initial: "Vitalité" })
})
}),
etat: new fields.SchemaField({
etat: new fields.StringField({ initial: "fringant" })
})
};
}
}

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

@@ -0,0 +1,27 @@
/**
* Index des DataModels pour Yggdrasill
* 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 FigurantDataModel } from './figurant.mjs';
// Modèles d'items
export { default as CompetenceDataModel } from './competence.mjs';
export { default as DonDataModel } from './don.mjs';
export { default as FaiblesseDataModel } from './faiblesse.mjs';
export { default as BlessureDataModel } from './blessure.mjs';
export { default as MaladieDataModel } from './maladie.mjs';
export { default as PoisonDataModel } from './poison.mjs';
export { default as ProuesseDataModel } from './prouesse.mjs';
export { default as SortsejdrDataModel } from './sortsejdr.mjs';
export { default as SortgaldrDataModel } from './sortgaldr.mjs';
export { default as RuneDataModel } from './rune.mjs';
export { default as ArmeccDataModel } from './armecc.mjs';
export { default as ArmedistDataModel } from './armedist.mjs';
export { default as ArmureDataModel } from './armure.mjs';
export { default as BouclierDataModel } from './bouclier.mjs';
export { default as EquipementDataModel } from './equipement.mjs';
export { default as MonnaieDataModel } from './monnaie.mjs';
export { default as EffetmagiqueDataModel } from './effetmagique.mjs';

View File

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

View File

@@ -0,0 +1,13 @@
/**
* Data model pour la monnaie
*/
export default class MonnaieDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
quantite: new fields.NumberField({ initial: 0, integer: true }),
enc: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,158 @@
/**
* Data model pour les personnages
*/
export default class PersonnageDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
biodata: new fields.SchemaField({
nom: new fields.StringField({ initial: "" }),
archetype: new fields.StringField({ initial: "" }),
profession: new fields.StringField({ initial: "" }),
royaume: new fields.StringField({ initial: "" }),
age: new fields.NumberField({ initial: 0, integer: true }),
taille: new fields.NumberField({ initial: 0, integer: true }),
poids: new fields.NumberField({ initial: 0, integer: true }),
don: new fields.StringField({ initial: "" }),
faiblesse: new fields.StringField({ initial: "" }),
pointlegende: new fields.NumberField({ initial: 0, integer: true }),
renomee: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" }),
notes: new fields.HTMLField({ initial: "" }),
tiragerunes: new fields.StringField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" })
}),
carac: new fields.SchemaField({
corps: new fields.SchemaField({
label: new fields.StringField({ initial: "Corps" }),
carac: new fields.SchemaField({
puissance: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Puissance" }),
categorie: new fields.StringField({ initial: "corps" }),
abbrev: new fields.StringField({ initial: "pui" })
}),
vigueur: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Vigueur" }),
categorie: new fields.StringField({ initial: "corps" }),
abbrev: new fields.StringField({ initial: "vig" })
}),
agilite: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Agilité" }),
categorie: new fields.StringField({ initial: "corps" }),
abbrev: new fields.StringField({ initial: "agi" })
})
})
}),
esprit: new fields.SchemaField({
label: new fields.StringField({ initial: "Esprit" }),
carac: new fields.SchemaField({
intellect: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Intellect" }),
categorie: new fields.StringField({ initial: "esprit" }),
abbrev: new fields.StringField({ initial: "int" })
}),
perception: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Perception" }),
categorie: new fields.StringField({ initial: "esprit" }),
abbrev: new fields.StringField({ initial: "per" })
}),
tenacite: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Tenacité" }),
categorie: new fields.StringField({ initial: "esprit" }),
abbrev: new fields.StringField({ initial: "ten" })
})
})
}),
ame: new fields.SchemaField({
label: new fields.StringField({ initial: "Ame" }),
carac: new fields.SchemaField({
charisme: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Charisme" }),
categorie: new fields.StringField({ initial: "ame" }),
abbrev: new fields.StringField({ initial: "cha" })
}),
communication: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Communication" }),
categorie: new fields.StringField({ initial: "ame" }),
abbrev: new fields.StringField({ initial: "com" })
}),
instinct: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Instinct" }),
categorie: new fields.StringField({ initial: "ame" }),
abbrev: new fields.StringField({ initial: "ins" })
})
})
})
}),
furor: new fields.SchemaField({
value: new fields.NumberField({ initial: 1, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true }),
label: new fields.StringField({ initial: "Furor" })
}),
xp: new fields.SchemaField({
total: new fields.NumberField({ initial: 1, integer: true }),
current: new fields.NumberField({ initial: 1, integer: true }),
label: new fields.StringField({ initial: "XP" })
}),
renommee: new fields.SchemaField({
value: new fields.NumberField({ initial: 1, integer: true }),
label: new fields.StringField({ initial: "Renommée" })
}),
status: new fields.SchemaField({
epuise: new fields.BooleanField({ initial: false }),
blesse: new fields.BooleanField({ initial: false }),
meurtri: new fields.BooleanField({ initial: false })
}),
caracsecondaire: new fields.SchemaField({
reaction: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Réaction" }),
abbrev: new fields.StringField({ initial: "rea" })
}),
defensephy: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
bonusmalus: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Défense Physique" }),
abbrev: new fields.StringField({ initial: "dp" })
}),
defensemen: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
bonusmalus: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Défense Mentale" }),
abbrev: new fields.StringField({ initial: "dm" })
}),
deplacement: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Déplacement" }),
abbrev: new fields.StringField({ initial: "dep" })
}),
capaenc: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Capacité d'Encombrement" }),
abbrev: new fields.StringField({ initial: "cpe" })
}),
pv: new fields.SchemaField({
value: new fields.NumberField({ initial: 10, integer: true }),
max: new fields.NumberField({ initial: 10, integer: true }),
min: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Points de Vie" }),
abbrev: new fields.StringField({ initial: "pv" })
})
})
};
}
}

13
modules/models/poison.mjs Normal file
View File

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

View File

@@ -0,0 +1,16 @@
/**
* Data model pour les prouesses
*/
export default class ProuesseDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
armes: new fields.StringField({ initial: "" }),
prerequis: new fields.StringField({ initial: "" }),
modificateur: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

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

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

View File

@@ -0,0 +1,13 @@
/**
* Data model pour les sorts Galdr
*/
export default class SortgaldrDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
voie: new fields.StringField({ initial: "" }),
sd: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,17 @@
/**
* Data model pour les sorts Sejdr
*/
export default class SortsejdrDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
forme: new fields.StringField({ initial: "" }),
preparation: new fields.StringField({ initial: "" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
malus: new fields.NumberField({ initial: 0, integer: true }),
duree: new fields.StringField({ initial: "" }),
zone: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -1,6 +1,6 @@
/* -------------------------------------------- */
import { YggdrasillUtility } from "./yggdrasill-utility.js";
import { YggdrasillRoll } from "./yggdrasill-roll-dialog.js";
import { YggdrasillRollDialog } from "./applications/yggdrasill-roll-dialog.mjs";
/* -------------------------------------------- */
const statusEffects = [
@@ -441,45 +441,37 @@ export class YggdrasillActor extends Actor {
let sumame = baseame.charisme.value + baseame.communication.value + baseame.instinct.value
let newPV = (sumcorps*3) + (sumesprit *2) + sumame;
if ( newPV != this.system.caracsecondaire.pv.max) {
this.system.caracsecondaire.pv.max = newPV;
this.update( { 'system.caracsecondaire.pv.max': newPV });
}
this.system.caracsecondaire.pv.max = newPV;
this.system.caracsecondaire.reaction.value = baseesprit.intellect.value + baseesprit.perception.value + baseame.instinct.value;
let newReac = baseesprit.intellect.value + baseesprit.perception.value + baseame.instinct.value;
if ( newReac != this.system.caracsecondaire.reaction.max) {
this.system.caracsecondaire.reaction.max = newReac
this.update( { 'system.caracsecondaire.reaction.max': newReac });
}
this.system.caracsecondaire.reaction.max = newReac
this.system.caracsecondaire.defensephy.value = basecorps.agilite.value + basecorps.vigueur.value + baseame.instinct.value;
let newDef = basecorps.agilite.value + basecorps.vigueur.value + baseame.instinct.value;
if ( newDef != this.system.caracsecondaire.defensephy.max) {
this.system.caracsecondaire.defensephy.max = newDef
this.update( { 'system.caracsecondaire.defensephy.max': newDef });
this.system.caracsecondaire.defensephy.max = newDef
// Initialize bonusmalus if not set
if (this.system.caracsecondaire.defensephy.bonusmalus === undefined) {
this.system.caracsecondaire.defensephy.bonusmalus = 0
}
this.system.caracsecondaire.defensephy.total = newDef + this.system.caracsecondaire.defensephy.bonusmalus
this.system.caracsecondaire.defensemen.value = baseesprit.tenacite.value + baseame.instinct.value + baseesprit.intellect.value;
newDef = baseesprit.tenacite.value + baseame.instinct.value + baseesprit.intellect.value;
if ( newDef != this.system.caracsecondaire.defensemen.max) {
this.system.caracsecondaire.defensemen.max = newDef
this.update( { 'system.caracsecondaire.defensemen.max': newDef });
this.system.caracsecondaire.defensemen.max = newDef
// Initialize bonusmalus if not set
if (this.system.caracsecondaire.defensemen.bonusmalus === undefined) {
this.system.caracsecondaire.defensemen.bonusmalus = 0
}
this.system.caracsecondaire.defensemen.total = newDef + this.system.caracsecondaire.defensemen.bonusmalus
this.system.caracsecondaire.deplacement.value = basecorps.agilite.value + basecorps.vigueur.value;
let depl = basecorps.agilite.value + basecorps.vigueur.value;
if ( depl != this.system.caracsecondaire.deplacement.max) {
this.system.caracsecondaire.deplacement.max = depl
this.update( { 'system.caracsecondaire.deplacement.max': depl });
}
this.system.caracsecondaire.deplacement.max = depl
this.system.caracsecondaire.capaenc.value = (basecorps.puissance.value * 2) + basecorps.vigueur.value;
let enc = (basecorps.puissance.value * 2) + basecorps.vigueur.value;
if ( enc != this.system.caracsecondaire.capaenc.max ) {
this.system.caracsecondaire.capaenc.max = enc
this.update( { 'system.caracsecondaire.capaenc.max': enc });
}
this.system.caracsecondaire.capaenc.max = enc
//console.log("CARAC SEC", this.system.caracsecondaire)
}
@@ -531,9 +523,7 @@ export class YggdrasillActor extends Actor {
isBlesse: this.system.etat.etat == "blesse",
}
this.addDefaultRoll(rollData)
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
await YggdrasillRollDialog.create( this, rollData);
} else {
ui.notifications.warn("Attribut non trouvée");
}
@@ -541,11 +531,11 @@ export class YggdrasillActor extends Actor {
/* -------------------------------------------- */
addDefaultRoll(rollData) {
rollData.optionsBonusMalus= YggdrasillUtility.createOptions(-15, 15)
rollData.bonusMalus= "0"
rollData.bonusMalus= 0
rollData.optionsFuror= YggdrasillUtility.createOptions(0, this.getCurrentFuror())
rollData.furorUsage= "0"
rollData.furorUsage= 0
rollData.optionsBD= YggdrasillUtility.createOptions(0, +15)
rollData.sr= "0"
rollData.sr= 0
rollData.puissanceRune = 1
rollData.optionsPuissanceRune= YggdrasillUtility.createOptions(1, +15)
rollData.supportRune= "peau"
@@ -571,9 +561,7 @@ export class YggdrasillActor extends Actor {
}
this.addDefaultRoll(rollData)
console.log("CARAC : ", rollData, this.system.carac);
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
await YggdrasillRollDialog.create( this, rollData);
} else {
ui.notifications.warn("Caractéristique non trouvée");
}
@@ -598,9 +586,7 @@ export class YggdrasillActor extends Actor {
isMeurtri: this.isMeurtri(),
}
this.addDefaultRoll(rollData)
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
await YggdrasillRollDialog.create( this, rollData);
} else {
ui.notifications.warn("Compétence non trouvée");
}
@@ -691,9 +677,7 @@ export class YggdrasillActor extends Actor {
}
this.addDefaultRoll(rollData)
rollData.sr = 14
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
await YggdrasillRollDialog.create( this, rollData);
} else {
ui.notifications.warn("Sortilège ou Compétence non trouvée !", sort, compName);
}
@@ -734,9 +718,7 @@ export class YggdrasillActor extends Actor {
}
this.addDefaultRoll(rollData)
rollData.sr = 14
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
await YggdrasillRollDialog.create( this, rollData);
} else {
ui.notifications.warn("Arme ou Compétence Martiale non trouvée !", arme, compName);
}

View File

@@ -55,16 +55,16 @@ export const YGGDRASILL_CONFIG = {
{ key:"visee", label:"Attaque Visée" }
],
optionsSR : [
{key: "0", label: "Aucun"},
{key: "5", label: "Très Simple (5)"},
{key: "7", label: "Simple (7)"},
{key: "10", label: "Aisé (10)"},
{key: "14", label: "Moyen (14)"},
{key: "19", label: "Difficile (19)"},
{key: "25", label: "Très Difficile (25)"},
{key: "32", label: "Exceptionnel (32)"},
{key: "40", label: "Légendaire (40)"},
{key: "49", label: "Divin (49)"}
{key: 0, label: "Aucun"},
{key: 5, label: "Très Simple (5)"},
{key: 7, label: "Simple (7)"},
{key: 10, label: "Aisé (10)"},
{key: 14, label: "Moyen (14)"},
{key: 19, label: "Difficile (19)"},
{key: 25, label: "Très Difficile (25)"},
{key: 32, label: "Exceptionnel (32)"},
{key: 40, label: "Légendaire (40)"},
{key: 49, label: "Divin (49)"}
],
optionsCarac: [

View File

@@ -1,103 +1,115 @@
import { YggdrasillUtility } from "./yggdrasill-utility.js";
import { YGGDRASILL_CONFIG } from "./yggdrasill-config.js";
const { HandlebarsApplicationMixin } = foundry.applications.api;
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
* @extends {ItemSheetV2}
*/
export class YggdrasillItemSheet extends foundry.appv1.sheets.ItemSheet {
export class YggdrasillItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options);
this.#dragDrop = this.#createDragDropHandlers();
}
#dragDrop;
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["fvtt-yggdrasill", "sheet", "item"],
template: "systems/fvtt-yggdrasill/templates/item-sheet.html",
width: 550,
height: 550
//tabs: [{navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description"}]
});
}
static DEFAULT_OPTIONS = {
classes: ["fvtt-yggdrasill", "item"],
position: {
width: 550,
height: 550,
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
actions: {
editImage: YggdrasillItemSheet.#onEditImage,
},
};
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
// Add "Post to chat" button
// We previously restricted this to GM and editable items only. If you ever find this comment because it broke something: eh, sorry!
buttons.unshift(
{
class: "post",
icon: "fas fa-comment",
onclick: ev => {}
})
return buttons
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = { primary: "description" };
/* -------------------------------------------- */
/** @override */
setPosition(options={}) {
const position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body");
const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
return position;
}
/* -------------------------------------------- */
async getData() {
const objectData = foundry.utils.duplicate(this.object);
let formData = {
title: this.title,
id: objectData.id,
type: objectData.type,
img: objectData.img,
name: objectData.name,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
data: foundry.utils.deepClone(this.object.system),
optionsBase: YggdrasillUtility.createDirectOptionList(0, 20),
optionsNiveaux4: Array.fromRange(5, 1),
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.description, {async: true}),
limited: this.object.limited,
options: this.options,
owner: this.document.isOwner,
isGM: game.user.isGM,
config: game.system.config
static PARTS = {
sheet: {
template: "systems/fvtt-yggdrasill/templates/item-{type}-sheet.hbs"
}
return formData;
}
};
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item");
const item = this.object.options.actor.items.get(li.data("item-id"));
item.sheet.render(true);
});
// Update Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
this.object.options.actor.deleteEmbeddedDocuments( "Item", [li.data("item-id") ] ).then( this.render(true));
});
async _prepareContext() {
// Ensure config is always available with fallback to direct import
const config = game.system?.config || game.system?.yggdrasill?.config || YGGDRASILL_CONFIG || {};
// Create options for niveau 0-5
const optionsNiveaux4 = {};
for (let i = 0; i <= 5; i++) {
optionsNiveaux4[`${i}`] = `${i}`;
}
const optionsBase = YggdrasillUtility.createDirectOptionList(0, 20) || {};
const context = {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
data: this.document.system,
source: this.document.toObject(),
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description || "", { async: true }),
enrichedEffet: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.effet || "", { async: true }),
isEditMode: true,
isEditable: this.isEditable,
editable: this.isEditable,
isGM: game.user.isGM,
config: config,
optionsBase: optionsBase,
optionsNiveaux4: optionsNiveaux4,
};
return context;
}
return context;
}
/* -------------------------------------------- */
get template()
{
let type = this.item.type;
return `systems/fvtt-yggdrasill/templates/item-${type}-sheet.html`;
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
return this.object.update(formData);
_onRender(context, options) {
super._onRender(context, options);
this.#dragDrop.forEach((d) => d.bind(this.element));
}
// #region Drag-and-Drop Workflow
/**
* Create drag-and-drop workflow handlers for this Application
*/
#createDragDropHandlers() {
return [];
}
// #region Actions
/**
* Handle editing the item image
* @param {Event} event - The triggering event
*/
static async #onEditImage(event) {
event.preventDefault();
const filePicker = new FilePicker({
type: "image",
current: this.document.img,
callback: (path) => {
this.document.update({ img: path });
},
});
filePicker.browse();
}
}

View File

@@ -9,14 +9,17 @@
/* -------------------------------------------- */
// Import Modules
import { YggdrasillActor } from "./yggdrasill-actor.js";
import { YggdrasillItemSheet } from "./yggdrasill-item-sheet.js";
import { YggdrasillActorSheet } from "./yggdrasill-actor-sheet.js";
import { YggdrasillFigurantSheet } from "./yggdrasill-figurant-sheet.js";
import { YggdrasillUtility } from "./yggdrasill-utility.js";
import { YggdrasillCombat } from "./yggdrasill-combat.js";
import { YGGDRASILL_CONFIG } from "./yggdrasill-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 */
/* -------------------------------------------- */
@@ -46,30 +49,82 @@ Hooks.once("init", async function () {
/* -------------------------------------------- */
// Define custom Entity classes
CONFIG.Actor.documentClass = YggdrasillActor;
CONFIG.Actor.dataModels = {
personnage: models.PersonnageDataModel,
figurant: models.FigurantDataModel
};
CONFIG.Combat.documentClass = YggdrasillCombat;
CONFIG.Item.dataModels = {
competence: models.CompetenceDataModel,
don: models.DonDataModel,
faiblesse: models.FaiblesseDataModel,
blessure: models.BlessureDataModel,
maladie: models.MaladieDataModel,
poison: models.PoisonDataModel,
prouesse: models.ProuesseDataModel,
sortsejdr: models.SortsejdrDataModel,
sortgaldr: models.SortgaldrDataModel,
rune: models.RuneDataModel,
armecc: models.ArmeccDataModel,
armedist: models.ArmedistDataModel,
armure: models.ArmureDataModel,
bouclier: models.BouclierDataModel,
equipement: models.EquipementDataModel,
monnaie: models.MonnaieDataModel,
effetmagique: models.EffetmagiqueDataModel
};
CONFIG.Yggdrasill = {
}
game.system.yggdrasill = {
config: YGGDRASILL_CONFIG,
models,
sheets
}
/* -------------------------------------------- */
// Register sheet application classes
// Register AppV2 Actor Sheets
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
foundry.documents.collections.Actors.registerSheet("fvtt-yggdrasill", YggdrasillActorSheet, { types: ["personnage"], makeDefault: true });
foundry.documents.collections.Actors.registerSheet("fvtt-yggdrasill", YggdrasillFigurantSheet, { types: ["figurant"], makeDefault: false });
foundry.documents.collections.Actors.registerSheet("fvtt-yggdrasill", sheets.YggdrasillPersonnageSheet, { types: ["personnage"], makeDefault: true });
foundry.documents.collections.Actors.registerSheet("fvtt-yggdrasill", sheets.YggdrasillFigurantSheet, { types: ["figurant"], makeDefault: true });
// Register AppV2 Item Sheets
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", YggdrasillItemSheet, { makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillCompetenceSheet, { types: ["competence"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillDonSheet, { types: ["don"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillFaiblesseSheet, { types: ["faiblesse"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillBlessureSheet, { types: ["blessure"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillMaladieSheet, { types: ["maladie"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillPoisonSheet, { types: ["poison"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillProuesseSheet, { types: ["prouesse"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillSortsejdrSheet, { types: ["sortsejdr"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillSortgaldrSheet, { types: ["sortgaldr"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillRuneSheet, { types: ["rune"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillArmeccSheet, { types: ["armecc"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillArmedistSheet, { types: ["armedist"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillArmureSheet, { types: ["armure"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillBouclierSheet, { types: ["bouclier"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillEquipementSheet, { types: ["equipement"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillMonnaieSheet, { types: ["monnaie"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillEffetmagiqueSheet, { types: ["effetmagique"], makeDefault: true });
});
/* -------------------------------------------- */
function welcomeMessage() {
async function welcomeMessage() {
const templateData = {};
const html = await foundry.applications.handlebars.renderTemplate("systems/fvtt-yggdrasill/templates/chat-welcome-message.hbs", templateData);
ChatMessage.create({
user: game.user.id,
whisper: [game.user.id],
content: `<div id="welcome-message-yggdrasill"><span class="rdd-roll-part"><strong>Bienvenue à Yggdrasill !</strong>
<p>Yggdrasill est un Jeu de Rôle publié par 7ième Cercle : https://www.7emecercle.com/7C_site/jeux-de-roles/yggdrasill/</p>
<br>Yggdrasill est une propriété de 7ième Cercle.
</div>
` });
content: html
});
}
/* -------------------------------------------- */
@@ -101,3 +156,28 @@ Hooks.on("chatMessage", (html, content, msg) => {
}
return true;
});
/* -------------------------------------------- */
/* Chat Message Rendering */
/* -------------------------------------------- */
Hooks.on("renderChatMessageHTML", (message, html, data) => {
// Handle collapsible magic details
html.querySelector('.magic-header.collapsible')?.addEventListener('click', function() {
const header = this;
const content = header.parentElement.querySelector('.magic-content');
// Toggle collapsed state
content?.classList.toggle('collapsed');
header.classList.toggle('expanded');
});
// Handle collapsible weapon details
html.querySelector('.weapon-header.collapsible')?.addEventListener('click', function() {
const header = this;
const content = header.parentElement.querySelector('.weapon-content');
// Toggle collapsed state
content?.classList.toggle('collapsed');
header.classList.toggle('expanded');
});
});

View File

@@ -1,212 +0,0 @@
import { YggdrasillUtility } from "./yggdrasill-utility.js";
const dureeGaldrSD = { "1d5a": 3, "1d10t": 6, "1d10m": 9, "1d10h": 12, "1d5j": 15};
const ciblesGaldrSD = { "1": 3, "2_4": 6, "5_9": 9, "10_49": 12, "50plus": 15};
const zonesciblesGaldrSD = { "INS10cm3": 3, "INS50cm3": 6, "INS1m3": 9, "INS5m3": 12, "INS10m3": 15};
export class YggdrasillRoll extends Dialog {
/* -------------------------------------------- */
static async create(actor, rollData ) {
let html
let h = 440;
if ( rollData.mode == "competence") {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-competence.html', rollData);
h = 340;
} else if (rollData.mode == "carac") {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-carac.html', rollData);
h = 320;
} else if (rollData.mode == "attribut") {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-attribut.html', rollData);
h = 320;
} else if (rollData.mode == "armecc") {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-armecc.html', rollData);
} else if (rollData.mode == "sejdr") {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-sejdr.html', rollData);
} else if (rollData.mode == "rune") {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-rune.html', rollData);
} else if (rollData.mode == "galdr") {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-galdr.html', rollData);
} else {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-armetir.html', rollData);
}
let options = { classes: ["yggdrasilldialog"], width: 600, height: h, 'z-index': 99999 };
return new YggdrasillRoll(actor, rollData, html, options );
}
/* -------------------------------------------- */
constructor(actor, rollData, html, options, close = undefined) {
let conf = {
title: (rollData.mode == "competence") ? "Compétence" : "Caractéristique",
content: html,
buttons: {
roll: {
icon: '<i class="fas fa-check"></i>',
label: "Lancer le Test",
callback: () => { this.roll() }
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Annuler",
callback: () => { this.close() }
} },
default: "Roll",
close: close
}
super(conf, options);
this.actor = actor;
this.rollData = rollData;
}
/* -------------------------------------------- */
roll () {
if ( this.rollData.mode == "attribut") {
YggdrasillUtility.rollAttribute(this.rollData)
} else {
YggdrasillUtility.rollYggdrasill( this.rollData )
}
}
/* -------------------------------------------- */
updateGaldrSR( ) {
let sdDuree = Number(dureeGaldrSD[this.rollData.dureeGaldr]);
let sdVar = 0;
if ( this.rollData.sort.system.voie == "illusion") {
sdVar = Number(zonesciblesGaldrSD[this.rollData.zoneGaldr]);
} else {
sdVar = Number(ciblesGaldrSD[this.rollData.nbCibles]);
}
let SR = Number(this.rollData.sort.system.sd) + sdDuree + sdVar;
$("#srTotal").text(SR);
this.rollData.sr = SR;
}
/* -------------------------------------------- */
updateRuneSR() {
let support = 0;
this.rollData.dureeRune = 6 - this.rollData.agiliteCarac.value;
if ( this.rollData.supportRune == "peau") {
support = 3;
this.rollData.echelleDuree = "Actions";
this.rollData.echelleDureeVie = "Heures"
}
if ( this.rollData.supportRune == "tissu") {
support = 6;
this.rollData.echelleDuree = "Tours";
this.rollData.echelleDureeVie = "Jours"
}
if ( this.rollData.supportRune == "cuir") {
support = 9;
this.rollData.echelleDuree = "Minutes";
this.rollData.echelleDureeVie = "Semaines"
}
if ( this.rollData.supportRune == "bois") {
support = 12;
this.rollData.echelleDuree = "Heures";
this.rollData.echelleDureeVie = "Mois"
}
if ( this.rollData.supportRune == "pierremetal") {
support = 15;
this.rollData.echelleDuree = "Jours";
this.rollData.echelleDureeVie = "Années"
}
let SR = this.rollData.puissanceRune + (Number(this.rollData.sort.system.niveau)*3) + support;
$("#srTotal").text(SR);
$("#runeDuree").text( this.rollData.dureeRune + " " + this.rollData.echelleDuree);
$("#runeDureeVie").text( this.rollData.competence.system.niveau + " " + this.rollData.echelleDureeVie);
this.rollData.sr = SR;
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
let dialog = this;
function onLoad() {
if (dialog.rollData.mode == "competence") {
let carac = dialog.actor.getCarac( "Puissance" );
dialog.rollData.selectedCarac = carac;
} else if (dialog.rollData.mode == "armecc" || dialog.rollData.mode == "armedist" ) {
$("#caracName").text(dialog.rollData.selectedCarac.label);
$("#attackDescr").text(dialog.rollData.attackDef.description);
} else if ( dialog.rollData.mode == "sejdr" || dialog.rollData.mode == "rune" || dialog.rollData.mode == "galdr" ) {
$("#caracName").text(dialog.rollData.selectedCarac.label);
}
if (dialog.rollData.mode == "rune" ) {
dialog.updateRuneSR();
}
if (dialog.rollData.mode == "galdr" ) {
dialog.updateGaldrSR();
}
if (dialog.rollData.mode == "attribut") {
$("#attrValue").text("2d10+"+dialog.rollData.subAttr.value);
} else {
$("#caracValue").text(dialog.rollData.selectedCarac.value+"d10");
}
}
$(function () { onLoad(); });
html.find('#caracName').change((event) => {
let caracKey = event.currentTarget.value;
let carac = this.actor.getCarac( caracKey );
this.rollData.selectedCarac = carac;
$("#caracValue").text(carac.value+"d10");
});
html.find('#typeAttack').change((event) => {
let attackType = event.currentTarget.value;
let attackDef
if ( this.rollData.mode == 'armecc')
attackDef = this.actor.getAttaqueData( attackType);
else
attackDef = this.actor.getTirData( attackType);
this.rollData.attackDef = attackDef;
this.rollData.selectedCarac = attackDef.carac;
$("#caracValue").text(attackDef.carac.value+"d10");
$("#caracName").text(attackDef.carac.label);
$("#attackDescr").text(attackDef.description);
$("#malus").text(attackDef.malus);
});
html.find('#supportRune').change((event) => {
this.rollData.supportRune = event.currentTarget.value;
this.updateRuneSR();
});
html.find('#puissanceRune').change((event) => {
this.rollData.puissanceRune = Number(event.currentTarget.value);
this.updateRuneSR();
});
html.find('#dureeGaldr').change((event) => {
this.rollData.dureeGaldr = event.currentTarget.value;
this.updateGaldrSR();
});
html.find('#nbCibles').change((event) => {
this.rollData.nbCibles = event.currentTarget.value;
this.updateGaldrSR();
});
html.find('#zoneGaldr').change((event) => {
this.rollData.zoneGaldr = event.currentTarget.value;
this.updateGaldrSR();
});
html.find('#bonusMalus').change((event) => {
this.rollData.bonusMalus = Number(event.currentTarget.value);
});
html.find('#furorUsage').change((event) => {
this.rollData.furorUsage = Number(event.currentTarget.value);
});
html.find('#sr').change((event) => {
this.rollData.sr = Number(event.currentTarget.value);
});
html.find('#bonusdefense').change((event) => {
this.rollData.bonusdefense = Number(event.currentTarget.value);
});
}
}

View File

@@ -156,7 +156,7 @@ export class YggdrasillUtility {
console.log("ROLLLL ATTR!!!!", rollData);
this.createChatWithRollMode( rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-yggdrasill/templates/chat-generic-result.html`, rollData)
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-yggdrasill/templates/chat-generic-result-new.hbs`, rollData)
});
}
@@ -218,9 +218,14 @@ export class YggdrasillUtility {
rollData.niveauCompetence = niveauCompetence
// Compute total SR
rollData.srFinal = rollData.sr;
if ( rollData.bonusdefense ) {
rollData.srFinal += rollData.bonusdefense;
// Pour les sorts, utiliser srTotal si calculé, sinon sr + bonusdefense
if (rollData.srTotal !== undefined) {
rollData.srFinal = rollData.srTotal
} else {
rollData.srFinal = rollData.sr
if (rollData.bonusdefense) {
rollData.srFinal += rollData.bonusdefense
}
}
if ( rollData.srFinal > 0 ) {
@@ -272,7 +277,7 @@ export class YggdrasillUtility {
console.log("ROLLLL!!!!", rollData);
this.createChatWithRollMode( rollData.alias, {
content: await renderTemplate(`systems/fvtt-yggdrasill/templates/chat-generic-result.html`, rollData)
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-yggdrasill/templates/chat-generic-result-new.hbs`, rollData)
});
}