Files
fvtt-donjon-et-cie/modules/donjon-et-cie-actor.mjs
T
uberwald c6ddc96148
Release Creation / build (release) Failing after 49s
Fiche PNJ : Autorise multi-attaques
2026-04-26 15:45:53 +02:00

267 lines
8.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Donjon & Cie - Systeme FoundryVTT
*
* Donjon & Cie est un jeu de role edite par John Doe.
* Ce systeme FoundryVTT est une implementation independante et n'est pas
* affilie a John Doe.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
import { DonjonEtCieUtility } from "./donjon-et-cie-utility.mjs";
import { DonjonEtCieRollDialog } from "./applications/donjon-et-cie-roll-dialog.mjs";
export class DonjonEtCieActor extends Actor {
static defaultPnjAttack = {
nom: "Attaque",
degats: "1d6",
notes: ""
};
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);
}
async resetUsage(itemId) {
const item = this.items.get(itemId);
if (item?.type === "entrainement") return item.resetUsageDie();
}
getPnjAttacks() {
if (this.type !== "pnj") return [];
const attacks = Array.isArray(this.system.attaques) ? this.system.attaques : [];
if (attacks.length) return attacks.map((attack, index) => ({
index,
nom: attack.nom || `Attaque ${index + 1}`,
degats: attack.degats || "",
notes: attack.notes || ""
}));
const legacy = this.system.attaque;
if (legacy) {
return [{
index: 0,
nom: legacy.nom || "Attaque",
degats: legacy.degats || "",
notes: legacy.notes || ""
}];
}
return [{ index: 0, ...foundry.utils.deepClone(this.constructor.defaultPnjAttack) }];
}
async createPnjAttack() {
if (this.type !== "pnj") return null;
const attaques = this.getPnjAttacks().map(({ nom, degats, notes }) => ({ nom, degats, notes }));
attaques.push(foundry.utils.deepClone(this.constructor.defaultPnjAttack));
return this.update({ "system.attaques": attaques });
}
async deletePnjAttack(index) {
if (this.type !== "pnj") return null;
const attaques = this.getPnjAttacks().map(({ nom, degats, notes }) => ({ nom, degats, notes }));
attaques.splice(Number(index), 1);
if (!attaques.length) attaques.push(foundry.utils.deepClone(this.constructor.defaultPnjAttack));
return this.update({ "system.attaques": attaques });
}
#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(index = 0) {
const attack = this.getPnjAttacks()[Number(index)] ?? null;
const attackName = attack?.nom || "Attaque";
const attackDamage = attack?.degats || "";
if (!attackDamage) return null;
return DonjonEtCieRollDialog.createDamage(this, {
name: `${this.name} · ${attackName}`,
type: "attaque",
system: {
degats: attackDamage,
portee: attack?.notes || ""
}
});
}
}