Initial release for FoundryVTT
This commit is contained in:
223
modules/applications/donjon-et-cie-roll-dialog.mjs
Normal file
223
modules/applications/donjon-et-cie-roll-dialog.mjs
Normal file
@@ -0,0 +1,223 @@
|
||||
import { DonjonEtCieRolls } from "../donjon-et-cie-rolls.mjs";
|
||||
import { DonjonEtCieUtility } from "../donjon-et-cie-utility.mjs";
|
||||
|
||||
export class DonjonEtCieRollDialog {
|
||||
static async createInitiative(actor) {
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/initiative-roll.hbs",
|
||||
{
|
||||
actorName: actor.name,
|
||||
dex: actor.system.caracteristiques?.dexterite?.value ?? 0,
|
||||
initiativeBonus: actor.system.combat?.initiativeBonus ?? 0
|
||||
}
|
||||
);
|
||||
|
||||
return foundry.applications.api.DialogV2.wait({
|
||||
window: { title: game.i18n.localize("DNC.Roll.Initiative"), icon: "fa-solid fa-bolt" },
|
||||
classes: ["dnc-roll-dialog"],
|
||||
content,
|
||||
modal: false,
|
||||
buttons: [
|
||||
{
|
||||
action: "roll",
|
||||
label: "Lancer",
|
||||
icon: "fa-solid fa-bolt",
|
||||
default: true,
|
||||
callback: async (event, button) => {
|
||||
const form = button.form.elements;
|
||||
return DonjonEtCieRolls.rollInitiative(actor, {
|
||||
mode: form.mode?.value ?? "normal"
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
rejectClose: false
|
||||
});
|
||||
}
|
||||
|
||||
static async createCharacteristic(actor, characteristicKey) {
|
||||
const characteristic = actor.system.caracteristiques?.[characteristicKey];
|
||||
if (!characteristic) return;
|
||||
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/characteristic-roll.hbs",
|
||||
{
|
||||
actorName: actor.name,
|
||||
characteristic,
|
||||
characteristicKey,
|
||||
favorOptions: DonjonEtCieUtility.getAvailableFavorOptions(actor),
|
||||
hasFavorOptions: DonjonEtCieUtility.getAvailableFavorOptions(actor).length > 0
|
||||
}
|
||||
);
|
||||
|
||||
return foundry.applications.api.DialogV2.wait({
|
||||
window: { title: game.i18n.localize("DNC.Roll.Characteristic"), icon: "fa-solid fa-dice-d20" },
|
||||
classes: ["dnc-roll-dialog"],
|
||||
content,
|
||||
modal: false,
|
||||
buttons: [
|
||||
{
|
||||
action: "roll",
|
||||
label: "Lancer",
|
||||
icon: "fa-solid fa-dice-d20",
|
||||
default: true,
|
||||
callback: async (event, button) => {
|
||||
const form = button.form.elements;
|
||||
return DonjonEtCieRolls.rollCharacteristic(actor, characteristicKey, {
|
||||
mode: form.mode?.value ?? "normal",
|
||||
favorKey: form.favorDepartment?.value ?? ""
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
rejectClose: false
|
||||
});
|
||||
}
|
||||
|
||||
static async createWeapon(actor, item) {
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/weapon-roll.hbs",
|
||||
{
|
||||
actorName: actor.name,
|
||||
item,
|
||||
characteristicLabel: DonjonEtCieUtility.getWeaponCharacteristicLabel(item.system.categorie),
|
||||
characteristicValue: actor.system.caracteristiques?.[DonjonEtCieUtility.getWeaponCharacteristicKey(item.system.categorie)]?.value ?? 0,
|
||||
favorOptions: DonjonEtCieUtility.getAvailableFavorOptions(actor),
|
||||
hasFavorOptions: DonjonEtCieUtility.getAvailableFavorOptions(actor).length > 0
|
||||
}
|
||||
);
|
||||
|
||||
return foundry.applications.api.DialogV2.wait({
|
||||
window: { title: game.i18n.localize("DNC.Roll.Attack"), icon: "fa-solid fa-sword" },
|
||||
classes: ["dnc-roll-dialog"],
|
||||
content,
|
||||
modal: false,
|
||||
buttons: [
|
||||
{
|
||||
action: "roll",
|
||||
label: "Attaquer",
|
||||
icon: "fa-solid fa-sword",
|
||||
default: true,
|
||||
callback: async (event, button) => {
|
||||
const form = button.form.elements;
|
||||
return DonjonEtCieRolls.rollWeapon(actor, item, {
|
||||
mode: form.mode?.value ?? "normal",
|
||||
favorKey: form.favorDepartment?.value ?? ""
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
rejectClose: false
|
||||
});
|
||||
}
|
||||
|
||||
static async createSpell(actor, item) {
|
||||
const characteristicKey = item.system.caracteristique || "intelligence";
|
||||
const characteristic = actor.system.caracteristiques?.[characteristicKey];
|
||||
const magicResources = DonjonEtCieUtility.getMagicResourceContext(actor);
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/spell-roll.hbs",
|
||||
{
|
||||
actorName: actor.name,
|
||||
item,
|
||||
characteristic,
|
||||
rank: magicResources.rank,
|
||||
currentPv: actor.system.sante?.pv?.value ?? 0,
|
||||
focusLabel: magicResources.focusLabel,
|
||||
focusDisplay: magicResources.focusDisplay,
|
||||
focusIsActive: magicResources.focusIsActive,
|
||||
chaosLabel: magicResources.chaosLabel,
|
||||
autoDisadvantage: Number(item.system.coutPv ?? 0) > magicResources.rank,
|
||||
favorOptions: DonjonEtCieUtility.getAvailableFavorOptions(actor),
|
||||
hasFavorOptions: DonjonEtCieUtility.getAvailableFavorOptions(actor).length > 0
|
||||
}
|
||||
);
|
||||
|
||||
return foundry.applications.api.DialogV2.wait({
|
||||
window: { title: game.i18n.localize("DNC.Roll.Spell"), icon: "fa-solid fa-wand-magic-sparkles" },
|
||||
classes: ["dnc-roll-dialog"],
|
||||
content,
|
||||
modal: false,
|
||||
buttons: [
|
||||
{
|
||||
action: "roll",
|
||||
label: "Lancer",
|
||||
icon: "fa-solid fa-wand-magic-sparkles",
|
||||
default: true,
|
||||
callback: async (event, button) => {
|
||||
const form = button.form.elements;
|
||||
return DonjonEtCieRolls.rollSpell(actor, item, {
|
||||
mode: form.mode?.value ?? "normal",
|
||||
favorKey: form.favorDepartment?.value ?? ""
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
rejectClose: false
|
||||
});
|
||||
}
|
||||
|
||||
static async createUsage(item) {
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/usage-roll.hbs",
|
||||
{
|
||||
item
|
||||
}
|
||||
);
|
||||
|
||||
return foundry.applications.api.DialogV2.wait({
|
||||
window: { title: game.i18n.localize("DNC.Roll.Usage"), icon: "fa-solid fa-hourglass-half" },
|
||||
classes: ["dnc-roll-dialog"],
|
||||
content,
|
||||
modal: false,
|
||||
buttons: [
|
||||
{
|
||||
action: "roll",
|
||||
label: "Utiliser",
|
||||
icon: "fa-solid fa-hourglass-half",
|
||||
default: true,
|
||||
callback: async (event, button) => {
|
||||
const form = button.form.elements;
|
||||
return DonjonEtCieRolls.rollUsage(item, {
|
||||
mode: form.mode?.value ?? "normal"
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
rejectClose: false
|
||||
});
|
||||
}
|
||||
|
||||
static async createDamage(actor, item) {
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/damage-roll.hbs",
|
||||
{
|
||||
actorName: actor?.name ?? item.actor?.name ?? "",
|
||||
item,
|
||||
actorBonus: actor?.system?.combat?.degatsBonus ?? 0
|
||||
}
|
||||
);
|
||||
|
||||
return foundry.applications.api.DialogV2.wait({
|
||||
window: { title: game.i18n.localize("DNC.Roll.Damage"), icon: "fa-solid fa-burst" },
|
||||
classes: ["dnc-roll-dialog"],
|
||||
content,
|
||||
modal: false,
|
||||
buttons: [
|
||||
{
|
||||
action: "roll",
|
||||
label: "Lancer",
|
||||
icon: "fa-solid fa-burst",
|
||||
default: true,
|
||||
callback: async (event, button) => {
|
||||
const form = button.form.elements;
|
||||
return DonjonEtCieRolls.rollDamage(actor, item, {
|
||||
mode: form.mode?.value ?? "normal"
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
rejectClose: false
|
||||
});
|
||||
}
|
||||
}
|
||||
3
modules/applications/sheets/_module.mjs
Normal file
3
modules/applications/sheets/_module.mjs
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as DonjonEtCieItemSheet } from "./base-item-sheet.mjs";
|
||||
export { default as DonjonEtCieEmployeSheet } from "./donjon-et-cie-employe-sheet.mjs";
|
||||
export { default as DonjonEtCiePNJSheet } from "./donjon-et-cie-pnj-sheet.mjs";
|
||||
225
modules/applications/sheets/base-actor-sheet.mjs
Normal file
225
modules/applications/sheets/base-actor-sheet.mjs
Normal file
@@ -0,0 +1,225 @@
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class DonjonEtCieActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["fvtt-donjon-et-cie", "sheet", "actor"],
|
||||
position: { width: 920, height: 820 },
|
||||
form: {
|
||||
submitOnChange: true,
|
||||
closeOnSubmit: false
|
||||
},
|
||||
window: {
|
||||
resizable: true
|
||||
},
|
||||
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: ".item-dropzone" }],
|
||||
actions: {
|
||||
editImage: DonjonEtCieActorSheet.#onEditImage,
|
||||
setTab: DonjonEtCieActorSheet.#onSetTab,
|
||||
createItem: DonjonEtCieActorSheet.#onCreateItem,
|
||||
editItem: DonjonEtCieActorSheet.#onEditItem,
|
||||
deleteItem: DonjonEtCieActorSheet.#onDeleteItem,
|
||||
rollHitDice: DonjonEtCieActorSheet.#onRollHitDice,
|
||||
rollInitiative: DonjonEtCieActorSheet.#onRollInitiative,
|
||||
rollCharacteristic: DonjonEtCieActorSheet.#onRollCharacteristic,
|
||||
rollWeapon: DonjonEtCieActorSheet.#onRollWeapon,
|
||||
rollDamage: DonjonEtCieActorSheet.#onRollDamage,
|
||||
rollSpell: DonjonEtCieActorSheet.#onRollSpell,
|
||||
rollUsage: DonjonEtCieActorSheet.#onRollUsage,
|
||||
useFavorService: DonjonEtCieActorSheet.#onUseFavorService,
|
||||
postItem: DonjonEtCieActorSheet.#onPostItem,
|
||||
adjustCounter: DonjonEtCieActorSheet.#onAdjustCounter
|
||||
}
|
||||
};
|
||||
|
||||
async _prepareContext() {
|
||||
const actor = this.document;
|
||||
return {
|
||||
actor,
|
||||
system: actor.system,
|
||||
source: actor.toObject(),
|
||||
config: game.system.donjonEtCie.config,
|
||||
characteristics: actor.getCharacteristicEntries(),
|
||||
sections: actor.getSectionData(),
|
||||
fields: actor.schema.fields,
|
||||
systemFields: actor.system.schema.fields,
|
||||
activeTab: this._activeTab ?? "combat"
|
||||
};
|
||||
}
|
||||
|
||||
_onRender(context, options) {
|
||||
super._onRender(context, options);
|
||||
this.#fixWindowShell();
|
||||
this._applyActiveTab();
|
||||
}
|
||||
|
||||
#fixWindowShell() {
|
||||
const app = this.element?.matches?.(".application") ? this.element : this.element?.closest(".application");
|
||||
const content = app?.querySelector(":scope > .window-content") ?? app?.querySelector(".window-content");
|
||||
const header = app?.querySelector(".window-header");
|
||||
|
||||
if (app) {
|
||||
app.style.display = "flex";
|
||||
app.style.flexDirection = "column";
|
||||
app.style.paddingTop = "0";
|
||||
app.style.overflow = "hidden";
|
||||
}
|
||||
|
||||
if (header) {
|
||||
header.style.width = "100%";
|
||||
header.style.flex = "0 0 auto";
|
||||
header.style.position = "relative";
|
||||
header.style.zIndex = "3";
|
||||
}
|
||||
|
||||
if (content) {
|
||||
content.style.width = "100%";
|
||||
content.style.flex = "1 1 auto";
|
||||
content.style.minHeight = "0";
|
||||
content.style.overflowY = "auto";
|
||||
content.style.overflowX = "hidden";
|
||||
}
|
||||
}
|
||||
|
||||
_canDragStart() {
|
||||
return this.isEditable;
|
||||
}
|
||||
|
||||
_canDragDrop() {
|
||||
return this.isEditable;
|
||||
}
|
||||
|
||||
_onDragStart(event) {
|
||||
const itemElement = event.currentTarget.closest(".item");
|
||||
if (!itemElement) return;
|
||||
|
||||
const itemId = itemElement.dataset.itemId;
|
||||
const item = this.document.items.get(itemId);
|
||||
if (!item) return;
|
||||
|
||||
event.dataTransfer.setData("text/plain", JSON.stringify({ type: "Item", uuid: item.uuid }));
|
||||
}
|
||||
|
||||
_onDragOver(event) {
|
||||
const dropTarget = event.target.closest(".item-section");
|
||||
this.#setDropTarget(dropTarget);
|
||||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
this.#setDropTarget(null);
|
||||
return super._onDrop(event);
|
||||
}
|
||||
|
||||
#setDropTarget(target) {
|
||||
this.element.querySelectorAll(".item-section.is-dragover").forEach((section) => section.classList.remove("is-dragover"));
|
||||
if (target instanceof HTMLElement) {
|
||||
target.classList.add("is-dragover");
|
||||
}
|
||||
}
|
||||
|
||||
_applyActiveTab() {
|
||||
const activeTab = this._activeTab ?? "combat";
|
||||
this.element.querySelectorAll("[data-tab-button]").forEach((button) => {
|
||||
const isActive = button.dataset.tab === activeTab;
|
||||
button.classList.toggle("active", isActive);
|
||||
button.setAttribute("aria-pressed", isActive ? "true" : "false");
|
||||
});
|
||||
|
||||
this.element.querySelectorAll("[data-tab-panel]").forEach((panel) => {
|
||||
const isActive = panel.dataset.tabPanel === activeTab;
|
||||
panel.classList.toggle("active", isActive);
|
||||
panel.toggleAttribute("hidden", !isActive);
|
||||
});
|
||||
}
|
||||
|
||||
static async #onEditImage(event) {
|
||||
event.preventDefault();
|
||||
const picker = new FilePicker({
|
||||
type: "image",
|
||||
current: this.document.img,
|
||||
callback: (path) => this.document.update({ img: path })
|
||||
});
|
||||
return picker.browse();
|
||||
}
|
||||
|
||||
static async #onCreateItem(event, target) {
|
||||
event.preventDefault();
|
||||
const type = target.dataset.type;
|
||||
if (!type) return;
|
||||
return this.document.createEmbeddedDocuments("Item", [{ name: `Nouveau ${type}`, type }], { renderSheet: true });
|
||||
}
|
||||
|
||||
static async #onSetTab(event, target) {
|
||||
event.preventDefault();
|
||||
const tab = target.dataset.tab;
|
||||
if (!tab) return;
|
||||
this._activeTab = tab;
|
||||
this._applyActiveTab();
|
||||
}
|
||||
|
||||
static async #onEditItem(event, target) {
|
||||
event.preventDefault();
|
||||
const item = this.document.items.get(target.closest("[data-item-id]")?.dataset.itemId);
|
||||
return item?.sheet.render(true);
|
||||
}
|
||||
|
||||
static async #onDeleteItem(event, target) {
|
||||
event.preventDefault();
|
||||
const itemId = target.closest("[data-item-id]")?.dataset.itemId;
|
||||
if (!itemId) return;
|
||||
return this.document.deleteEmbeddedDocuments("Item", [itemId]);
|
||||
}
|
||||
|
||||
static async #onRollCharacteristic(event, target) {
|
||||
event.preventDefault();
|
||||
return this.document.rollCharacteristic(target.dataset.characteristic);
|
||||
}
|
||||
|
||||
static async #onRollInitiative(event) {
|
||||
event.preventDefault();
|
||||
return this.document.rollInitiative();
|
||||
}
|
||||
|
||||
static async #onRollHitDice(event) {
|
||||
event.preventDefault();
|
||||
return this.document.rollHitDice();
|
||||
}
|
||||
|
||||
static async #onRollWeapon(event, target) {
|
||||
event.preventDefault();
|
||||
return this.document.rollWeapon(target.closest("[data-item-id]")?.dataset.itemId);
|
||||
}
|
||||
|
||||
static async #onRollDamage(event, target) {
|
||||
event.preventDefault();
|
||||
return this.document.rollDamage(target.closest("[data-item-id]")?.dataset.itemId);
|
||||
}
|
||||
|
||||
static async #onRollSpell(event, target) {
|
||||
event.preventDefault();
|
||||
return this.document.rollSpell(target.closest("[data-item-id]")?.dataset.itemId);
|
||||
}
|
||||
|
||||
static async #onRollUsage(event, target) {
|
||||
event.preventDefault();
|
||||
return this.document.rollUsage(target.closest("[data-item-id]")?.dataset.itemId);
|
||||
}
|
||||
|
||||
static async #onUseFavorService(event, target) {
|
||||
event.preventDefault();
|
||||
return this.document.useFavorService(target.dataset.department);
|
||||
}
|
||||
|
||||
static async #onPostItem(event, target) {
|
||||
event.preventDefault();
|
||||
const item = this.document.items.get(target.closest("[data-item-id]")?.dataset.itemId);
|
||||
return item?.postToChat();
|
||||
}
|
||||
|
||||
static async #onAdjustCounter(event, target) {
|
||||
event.preventDefault();
|
||||
const path = target.dataset.path;
|
||||
const delta = Number(target.dataset.delta ?? 0);
|
||||
if (!path || Number.isNaN(delta)) return;
|
||||
return this.document.adjustNumericField(path, delta);
|
||||
}
|
||||
}
|
||||
110
modules/applications/sheets/base-item-sheet.mjs
Normal file
110
modules/applications/sheets/base-item-sheet.mjs
Normal file
@@ -0,0 +1,110 @@
|
||||
import { DonjonEtCieUtility } from "../../donjon-et-cie-utility.mjs";
|
||||
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class DonjonEtCieItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["fvtt-donjon-et-cie", "sheet", "item"],
|
||||
position: { width: 640, height: 700 },
|
||||
form: {
|
||||
submitOnChange: true,
|
||||
closeOnSubmit: false
|
||||
},
|
||||
window: {
|
||||
resizable: true
|
||||
},
|
||||
actions: {
|
||||
editImage: DonjonEtCieItemSheet.#onEditImage,
|
||||
postItem: DonjonEtCieItemSheet.#onPostItem,
|
||||
rollItem: DonjonEtCieItemSheet.#onRollItem,
|
||||
rollDamageItem: DonjonEtCieItemSheet.#onRollDamageItem
|
||||
}
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-donjon-et-cie/templates/items/item-sheet.hbs" }
|
||||
};
|
||||
|
||||
async _prepareContext() {
|
||||
const item = this.document;
|
||||
return {
|
||||
item,
|
||||
system: item.system,
|
||||
source: item.toObject(),
|
||||
config: game.system.donjonEtCie.config,
|
||||
fields: item.schema.fields,
|
||||
systemFields: item.system.schema.fields,
|
||||
isWeapon: item.type === "arme",
|
||||
isArmor: item.type === "armure",
|
||||
isConsumable: item.type === "consommable",
|
||||
isSpell: item.type === "sortilege",
|
||||
canRollDamage: Boolean(item.system.degats),
|
||||
isEquipment: item.type === "equipement",
|
||||
isCapacity: item.type === "capacite",
|
||||
isLanguage: item.type === "langue",
|
||||
isTrait: item.type === "trait",
|
||||
armorProtectionDisplay: Number(item.system.resultatProtection ?? 0) > 0 ? item.system.resultatProtection : "—",
|
||||
weaponCharacteristicLabel: item.type === "arme" ? DonjonEtCieUtility.getWeaponCharacteristicLabel(item.system.categorie) : null,
|
||||
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(item.system.description ?? "", { async: true }),
|
||||
enrichedNotes: await foundry.applications.ux.TextEditor.implementation.enrichHTML(item.system.notes ?? "", { async: true })
|
||||
};
|
||||
}
|
||||
|
||||
_onRender(context, options) {
|
||||
super._onRender(context, options);
|
||||
this.#fixWindowShell();
|
||||
}
|
||||
|
||||
#fixWindowShell() {
|
||||
const content = this.element?.closest(".window-content") ?? this.element?.parentElement;
|
||||
const app = content?.closest(".application") ?? this.element?.closest(".application");
|
||||
const header = app?.querySelector(".window-header");
|
||||
|
||||
if (app) {
|
||||
app.style.display = "flex";
|
||||
app.style.flexDirection = "column";
|
||||
app.style.paddingTop = "0";
|
||||
app.style.overflow = "hidden";
|
||||
}
|
||||
|
||||
if (header) {
|
||||
header.style.width = "100%";
|
||||
header.style.flex = "0 0 auto";
|
||||
header.style.position = "relative";
|
||||
header.style.zIndex = "3";
|
||||
}
|
||||
|
||||
if (content) {
|
||||
content.style.width = "100%";
|
||||
content.style.flex = "1 1 auto";
|
||||
content.style.minHeight = "0";
|
||||
content.style.overflowY = "auto";
|
||||
content.style.overflowX = "hidden";
|
||||
}
|
||||
}
|
||||
|
||||
static async #onEditImage(event) {
|
||||
event.preventDefault();
|
||||
const picker = new FilePicker({
|
||||
type: "image",
|
||||
current: this.document.img,
|
||||
callback: (path) => this.document.update({ img: path })
|
||||
});
|
||||
return picker.browse();
|
||||
}
|
||||
|
||||
static async #onPostItem(event) {
|
||||
event.preventDefault();
|
||||
return this.document.postToChat();
|
||||
}
|
||||
|
||||
static async #onRollItem(event) {
|
||||
event.preventDefault();
|
||||
return this.document.roll();
|
||||
}
|
||||
|
||||
static async #onRollDamageItem(event) {
|
||||
event.preventDefault();
|
||||
return this.document.rollDamage();
|
||||
}
|
||||
}
|
||||
37
modules/applications/sheets/donjon-et-cie-employe-sheet.mjs
Normal file
37
modules/applications/sheets/donjon-et-cie-employe-sheet.mjs
Normal file
@@ -0,0 +1,37 @@
|
||||
import DonjonEtCieActorSheet from "./base-actor-sheet.mjs";
|
||||
import { DonjonEtCieUtility } from "../../donjon-et-cie-utility.mjs";
|
||||
|
||||
export default class DonjonEtCieEmployeSheet extends DonjonEtCieActorSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "employe"],
|
||||
position: { width: 980, height: 860 }
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-donjon-et-cie/templates/actors/employe-sheet.hbs" }
|
||||
};
|
||||
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
const indexedSections = Object.fromEntries(context.sections.map((section) => [section.key, section]));
|
||||
const getSection = (key) => indexedSections[key] ?? {
|
||||
key,
|
||||
label: context.config.actorSections[key]?.label ?? key,
|
||||
createType: context.config.actorSections[key]?.createType ?? key,
|
||||
items: []
|
||||
};
|
||||
|
||||
return {
|
||||
...context,
|
||||
magicResources: DonjonEtCieUtility.getMagicResourceContext(this.document),
|
||||
favorEntries: this.document.getFavorEntries(),
|
||||
chaosTable: DonjonEtCieUtility.getChaosTableEntries(),
|
||||
traitsSection: getSection("traits"),
|
||||
combatSections: ["armes", "armures", "consommables", "equipements"].map(getSection),
|
||||
spellSection: getSection("sortileges"),
|
||||
capacitySection: getSection("capacites"),
|
||||
profileSections: ["langues"].map(getSection)
|
||||
};
|
||||
}
|
||||
}
|
||||
57
modules/applications/sheets/donjon-et-cie-pnj-sheet.mjs
Normal file
57
modules/applications/sheets/donjon-et-cie-pnj-sheet.mjs
Normal file
@@ -0,0 +1,57 @@
|
||||
import DonjonEtCieActorSheet from "./base-actor-sheet.mjs";
|
||||
import { DonjonEtCieUtility } from "../../donjon-et-cie-utility.mjs";
|
||||
|
||||
export default class DonjonEtCiePNJSheet extends DonjonEtCieActorSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "pnj"],
|
||||
position: { width: 840, height: 760 },
|
||||
actions: {
|
||||
...super.DEFAULT_OPTIONS.actions,
|
||||
rollPnjArmor: DonjonEtCiePNJSheet.#onRollPnjArmor,
|
||||
rollPnjCourage: DonjonEtCiePNJSheet.#onRollPnjCourage,
|
||||
rollPnjAttackDamage: DonjonEtCiePNJSheet.#onRollPnjAttackDamage
|
||||
}
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-donjon-et-cie/templates/actors/pnj-sheet.hbs" }
|
||||
};
|
||||
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
const system = this.document.system;
|
||||
const indexedSections = Object.fromEntries(context.sections.map((section) => [section.key, section]));
|
||||
const getSection = (key) => indexedSections[key] ?? {
|
||||
key,
|
||||
label: context.config.actorSections[key]?.label ?? key,
|
||||
createType: context.config.actorSections[key]?.createType ?? key,
|
||||
items: []
|
||||
};
|
||||
|
||||
return {
|
||||
...context,
|
||||
capacitySection: getSection("capacites"),
|
||||
spellSection: getSection("sortileges"),
|
||||
armorDisplay: Number(system.defense?.armure?.delta ?? 0) ? `Δ${system.defense.armure.delta}` : "—",
|
||||
storedArmor: Number(system.defense?.armure?.resultatProtection ?? 0) > 0 ? system.defense.armure.resultatProtection : "—",
|
||||
courageDisplay: Number(system.defense?.courage?.delta ?? 0) ? `Δ${system.defense.courage.delta}` : "—",
|
||||
hasAttackDamage: Boolean(system.attaque?.degats)
|
||||
};
|
||||
}
|
||||
|
||||
static async #onRollPnjArmor(event) {
|
||||
event.preventDefault();
|
||||
return this.document.rollPnjArmor();
|
||||
}
|
||||
|
||||
static async #onRollPnjCourage(event) {
|
||||
event.preventDefault();
|
||||
return this.document.rollPnjCourage();
|
||||
}
|
||||
|
||||
static async #onRollPnjAttackDamage(event) {
|
||||
event.preventDefault();
|
||||
return this.document.rollPnjAttackDamage();
|
||||
}
|
||||
}
|
||||
203
modules/donjon-et-cie-actor.mjs
Normal file
203
modules/donjon-et-cie-actor.mjs
Normal file
@@ -0,0 +1,203 @@
|
||||
import { DonjonEtCieUtility } from "./donjon-et-cie-utility.mjs";
|
||||
import { DonjonEtCieRollDialog } from "./applications/donjon-et-cie-roll-dialog.mjs";
|
||||
|
||||
export class DonjonEtCieActor extends Actor {
|
||||
prepareDerivedData() {
|
||||
super.prepareDerivedData();
|
||||
|
||||
const pv = this.system.sante?.pv;
|
||||
if (pv && pv.value > pv.max) {
|
||||
pv.max = pv.value;
|
||||
}
|
||||
}
|
||||
|
||||
getCharacteristicEntries() {
|
||||
return DonjonEtCieUtility.getCharacteristicEntries(this.system);
|
||||
}
|
||||
|
||||
getSectionData() {
|
||||
return DonjonEtCieUtility.buildActorSections(this);
|
||||
}
|
||||
|
||||
getFavorEntries() {
|
||||
return DonjonEtCieUtility.getFavorEntries(this.system);
|
||||
}
|
||||
|
||||
#getStoredArmorContext() {
|
||||
if (this.type === "pnj") {
|
||||
const stored = Number(this.system.defense?.armure?.resultatProtection ?? 0);
|
||||
return {
|
||||
label: "ARM",
|
||||
hasArmor: true,
|
||||
before: stored,
|
||||
update: async (value) => this.update({ "system.defense.armure.resultatProtection": Math.max(0, Number(value ?? 0)) })
|
||||
};
|
||||
}
|
||||
|
||||
const armors = [...this.items.filter((item) => item.type === "armure")].sort((a, b) => {
|
||||
const equippedScore = Number(Boolean(b.system.equipee)) - Number(Boolean(a.system.equipee));
|
||||
if (equippedScore) return equippedScore;
|
||||
|
||||
const protectionScore = Number(b.system.resultatProtection ?? 0) - Number(a.system.resultatProtection ?? 0);
|
||||
if (protectionScore) return protectionScore;
|
||||
|
||||
return a.name.localeCompare(b.name, "fr", { sensitivity: "base" });
|
||||
});
|
||||
|
||||
const armor = armors.find((item) => item.system.equipee || Number(item.system.resultatProtection ?? 0) > 0) ?? null;
|
||||
if (!armor) {
|
||||
return {
|
||||
label: "Armure",
|
||||
hasArmor: false,
|
||||
before: 0,
|
||||
update: null
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
label: armor.name,
|
||||
hasArmor: true,
|
||||
before: Number(armor.system.resultatProtection ?? 0),
|
||||
update: async (value) => armor.update({ "system.resultatProtection": Math.max(0, Number(value ?? 0)) })
|
||||
};
|
||||
}
|
||||
|
||||
async adjustNumericField(path, delta) {
|
||||
const current = Number(foundry.utils.getProperty(this, path) ?? 0);
|
||||
let next = current + Number(delta);
|
||||
|
||||
if (path === "system.sante.pv.value") {
|
||||
const max = Number(this.system.sante?.pv?.max ?? next);
|
||||
next = Math.max(0, Math.min(next, max));
|
||||
} else {
|
||||
next = Math.max(0, next);
|
||||
}
|
||||
|
||||
return this.update({ [path]: next });
|
||||
}
|
||||
|
||||
async applyIncomingDamage(damage, { useArmor = false } = {}) {
|
||||
const incoming = Math.max(0, Number(damage ?? 0));
|
||||
const pvBefore = Number(this.system.sante?.pv?.value ?? 0);
|
||||
const pvMax = Number(this.system.sante?.pv?.max ?? pvBefore);
|
||||
const armor = this.#getStoredArmorContext();
|
||||
const armorBefore = useArmor ? Number(armor.before ?? 0) : 0;
|
||||
const armorAbsorbed = Math.min(incoming, armorBefore);
|
||||
const armorAfter = Math.max(armorBefore - armorAbsorbed, 0);
|
||||
const hpDamage = Math.max(incoming - armorAbsorbed, 0);
|
||||
const pvAfter = Math.max(pvBefore - hpDamage, 0);
|
||||
|
||||
if (useArmor && armor.hasArmor && armor.update && armorAfter !== armorBefore) {
|
||||
await armor.update(armorAfter);
|
||||
}
|
||||
|
||||
if (hpDamage !== 0) {
|
||||
await this.update({ "system.sante.pv.value": pvAfter });
|
||||
}
|
||||
|
||||
return {
|
||||
incoming,
|
||||
useArmor,
|
||||
armorLabel: armor.label,
|
||||
armorAvailable: armor.hasArmor,
|
||||
armorBefore,
|
||||
armorAbsorbed,
|
||||
armorAfter,
|
||||
hpDamage,
|
||||
pvBefore,
|
||||
pvAfter,
|
||||
pvMax
|
||||
};
|
||||
}
|
||||
|
||||
async rollCharacteristic(key) {
|
||||
return DonjonEtCieRollDialog.createCharacteristic(this, key);
|
||||
}
|
||||
|
||||
async useFavorService(departmentKey) {
|
||||
return game.system.donjonEtCie.rolls.useFavorService(this, departmentKey);
|
||||
}
|
||||
|
||||
async rollInitiative() {
|
||||
return DonjonEtCieRollDialog.createInitiative(this);
|
||||
}
|
||||
|
||||
async rollHitDice() {
|
||||
return game.system.donjonEtCie.rolls.rollHitDice(this);
|
||||
}
|
||||
|
||||
async rollWeapon(itemId) {
|
||||
const item = this.items.get(itemId);
|
||||
if (item) return DonjonEtCieRollDialog.createWeapon(this, item);
|
||||
}
|
||||
|
||||
async rollDamage(itemId) {
|
||||
const item = this.items.get(itemId);
|
||||
if (item) return DonjonEtCieRollDialog.createDamage(this, item);
|
||||
}
|
||||
|
||||
async rollSpell(itemId) {
|
||||
const item = this.items.get(itemId);
|
||||
if (item) return DonjonEtCieRollDialog.createSpell(this, item);
|
||||
}
|
||||
|
||||
async rollUsage(itemId) {
|
||||
const item = this.items.get(itemId);
|
||||
if (item) return DonjonEtCieRollDialog.createUsage(item);
|
||||
}
|
||||
|
||||
#createPnjResourceProxy({ label, deltaPath, protectionPath = null }) {
|
||||
const delta = Number(foundry.utils.getProperty(this, deltaPath) ?? 0);
|
||||
const protection = protectionPath ? Number(foundry.utils.getProperty(this, protectionPath) ?? 0) : 0;
|
||||
|
||||
return {
|
||||
actor: this,
|
||||
type: protectionPath ? "armure" : "ressource",
|
||||
name: `${this.name} · ${label}`,
|
||||
system: {
|
||||
delta,
|
||||
resultatProtection: protection
|
||||
},
|
||||
update: async (data) => {
|
||||
const updateData = {};
|
||||
if (Object.hasOwn(data, "system.delta")) {
|
||||
updateData[deltaPath] = data["system.delta"];
|
||||
}
|
||||
if (protectionPath && Object.hasOwn(data, "system.resultatProtection")) {
|
||||
updateData[protectionPath] = data["system.resultatProtection"];
|
||||
}
|
||||
return Object.keys(updateData).length ? this.update(updateData) : this;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async rollPnjArmor() {
|
||||
return DonjonEtCieRollDialog.createUsage(this.#createPnjResourceProxy({
|
||||
label: "ARM",
|
||||
deltaPath: "system.defense.armure.delta",
|
||||
protectionPath: "system.defense.armure.resultatProtection"
|
||||
}));
|
||||
}
|
||||
|
||||
async rollPnjCourage() {
|
||||
return DonjonEtCieRollDialog.createUsage(this.#createPnjResourceProxy({
|
||||
label: "COU",
|
||||
deltaPath: "system.defense.courage.delta"
|
||||
}));
|
||||
}
|
||||
|
||||
async rollPnjAttackDamage() {
|
||||
const attackName = this.system.attaque?.nom || "Attaque";
|
||||
const attackDamage = this.system.attaque?.degats || "";
|
||||
if (!attackDamage) return null;
|
||||
|
||||
return DonjonEtCieRollDialog.createDamage(this, {
|
||||
name: `${this.name} · ${attackName}`,
|
||||
type: "attaque",
|
||||
system: {
|
||||
degats: attackDamage,
|
||||
portee: this.system.attaque?.notes || ""
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
117
modules/donjon-et-cie-config.mjs
Normal file
117
modules/donjon-et-cie-config.mjs
Normal file
@@ -0,0 +1,117 @@
|
||||
export const DONJON_ET_CIE = {
|
||||
id: "fvtt-donjon-et-cie",
|
||||
characteristics: {
|
||||
force: { label: "FORce", short: "FOR" },
|
||||
dexterite: { label: "DEXterite", short: "DEX" },
|
||||
constitution: { label: "CONstitution", short: "CON" },
|
||||
intelligence: { label: "INTelligence", short: "INT" },
|
||||
sagesse: { label: "SAGesse", short: "SAG" },
|
||||
charisme: { label: "CHArisme", short: "CHA" }
|
||||
},
|
||||
characteristicOptions: {
|
||||
force: "FORce",
|
||||
dexterite: "DEXterite",
|
||||
constitution: "CONstitution",
|
||||
intelligence: "INTelligence",
|
||||
sagesse: "SAGesse",
|
||||
charisme: "CHArisme"
|
||||
},
|
||||
usageDieOptions: {
|
||||
0: "Aucun",
|
||||
4: "Δ4",
|
||||
6: "Δ6",
|
||||
8: "Δ8",
|
||||
10: "Δ10",
|
||||
12: "Δ12"
|
||||
},
|
||||
favorDepartments: {
|
||||
entreesSorties: "Entrees et Sorties",
|
||||
relationsMecenes: "Relations Mecenes",
|
||||
relationsInterieures: "Relations Interieures",
|
||||
conception: "Conception",
|
||||
materiel: "Materiel",
|
||||
arpentage: "Arpentage",
|
||||
terminaison: "Terminaison",
|
||||
recrutement: "Recrutement",
|
||||
reception: "Reception",
|
||||
conditionnement: "Conditionnement",
|
||||
supervision: "Supervision",
|
||||
exploration: "Exploration",
|
||||
reclame: "Reclame",
|
||||
entretien: "Entretien"
|
||||
},
|
||||
chaosTable: {
|
||||
1: {
|
||||
title: "Erreur",
|
||||
effect: "L'effet du sort est inverse ou transforme de maniere dramatique."
|
||||
},
|
||||
2: {
|
||||
title: "Mutation",
|
||||
effect: "La magie fonctionne, mais transforme le personnage et laisse des sequelles : deformation, cicatrice, etc."
|
||||
},
|
||||
3: {
|
||||
title: "Oubli",
|
||||
effect: "Le sort fonctionne, mais le personnage l'oublie et ne s'en souviendra qu'apres une bonne nuit de sommeil."
|
||||
},
|
||||
4: {
|
||||
title: "Drain",
|
||||
effect: "Le personnage perd un nombre de points egal au cout du sort dans une caracteristique determinee au hasard. Ces points se recuperent au rythme d'un par jour."
|
||||
},
|
||||
5: {
|
||||
title: "Feu d'artifice",
|
||||
effect: "Du bruit, de la lumiere, aucun effet tangible, sinon que les vetements du magicien prennent certainement feu."
|
||||
},
|
||||
6: {
|
||||
title: "Pic de pouvoir",
|
||||
effect: "Aucune magie ne prend effet, mais le personnage regagne les points de vie depenses pour le sort."
|
||||
},
|
||||
7: {
|
||||
title: "Sort amoindri",
|
||||
effect: "La zone d'effet, le nombre de cibles, les dommages, tout est divise par deux."
|
||||
},
|
||||
8: {
|
||||
title: "Absence de controle",
|
||||
effect: "Votre magie a un effet secondaire negatif."
|
||||
},
|
||||
9: {
|
||||
title: "Fuite de pouvoir",
|
||||
effect: "Le sort fonctionne mais coute le double de son cout en PV (en tout). Si le personnage tombe a 0 PV, il perd connaissance."
|
||||
},
|
||||
10: {
|
||||
title: "Effet retarde",
|
||||
effect: "La magie prend effet normalement... mais dans d4 tours."
|
||||
},
|
||||
11: {
|
||||
title: "Mal vise",
|
||||
effect: "Le sort affecte une autre cible que celle que vous visez, au choix du MJ."
|
||||
},
|
||||
12: {
|
||||
title: "BAM !",
|
||||
effect: "Les effets, nombre de cibles ou taille du sort sont doubles."
|
||||
}
|
||||
},
|
||||
weaponCategoryOptions: {
|
||||
melee: "Corps a corps",
|
||||
distance: "Distance"
|
||||
},
|
||||
actorSections: {
|
||||
traits: { label: "Traits", createType: "trait" },
|
||||
langues: { label: "Langues", createType: "langue" },
|
||||
capacites: { label: "Capacites", createType: "capacite" },
|
||||
sortileges: { label: "Sortileges", createType: "sortilege" },
|
||||
armes: { label: "Armes", createType: "arme" },
|
||||
armures: { label: "Armures", createType: "armure" },
|
||||
equipements: { label: "Equipements", createType: "equipement" },
|
||||
consommables: { label: "Consommables", createType: "consommable" }
|
||||
},
|
||||
sectionTypes: {
|
||||
traits: ["trait"],
|
||||
langues: ["langue"],
|
||||
capacites: ["capacite"],
|
||||
sortileges: ["sortilege"],
|
||||
armes: ["arme"],
|
||||
armures: ["armure"],
|
||||
equipements: ["equipement"],
|
||||
consommables: ["consommable"]
|
||||
}
|
||||
};
|
||||
45
modules/donjon-et-cie-item.mjs
Normal file
45
modules/donjon-et-cie-item.mjs
Normal file
@@ -0,0 +1,45 @@
|
||||
import { DonjonEtCieRollDialog } from "./applications/donjon-et-cie-roll-dialog.mjs";
|
||||
import { DonjonEtCieUtility } from "./donjon-et-cie-utility.mjs";
|
||||
|
||||
export class DonjonEtCieItem extends Item {
|
||||
async _preCreate(data, options, user) {
|
||||
await super._preCreate(data, options, user);
|
||||
|
||||
const currentImg = data.img ?? this.img;
|
||||
if (currentImg && !currentImg.startsWith("icons/svg/")) return;
|
||||
|
||||
this.updateSource({ img: DonjonEtCieUtility.getDefaultItemIcon(this.type) });
|
||||
}
|
||||
|
||||
get usageDie() {
|
||||
return Number(this.system.delta ?? 0);
|
||||
}
|
||||
|
||||
async roll() {
|
||||
if (this.type === "arme") return DonjonEtCieRollDialog.createWeapon(this.actor, this);
|
||||
if (this.type === "sortilege") return DonjonEtCieRollDialog.createSpell(this.actor, this);
|
||||
if (this.usageDie) return DonjonEtCieRollDialog.createUsage(this);
|
||||
return this.postToChat();
|
||||
}
|
||||
|
||||
async rollDamage() {
|
||||
if (!this.system.degats) return null;
|
||||
return DonjonEtCieRollDialog.createDamage(this.actor, this);
|
||||
}
|
||||
|
||||
async postToChat() {
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/fvtt-donjon-et-cie/templates/chat/item-card.hbs",
|
||||
{
|
||||
item: this,
|
||||
usageLabel: DonjonEtCieUtility.formatUsageDie(this.usageDie)
|
||||
}
|
||||
);
|
||||
|
||||
return ChatMessage.create({
|
||||
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
|
||||
user: game.user.id,
|
||||
content
|
||||
});
|
||||
}
|
||||
}
|
||||
103
modules/donjon-et-cie-main.mjs
Normal file
103
modules/donjon-et-cie-main.mjs
Normal file
@@ -0,0 +1,103 @@
|
||||
import { DONJON_ET_CIE } from "./donjon-et-cie-config.mjs";
|
||||
import { DonjonEtCieUtility } from "./donjon-et-cie-utility.mjs";
|
||||
import { DonjonEtCieActor } from "./donjon-et-cie-actor.mjs";
|
||||
import { DonjonEtCieItem } from "./donjon-et-cie-item.mjs";
|
||||
import * as models from "./models/index.mjs";
|
||||
import * as sheets from "./applications/sheets/_module.mjs";
|
||||
import { DonjonEtCieRollDialog } from "./applications/donjon-et-cie-roll-dialog.mjs";
|
||||
import { DonjonEtCieRolls } from "./donjon-et-cie-rolls.mjs";
|
||||
|
||||
function onChatActionClick(event) {
|
||||
const button = event.target.closest("[data-action='rollChatDamage'], [data-action='rollSpellChaos'], [data-action='applyDamage']");
|
||||
if (!(button instanceof HTMLElement)) return;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
void (async () => {
|
||||
if (button.dataset.action === "rollSpellChaos") {
|
||||
const actorUuid = button.dataset.actorUuid;
|
||||
const itemUuid = button.dataset.itemUuid;
|
||||
if (!actorUuid || !itemUuid) return;
|
||||
const [actor, item] = await Promise.all([fromUuid(actorUuid), fromUuid(itemUuid)]);
|
||||
return DonjonEtCieRolls.rollSpellChaos(actor, item);
|
||||
}
|
||||
|
||||
if (button.dataset.action === "applyDamage") {
|
||||
const card = button.closest(".dnc-chat-card-damage");
|
||||
const select = card?.querySelector("[data-role='damage-target']");
|
||||
const targetUuid = select instanceof HTMLSelectElement ? select.value : "";
|
||||
if (!targetUuid) {
|
||||
ui.notifications.warn(game.i18n.localize("DNC.Chat.SelectTarget"));
|
||||
return null;
|
||||
}
|
||||
|
||||
const target = await fromUuid(targetUuid);
|
||||
if (!target) {
|
||||
ui.notifications.warn(game.i18n.localize("DNC.Chat.TargetUnavailable"));
|
||||
return null;
|
||||
}
|
||||
|
||||
return DonjonEtCieRolls.applyDamage(target, {
|
||||
damage: Number(button.dataset.damage ?? 0),
|
||||
useArmor: button.dataset.useArmor === "true",
|
||||
sourceLabel: button.dataset.sourceLabel ?? ""
|
||||
});
|
||||
}
|
||||
|
||||
const itemUuid = button.dataset.itemUuid;
|
||||
if (!itemUuid) return;
|
||||
const item = await fromUuid(itemUuid);
|
||||
return item?.rollDamage?.();
|
||||
})();
|
||||
}
|
||||
|
||||
Hooks.once("init", async () => {
|
||||
console.log("Initialisation du systeme Donjon & Cie");
|
||||
|
||||
await DonjonEtCieUtility.preloadHandlebarsTemplates();
|
||||
|
||||
CONFIG.Combat.initiative = {
|
||||
formula: "1d20 + @system.caracteristiques.dexterite.value + @system.combat.initiativeBonus",
|
||||
decimals: 0
|
||||
};
|
||||
|
||||
CONFIG.Actor.documentClass = DonjonEtCieActor;
|
||||
CONFIG.Actor.dataModels = {
|
||||
employe: models.EmployeDataModel,
|
||||
pnj: models.PnjDataModel
|
||||
};
|
||||
|
||||
CONFIG.Item.documentClass = DonjonEtCieItem;
|
||||
CONFIG.Item.dataModels = {
|
||||
trait: models.TraitDataModel,
|
||||
langue: models.LangueDataModel,
|
||||
capacite: models.CapaciteDataModel,
|
||||
sortilege: models.SortilegeDataModel,
|
||||
arme: models.ArmeDataModel,
|
||||
armure: models.ArmureDataModel,
|
||||
equipement: models.EquipementDataModel,
|
||||
consommable: models.ConsommableDataModel
|
||||
};
|
||||
|
||||
game.system.donjonEtCie = {
|
||||
config: DONJON_ET_CIE,
|
||||
models,
|
||||
sheets,
|
||||
rolls: DonjonEtCieRolls,
|
||||
dialogs: DonjonEtCieRollDialog,
|
||||
utility: DonjonEtCieUtility
|
||||
};
|
||||
|
||||
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
|
||||
foundry.documents.collections.Actors.registerSheet("fvtt-donjon-et-cie", sheets.DonjonEtCieEmployeSheet, { types: ["employe"], makeDefault: true });
|
||||
foundry.documents.collections.Actors.registerSheet("fvtt-donjon-et-cie", sheets.DonjonEtCiePNJSheet, { types: ["pnj"], makeDefault: true });
|
||||
|
||||
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
|
||||
for (const type of ["trait", "langue", "capacite", "sortilege", "arme", "armure", "equipement", "consommable"]) {
|
||||
foundry.documents.collections.Items.registerSheet("fvtt-donjon-et-cie", sheets.DonjonEtCieItemSheet, { types: [type], makeDefault: true });
|
||||
}
|
||||
});
|
||||
|
||||
Hooks.once("ready", () => {
|
||||
document.addEventListener("click", onChatActionClick);
|
||||
});
|
||||
546
modules/donjon-et-cie-rolls.mjs
Normal file
546
modules/donjon-et-cie-rolls.mjs
Normal file
@@ -0,0 +1,546 @@
|
||||
import { DonjonEtCieUtility } from "./donjon-et-cie-utility.mjs";
|
||||
import { DONJON_ET_CIE } from "./donjon-et-cie-config.mjs";
|
||||
|
||||
export class DonjonEtCieRolls {
|
||||
static async #createChatCard(actor, template, context) {
|
||||
const content = await foundry.applications.handlebars.renderTemplate(template, context);
|
||||
await ChatMessage.create({
|
||||
speaker: ChatMessage.getSpeaker({ actor }),
|
||||
user: game.user.id,
|
||||
content
|
||||
});
|
||||
}
|
||||
|
||||
static #selectKeptValue(values, mode, favorable = "low") {
|
||||
if (!values.length) return null;
|
||||
if (mode === "normal") return values[0];
|
||||
|
||||
const selector = favorable === "low"
|
||||
? (mode === "avantage" ? Math.min : Math.max)
|
||||
: (mode === "avantage" ? Math.max : Math.min);
|
||||
|
||||
return selector(...values);
|
||||
}
|
||||
|
||||
static #getModeLabel(mode) {
|
||||
if (mode === "avantage") return "Avantage";
|
||||
if (mode === "desavantage") return "Desavantage";
|
||||
return null;
|
||||
}
|
||||
|
||||
static #applyFavorMode(mode) {
|
||||
if (mode === "desavantage") return "normal";
|
||||
return "avantage";
|
||||
}
|
||||
|
||||
static async #resolveFormulaRoll(formula, data = {}, { mode = "normal", favorable = "high" } = {}) {
|
||||
const rollCount = mode === "normal" ? 1 : 2;
|
||||
const rolls = await Promise.all(Array.from({ length: rollCount }, () => (new Roll(formula, data)).evaluate()));
|
||||
const values = rolls.map((roll) => roll.total);
|
||||
const kept = this.#selectKeptValue(values, mode, favorable);
|
||||
const keptIndex = Math.max(0, values.findIndex((value) => value === kept));
|
||||
const keptRoll = rolls[keptIndex] ?? rolls[0];
|
||||
|
||||
return { rolls, values, kept, keptIndex, keptRoll, mode, formula: keptRoll.formula };
|
||||
}
|
||||
|
||||
static async #resolveCharacteristic(actor, characteristicKey, { mode = "normal" } = {}) {
|
||||
const characteristic = actor.system.caracteristiques?.[characteristicKey];
|
||||
if (!characteristic) return null;
|
||||
|
||||
const target = Number(characteristic.value ?? 0);
|
||||
const rollCount = mode === "normal" ? 1 : 2;
|
||||
const roll = await (new Roll(`${rollCount}d20`)).evaluate();
|
||||
const values = roll.dice[0]?.results?.map((result) => result.result) ?? [];
|
||||
const kept = this.#selectKeptValue(values, mode, "low");
|
||||
const success = kept <= target;
|
||||
|
||||
return { characteristic, characteristicKey, target, values, kept, success, mode, isNaturalOne: kept === 1, isNaturalTwenty: kept === 20 };
|
||||
}
|
||||
|
||||
static async #resolveFavorBoost(actor, favorKey, mode = "normal") {
|
||||
if (!favorKey) return null;
|
||||
|
||||
const label = DonjonEtCieUtility.getFavorLabel(favorKey);
|
||||
const path = `system.faveurs.${favorKey}.delta`;
|
||||
const before = Number(foundry.utils.getProperty(actor, path) ?? 0);
|
||||
if (!before) {
|
||||
ui.notifications.warn(`Aucune faveur disponible pour ${label}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const resolved = await this.#resolveFormulaRoll(`1d${before}`, {}, { favorable: "high" });
|
||||
const result = resolved.kept;
|
||||
const degraded = result <= 3;
|
||||
const after = degraded ? DonjonEtCieUtility.degradeUsageDie(before) : before;
|
||||
if (after !== before) {
|
||||
await actor.update({ [path]: after });
|
||||
}
|
||||
|
||||
return {
|
||||
key: favorKey,
|
||||
label,
|
||||
before,
|
||||
after,
|
||||
result,
|
||||
degraded,
|
||||
stable: !degraded,
|
||||
effectiveMode: this.#applyFavorMode(mode),
|
||||
modeBefore: mode,
|
||||
modeAfter: this.#applyFavorMode(mode),
|
||||
note: degraded
|
||||
? "Le coup de pouce reste anonyme : un collegue du departement a donne l'info utile."
|
||||
: "Le coup de pouce tient bon : nommez le collegue, ses trois traits et la relation pour le trombinoscope."
|
||||
};
|
||||
}
|
||||
|
||||
static async useFavorService(actor, favorKey) {
|
||||
if (!favorKey) return null;
|
||||
|
||||
const label = DonjonEtCieUtility.getFavorLabel(favorKey);
|
||||
const path = `system.faveurs.${favorKey}.delta`;
|
||||
const before = Number(foundry.utils.getProperty(actor, path) ?? 0);
|
||||
if (!before) {
|
||||
ui.notifications.warn(`Aucune faveur disponible pour ${label}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const after = DonjonEtCieUtility.degradeUsageDie(before);
|
||||
await actor.update({ [path]: after });
|
||||
|
||||
await this.#createChatCard(actor, "systems/fvtt-donjon-et-cie/templates/chat/favor-card.hbs", {
|
||||
title: game.i18n.localize("DNC.Roll.Favor"),
|
||||
subtitle: label,
|
||||
kindLabel: "Service",
|
||||
before: DonjonEtCieUtility.formatUsageDie(before),
|
||||
after: DonjonEtCieUtility.formatUsageDie(after),
|
||||
autoSpent: true,
|
||||
note: "La faveur est brulee pour obtenir directement l'aide souhaitee, a la discretion du MJ."
|
||||
});
|
||||
|
||||
return { key: favorKey, label, before, after };
|
||||
}
|
||||
|
||||
static async #ensureFocus(actor) {
|
||||
const focusDelta = Number(actor.system.magie?.focus?.delta ?? 0);
|
||||
const focusResult = Number(actor.system.magie?.focus?.resultat ?? 0);
|
||||
const focusSceneId = actor.system.magie?.focus?.sceneId ?? "";
|
||||
const currentSceneId = DonjonEtCieUtility.getCurrentSceneId();
|
||||
const sameScene = focusSceneId === currentSceneId;
|
||||
const activeFocus = sameScene ? focusResult : 0;
|
||||
|
||||
if (!focusDelta) {
|
||||
return { delta: 0, activeValue: 0, rolled: false, before: 0, after: 0, degraded: false };
|
||||
}
|
||||
|
||||
if (sameScene) {
|
||||
return { delta: focusDelta, activeValue: activeFocus, rolled: false, before: focusDelta, after: focusDelta, degraded: false };
|
||||
}
|
||||
|
||||
const resolved = await this.#resolveFormulaRoll(`1d${focusDelta}`, {}, { favorable: "high" });
|
||||
const result = resolved.kept;
|
||||
const degraded = result <= 3;
|
||||
const after = degraded ? DonjonEtCieUtility.degradeUsageDie(focusDelta) : focusDelta;
|
||||
const updateData = {
|
||||
"system.magie.focus.resultat": result,
|
||||
"system.magie.focus.sceneId": currentSceneId
|
||||
};
|
||||
|
||||
if (after !== focusDelta) {
|
||||
updateData["system.magie.focus.delta"] = after;
|
||||
}
|
||||
|
||||
await actor.update(updateData);
|
||||
|
||||
return {
|
||||
delta: after,
|
||||
activeValue: result,
|
||||
rolled: true,
|
||||
before: focusDelta,
|
||||
after,
|
||||
degraded,
|
||||
values: resolved.values
|
||||
};
|
||||
}
|
||||
|
||||
static async rollCharacteristic(actor, characteristicKey, { mode = "normal", label = null, favorKey = "" } = {}) {
|
||||
const favor = await this.#resolveFavorBoost(actor, favorKey, mode);
|
||||
const effectiveMode = favor?.effectiveMode ?? mode;
|
||||
const result = await this.#resolveCharacteristic(actor, characteristicKey, { mode: effectiveMode });
|
||||
if (!result) return null;
|
||||
|
||||
await this.#createChatCard(actor, "systems/fvtt-donjon-et-cie/templates/chat/roll-card.hbs", {
|
||||
title: label ?? "Jet de caracteristique",
|
||||
subtitle: result.characteristic.label,
|
||||
formula: result.values.length > 1 ? "2d20" : "1d20",
|
||||
mode: effectiveMode,
|
||||
modeLabel: this.#getModeLabel(effectiveMode),
|
||||
target: result.target,
|
||||
targetPillLabel: "Cible",
|
||||
targetPillValue: result.target,
|
||||
values: result.values,
|
||||
kept: result.kept,
|
||||
keptPillLabel: "Garde",
|
||||
keptPillValue: result.kept,
|
||||
success: result.success,
|
||||
favorLabel: favor?.label ?? null,
|
||||
favorNote: favor?.note ?? null,
|
||||
details: [
|
||||
{ label: "Caracteristique", value: result.characteristic.label },
|
||||
{ label: "Valeur cible", value: result.target },
|
||||
...(favor ? [
|
||||
{ label: "Faveur", value: favor.label },
|
||||
{ label: "Dé de faveur", value: favor.result },
|
||||
{ label: "Avant", value: DonjonEtCieUtility.formatUsageDie(favor.before) },
|
||||
{ label: "Apres", value: DonjonEtCieUtility.formatUsageDie(favor.after) }
|
||||
] : [])
|
||||
]
|
||||
});
|
||||
|
||||
return { ...result, favor, mode: effectiveMode };
|
||||
}
|
||||
|
||||
static async rollInitiative(actor, { mode = "normal" } = {}) {
|
||||
const dex = Number(actor.system.caracteristiques?.dexterite?.value ?? 0);
|
||||
const sheetBonus = Number(actor.system.combat?.initiativeBonus ?? 0);
|
||||
const result = await this.#resolveFormulaRoll("1d20 + @dex + @sheetBonus", { dex, sheetBonus }, { mode, favorable: "high" });
|
||||
const dieValues = result.rolls.map((roll) => roll.dice[0]?.results?.[0]?.result ?? roll.total);
|
||||
const die = dieValues[result.keptIndex] ?? dieValues[0] ?? result.kept;
|
||||
|
||||
let syncedCombat = null;
|
||||
const activeCombat = game.combats?.contents?.find((combat) => combat.active);
|
||||
const combatant = activeCombat?.combatants?.find((entry) => entry.actorId === actor.id);
|
||||
if (combatant) {
|
||||
await activeCombat.setInitiative(combatant.id, result.kept);
|
||||
const ordered = [...activeCombat.combatants].sort((a, b) => (b.initiative ?? -Infinity) - (a.initiative ?? -Infinity));
|
||||
syncedCombat = {
|
||||
name: activeCombat.name,
|
||||
initiative: result.kept,
|
||||
rank: ordered.findIndex((entry) => entry.id === combatant.id) + 1,
|
||||
total: ordered.length
|
||||
};
|
||||
}
|
||||
|
||||
await this.#createChatCard(actor, "systems/fvtt-donjon-et-cie/templates/chat/initiative-card.hbs", {
|
||||
title: game.i18n.localize("DNC.Roll.Initiative"),
|
||||
actorName: actor.name,
|
||||
total: result.kept,
|
||||
formula: result.rolls.length > 1 ? `2 × ${result.formula}` : result.formula,
|
||||
die,
|
||||
dieValues,
|
||||
dex,
|
||||
bonus: sheetBonus,
|
||||
mode: result.mode,
|
||||
modeLabel: this.#getModeLabel(result.mode),
|
||||
syncedCombat
|
||||
});
|
||||
|
||||
return { total: result.kept, die, dieValues, dex, bonus: sheetBonus, mode: result.mode, syncedCombat };
|
||||
}
|
||||
|
||||
static async rollHitDice(actor) {
|
||||
const formula = String(actor.system.sante?.dv ?? "").trim();
|
||||
if (!formula) return null;
|
||||
|
||||
let roll;
|
||||
try {
|
||||
roll = await (new Roll(formula)).evaluate();
|
||||
} catch (error) {
|
||||
ui.notifications.error(`Formule de DV invalide : ${formula}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const dieValues = roll.dice.flatMap((die) => die.results?.map((result) => result.result) ?? []);
|
||||
|
||||
await this.#createChatCard(actor, "systems/fvtt-donjon-et-cie/templates/chat/hit-dice-card.hbs", {
|
||||
title: game.i18n.localize("DNC.Roll.HitDice"),
|
||||
actorName: actor.name,
|
||||
formula: roll.formula,
|
||||
total: roll.total,
|
||||
dieValues
|
||||
});
|
||||
|
||||
return { formula: roll.formula, total: roll.total, dieValues };
|
||||
}
|
||||
|
||||
static async rollWeapon(actor, item, { mode = "normal", favorKey = "" } = {}) {
|
||||
const characteristicKey = DonjonEtCieUtility.getWeaponCharacteristicKey(item.system.categorie);
|
||||
const favor = await this.#resolveFavorBoost(actor, favorKey, mode);
|
||||
const effectiveMode = favor?.effectiveMode ?? mode;
|
||||
const result = await this.#resolveCharacteristic(actor, characteristicKey, { mode: effectiveMode });
|
||||
|
||||
if (!result) return null;
|
||||
|
||||
const characteristicLabel = DONJON_ET_CIE.characteristics[characteristicKey]?.label ?? characteristicKey;
|
||||
const characteristicShort = DONJON_ET_CIE.characteristics[characteristicKey]?.short ?? characteristicKey;
|
||||
|
||||
await this.#createChatCard(actor, "systems/fvtt-donjon-et-cie/templates/chat/roll-card.hbs", {
|
||||
title: `${game.i18n.localize("DNC.Roll.Attack")} : ${item.name}`,
|
||||
subtitle: DONJON_ET_CIE.weaponCategoryOptions[item.system.categorie] ?? item.system.categorie,
|
||||
formula: result.values.length > 1 ? "2d20" : "1d20",
|
||||
mode: effectiveMode,
|
||||
modeLabel: this.#getModeLabel(effectiveMode),
|
||||
target: result.target,
|
||||
targetPillLabel: characteristicShort,
|
||||
targetPillValue: result.target,
|
||||
values: result.values,
|
||||
kept: result.kept,
|
||||
keptPillLabel: "Jet",
|
||||
keptPillValue: result.kept,
|
||||
success: result.success,
|
||||
favorLabel: favor?.label ?? null,
|
||||
favorNote: favor?.note ?? null,
|
||||
showDamageButton: result.success && Boolean(item.system.degats),
|
||||
itemUuid: item.uuid,
|
||||
details: [
|
||||
{ label: "Arme", value: item.name },
|
||||
{ label: "Caracteristique", value: characteristicLabel },
|
||||
{ label: `Valeur de ${characteristicLabel}`, value: result.target },
|
||||
{ label: "Degats", value: item.system.degats || "—" },
|
||||
{ label: "Portee", value: item.system.portee || "—" },
|
||||
...(favor ? [
|
||||
{ label: "Faveur", value: favor.label },
|
||||
{ label: "Dé de faveur", value: favor.result },
|
||||
{ label: "Avant", value: DonjonEtCieUtility.formatUsageDie(favor.before) },
|
||||
{ label: "Apres", value: DonjonEtCieUtility.formatUsageDie(favor.after) }
|
||||
] : [])
|
||||
]
|
||||
});
|
||||
|
||||
return { ...result, favor, mode: effectiveMode };
|
||||
}
|
||||
|
||||
static async rollDamage(actor, item, { mode = "normal" } = {}) {
|
||||
if (!item.system.degats) return null;
|
||||
const actorBonus = Number(actor?.system?.combat?.degatsBonus ?? 0);
|
||||
const totalBonus = actorBonus;
|
||||
const formula = totalBonus ? `${item.system.degats} + ${totalBonus}` : item.system.degats;
|
||||
const result = await this.#resolveFormulaRoll(formula, {}, { mode, favorable: "high" });
|
||||
const targets = DonjonEtCieUtility.getSceneDamageTargets();
|
||||
const rollDieLabels = result.rolls.map((roll) => {
|
||||
const dieValues = roll.dice.flatMap((die) => die.results?.map((dieResult) => dieResult.result) ?? []);
|
||||
return dieValues.length ? dieValues.join(" + ") : String(roll.total ?? "—");
|
||||
});
|
||||
const keptDieLabel = rollDieLabels[result.keptIndex] ?? rollDieLabels[0] ?? String(result.kept);
|
||||
|
||||
await this.#createChatCard(actor ?? item.actor, "systems/fvtt-donjon-et-cie/templates/chat/damage-card.hbs", {
|
||||
title: `${game.i18n.localize("DNC.Roll.Damage")} : ${item.name}`,
|
||||
subtitle: item.system.portee || item.type,
|
||||
formula: result.rolls.length > 1 ? `2 × ${result.formula}` : result.formula,
|
||||
mode: result.mode,
|
||||
modeLabel: this.#getModeLabel(result.mode),
|
||||
rollDieLabels,
|
||||
keptDieLabel,
|
||||
values: result.values,
|
||||
total: result.kept,
|
||||
bonus: totalBonus,
|
||||
baseDamage: item.system.degats,
|
||||
sourceLabel: item.name,
|
||||
targets,
|
||||
hasTargets: targets.length > 0
|
||||
});
|
||||
|
||||
return { total: result.kept, formula: result.formula, bonus: totalBonus, values: result.values, mode: result.mode };
|
||||
}
|
||||
|
||||
static async applyDamage(target, { damage = 0, useArmor = false, sourceLabel = "" } = {}) {
|
||||
const actor = target?.actor ?? target;
|
||||
if (!actor || actor.documentName !== "Actor") {
|
||||
ui.notifications.warn(game.i18n.localize("DNC.Chat.InvalidDamageTarget"));
|
||||
return null;
|
||||
}
|
||||
|
||||
const targetName = target?.name ?? actor.name;
|
||||
const applied = await actor.applyIncomingDamage(damage, { useArmor });
|
||||
|
||||
await this.#createChatCard(actor, "systems/fvtt-donjon-et-cie/templates/chat/damage-application-card.hbs", {
|
||||
title: game.i18n.localize("DNC.Chat.DamageApplied"),
|
||||
subtitle: targetName,
|
||||
sourceLabel,
|
||||
total: applied.hpDamage,
|
||||
incoming: applied.incoming,
|
||||
useArmor: applied.useArmor,
|
||||
armorLabel: applied.armorLabel,
|
||||
armorAvailable: applied.armorAvailable,
|
||||
armorBefore: applied.armorBefore,
|
||||
armorAbsorbed: applied.armorAbsorbed,
|
||||
armorAfter: applied.armorAfter,
|
||||
pvBefore: applied.pvBefore,
|
||||
pvAfter: applied.pvAfter,
|
||||
pvMax: applied.pvMax
|
||||
});
|
||||
|
||||
return { actor, targetName, ...applied };
|
||||
}
|
||||
|
||||
static async rollSpell(actor, item, { mode = "normal", favorKey = "" } = {}) {
|
||||
const characteristicKey = item.system.caracteristique || "intelligence";
|
||||
const focus = await this.#ensureFocus(actor);
|
||||
const rank = Number(actor.system.anciennete?.rang ?? actor.system.sante?.dv ?? 0);
|
||||
const cost = Number(item.system.coutPv ?? 0);
|
||||
const autoDisadvantage = cost > rank;
|
||||
const baseMode = autoDisadvantage ? "desavantage" : mode;
|
||||
const favor = await this.#resolveFavorBoost(actor, favorKey, baseMode);
|
||||
const effectiveMode = favor?.effectiveMode ?? baseMode;
|
||||
const result = await this.#resolveCharacteristic(actor, characteristicKey, { mode: effectiveMode });
|
||||
|
||||
if (!result) return null;
|
||||
|
||||
const currentPv = Number(actor.system.sante?.pv?.value ?? 0);
|
||||
const availableMagicHp = currentPv + focus.activeValue;
|
||||
|
||||
if (cost > availableMagicHp) {
|
||||
ui.notifications.warn("Le lanceur ne dispose pas d'assez de PV et de focus pour payer ce sort.");
|
||||
return null;
|
||||
}
|
||||
|
||||
const characteristicShort = DONJON_ET_CIE.characteristics[characteristicKey]?.short ?? characteristicKey;
|
||||
const success = result.isNaturalTwenty ? false : result.success;
|
||||
const focusSpent = result.isNaturalOne ? 0 : Math.min(cost, focus.activeValue);
|
||||
const focusRemaining = Math.max(focus.activeValue - focusSpent, 0);
|
||||
const spentPv = result.isNaturalOne ? 0 : Math.max(cost - focusSpent, 0);
|
||||
const remainingPv = Math.max(currentPv - spentPv, 0);
|
||||
const updateData = {};
|
||||
|
||||
if (spentPv !== 0) {
|
||||
updateData["system.sante.pv.value"] = remainingPv;
|
||||
}
|
||||
|
||||
if (focusSpent !== 0) {
|
||||
updateData["system.magie.focus.resultat"] = focusRemaining;
|
||||
}
|
||||
|
||||
if (Object.keys(updateData).length) {
|
||||
await actor.update(updateData);
|
||||
}
|
||||
|
||||
const canInvokeChaos = !success && !result.isNaturalTwenty && Number(actor.system.magie?.chaos?.delta ?? 12) >= 4;
|
||||
const specialNote = result.isNaturalTwenty
|
||||
? "20 naturel : la magie tourne a la catastrophe, au choix du MJ."
|
||||
: (result.isNaturalOne ? "1 naturel : effet benefique possible ; par defaut, aucun PV n'est depense." : null);
|
||||
|
||||
await this.#createChatCard(actor, "systems/fvtt-donjon-et-cie/templates/chat/spell-card.hbs", {
|
||||
title: `${game.i18n.localize("DNC.Roll.Spell")} : ${item.name}`,
|
||||
subtitle: item.system.portee || "Sortilege",
|
||||
formula: result.values.length > 1 ? "2d20" : "1d20",
|
||||
mode: effectiveMode,
|
||||
modeLabel: this.#getModeLabel(effectiveMode),
|
||||
autoDisadvantage,
|
||||
autoDisadvantageCanceled: autoDisadvantage && Boolean(favor),
|
||||
favorLabel: favor?.label ?? null,
|
||||
favorNote: favor?.note ?? null,
|
||||
targetPillLabel: characteristicShort,
|
||||
targetPillValue: result.target,
|
||||
values: result.values,
|
||||
kept: result.kept,
|
||||
keptPillLabel: "Jet",
|
||||
keptPillValue: result.kept,
|
||||
success,
|
||||
specialNote,
|
||||
showDamageButton: success && Boolean(item.system.degats),
|
||||
showChaosButton: canInvokeChaos,
|
||||
itemUuid: item.uuid,
|
||||
actorUuid: actor.uuid,
|
||||
details: [
|
||||
{ label: "Sortilege", value: item.name },
|
||||
{ label: "Caracteristique", value: result.characteristic.label },
|
||||
{ label: "Valeur de la caracteristique", value: result.target },
|
||||
{ label: "Cout en PV", value: cost },
|
||||
{ label: "Focus", value: focus.activeValue > 0 ? `${focus.activeValue} (${DonjonEtCieUtility.formatUsageDie(focus.before)})` : "—" },
|
||||
{ label: "Focus depense", value: focusSpent },
|
||||
{ label: "Focus restant", value: focusRemaining },
|
||||
{ label: "PV depenses", value: spentPv },
|
||||
{ label: "PV restants", value: remainingPv },
|
||||
{ label: "Rang du lanceur", value: rank },
|
||||
{ label: "Difficulte", value: item.system.difficulte ?? 0 },
|
||||
{ label: "Effet", value: item.system.effet || "—" },
|
||||
...(favor ? [
|
||||
{ label: "Faveur", value: favor.label },
|
||||
{ label: "Dé de faveur", value: favor.result },
|
||||
{ label: "Avant", value: DonjonEtCieUtility.formatUsageDie(favor.before) },
|
||||
{ label: "Apres", value: DonjonEtCieUtility.formatUsageDie(favor.after) }
|
||||
] : [])
|
||||
],
|
||||
focusRolled: focus.rolled,
|
||||
focusValue: focus.activeValue,
|
||||
focusSpent,
|
||||
focusRemaining,
|
||||
focusBeforeLabel: DonjonEtCieUtility.formatUsageDie(focus.before),
|
||||
focusAfterLabel: DonjonEtCieUtility.formatUsageDie(focus.after),
|
||||
focusDegraded: focus.degraded,
|
||||
spentPv,
|
||||
remainingPv
|
||||
});
|
||||
|
||||
return { ...result, success, spentPv, remainingPv, cost, focus, focusSpent, focusRemaining, favor, mode: effectiveMode };
|
||||
}
|
||||
|
||||
static async rollSpellChaos(actor, item) {
|
||||
const before = Number(actor?.system?.magie?.chaos?.delta ?? 12);
|
||||
if (!before || before < 4) {
|
||||
ui.notifications.warn("Le Chaos n'est pas disponible pour ce sort.");
|
||||
return null;
|
||||
}
|
||||
|
||||
const resolved = await this.#resolveFormulaRoll(`1d${before}`, {}, { favorable: "high" });
|
||||
const result = resolved.kept;
|
||||
const degraded = result <= 3;
|
||||
const after = degraded ? DonjonEtCieUtility.degradeUsageDie(before) : before;
|
||||
const chaosEntry = DONJON_ET_CIE.chaosTable[result] ?? null;
|
||||
|
||||
if (after !== before) {
|
||||
await actor.update({ "system.magie.chaos.delta": after });
|
||||
}
|
||||
|
||||
await this.#createChatCard(actor, "systems/fvtt-donjon-et-cie/templates/chat/chaos-card.hbs", {
|
||||
title: `Chaos : ${item.name}`,
|
||||
value: result,
|
||||
before: DonjonEtCieUtility.formatUsageDie(before),
|
||||
after: DonjonEtCieUtility.formatUsageDie(after),
|
||||
chaosEntry,
|
||||
degraded,
|
||||
exhausted: after < 4,
|
||||
itemName: item.name
|
||||
});
|
||||
|
||||
return { result, before, after, degraded, chaosEntry };
|
||||
}
|
||||
|
||||
static async rollUsage(item, { mode = "normal" } = {}) {
|
||||
const before = Number(item.system.delta ?? 0);
|
||||
if (!before) return null;
|
||||
|
||||
const resolved = await this.#resolveFormulaRoll(`1d${before}`, {}, { mode, favorable: "high" });
|
||||
const result = resolved.kept;
|
||||
const degraded = result <= 3;
|
||||
const after = degraded ? DonjonEtCieUtility.degradeUsageDie(before) : before;
|
||||
const updateData = {};
|
||||
|
||||
if (item.type === "armure") {
|
||||
updateData["system.resultatProtection"] = result;
|
||||
}
|
||||
|
||||
if (after !== before) {
|
||||
updateData["system.delta"] = after;
|
||||
}
|
||||
|
||||
if (Object.keys(updateData).length) {
|
||||
await item.update(updateData);
|
||||
}
|
||||
|
||||
await this.#createChatCard(item.actor, "systems/fvtt-donjon-et-cie/templates/chat/usage-card.hbs", {
|
||||
title: `${game.i18n.localize("DNC.Roll.Usage")} : ${item.name}`,
|
||||
value: result,
|
||||
values: resolved.values,
|
||||
mode: resolved.mode,
|
||||
modeLabel: this.#getModeLabel(resolved.mode),
|
||||
before: DonjonEtCieUtility.formatUsageDie(before),
|
||||
after: DonjonEtCieUtility.formatUsageDie(after),
|
||||
protectionStored: item.type === "armure" ? result : null,
|
||||
degraded,
|
||||
exhausted: after === 0
|
||||
});
|
||||
|
||||
return { result, values: resolved.values, mode: resolved.mode, before, after, degraded };
|
||||
}
|
||||
}
|
||||
189
modules/donjon-et-cie-utility.mjs
Normal file
189
modules/donjon-et-cie-utility.mjs
Normal file
@@ -0,0 +1,189 @@
|
||||
import { DONJON_ET_CIE } from "./donjon-et-cie-config.mjs";
|
||||
|
||||
export class DonjonEtCieUtility {
|
||||
static defaultItemIcons = {
|
||||
arme: "systems/fvtt-donjon-et-cie/assets/icons/system/items/arme.svg",
|
||||
armure: "systems/fvtt-donjon-et-cie/assets/icons/system/items/armure.svg",
|
||||
trait: "systems/fvtt-donjon-et-cie/assets/icons/system/items/trait.svg",
|
||||
sortilege: "systems/fvtt-donjon-et-cie/assets/icons/system/items/sortilege.svg",
|
||||
equipement: "systems/fvtt-donjon-et-cie/assets/icons/system/items/equipement.svg",
|
||||
other: "systems/fvtt-donjon-et-cie/assets/icons/system/items/autre.svg"
|
||||
};
|
||||
|
||||
static async preloadHandlebarsTemplates() {
|
||||
return foundry.applications.handlebars.loadTemplates([
|
||||
"systems/fvtt-donjon-et-cie/templates/actors/employe-sheet.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/actors/pnj-sheet.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/items/item-sheet.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/characteristic-roll.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/initiative-roll.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/weapon-roll.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/damage-roll.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/spell-roll.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/usage-roll.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/chat/roll-card.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/chat/spell-card.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/chat/chaos-card.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/chat/hit-dice-card.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/chat/damage-card.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/chat/damage-application-card.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/chat/favor-card.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/chat/initiative-card.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/chat/usage-card.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/chat/item-card.hbs"
|
||||
]);
|
||||
}
|
||||
|
||||
static getCharacteristicEntries(system) {
|
||||
return Object.entries(DONJON_ET_CIE.characteristics).map(([key, metadata]) => ({
|
||||
key,
|
||||
label: metadata.label,
|
||||
short: metadata.short,
|
||||
value: system.caracteristiques?.[key]?.value ?? 0
|
||||
}));
|
||||
}
|
||||
|
||||
static formatUsageDie(value) {
|
||||
return value ? `Δ${value}` : "—";
|
||||
}
|
||||
|
||||
static getDefaultItemIcon(type) {
|
||||
return this.defaultItemIcons[type] ?? this.defaultItemIcons.other;
|
||||
}
|
||||
|
||||
static getCurrentSceneId() {
|
||||
return canvas?.scene?.id ?? game.scenes?.current?.id ?? "global";
|
||||
}
|
||||
|
||||
static getSceneDamageTargets() {
|
||||
const scene = canvas?.scene ?? game.scenes?.current;
|
||||
const tokens = scene?.tokens?.contents ?? [];
|
||||
|
||||
return tokens
|
||||
.map((token) => {
|
||||
const actor = token.actor;
|
||||
if (!actor || !["employe", "pnj"].includes(actor.type)) return null;
|
||||
|
||||
const tokenName = token.name || actor.name;
|
||||
const actorName = actor.name || tokenName;
|
||||
const label = tokenName === actorName ? tokenName : `${tokenName} (${actorName})`;
|
||||
|
||||
return {
|
||||
tokenId: token.id,
|
||||
tokenUuid: token.uuid,
|
||||
actorUuid: actor.uuid,
|
||||
label
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => a.label.localeCompare(b.label, "fr", { sensitivity: "base" }));
|
||||
}
|
||||
|
||||
static getMagicResourceContext(actor) {
|
||||
const rank = Number(actor.system.anciennete?.rang ?? actor.system.sante?.dv ?? 0);
|
||||
const focusDelta = Number(actor.system.magie?.focus?.delta ?? 0);
|
||||
const focusResult = Number(actor.system.magie?.focus?.resultat ?? 0);
|
||||
const focusSceneId = actor.system.magie?.focus?.sceneId ?? "";
|
||||
const activeFocus = focusSceneId === this.getCurrentSceneId() ? focusResult : 0;
|
||||
const chaosDelta = Number(actor.system.magie?.chaos?.delta ?? 12);
|
||||
|
||||
return {
|
||||
rank,
|
||||
focusDelta,
|
||||
focusLabel: this.formatUsageDie(focusDelta),
|
||||
focusSceneId,
|
||||
focusStoredResult: focusResult,
|
||||
focusActiveValue: activeFocus,
|
||||
focusIsActive: activeFocus > 0,
|
||||
focusDisplay: activeFocus > 0 ? `${activeFocus} (${this.formatUsageDie(focusDelta)})` : "—",
|
||||
chaosDelta,
|
||||
chaosLabel: this.formatUsageDie(chaosDelta),
|
||||
chaosAvailable: chaosDelta >= 4
|
||||
};
|
||||
}
|
||||
|
||||
static getFavorLabel(key) {
|
||||
return DONJON_ET_CIE.favorDepartments[key] ?? key;
|
||||
}
|
||||
|
||||
static getFavorEntries(system) {
|
||||
const favors = system.faveurs ?? {};
|
||||
return Object.entries(DONJON_ET_CIE.favorDepartments).map(([key, label]) => {
|
||||
const delta = Number(favors[key]?.delta ?? 0);
|
||||
return {
|
||||
key,
|
||||
label,
|
||||
delta,
|
||||
deltaLabel: this.formatUsageDie(delta),
|
||||
hasFavor: delta > 0
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
static getAvailableFavorOptions(actor) {
|
||||
return this.getFavorEntries(actor.system)
|
||||
.filter((entry) => entry.hasFavor)
|
||||
.map((entry) => ({ value: entry.key, label: `${entry.label} (${entry.deltaLabel})` }));
|
||||
}
|
||||
|
||||
static getChaosTableEntries() {
|
||||
return Object.entries(DONJON_ET_CIE.chaosTable)
|
||||
.map(([value, entry]) => ({ value: Number(value), ...entry }))
|
||||
.sort((a, b) => a.value - b.value);
|
||||
}
|
||||
|
||||
static degradeUsageDie(value) {
|
||||
const sequence = [12, 10, 8, 6, 4];
|
||||
const index = sequence.indexOf(Number(value));
|
||||
if (index === -1) return 0;
|
||||
return sequence[index + 1] ?? 0;
|
||||
}
|
||||
|
||||
static sortByName(documents) {
|
||||
return [...documents].sort((a, b) => a.name.localeCompare(b.name, "fr", { sensitivity: "base" }));
|
||||
}
|
||||
|
||||
static getWeaponCharacteristicKey(category) {
|
||||
return category === "distance" ? "dexterite" : "force";
|
||||
}
|
||||
|
||||
static getWeaponCharacteristicLabel(category) {
|
||||
const key = this.getWeaponCharacteristicKey(category);
|
||||
return DONJON_ET_CIE.characteristics[key]?.label ?? key;
|
||||
}
|
||||
|
||||
static enrichItemForSheet(item) {
|
||||
const system = item.system;
|
||||
const delta = Number(system.delta ?? 0);
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
type: item.type,
|
||||
img: item.img,
|
||||
system,
|
||||
uuid: item.uuid,
|
||||
usageLabel: delta > 0 ? this.formatUsageDie(delta) : null,
|
||||
protectionLabel: item.type === "armure" && Number(system.resultatProtection ?? 0) > 0 ? `Protection ${system.resultatProtection}` : null,
|
||||
weaponCharacteristicLabel: item.type === "arme" ? this.getWeaponCharacteristicLabel(system.categorie) : null,
|
||||
canRoll: ["arme", "sortilege"].includes(item.type),
|
||||
canUse: delta > 0,
|
||||
canRollDamage: Boolean(system.degats),
|
||||
rollAction: item.type === "sortilege" ? "rollSpell" : "rollWeapon",
|
||||
damageAction: "rollDamage",
|
||||
isEquipped: Boolean(system.equipee)
|
||||
};
|
||||
}
|
||||
|
||||
static buildActorSections(actor) {
|
||||
return Object.entries(DONJON_ET_CIE.actorSections).map(([key, metadata]) => {
|
||||
const types = DONJON_ET_CIE.sectionTypes[key];
|
||||
const items = this.sortByName(actor.items.filter((item) => types.includes(item.type))).map((item) => this.enrichItemForSheet(item));
|
||||
return {
|
||||
key,
|
||||
label: metadata.label,
|
||||
createType: metadata.createType,
|
||||
items
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
16
modules/models/arme.mjs
Normal file
16
modules/models/arme.mjs
Normal file
@@ -0,0 +1,16 @@
|
||||
import BaseItemDataModel from "./base-item.mjs";
|
||||
|
||||
export default class ArmeDataModel extends BaseItemDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
categorie: new fields.StringField({ initial: "melee" }),
|
||||
caracteristique: new fields.StringField({ initial: "force" }),
|
||||
degats: new fields.StringField({ initial: "1d6" }),
|
||||
portee: new fields.StringField({ initial: "" }),
|
||||
mains: new fields.NumberField({ initial: 1, integer: true }),
|
||||
equipee: new fields.BooleanField({ initial: false })
|
||||
};
|
||||
}
|
||||
}
|
||||
14
modules/models/armure.mjs
Normal file
14
modules/models/armure.mjs
Normal file
@@ -0,0 +1,14 @@
|
||||
import BaseItemDataModel from "./base-item.mjs";
|
||||
|
||||
export default class ArmureDataModel extends BaseItemDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
delta: new fields.NumberField({ initial: 8, integer: true }),
|
||||
resultatProtection: new fields.NumberField({ initial: 0, integer: true }),
|
||||
equipee: new fields.BooleanField({ initial: false }),
|
||||
encombrement: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
9
modules/models/base-item.mjs
Normal file
9
modules/models/base-item.mjs
Normal file
@@ -0,0 +1,9 @@
|
||||
export default class BaseItemDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
notes: new fields.HTMLField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
12
modules/models/capacite.mjs
Normal file
12
modules/models/capacite.mjs
Normal file
@@ -0,0 +1,12 @@
|
||||
import BaseItemDataModel from "./base-item.mjs";
|
||||
|
||||
export default class CapaciteDataModel extends BaseItemDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
cout: new fields.StringField({ initial: "" }),
|
||||
effet: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
13
modules/models/consommable.mjs
Normal file
13
modules/models/consommable.mjs
Normal file
@@ -0,0 +1,13 @@
|
||||
import BaseItemDataModel from "./base-item.mjs";
|
||||
|
||||
export default class ConsommableDataModel extends BaseItemDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
quantite: new fields.NumberField({ initial: 1, integer: true }),
|
||||
delta: new fields.NumberField({ initial: 6, integer: true }),
|
||||
effet: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
66
modules/models/employe.mjs
Normal file
66
modules/models/employe.mjs
Normal file
@@ -0,0 +1,66 @@
|
||||
import { DONJON_ET_CIE } from "../donjon-et-cie-config.mjs";
|
||||
|
||||
export default class EmployeDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
const makeCharacteristic = (label, short) => new fields.SchemaField({
|
||||
label: new fields.StringField({ initial: label }),
|
||||
short: new fields.StringField({ initial: short }),
|
||||
value: new fields.NumberField({ initial: 10, integer: true })
|
||||
});
|
||||
const favorFields = Object.fromEntries(Object.keys(DONJON_ET_CIE.favorDepartments).map((key) => [
|
||||
key,
|
||||
new fields.SchemaField({
|
||||
delta: new fields.NumberField({ initial: 0, integer: true })
|
||||
})
|
||||
]));
|
||||
|
||||
return {
|
||||
concept: new fields.StringField({ initial: "" }),
|
||||
anciennete: new fields.SchemaField({
|
||||
rang: new fields.NumberField({ initial: 1, integer: true }),
|
||||
libelle: new fields.StringField({ initial: "Nouvel employe" })
|
||||
}),
|
||||
caracteristiques: new fields.SchemaField({
|
||||
force: makeCharacteristic("FORce", "FOR"),
|
||||
dexterite: makeCharacteristic("DEXterite", "DEX"),
|
||||
constitution: makeCharacteristic("CONstitution", "CON"),
|
||||
intelligence: makeCharacteristic("INTelligence", "INT"),
|
||||
sagesse: makeCharacteristic("SAGesse", "SAG"),
|
||||
charisme: makeCharacteristic("CHArisme", "CHA")
|
||||
}),
|
||||
sante: new fields.SchemaField({
|
||||
dv: new fields.StringField({ initial: "1d6" }),
|
||||
pv: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 6, integer: true }),
|
||||
max: new fields.NumberField({ initial: 6, integer: true })
|
||||
})
|
||||
}),
|
||||
combat: new fields.SchemaField({
|
||||
initiativeBonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
degatsBonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
attaquesCorpsACorps: new fields.NumberField({ initial: 1, integer: true }),
|
||||
attaquesDistance: new fields.NumberField({ initial: 1, integer: true })
|
||||
}),
|
||||
magie: new fields.SchemaField({
|
||||
focus: new fields.SchemaField({
|
||||
delta: new fields.NumberField({ initial: 0, integer: true }),
|
||||
resultat: new fields.NumberField({ initial: 0, integer: true }),
|
||||
sceneId: new fields.StringField({ initial: "" })
|
||||
}),
|
||||
chaos: new fields.SchemaField({
|
||||
delta: new fields.NumberField({ initial: 12, integer: true })
|
||||
})
|
||||
}),
|
||||
profil: new fields.SchemaField({
|
||||
objectifPersonnel: new fields.HTMLField({ initial: "" }),
|
||||
suspicion: new fields.NumberField({ initial: 0, integer: true }),
|
||||
avertissements: new fields.NumberField({ initial: 0, integer: true }),
|
||||
missionsReussies: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
faveurs: new fields.SchemaField(favorFields),
|
||||
notes: new fields.HTMLField({ initial: "" }),
|
||||
gmnotes: new fields.HTMLField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
13
modules/models/equipement.mjs
Normal file
13
modules/models/equipement.mjs
Normal file
@@ -0,0 +1,13 @@
|
||||
import BaseItemDataModel from "./base-item.mjs";
|
||||
|
||||
export default class EquipementDataModel extends BaseItemDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
quantite: new fields.NumberField({ initial: 1, integer: true }),
|
||||
equipee: new fields.BooleanField({ initial: false }),
|
||||
emplacement: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
11
modules/models/index.mjs
Normal file
11
modules/models/index.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
export { default as BaseItemDataModel } from "./base-item.mjs";
|
||||
export { default as TraitDataModel } from "./trait.mjs";
|
||||
export { default as LangueDataModel } from "./langue.mjs";
|
||||
export { default as CapaciteDataModel } from "./capacite.mjs";
|
||||
export { default as SortilegeDataModel } from "./sortilege.mjs";
|
||||
export { default as ArmeDataModel } from "./arme.mjs";
|
||||
export { default as ArmureDataModel } from "./armure.mjs";
|
||||
export { default as EquipementDataModel } from "./equipement.mjs";
|
||||
export { default as ConsommableDataModel } from "./consommable.mjs";
|
||||
export { default as EmployeDataModel } from "./employe.mjs";
|
||||
export { default as PnjDataModel } from "./pnj.mjs";
|
||||
11
modules/models/langue.mjs
Normal file
11
modules/models/langue.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import BaseItemDataModel from "./base-item.mjs";
|
||||
|
||||
export default class LangueDataModel extends BaseItemDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
niveau: new fields.StringField({ initial: "courant" })
|
||||
};
|
||||
}
|
||||
}
|
||||
36
modules/models/pnj.mjs
Normal file
36
modules/models/pnj.mjs
Normal file
@@ -0,0 +1,36 @@
|
||||
export default class PnjDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
espece: new fields.StringField({ initial: "" }),
|
||||
categorie: new fields.StringField({ initial: "Resident" }),
|
||||
role: new fields.StringField({ initial: "" }),
|
||||
resume: new fields.StringField({ initial: "" }),
|
||||
sante: new fields.SchemaField({
|
||||
dv: new fields.StringField({ initial: "1d8" }),
|
||||
pv: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 6, integer: true }),
|
||||
max: new fields.NumberField({ initial: 6, integer: true })
|
||||
})
|
||||
}),
|
||||
defense: new fields.SchemaField({
|
||||
armure: new fields.SchemaField({
|
||||
delta: new fields.NumberField({ initial: 0, integer: true }),
|
||||
resultatProtection: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
courage: new fields.SchemaField({
|
||||
delta: new fields.NumberField({ initial: 0, integer: true })
|
||||
})
|
||||
}),
|
||||
attaque: new fields.SchemaField({
|
||||
nom: new fields.StringField({ initial: "Attaque" }),
|
||||
degats: new fields.StringField({ initial: "1d6" }),
|
||||
notes: new fields.StringField({ initial: "" })
|
||||
}),
|
||||
pouvoirsSpeciaux: new fields.HTMLField({ initial: "" }),
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
notes: new fields.HTMLField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
17
modules/models/sortilege.mjs
Normal file
17
modules/models/sortilege.mjs
Normal file
@@ -0,0 +1,17 @@
|
||||
import BaseItemDataModel from "./base-item.mjs";
|
||||
|
||||
export default class SortilegeDataModel extends BaseItemDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
caracteristique: new fields.StringField({ initial: "intelligence" }),
|
||||
difficulte: new fields.NumberField({ initial: 0, integer: true }),
|
||||
coutPv: new fields.NumberField({ initial: 0, integer: true }),
|
||||
portee: new fields.StringField({ initial: "" }),
|
||||
duree: new fields.StringField({ initial: "" }),
|
||||
effet: new fields.StringField({ initial: "" }),
|
||||
degats: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
11
modules/models/trait.mjs
Normal file
11
modules/models/trait.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import BaseItemDataModel from "./base-item.mjs";
|
||||
|
||||
export default class TraitDataModel extends BaseItemDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
etiquette: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user