diff --git a/changelog.md b/changelog.md
index 1f36d362..b07eda4d 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,21 @@
# v11.0
+## v11.1.0 - Les choix de Werther de Zloth
+- Les options suivantes peuvent être désactivées:
+ - La transformation de stress à Château Dormant
+ - La récuperation de chance à Château Dormant
+ - La récupération d'éthylisme
+ - La récupération de rêve (y compris fleurs de rêve et Rêves de Dragon: la rencontre a lieu, mais ne donne pas de rêve)
+ - Le jet de moral de Château Dormant
+- Séparation des véhicules dans leur propre acteur
+- Séparation des entités dans leur propre acteur
+- Séparation des créatures dans leur propre acteur
+- La fenêtre de signes draconiques ne sélectionne plus tout les haut-rêvants par défaut
+- Un nouveau personnage a automatiquement son token relié
+- corrections de bugs
+ - si on n'utilise pas les règles de fatigues, un reflet de rêve pouvait garder le Haut-rêvant dans les TMRs pour toujours
+ - certaines macros ne marchaient pas pour les créatures/entités/véhicules/commerces
+ - en cas de charge, les particulières sont toujours en force (p125)
+
## v11.0.28 - les fractures de Khrachtchoum
- La gravité de la blessure est affichée dans le résumé de l'encaissement
- Lors du changement d'acteur pendant le round
diff --git a/module/actor-sheet.js b/module/actor-sheet.js
index b1716797..750c6896 100644
--- a/module/actor-sheet.js
+++ b/module/actor-sheet.js
@@ -11,17 +11,18 @@ import { ReglesOptionnelles } from "./settings/regles-optionnelles.js";
import { RdDSheetUtility } from "./rdd-sheet-utility.js";
import { STATUSES } from "./settings/status-effects.js";
import { MAINS_DIRECTRICES } from "./actor.js";
-import { RdDBaseActorSheet } from "./actor/base-actor-sheet.js";
+import { RdDBaseActorReveSheet } from "./actor/base-actor-reve-sheet.js";
import { RdDItem } from "./item.js";
import { RdDItemBlessure } from "./item/blessure.js";
import { RdDEmpoignade } from "./rdd-empoignade.js";
+import { ChatUtility } from "./chat-utility.js";
/* -------------------------------------------- */
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
-export class RdDActorSheet extends RdDBaseActorSheet {
+export class RdDActorSheet extends RdDBaseActorReveSheet {
/** @override */
static get defaultOptions() {
@@ -56,7 +57,7 @@ export class RdDActorSheet extends RdDBaseActorSheet {
surprise: RdDBonus.find(this.actor.getSurprise(false)).descr,
resumeBlessures: this.actor.computeResumeBlessure(this.actor.system.blessures),
caracTotal: RdDCarac.computeTotal(this.actor.system.carac, this.actor.system.beaute),
- surEncombrementMessage: this.actor.getMessageSurEncombrement(),
+ surEncombrementMessage: this.actor.isSurenc() ? "Sur-Encombrement!" : "",
malusArmure: this.actor.getMalusArmure()
})
@@ -115,7 +116,8 @@ export class RdDActorSheet extends RdDBaseActorSheet {
return formData;
}
- /* -------------------------------------------- */ /** @override */
+ /* -------------------------------------------- */
+ /** @override */
activateListeners(html) {
super.activateListeners(html);
@@ -124,10 +126,10 @@ export class RdDActorSheet extends RdDBaseActorSheet {
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
- this.html.find('.item-action').click(async event => {
- const item = RdDSheetUtility.getItem(event, this.actor);
- item?.actionPrincipale(this.actor, async () => this.render())
- });
+ this.html.find('.sheet-possession-attack').click(async event => {
+ const poss = RdDSheetUtility.getItem(event, this.actor)
+ this.actor.conjurerPossession(poss)
+ })
this.html.find('.subacteur-delete').click(async event => {
const li = RdDSheetUtility.getEventElement(event);
@@ -158,18 +160,6 @@ export class RdDActorSheet extends RdDBaseActorSheet {
this.actor.updateCompteurValue("experience", parseInt(event.target.value));
});
- this.html.find('.encaisser-direct').click(async event => {
- this.actor.encaisser();
- })
- this.html.find('.sheet-possession-attack').click(async event => {
- const poss = RdDSheetUtility.getItem(event, this.actor)
- this.actor.conjurerPossession(poss)
- })
- this.html.find('.remise-a-neuf').click(async event => {
- if (game.user.isGM) {
- this.actor.remiseANeuf();
- }
- });
this.html.find('.creer-tache').click(async event => {
this.createEmptyTache();
});
@@ -206,11 +196,6 @@ export class RdDActorSheet extends RdDBaseActorSheet {
});
// Roll Carac
- this.html.find('.carac-label a').click(async event => {
- let caracName = event.currentTarget.attributes.name.value;
- this.actor.rollCarac(caracName.toLowerCase());
- });
-
this.html.find('.chance-actuelle').click(async event => {
this.actor.rollCarac('chance-actuelle');
});
@@ -219,14 +204,10 @@ export class RdDActorSheet extends RdDBaseActorSheet {
this.actor.rollAppelChance();
});
+ // Roll Skill
this.html.find('[name="jet-astrologie"]').click(async event => {
this.actor.astrologieNombresAstraux();
});
-
- // Roll Skill
- this.html.find('a.competence-label').click(async event => {
- this.actor.rollCompetence(RdDSheetUtility.getItemId(event));
- });
this.html.find('.tache-label a').click(async event => {
this.actor.rollTache(RdDSheetUtility.getItemId(event));
});
@@ -292,35 +273,12 @@ export class RdDActorSheet extends RdDBaseActorSheet {
ui.notifications.info("Impossible de lancer l'initiative sans être dans un combat.");
}
});
- // Display TMR, visualisation
- this.html.find('.visu-tmr').click(async event => {
- this.actor.displayTMR("visu");
- });
+ // Display TMR
+ this.html.find('.visu-tmr').click(async event => { this.actor.displayTMR("visu") })
+ this.html.find('.monte-tmr').click(async event => { this.actor.displayTMR("normal") })
+ this.html.find('.monte-tmr-rapide').click(async event => { this.actor.displayTMR("rapide") })
- // Display TMR, normal
- this.html.find('.monte-tmr').click(async event => {
- this.actor.displayTMR("normal");
- });
-
- // Display TMR, fast
- this.html.find('.monte-tmr-rapide').click(async event => {
- this.actor.displayTMR("rapide");
- });
-
- this.html.find('.repos').click(async event => {
- await this.actor.repos();
- });
- this.html.find('.delete-active-effect').click(async event => {
- if (game.user.isGM) {
- let effect = this.html.find(event.currentTarget).parents(".active-effect").data('effect');
- this.actor.removeEffect(effect);
- }
- });
- this.html.find('.enlever-tous-effets').click(async event => {
- if (game.user.isGM) {
- await this.actor.removeEffects();
- }
- });
+ this.html.find('.repos').click(async event => { await this.actor.repos() })
this.html.find('.carac-xp-augmenter').click(async event => {
let caracName = event.currentTarget.name.replace("augmenter.", "");
this.actor.updateCaracXPAuto(caracName);
@@ -334,30 +292,20 @@ export class RdDActorSheet extends RdDBaseActorSheet {
if (this.options.vueDetaillee) {
// On carac change
- this.html.find('.carac-value').change(async event => {
- let caracName = event.currentTarget.name.replace(".value", "").replace("system.carac.", "");
- this.actor.updateCarac(caracName, parseInt(event.target.value));
- });
this.html.find('input.carac-xp').change(async event => {
let caracName = event.currentTarget.name.replace(".xp", "").replace("system.carac.", "");
this.actor.updateCaracXP(caracName, parseInt(event.target.value));
});
- // On competence change
- this.html.find('.competence-value').change(async event => {
- let compName = event.currentTarget.attributes.compname.value;
- //console.log("Competence changed :", compName);
- this.actor.updateCompetence(compName, parseInt(event.target.value));
- });
// On competence xp change
this.html.find('input.competence-xp').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCompetenceXP(compName, parseInt(event.target.value));
});
- // On competence xp change
this.html.find('input.competence-xp-sort').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCompetenceXPSort(compName, parseInt(event.target.value));
});
+
this.html.find('.toggle-archetype').click(async event => {
this.options.vueArchetype = !this.options.vueArchetype;
this.render(true);
@@ -394,11 +342,6 @@ export class RdDActorSheet extends RdDBaseActorSheet {
this.actor.setPointsDeSeuil(event.currentTarget.value);
});
- // On stress change
- this.html.find('.compteur-edit').change(async event => {
- let fieldName = event.currentTarget.attributes.name.value;
- this.actor.updateCompteurValue(fieldName, parseInt(event.target.value));
- });
this.html.find('.stress-test').click(async event => {
this.actor.transformerStress();
@@ -420,7 +363,7 @@ export class RdDActorSheet extends RdDBaseActorSheet {
this.actor.jetVie();
});
this.html.find('.jet-endurance').click(async event => {
- this.actor.jetEndurance();
+ await this.jetEndurance();
});
this.html.find('.vie-plus').click(async event => {
@@ -429,12 +372,6 @@ export class RdDActorSheet extends RdDBaseActorSheet {
this.html.find('.vie-moins').click(async event => {
this.actor.santeIncDec("vie", -1);
});
- this.html.find('.endurance-plus').click(async event => {
- this.actor.santeIncDec("endurance", 1);
- });
- this.html.find('.endurance-moins').click(async event => {
- this.actor.santeIncDec("endurance", -1);
- });
this.html.find('.ptreve-actuel-plus').click(async event => {
this.actor.reveActuelIncDec(1);
});
@@ -449,6 +386,16 @@ export class RdDActorSheet extends RdDBaseActorSheet {
});
}
+ async jetEndurance() {
+ const endurance = this.actor.getEnduranceActuelle()
+ const result = await this.actor.jetEndurance(endurance);
+ ChatMessage.create({
+ content: `Jet d'Endurance : ${result.jetEndurance} / ${endurance}
+
${this.actor.name} a ${result.sonne ? 'échoué' : 'réussi'} son Jet d'Endurance ${result.sonne ? 'et devient Sonné' : ''}`,
+ whisper: ChatUtility.getWhisperRecipientsAndGMs(this.actor.name)
+ });
+ }
+
getBlessure(event) {
const itemId = this.html.find(event.currentTarget).parents(".item-blessure").data('item-id');
const blessure = this.actor.getItem(itemId, 'blessure');
diff --git a/module/actor.js b/module/actor.js
index 7b41567c..c0e7f15a 100644
--- a/module/actor.js
+++ b/module/actor.js
@@ -10,13 +10,9 @@ import { RdDRollTables } from "./rdd-rolltables.js";
import { ChatUtility } from "./chat-utility.js";
import { RdDItemSort } from "./item-sort.js";
import { Grammar } from "./grammar.js";
-import { RdDEncaisser } from "./rdd-roll-encaisser.js";
-import { RdDCombat } from "./rdd-combat.js";
import { RdDItemCompetence } from "./item-competence.js";
-import { RdDItemArme } from "./item-arme.js";
import { RdDAlchimie } from "./rdd-alchimie.js";
-import { STATUSES, StatusEffects } from "./settings/status-effects.js";
-import { RdDItemCompetenceCreature } from "./item-competencecreature.js";
+import { STATUSES } from "./settings/status-effects.js";
import { RdDItemSigneDraconique } from "./item/signedraconique.js";
import { ReglesOptionnelles } from "./settings/regles-optionnelles.js";
import { EffetsDraconiques } from "./tmr/effets-draconiques.js";
@@ -26,11 +22,9 @@ import { DialogConsommer } from "./dialog-item-consommer.js";
import { DialogFabriquerPotion } from "./dialog-fabriquer-potion.js";
import { RollDataAjustements } from "./rolldata-ajustements.js";
import { RdDPossession } from "./rdd-possession.js";
-import { ENTITE_INCARNE, ENTITE_NONINCARNE, SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js";
+import { SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js";
import { RdDConfirm } from "./rdd-confirm.js";
-import { DialogValidationEncaissement } from "./dialog-validation-encaissement.js";
import { RdDRencontre } from "./item/rencontre.js";
-import { Targets } from "./targets.js";
import { DialogRepos } from "./sommeil/dialog-repos.js";
import { RdDBaseActor } from "./actor/base-actor.js";
import { RdDTimestamp } from "./time/rdd-timestamp.js";
@@ -39,6 +33,7 @@ import { AppAstrologie } from "./sommeil/app-astrologie.js";
import { RdDEmpoignade } from "./rdd-empoignade.js";
import { ExperienceLog, XP_TOPIC } from "./actor/experience-log.js";
import { TYPES } from "./item.js";
+import { RdDBaseActorSang } from "./actor/base-actor-sang.js";
const POSSESSION_SANS_DRACONIC = {
img: 'systems/foundryvtt-reve-de-dragon/icons/entites/possession.webp',
@@ -56,149 +51,70 @@ export const MAINS_DIRECTRICES = ['Droitier', 'Gaucher', 'Ambidextre']
* Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system.
* @extends {Actor}
*/
-export class RdDActor extends RdDBaseActor {
-
- /* -------------------------------------------- */
- prepareData() {
- super.prepareData();
-
- // Dynamic computing fields
- this.encTotal = 0;
- // TODO: separate derived/base data preparation
- // TODO: split by actor class
-
- // Make separate methods for each Actor type (character, npc, etc.) to keep
- // things organized.
- if (this.isPersonnage()) this._prepareCharacterData(this)
- if (this.isCreatureEntite()) this._prepareCreatureData(this)
- if (this.isVehicule()) this._prepareVehiculeData(this)
- this.computeEtatGeneral();
- }
-
- /* -------------------------------------------- */
- _prepareCreatureData(actorData) {
- this.computeEncTotal();
- }
-
- /* -------------------------------------------- */
- _prepareVehiculeData(actorData) {
- this.computeEncTotal();
- }
+export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */
/**
* Prepare Character type specific data
*/
- async _prepareCharacterData(actorData) {
- // Initialize empty items
- RdDCarac.computeCarac(actorData.system)
- this.computeIsHautRevant();
- await this.cleanupConteneurs();
- await this.computeEncTotal();
+ prepareActorData() {
+ this.$computeCaracDerivee()
+ this.$computeIsHautRevant()
}
/* -------------------------------------------- */
- async cleanupConteneurs() {
- let updates = this.itemTypes['conteneur']
- .filter(c => c.system.contenu.filter(id => this.getItem(id) == undefined).length > 0)
- .map(c => { return { _id: c._id, 'system.contenu': c.system.contenu.filter(id => this.getItem(id) != undefined) } });
- if (updates.length > 0) {
- await this.updateEmbeddedDocuments("Item", updates)
- }
+ $computeCaracDerivee() {
+ this.system.carac.force.value = Math.min(this.system.carac.force.value, parseInt(this.system.carac.taille.value) + 4);
+
+ this.system.carac.derobee.value = Math.floor(parseInt(((21 - this.system.carac.taille.value)) + parseInt(this.system.carac.agilite.value)) / 2);
+ let bonusDomKey = Math.floor((parseInt(this.system.carac.force.value) + parseInt(this.system.carac.taille.value)) / 2);
+ let tailleData = RdDCarac.getCaracDerivee(bonusDomKey);
+ this.system.attributs.plusdom.value = tailleData.plusdom;
+
+ this.system.attributs.sconst.value = RdDCarac.calculSConst(this.system.carac.constitution.value);
+ this.system.attributs.sust.value = RdDCarac.getCaracDerivee(this.system.carac.taille.value).sust;
+
+ this.system.attributs.encombrement.value = (parseInt(this.system.carac.force.value) + parseInt(this.system.carac.taille.value)) / 2;
+ this.system.carac.melee.value = Math.floor((parseInt(this.system.carac.force.value) + parseInt(this.system.carac.agilite.value)) / 2);
+ this.system.carac.tir.value = Math.floor((parseInt(this.system.carac.vue.value) + parseInt(this.system.carac.dexterite.value)) / 2);
+ this.system.carac.lancer.value = Math.floor((parseInt(this.system.carac.tir.value) + parseInt(this.system.carac.force.value)) / 2);
+
+ this.system.sante.vie.max = Math.ceil((parseInt(this.system.carac.taille.value) + parseInt(this.system.carac.constitution.value)) / 2);
+
+ this.system.sante.vie.value = Math.min(this.system.sante.vie.value, this.system.sante.vie.max)
+ this.system.sante.endurance.max = Math.max(parseInt(this.system.carac.taille.value) + parseInt(this.system.carac.constitution.value), parseInt(this.system.sante.vie.max) + parseInt(this.system.carac.volonte.value));
+ this.system.sante.endurance.value = Math.min(this.system.sante.endurance.value, this.system.sante.endurance.max);
+ this.system.sante.fatigue.max = this.getFatigueMax();
+ this.system.sante.fatigue.value = Math.min(this.system.sante.fatigue.value, this.system.sante.fatigue.max);
+
+ //Compteurs
+ this.system.reve.reve.max = this.system.carac.reve.value;
+ this.system.compteurs.chance.max = this.system.carac.chance.value;
}
canReceive(item) {
- if (this.isCreature()) {
- return item.type == TYPES.competencecreature || item.isInventaire();
- }
- if (this.isEntite()) {
- return item.type == 'competencecreature';
- }
- if (this.isVehicule()) {
- return item.isInventaire();
- }
- if (this.isPersonnage()) {
- switch (item.type) {
- case 'competencecreature': case 'tarot': case 'service':
- return false;
- }
- return true;
- }
- return false;
+ return ![TYPES.competencecreature, TYPES.tarot, TYPES.service].includes(item.type)
}
/* -------------------------------------------- */
- isHautRevant() {
- return this.isPersonnage() && this.system.attributs.hautrevant.value != ""
- }
- /* -------------------------------------------- */
- getFatigueActuelle() {
- if (ReglesOptionnelles.isUsing("appliquer-fatigue") && this.isPersonnage()) {
- return this.system.sante.fatigue?.value;
- }
- return 0;
- }
- /* -------------------------------------------- */
- getFatigueMax() {
- if (!this.isPersonnage()) {
- return 1;
- }
- return Misc.toInt(this.system.sante.fatigue?.max);
- }
- /* -------------------------------------------- */
+ isPersonnage() { return true }
+ isHautRevant() { return this.system.attributs.hautrevant.value != "" }
+
getReveActuel() {
- switch (this.type) {
- case 'personnage':
- return Misc.toInt(this.system.reve?.reve?.value ?? this.carac.reve.value);
- case 'creature':
- case 'entite':
- return Misc.toInt(this.system.carac.reve?.value)
- case 'vehicule':
- default:
- return 0;
- }
+ return Misc.toInt(this.system.reve?.reve?.value ?? this.carac.reve.value);
}
- /* -------------------------------------------- */
getChanceActuel() {
return Misc.toInt(this.system.compteurs.chance?.value ?? 10);
}
+
+ getAgilite() { return Number(this.system.carac.agilite?.value ?? 0) }
+ getChance() { return Number(this.system.carac.chance?.value ?? 0) }
+
/* -------------------------------------------- */
- getTaille() {
- return Misc.toInt(this.system.carac.taille?.value);
- }
- /* -------------------------------------------- */
- getForce() {
- if (this.isEntite()) {
- return Misc.toInt(this.system.carac.reve?.value);
- }
- return Misc.toInt(this.system.carac.force?.value);
- }
- /* -------------------------------------------- */
- getAgilite() {
- switch (this.type) {
- case 'personnage': return Misc.toInt(this.system.carac.agilite?.value);
- case 'creature': return Misc.toInt(this.system.carac.force?.value);
- case 'entite': return Misc.toInt(this.system.carac.reve?.value);
- }
- return 10;
- }
- /* -------------------------------------------- */
- getChance() {
- return Number(this.system.carac.chance?.value ?? 10);
- }
getMoralTotal() {
return Number(this.system.compteurs.moral?.value ?? 0);
}
- /* -------------------------------------------- */
- getBonusDegat() {
- // TODO: gérer séparation et +dom créature/entité indépendament de la compétence
- return Number(this.system.attributs.plusdom.value ?? 0);
- }
- /* -------------------------------------------- */
- getProtectionNaturelle() {
- return Number(this.system.attributs.protection.value ?? 0);
- }
/* -------------------------------------------- */
getEtatGeneral(options = { ethylisme: false }) {
@@ -217,36 +133,11 @@ export class RdDActor extends RdDBaseActor {
/* -------------------------------------------- */
getMalusArmure() {
- if (this.isPersonnage()) {
- return this.itemTypes[TYPES.armure].filter(it => it.system.equipe)
- .map(it => it.system.malus)
- .reduce(Misc.sum(), 0);
- }
- return 0;
+ return this.itemTypes[TYPES.armure].filter(it => it.system.equipe)
+ .map(it => it.system.malus)
+ .reduce(Misc.sum(), 0);
}
- /* -------------------------------------------- */
- getEncTotal() {
- return Math.floor(this.encTotal ?? 0);
- }
-
- /* -------------------------------------------- */
- getCompetence(idOrName, options = {}) {
- if (idOrName instanceof Item) {
- return idOrName.isCompetence() ? idOrName : undefined
- }
- return RdDItemCompetence.findCompetence(this.items, idOrName, options)
- }
-
- getCompetences(name) {
- return RdDItemCompetence.findCompetences(this.items, name)
- }
- getCompetenceCorpsACorps(options = {}) {
- return this.getCompetence("Corps à corps", options)
- }
- getCompetencesEsquive() {
- return this.getCompetences("esquive")
- }
/* -------------------------------------------- */
getTache(id) {
return this.findItemLike(id, 'tache');
@@ -284,27 +175,12 @@ export class RdDActor extends RdDBaseActor {
}
getDraconicOuPossession() {
- const possession = this.itemTypes[TYPES.competencecreature].filter(it => it.system.categorie == 'possession')
+ return [...this.getDraconicList().filter(it => it.system.niveau >= 0),
+ super.getDraconicOuPossession()]
.sort(Misc.descending(it => it.system.niveau))
- .find(it => true);
- if (possession) {
- return possession;
- }
- const draconics = [...this.getDraconicList().filter(it => it.system.niveau >= 0),
- POSSESSION_SANS_DRACONIC]
- .sort(Misc.descending(it => it.system.niveau));
- return draconics[0];
+ .find(it => true)
}
- getPossession(possessionId) {
- return this.itemTypes[TYPES.possession].find(it => it.system.possessionid == possessionId);
- }
- getPossessions() {
- return this.itemTypes[TYPES.possession];
- }
- getEmpoignades() {
- return this.itemTypes[TYPES.empoignade];
- }
getDemiReve() {
return this.system.reve.tmrpos.coord;
}
@@ -333,66 +209,11 @@ export class RdDActor extends RdDBaseActor {
}
}
- /* -------------------------------------------- */
- getSurprise(isCombat = undefined) {
- let niveauSurprise = this.getEffects()
- .map(effect => StatusEffects.valeurSurprise(effect, isCombat))
- .reduce(Misc.sum(), 0);
- if (niveauSurprise > 1) {
- return 'totale';
- }
- if (niveauSurprise == 1) {
- return 'demi';
- }
- return '';
- }
-
/* -------------------------------------------- */
hasArmeeMeleeEquipee() { // Return true si l'acteur possède au moins 1 arme de mêlée équipée
return this.itemTypes['arme'].find(it => it.system.equipe && it.system.competence != "")
}
- /* -------------------------------------------- */
- async roll() {
- RdDEmpoignade.checkEmpoignadeEnCours(this)
-
- const carac = mergeObject(duplicate(this.system.carac),
- {
- 'reve-actuel': this.getCaracReveActuel(),
- 'chance-actuelle': this.getCaracChanceActuelle()
- });
-
- await this._openRollDialog({
- name: `jet-${this.id}`,
- label: `Jet de ${this.name}`,
- template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll.html',
- rollData: {
- carac: carac,
- selectedCarac: carac.apparence,
- selectedCaracName: 'apparence',
- competences: this.itemTypes['competence']
- },
- callbackAction: r => this.$onRollCaracResult(r)
- });
- }
-
- async _openRollDialog({ name, label, template, rollData, callbackAction }) {
- const dialog = await RdDRoll.create(this, rollData,
- { html: template, close: html => { this.tmrApp?.restoreTMRAfterAction() } },
- {
- name: name,
- label: label,
- callbacks: [
- this.createCallbackExperience(),
- this.createCallbackAppelAuMoral(),
- { action: callbackAction }
- ]
- });
- dialog.render(true);
- return dialog
- }
-
-
async prepareChateauDormant(consigne) {
if (consigne.ignorer) {
return;
@@ -407,10 +228,6 @@ export class RdDActor extends RdDBaseActor {
}
}
- findPlayer() {
- return game.users.players.find(player => player.active && player.character?.id == this.id);
- }
-
async onTimeChanging(oldTimestamp, newTimestamp) {
await super.onTimeChanging(oldTimestamp, newTimestamp);
await this.setInfoSommeilInsomnie();
@@ -486,7 +303,7 @@ export class RdDActor extends RdDBaseActor {
};
await this._recuperationSante(message)
- await this._jetDeMoralChateauDormant(message);
+ await this._recupereMoralChateauDormant(message);
await this._recupereChance();
await this.transformerStress();
await this.retourSeuilDeReve(message);
@@ -524,6 +341,8 @@ export class RdDActor extends RdDBaseActor {
/* -------------------------------------------- */
async _recupereChance() {
+ if (!ReglesOptionnelles.isUsing("recuperation-chance")) { return }
+
// On ne récupère un point de chance que si aucun appel à la chance dans la journée
if (this.getChanceActuel() < this.getChance() && !this.getFlag(SYSTEM_RDD, 'utilisationChance')) {
await this.chanceActuelleIncDec(1);
@@ -532,7 +351,9 @@ export class RdDActor extends RdDBaseActor {
await this.unsetFlag(SYSTEM_RDD, 'utilisationChance');
}
- async _jetDeMoralChateauDormant(message) {
+ async _recupereMoralChateauDormant(message) {
+ if (!ReglesOptionnelles.isUsing("recuperation-moral")) { return }
+
const etatMoral = this.system.sommeil?.moral ?? 'neutre';
const jetMoral = await this._jetDeMoral(etatMoral);
message.content += ` -- le jet de moral est ${etatMoral}, le moral ` + this._messageAjustementMoral(jetMoral.ajustement);
@@ -568,12 +389,6 @@ export class RdDActor extends RdDBaseActor {
await this.supprimerBlessures(it => it.system.gravite <= 0);
}
- async supprimerBlessures(filterToDelete) {
- const toDelete = this.filterItems(filterToDelete, TYPES.blessure)
- .map(it => it.id);
- await this.deleteEmbeddedDocuments('Item', toDelete);
- }
-
/* -------------------------------------------- */
async _recupererVie(message, isMaladeEmpoisonne) {
const tData = this.system
@@ -615,26 +430,19 @@ export class RdDActor extends RdDBaseActor {
/* -------------------------------------------- */
async remiseANeuf() {
- if (this.isEntite([ENTITE_NONINCARNE])) {
- return;
- }
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
content: 'Remise à neuf de ' + this.name
});
- const updates = {
- 'system.sante.endurance.value': this.system.sante.endurance.max
- };
- if (this.isPersonnage() || this.isCreature()) {
- await this.supprimerBlessures(it => true);
- updates['system.sante.vie.value'] = this.system.sante.vie.max;
- updates['system.sante.fatigue.value'] = 0;
- }
- if (this.isPersonnage()) {
- updates['system.compteurs.ethylisme'] = { value: 1, nb_doses: 0, jet_moral: false };
- }
- await this.update(updates);
+ await this.supprimerBlessures(it => true);
await this.removeEffects(e => e.flags.core.statusId !== STATUSES.StatusDemiReve);
+ const updates = {
+ 'system.sante.endurance.value': this.system.sante.endurance.max,
+ 'system.sante.vie.value': this.system.sante.vie.max,
+ 'system.sante.fatigue.value': 0,
+ 'system.compteurs.ethylisme': { value: 1, nb_doses: 0, jet_moral: false }
+ };
+ await this.update(updates);
}
/* -------------------------------------------- */
@@ -710,8 +518,17 @@ export class RdDActor extends RdDBaseActor {
return 'eveil';
}
else {
- await this.reveActuelIncDec(reve);
- jetsReve.push(reve);
+ if (!ReglesOptionnelles.isUsing("recuperation-reve")) {
+ ChatMessage.create({
+ whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
+ content: `Pas de récupération de rêve (${reve} points ignorés)`
+ });
+ jetsReve.push(0);
+ }
+ else {
+ await this.reveActuelIncDec(reve);
+ jetsReve.push(reve);
+ }
}
}
return 'dort';
@@ -719,6 +536,7 @@ export class RdDActor extends RdDBaseActor {
/* -------------------------------------------- */
async _recupererEthylisme(message) {
+ if (!ReglesOptionnelles.isUsing("recuperation-ethylisme")) { return; }
let value = Math.min(Number.parseInt(this.system.compteurs.ethylisme.value) + 1, 1);
if (value <= 0) {
message.content += `Vous dégrisez un peu (${RdDUtility.getNomEthylisme(value)}). `;
@@ -750,7 +568,7 @@ export class RdDActor extends RdDBaseActor {
async recupererFatigue(message) {
if (ReglesOptionnelles.isUsing("appliquer-fatigue")) {
let fatigue = this.system.sante.fatigue.value;
- const fatigueMin = this._computeFatigueMin();
+ const fatigueMin = this.getFatigueMin();
if (fatigue <= fatigueMin) {
return;
}
@@ -877,7 +695,7 @@ export class RdDActor extends RdDBaseActor {
this.setPointsDeChance(to);
}
}
- let selectedCarac = RdDActor._findCaracByName(this.system.carac, caracName);
+ let selectedCarac = RdDBaseActor._findCaracByName(this.system.carac, caracName);
const from = selectedCarac.value
await this.update({ [`system.carac.${caracName}.value`]: to });
await ExperienceLog.add(this, XP_TOPIC.CARAC, from, to, caracName);
@@ -888,7 +706,7 @@ export class RdDActor extends RdDBaseActor {
if (caracName == 'Taille') {
return;
}
- let selectedCarac = RdDActor._findCaracByName(this.system.carac, caracName);
+ let selectedCarac = RdDBaseActor._findCaracByName(this.system.carac, caracName);
if (!selectedCarac.derivee) {
const from = Number(selectedCarac.xp);
await this.update({ [`system.carac.${caracName}.xp`]: to });
@@ -902,7 +720,7 @@ export class RdDActor extends RdDBaseActor {
if (caracName == 'Taille') {
return;
}
- let carac = RdDActor._findCaracByName(this.system.carac, caracName);
+ let carac = RdDBaseActor._findCaracByName(this.system.carac, caracName);
if (carac) {
carac = duplicate(carac);
const fromXp = Number(carac.xp);
@@ -976,25 +794,6 @@ export class RdDActor extends RdDBaseActor {
await ExperienceLog.add(this, XP_TOPIC.NIVEAU, fromNiveau, toNiveau, competence.name);
}
- /* -------------------------------------------- */
- async updateCreatureCompetence(idOrName, fieldName, value) {
- let competence = this.getCompetence(idOrName);
- if (competence) {
- function getPath(fieldName) {
- switch (fieldName) {
- case "niveau": return 'system.niveau';
- case "dommages": return 'system.dommages';
- case "carac_value": return 'system.carac_value';
- }
- return undefined
- }
- const path = getPath(fieldName);
- if (path) {
- await this.updateEmbeddedDocuments('Item', [{ _id: competence.id, [path]: value }]); // updates one EmbeddedEntity
- }
- }
- }
-
/* -------------------------------------------- */
async updateCompetence(idOrName, compValue) {
const competence = this.getCompetence(idOrName);
@@ -1099,7 +898,7 @@ export class RdDActor extends RdDBaseActor {
/* -------------------------------------------- */
async distribuerStress(compteur, stress, motif) {
- if (game.user.isGM && this.hasPlayerOwner && this.isPersonnage()) {
+ if (game.user.isGM && this.hasPlayerOwner) {
switch (compteur) {
case 'stress': case 'experience':
await this.addCompteurValue(compteur, stress, motif);
@@ -1116,98 +915,17 @@ export class RdDActor extends RdDBaseActor {
await this.update({ [`system.attributs.${fieldName}.value`]: fieldValue });
}
-
-
- isSurenc() {
- return this.isPersonnage() ? (this.computeMalusSurEncombrement() < 0) : false
- }
-
/* -------------------------------------------- */
- computeMalusSurEncombrement() {
- switch (this.type) {
- case 'entite': case 'vehicule':
- return 0;
- }
- return Math.min(0, Math.floor(this.getEncombrementMax() - this.encTotal));
+ $computeIsHautRevant() {
+ this.system.attributs.hautrevant.value = this.itemTypes['tete'].find(it => Grammar.equalsInsensitive(it.name, 'don de haut-reve'))
+ ? "Haut rêvant"
+ : "";
}
- getMessageSurEncombrement() {
- return this.computeMalusSurEncombrement() < 0 ? "Sur-Encombrement!" : "";
+ malusEthylisme() {
+ return Math.min(0, (this.system.compteurs.ethylisme?.value ?? 0));
}
- /* -------------------------------------------- */
- getEncombrementMax() {
- switch (this.type) {
- case 'vehicule':
- return this.system.capacite_encombrement;
- case 'entite':
- return 0;
- default:
- return this.system.attributs.encombrement.value
- }
- }
-
- /* -------------------------------------------- */
- computeIsHautRevant() {
- if (this.isPersonnage()) {
- this.system.attributs.hautrevant.value = this.itemTypes['tete'].find(it => Grammar.equalsInsensitive(it.name, 'don de haut-reve'))
- ? "Haut rêvant"
- : "";
- }
- }
-
- /* -------------------------------------------- */
- computeResumeBlessure() {
- const blessures = this.filterItems(it => it.system.gravite > 0, 'blessure')
-
- const nbLegeres = blessures.filter(it => it.isLegere()).length;
- const nbGraves = blessures.filter(it => it.isGrave()).length;
- const nbCritiques = blessures.filter(it => it.isCritique()).length;
-
- if (nbLegeres + nbGraves + nbCritiques == 0) {
- return "Aucune blessure";
- }
- let resume = "Blessures:";
- if (nbLegeres > 0) {
- resume += " " + nbLegeres + " légère" + (nbLegeres > 1 ? "s" : "");
- }
- if (nbGraves > 0) {
- if (nbLegeres > 0)
- resume += ",";
- resume += " " + nbGraves + " grave" + (nbGraves > 1 ? "s" : "");
- }
- if (nbCritiques > 0) {
- if (nbGraves > 0 || nbLegeres > 0)
- resume += ",";
- resume += " une CRITIQUE !";
- }
- return resume;
- }
-
- recompute() {
- this.computeEtatGeneral();
- }
-
- /* -------------------------------------------- */
- computeEtatGeneral() {
- // Pas d'état général pour les entités forçage à 0
- if (this.type == 'vehicule') {
- return
- }
- if (this.type == 'entite') {
- this.system.compteurs.etat.value = 0;
- return
- }
- // Pour les autres
- let state = Math.min(this.system.sante.vie.value - this.system.sante.vie.max, 0);
- if (ReglesOptionnelles.isUsing("appliquer-fatigue") && this.system.sante.fatigue) {
- state += RdDUtility.currentFatigueMalus(this.system.sante.fatigue.value, this.system.sante.endurance.max);
- }
- // Ajout de l'éthylisme
- state += Math.min(0, (this.system.compteurs.ethylisme?.value ?? 0));
-
- this.system.compteurs.etat.value = state;
- }
/* -------------------------------------------- */
async actionRefoulement(item) {
@@ -1352,7 +1070,7 @@ export class RdDActor extends RdDBaseActor {
/* -------------------------------------------- */
async reveActuelIncDec(value) {
- let reve = Math.max(this.system.reve.reve.value + value, 0);
+ const reve = Math.max(this.system.reve.reve.value + value, 0);
await this.update({ "system.reve.reve.value": reve });
}
@@ -1377,112 +1095,21 @@ export class RdDActor extends RdDBaseActor {
await this.updateCompteurValue("chance", chance);
}
- /* -------------------------------------------- */
- getSonne() {
- return this.getEffect(STATUSES.StatusStunned);
- }
-
- /* -------------------------------------------- */
- async finDeRound(options = { terminer: false }) {
- await this.$finDeRoundSuppressionEffetsTermines(options);
- await this.$finDeRoundBlessuresGraves();
- await this.$finDeRoundSupprimerObsoletes();
- await this.$finDeRoundEmpoignade();
- }
-
- async $finDeRoundSuppressionEffetsTermines(options) {
- for (let effect of this.getEffects()) {
- if (effect.duration.type !== 'none' && (effect.duration.remaining <= 0 || options.terminer)) {
- await effect.delete();
- ChatMessage.create({ content: `${this.name} n'est plus ${Misc.lowerFirst(game.i18n.localize(effect.system.label))} !` });
- }
- }
- }
-
- async $finDeRoundBlessuresGraves() {
- if (this.isPersonnage() || this.isCreature()) {
- const nbGraves = this.filterItems(it => it.isGrave(), 'blessure').length;
- if (nbGraves > 0) {
- // Gestion blessure graves : -1 pt endurance par blessure grave
- await this.santeIncDec("endurance", -nbGraves);
- }
- }
- }
-
- async $finDeRoundSupprimerObsoletes() {
- const obsoletes = []
- .concat(this.itemTypes[TYPES.empoignade].filter(it => it.system.pointsemp <= 0))
- .concat(this.itemTypes[TYPES.possession].filter(it => it.system.compteur < -2 || it.system.compteur > 2))
- .map(it => it.id);
- await this.deleteEmbeddedDocuments('Item', obsoletes);
- }
-
- async $finDeRoundEmpoignade() {
- const immobilisations = this.itemTypes[TYPES.empoignade].filter(it => it.system.pointsemp >= 2 && it.system.empoigneurid == this.id);
- immobilisations.forEach(emp => RdDEmpoignade.onImmobilisation(this,
- game.actors.get(emp.system.empoigneid),
- emp
- ))
- }
- /* -------------------------------------------- */
- async setSonne(sonne = true) {
- if (this.isEntite()) {
- return;
- }
- if (!game.combat && sonne) {
- ui.notifications.info("Le personnage est hors combat, il ne reste donc pas sonné");
- return;
- }
- await this.setEffect(STATUSES.StatusStunned, sonne);
- }
-
- /* -------------------------------------------- */
- getSConst() {
- if (this.isEntite()) {
- return 0;
- }
- return RdDCarac.calculSConst(this.system.carac.constitution.value)
- }
-
-
- async ajoutXpConstitution(xp) {
- await this.update({ "system.carac.constitution.xp": Misc.toInt(this.system.carac.constitution.xp) + xp });
- }
-
- /* -------------------------------------------- */
- countBlessures(filter = it => !it.isContusion()) {
- return this.filterItems(filter, 'blessure').length
- }
-
- /* -------------------------------------------- */
- async testSiSonne(endurance) {
- const result = await this._jetEndurance(endurance);
- if (result.roll.total == 1) {
+ async jetEndurance(resteEndurance = undefined) {
+ const result = super.jetEndurance(resteEndurance);
+ if (result.jetEndurance == 1) {
ChatMessage.create({ content: await this._gainXpConstitutionJetEndurance() });
}
return result;
}
/* -------------------------------------------- */
- async jetEndurance() {
- const endurance = this.system.sante.endurance.value;
+ getSConst() {
+ return RdDCarac.calculSConst(this.system.carac.constitution.value)
+ }
- const result = await this._jetEndurance(this.system.sante.endurance.value)
- const message = {
- content: "Jet d'Endurance : " + result.roll.total + " / " + endurance + "
",
- whisper: ChatMessage.getWhisperRecipients(this.name)
- };
- if (result.sonne) {
- message.content += `${this.name} a échoué son Jet d'Endurance et devient Sonné`;
- }
- else if (result.roll.total == 1) {
- message.content += await this._gainXpConstitutionJetEndurance();
- }
- else {
- message.content += `${this.name} a réussi son Jet d'Endurance !`;
- }
-
- ChatMessage.create(message);
+ async ajoutXpConstitution(xp) {
+ await this.update({ "system.carac.constitution.xp": Misc.toInt(this.system.carac.constitution.xp) + xp });
}
async _gainXpConstitutionJetEndurance() {
@@ -1490,116 +1117,6 @@ export class RdDActor extends RdDBaseActor {
return `${this.name} a obtenu 1 sur son Jet d'Endurance et a gagné 1 point d'Expérience en Constitution. Ce point d'XP a été ajouté automatiquement.`;
}
- async _jetEndurance(endurance) {
- const roll = await RdDDice.roll("1d20");
- let result = {
- roll: roll,
- sonne: roll.total > endurance || roll.total == 20 // 20 is always a failure
- }
- if (result.sonne) {
- await this.setSonne();
- }
- return result;
- }
-
- /* -------------------------------------------- */
- async jetVie() {
- let roll = await RdDDice.roll("1d20");
- let msgText = "Jet de Vie : " + roll.total + " / " + this.system.sante.vie.value + "
";
- if (roll.total <= this.system.sante.vie.value) {
- msgText += "Jet réussi, pas de perte de point de vie (prochain jet dans 1 round pour 1 critique, SC minutes pour une grave)";
- if (roll.total == 1) {
- msgText += "La durée entre 2 jets de vie est multipliée par 20 (20 rounds pour une critique, SCx20 minutes pour une grave)";
- }
- } else {
- msgText += "Jet échoué, vous perdez 1 point de vie";
- await this.santeIncDec("vie", -1);
- if (roll.total == 20) {
- msgText += "Votre personnage est mort !!!!!";
- }
- }
- const message = {
- content: msgText,
- whisper: ChatMessage.getWhisperRecipients(this.name)
- };
- ChatMessage.create(message);
- }
-
- /* -------------------------------------------- */
- async santeIncDec(name, inc, isCritique = false) {
- if (name == 'fatigue' && !ReglesOptionnelles.isUsing("appliquer-fatigue")) {
- return;
- }
- const sante = duplicate(this.system.sante)
- let compteur = sante[name];
- if (!compteur) {
- return;
- }
- let result = {
- sonne: false,
- };
-
- let minValue = name == "vie" ? -this.getSConst() - 1 : 0;
-
- result.newValue = Math.max(minValue, Math.min(compteur.value + inc, compteur.max));
- //console.log("New value ", inc, minValue, result.newValue);
- let fatigue = 0;
- if (name == "endurance" && !this.isEntite()) {
- if (result.newValue == 0 && inc < 0 && !isCritique) { // perte endurance et endurance devient 0 (sauf critique) -> -1 vie
- sante.vie.value--;
- result.perteVie = true;
- }
- result.newValue = Math.max(0, result.newValue);
- if (inc > 0) { // le max d'endurance s'applique seulement à la récupération
- result.newValue = Math.min(result.newValue, this._computeEnduranceMax())
- }
- const perte = compteur.value - result.newValue;
- result.perte = perte;
- if (perte > 1) {
- // Peut-être sonné si 2 points d'endurance perdus d'un coup
- const testIsSonne = await this.testSiSonne(result.newValue);
- result.sonne = testIsSonne.sonne;
- result.jetEndurance = testIsSonne.roll.total;
- } else if (inc > 0) {
- await this.setSonne(false);
- }
- if (sante.fatigue && inc < 0) { // Each endurance lost -> fatigue lost
- fatigue = perte;
- }
- }
- compteur.value = result.newValue;
- // If endurance lost, then the same amount of fatigue cannot be recovered
- if (ReglesOptionnelles.isUsing("appliquer-fatigue") && sante.fatigue && fatigue > 0) {
- sante.fatigue.value = Math.max(sante.fatigue.value + fatigue, this._computeFatigueMin());
- }
- await this.update({ "system.sante": sante })
- if (this.isDead()) {
- await this.setEffect(STATUSES.StatusComma, true);
- }
- return result;
- }
-
- async vehicleIncDec(name, inc) {
- if (!this.isVehicule() || !['resistance', 'structure'].includes(name)) {
- return
- }
- const value = this.system.etat[name].value;
- const max = this.system.etat[name].max;
- const newValue = value + inc;
- if (0 <= newValue && newValue <= max) {
- await this.update({ [`system.etat.${name}.value`]: newValue })
- }
- }
-
- isDead() {
- return !this.isEntite() && this.system.sante.vie.value < -this.getSConst()
- }
-
- /* -------------------------------------------- */
- _computeFatigueMin() {
- return this.system.sante.endurance.max - this.system.sante.endurance.value;
- }
-
/* -------------------------------------------- */
_computeEnduranceMax() {
const diffVie = this.system.sante.vie.max - this.system.sante.vie.value;
@@ -1701,7 +1218,7 @@ export class RdDActor extends RdDBaseActor {
case 'brut': {
let d = new Dialog({
title: "Nourriture brute",
- content: `Que faire de votre ${item.name}`,
+ content: `Que faire de votre ${item.name}`,
buttons: {
'cuisiner': { icon: '', label: 'Cuisiner', callback: async () => await this.preparerNourriture(item) },
'manger': { icon: '', label: 'Manger cru', callback: async () => await this.mangerNourriture(item, onActionItem) }
@@ -1911,6 +1428,7 @@ export class RdDActor extends RdDBaseActor {
/* -------------------------------------------- */
async transformerStress() {
+ if (!ReglesOptionnelles.isUsing("transformation-stress")) { return }
const fromStress = Number(this.system.compteurs.stress.value);
if (this.system.sommeil?.insomnie || fromStress <= 0) {
return;
@@ -2003,7 +1521,7 @@ export class RdDActor extends RdDBaseActor {
/* -------------------------------------------- */
async checkCaracXP(caracName, display = true) {
- let carac = RdDActor._findCaracByName(this.system.carac, caracName);
+ let carac = RdDBaseActor._findCaracByName(this.system.carac, caracName);
if (carac && carac.xp > 0) {
const niveauSuivant = Number(carac.value) + 1;
let xpNeeded = RdDCarac.getCaracNextXp(niveauSuivant);
@@ -2062,7 +1580,6 @@ export class RdDActor extends RdDBaseActor {
/* -------------------------------------------- */
async appliquerAjoutExperience(rollData, hideChatMessage = 'show') {
- if (!this.isPersonnage()) return;
hideChatMessage = hideChatMessage == 'hide' || (Misc.isRollModeHiddenToPlayer() && !game.user.isGM)
let xpData = await this._appliquerExperience(rollData.rolled, rollData.selectedCarac.label, rollData.competence, rollData.jetResistance);
if (xpData) {
@@ -2081,7 +1598,6 @@ export class RdDActor extends RdDBaseActor {
/* -------------------------------------------- */
async _appliquerAppelMoral(rollData) {
- if (!this.isPersonnage()) return;
if (!rollData.use.moral) return;
if (rollData.rolled.isEchec ||
(rollData.ajustements.diviseurSignificative && (rollData.rolled.roll * rollData.ajustements.diviseurSignificative > rollData.score))) {
@@ -2152,7 +1668,7 @@ export class RdDActor extends RdDBaseActor {
const draconicList = this.computeDraconicAndSortIndex(sorts);
const reve = duplicate(this.system.carac.reve);
- const dialog = await this._openRollDialog({
+ const dialog = await this.openRollDialog({
name: 'lancer-un-sort',
label: 'Lancer un sort',
template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-sort.html',
@@ -2196,7 +1712,7 @@ export class RdDActor extends RdDBaseActor {
}
/* -------------------------------------------- */
- getTMRFatigue() { // Pour l'instant uniquement Inertie Draconique
+ getCoutFatigueTMR() { // Pour l'instant uniquement Inertie Draconique
let countInertieDraconique = EffetsDraconiques.countInertieDraconique(this);
if (countInertieDraconique > 0) {
ChatMessage.create({
@@ -2272,29 +1788,6 @@ export class RdDActor extends RdDBaseActor {
}
}
- /* -------------------------------------------- */
- async rollCarac(caracName, jetResistance = undefined) {
- RdDEmpoignade.checkEmpoignadeEnCours(this)
- let selectedCarac = this.getCaracByName(caracName)
- await this._openRollDialog({
- name: 'jet-' + caracName,
- label: 'Jet ' + Grammar.apostrophe('de', selectedCarac.label),
- template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html',
- rollData: {
- selectedCarac: selectedCarac,
- competences: this.itemTypes['competence'],
- jetResistance: jetResistance ? caracName : undefined
- },
- callbackAction: r => this.$onRollCaracResult(r)
- });
- }
-
- /* -------------------------------------------- */
- async $onRollCaracResult(rollData) {
- // Final chat message
- await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-general.html');
- }
-
/**
* Méthode pour faire un jet prédéterminer sans ouvrir la fenêtre de dialogue
* @param {*} caracName
@@ -2340,45 +1833,6 @@ export class RdDActor extends RdDBaseActor {
}
}
- /* -------------------------------------------- */
- async rollCompetence(idOrName, options = { tryTarget: true }) {
- RdDEmpoignade.checkEmpoignadeEnCours(this)
- const competence = this.getCompetence(idOrName);
- let rollData = { carac: this.system.carac, competence: competence }
- if (competence.type == TYPES.competencecreature) {
- const arme = RdDItemCompetenceCreature.armeCreature(competence)
- if (arme && options.tryTarget && Targets.hasTargets()) {
- Targets.selectOneToken(target => {
- if (arme.action == "possession") {
- RdDPossession.onAttaquePossession(target, this, competence)
- }
- else {
- RdDCombat.rddCombatTarget(target, this).attaque(competence, arme)
- }
- });
- return;
- }
- // Transformer la competence de créature
- RdDItemCompetenceCreature.setRollDataCreature(rollData)
- }
-
- await this._openRollDialog({
- name: 'jet-competence',
- label: 'Jet ' + Grammar.apostrophe('de', competence.name),
- template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html',
- rollData: rollData,
- callbackAction: r => this.$onRollCompetence(r, options)
- });
- }
-
- /* -------------------------------------------- */
- async $onRollCompetence(rollData, options) {
- await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-competence.html')
- if (options?.onRollAutomate) {
- options.onRollAutomate(rollData);
- }
- }
-
/* -------------------------------------------- */
async creerTacheDepuisLivre(item, options = { renderSheet: true }) {
const nomTache = "Lire " + item.name;
@@ -2405,7 +1859,6 @@ export class RdDActor extends RdDBaseActor {
}
blessuresASoigner() {
- // TODO or not TODO: filtrer les blessures poour lesquels on ne peut plus faire de premiers soins?
return this.filterItems(it => it.system.gravite > 0 && it.system.gravite <= 6 && !(it.system.premierssoins.done && it.system.soinscomplets.done), 'blessure')
}
@@ -2423,7 +1876,7 @@ export class RdDActor extends RdDBaseActor {
async rollCaracCompetence(caracName, compName, diff, options = { title: "" }) {
RdDEmpoignade.checkEmpoignadeEnCours(this)
const competence = this.getCompetence(compName);
- await this._openRollDialog({
+ await this.openRollDialog({
name: 'jet-competence',
label: 'Jet ' + Grammar.apostrophe('de', competence.name),
template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html',
@@ -2447,7 +1900,7 @@ export class RdDActor extends RdDBaseActor {
const compData = this.getCompetence(tacheData.system.competence)
compData.system.defaut_carac = tacheData.system.carac; // Patch !
- await this._openRollDialog({
+ await this.openRollDialog({
name: 'jet-competence',
label: 'Jet de Tâche ' + tacheData.name,
template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html',
@@ -2510,7 +1963,7 @@ export class RdDActor extends RdDBaseActor {
artData.forceCarac[selected] = duplicate(this.system.carac[selected]);
}
- await this._openRollDialog({
+ await this.openRollDialog({
name: `jet-${artData.art}`,
label: `${artData.verbe} ${oeuvre.name}`,
template: `systems/foundryvtt-reve-de-dragon/templates/dialog-roll-${oeuvre.type}.html`,
@@ -2750,7 +2203,7 @@ export class RdDActor extends RdDBaseActor {
const dialog = await RdDRoll.create(this, rollData,
{
html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-signedraconique.html',
- close: html => { this.tmrApp?.restoreTMRAfterAction() }
+ close: async html => await this._onCloseRollDialog(html)
},
{
name: 'lire-signe-draconique',
@@ -2765,6 +2218,10 @@ export class RdDActor extends RdDBaseActor {
this.tmrApp?.setTMRPendingAction(dialog);
}
+ async _onCloseRollDialog(html) {
+ this.tmrApp?.restoreTMRAfterAction()
+ }
+
/* -------------------------------------------- */
async _rollLireSigneDraconique(rollData) {
const compData = rollData.competence;
@@ -2786,7 +2243,7 @@ export class RdDActor extends RdDBaseActor {
/* -------------------------------------------- */
async rollAppelChance(onSuccess = () => { }, onEchec = () => { }) {
- await this._openRollDialog({
+ await this.openRollDialog({
name: 'appelChance',
label: 'Appel à la chance',
template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html',
@@ -2830,20 +2287,15 @@ export class RdDActor extends RdDBaseActor {
/* -------------------------------------------- */
getHeureNaissance() {
- if (this.isPersonnage()) {
- return this.system.heure;
- }
- return 0;
+ return this.system.heure ?? 0;
}
/* -------------------------------------------- */
ajustementAstrologique() {
- if (this.isCreatureEntite()) {
- return 0;
- }
// selon l'heure de naissance...
return game.system.rdd.calendrier.getAjustementAstrologique(this.system.heure, this.name)
}
+
/* -------------------------------------------- */
checkDesirLancinant() {
let queue = this.filterItems(it => it.type == 'queue' || it.type == 'ombre')
@@ -2853,7 +2305,6 @@ export class RdDActor extends RdDBaseActor {
/* -------------------------------------------- */
async _appliquerExperience(rolled, caracName, competence, jetResistance) {
- if (!this.isPersonnage()) return;
// Pas d'XP
if (!rolled.isPart || rolled.finalLevel >= 0) {
return undefined;
@@ -2904,7 +2355,7 @@ export class RdDActor extends RdDBaseActor {
async _xpCarac(xpData) {
if (xpData.xpCarac > 0) {
let carac = duplicate(this.system.carac);
- let selectedCarac = RdDActor._findCaracByName(carac, xpData.caracName);
+ let selectedCarac = RdDBaseActor._findCaracByName(carac, xpData.caracName);
if (!selectedCarac.derivee) {
const from = Number(selectedCarac.xp);
const to = from + xpData.xpCarac;
@@ -2957,51 +2408,6 @@ export class RdDActor extends RdDBaseActor {
await AppAstrologie.create(this);
}
- /* -------------------------------------------- */
- getCaracByName(name) {
- switch (Grammar.toLowerCaseNoAccent(name)) {
- case 'reve-actuel': case 'reve actuel':
- return this.getCaracReveActuel();
- case 'chance-actuelle': case 'chance-actuelle':
- return this.getCaracChanceActuelle();
- }
- return RdDActor._findCaracByName(this.system.carac, name);
- }
-
- getCaracChanceActuelle() {
- return {
- label: 'Chance actuelle',
- value: this.getChanceActuel(),
- type: "number"
- };
- }
-
- getCaracReveActuel() {
- return {
- label: 'Rêve actuel',
- value: this.getReveActuel(),
- type: "number"
- };
- }
-
- /* -------------------------------------------- */
- static _findCaracByName(carac, name) {
- name = Grammar.toLowerCaseNoAccent(name);
- switch (name) {
- case 'reve-actuel': case 'reve actuel':
- return carac.reve;
- case 'chance-actuelle': case 'chance actuelle':
- return carac.chance;
- }
-
- const caracList = Object.entries(carac);
- let entry = Misc.findFirstLike(name, caracList, { mapper: it => it[0], description: 'caractéristique' });
- if (!entry || entry.length == 0) {
- entry = Misc.findFirstLike(name, caracList, { mapper: it => it[1].label, description: 'caractéristique' });
- }
- return entry && entry.length > 0 ? carac[entry[0]] : undefined;
- }
-
/* -------------------------------------------- */
countMonteeLaborieuse() { // Return +1 par queue/ombre/souffle Montée Laborieuse présente
let countMonteeLaborieuse = EffetsDraconiques.countMonteeLaborieuse(this);
@@ -3076,61 +2482,6 @@ export class RdDActor extends RdDBaseActor {
}
/* -------------------------------------------- */
- getCompetenceArme(arme, competenceName) {
- switch (arme.type) {
- case TYPES.competencecreature:
- return arme.name
- case TYPES.arme:
- switch (competenceName) {
- case 'competence': return arme.system.competence;
- case 'unemain': return RdDItemArme.competence1Mains(arme);
- case 'deuxmains': return RdDItemArme.competence2Mains(arme);
- case 'tir': return arme.system.tir;
- case 'lancer': return arme.system.lancer;
- }
- }
- return undefined
- }
-
- /* -------------------------------------------- */
- /**
- *
- * @param {*} arme item d'arme/compétence de créature
- * @param {*} categorieArme catégorie d'attaque à utiliser: competence (== melee), lancer, tir; naturelle, possession
- * @returns
- */
- rollArme(arme, categorieArme = "competence") {
- let compToUse = this.getCompetenceArme(arme, categorieArme)
- if (!Targets.hasTargets()) {
- RdDConfirm.confirmer({
- settingConfirmer: "confirmer-combat-sans-cible",
- content: `
Voulez vous faire un jet de ${compToUse} sans choisir de cible valide?
-
Tous les jets de combats devront être gérés à la main
-
`,
- title: 'Ne pas utiliser les automatisation de combat',
- buttonLabel: "Pas d'automatisation",
- onAction: async () => {
- this.rollCompetence(compToUse, { tryTarget: false })
- }
- });
- return;
- }
-
- Targets.selectOneToken(target => {
- if (Targets.isTargetEntite(target)) {
- ui.notifications.warn(`Vous ne pouvez pas attaquer une entité non incarnée avec votre ${arme.name}!!!!`);
- return;
- }
-
- const competence = this.getCompetence(compToUse)
- //console.log("RollArme", competence, arme)
- if (competence.isCompetencePossession()) {
- return RdDPossession.onAttaquePossession(target, this, competence);
- }
- RdDCombat.rddCombatTarget(target, this).attaque(competence, arme);
- })
- }
-
async rollSoins(blesse, blessureId) {
const blessure = blesse.blessuresASoigner().find(it => it.id == blessureId);
if (blessure) {
@@ -3207,12 +2558,6 @@ export class RdDActor extends RdDBaseActor {
RdDPossession.onConjurerPossession(this, possession)
}
- /* -------------------------------------------- */
- getArmeParade(armeParadeId) {
- const item = armeParadeId ? this.getEmbeddedDocument('Item', armeParadeId) : undefined;
- return RdDItemArme.getArme(item);
- }
-
/* -------------------------------------------- */
verifierForceMin(item) {
if (item.type == 'arme' && item.system.force > this.system.carac.force.value) {
@@ -3229,7 +2574,7 @@ export class RdDActor extends RdDBaseActor {
if (item?.isEquipable()) {
const isEquipe = !item.system.equipe;
await this.updateEmbeddedDocuments('Item', [{ _id: item.id, "system.equipe": isEquipe }]);
- this.computeEncTotal(); // Mise à jour encombrement
+ this.computeEncTotal();
if (isEquipe)
this.verifierForceMin(item);
}
@@ -3262,108 +2607,6 @@ export class RdDActor extends RdDBaseActor {
return protection;
}
-
- /* -------------------------------------------- */
- async encaisser() {
- await RdDEncaisser.encaisser(this);
- }
-
- /* -------------------------------------------- */
- async encaisserDommages(rollData, attacker = undefined, show = undefined) {
- if (attacker && !await attacker.accorder(this, 'avant-encaissement')) {
- return;
- }
- const attackerId = attacker?.id;
- if (ReglesOptionnelles.isUsing('validation-encaissement-gr') && !game.user.isGM) {
- RdDBaseActor.remoteActorCall({
- tokenId: this.token?.id,
- actorId: this.id,
- method: 'appliquerEncaissement',
- args: [rollData, show, attackerId]
- });
- return;
- }
- await this.appliquerEncaissement(rollData, show, attackerId);
- }
-
- async appliquerEncaissement(rollData, show, attackerId) {
- const armure = await this.computeArmure(rollData);
- if (ReglesOptionnelles.isUsing('validation-encaissement-gr')) {
- DialogValidationEncaissement.validerEncaissement(this, rollData, armure, show, attackerId, (encaissement, show, attackerId) => this._appliquerEncaissement(encaissement, show, attackerId));
- }
- else {
- let encaissement = await RdDUtility.jetEncaissement(rollData, armure, { showDice: SHOW_DICE });
- await this._appliquerEncaissement(encaissement, show, attackerId);
- }
- }
-
- async _appliquerEncaissement(encaissement, show, attackedId) {
- const attacker = attackedId ? game.actors.get(attackedId) : undefined
- let santeOrig = duplicate(this.system.sante);
-
- const blessure = await this.ajouterBlessure(encaissement, attacker); // Will update the result table
- const perteVie = this.isEntite()
- ? { newValue: 0 }
- : await this.santeIncDec("vie", -encaissement.vie);
- const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, blessure?.isCritique());
-
- mergeObject(encaissement, {
- alias: this.name,
- hasPlayerOwner: this.hasPlayerOwner,
- resteEndurance: this.system.sante.endurance.value,
- sonne: perteEndurance.sonne,
- jetEndurance: perteEndurance.jetEndurance,
- endurance: santeOrig.endurance.value - perteEndurance.newValue,
- vie: this.isEntite() ? 0 : (santeOrig.vie.value - perteVie.newValue),
- blessure: blessure,
- show: show ?? {}
- });
-
- await ChatUtility.createChatWithRollMode(this.name, {
- roll: encaissement.roll,
- content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement)
- });
-
- if (!encaissement.hasPlayerOwner && encaissement.endurance != 0) {
- encaissement = duplicate(encaissement);
- encaissement.isGM = true;
- ChatMessage.create({
- whisper: ChatMessage.getWhisperRecipients("GM"),
- content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement)
- });
- }
- }
-
- /* -------------------------------------------- */
- async ajouterBlessure(encaissement, attacker = undefined) {
- if (this.isEntite()) return; // Une entité n'a pas de blessures
- if (encaissement.gravite < 0) return;
- if (encaissement.gravite > 0) {
- while (this.countBlessures(it => it.system.gravite == encaissement.gravite) >= RdDItemBlessure.maxBlessures(encaissement.gravite) && encaissement.gravite <= 6) {
- // Aggravation
- encaissement.gravite += 2
- if (encaissement.gravite > 2) {
- encaissement.vie += 2;
- }
- }
- }
- const endActuelle = Number(this.system.sante.endurance.value);
- const blessure = await RdDItemBlessure.createBlessure(this, encaissement.gravite, encaissement.dmg.loc.label, attacker);
- if (blessure.isCritique()) {
- encaissement.endurance = endActuelle;
- }
-
- if (blessure.isMort()) {
- this.setEffect(STATUSES.StatusComma, true);
- encaissement.mort = true;
- ChatMessage.create({
- content: `
- ${this.name} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !`
- });
- }
- return blessure;
- }
-
/* -------------------------------------------- */
/** @override */
getRollData() {
@@ -3392,71 +2635,6 @@ export class RdDActor extends RdDBaseActor {
return itemUse[itemId] ?? 0;
}
- /* -------------------------------------------- */
- /* -- entites -- */
- /* retourne true si on peut continuer, false si on ne peut pas continuer */
- async targetEntiteNonAccordee(target, when = 'avant-encaissement') {
- if (target) {
- return !await this.accorder(target.actor, when);
- }
- return false;
- }
-
- /* -------------------------------------------- */
- async accorder(entite, when = 'avant-encaissement') {
- if (when != game.settings.get(SYSTEM_RDD, "accorder-entite-cauchemar")
- || entite == undefined
- || !entite.isEntite([ENTITE_INCARNE])
- || entite.isEntiteAccordee(this)) {
- return true;
- }
- const rolled = await RdDResolutionTable.roll(this.getReveActuel(), - Number(entite.system.carac.niveau.value));
- const rollData = {
- alias: this.name,
- rolled: rolled,
- entite: entite.name,
- selectedCarac: this.system.carac.reve
- };
-
- if (rolled.isSuccess) {
- await entite.setEntiteReveAccordee(this);
- }
-
- await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-accorder-cauchemar.html');
- if (rolled.isPart) {
- await this.appliquerAjoutExperience(rollData, true);
- }
- return rolled.isSuccess;
- }
-
- /* -------------------------------------------- */
- isEntite(typeentite = []) {
- return this.type == 'entite' && (typeentite.length == 0 || typeentite.includes(this.system.definition.typeentite));
- }
-
- /* -------------------------------------------- */
- isEntiteAccordee(attaquant) {
- if (!this.isEntite([ENTITE_INCARNE])) { return true; }
- let resonnance = this.system.sante.resonnance;
- return (resonnance.actors.find(it => it == attaquant.id));
- }
-
- /* -------------------------------------------- */
- async setEntiteReveAccordee(attaquant) {
- if (!this.isEntite([ENTITE_INCARNE])) {
- ui.notifications.error("Impossible de s'accorder à " + this.name + ": ce n'est pas une entite de cauchemer/rêve");
- return;
- }
- let resonnance = duplicate(this.system.sante.resonnance);
- if (resonnance.actors.find(it => it == attaquant.id)) {
- // déjà accordé
- return;
- }
- resonnance.actors.push(attaquant.id);
- await this.update({ "system.sante.resonnance": resonnance });
- return;
- }
-
/* -------------------------------------------- */
async effectuerTacheAlchimie(recetteId, tacheAlchimie, texteTache) {
let recetteData = this.findItemLike(recetteId, 'recettealchimique');
@@ -3760,43 +2938,8 @@ export class RdDActor extends RdDBaseActor {
}
/* -------------------------------------------- */
- getEffects(filter = e => true) {
- return this.getEmbeddedCollection("ActiveEffect").filter(filter);
- }
-
- /* -------------------------------------------- */
- getEffect(statusId) {
- return this.getEmbeddedCollection("ActiveEffect").find(it => it.flags?.core?.statusId == statusId);
- }
-
- /* -------------------------------------------- */
- async setEffect(statusId, status) {
- if (this.isEntite() || this.isVehicule()) {
- return;
- }
- console.log("setEffect", statusId, status)
- const effect = this.getEffect(statusId);
- if (!status && effect) {
- await this.deleteEmbeddedDocuments('ActiveEffect', [effect.id]);
- }
- if (status && !effect) {
- await this.createEmbeddedDocuments("ActiveEffect", [StatusEffects.status(statusId)]);
- }
- }
-
- async removeEffect(statusId) {
- const effect = this.getEffect(statusId);
- if (effect) {
- await this.deleteEmbeddedDocuments('ActiveEffect', [effect.id]);
- }
- }
-
- /* -------------------------------------------- */
- async removeEffects(filter = e => true) {
- if (game.user.isGM) {
- const ids = this.getEffects(filter).map(it => it.id);
- await this.deleteEmbeddedDocuments('ActiveEffect', ids);
- }
+ isEffectAllowed(statusId) {
+ return true
}
/* -------------------------------------------- */
diff --git a/module/actor/base-actor-reve-sheet.js b/module/actor/base-actor-reve-sheet.js
new file mode 100644
index 00000000..ea074ff1
--- /dev/null
+++ b/module/actor/base-actor-reve-sheet.js
@@ -0,0 +1,81 @@
+import { RdDSheetUtility } from "../rdd-sheet-utility.js";
+import { RdDBaseActorSheet } from "./base-actor-sheet.js";
+
+/* -------------------------------------------- */
+/**
+ * Extend the basic ActorSheet with some very simple modifications
+ * @extends {ActorSheet}
+ */
+export class RdDBaseActorReveSheet extends RdDBaseActorSheet {
+
+ /* -------------------------------------------- */
+ /** @override */
+ activateListeners(html) {
+ super.activateListeners(html);
+
+ // Everything below here is only needed if the sheet is editable
+ if (!this.options.editable) return;
+
+ this.html.find('.item-action').click(async event => {
+ const item = RdDSheetUtility.getItem(event, this.actor);
+ item?.actionPrincipale(this.actor, async () => this.render())
+ });
+
+ this.html.find('.encaisser-direct').click(async event => {
+ this.actor.encaisser();
+ })
+ this.html.find('.remise-a-neuf').click(async event => {
+ if (game.user.isGM) {
+ this.actor.remiseANeuf();
+ }
+ });
+
+ this.html.find('.carac-label a').click(async event => {
+ let caracName = event.currentTarget.attributes.name.value;
+ this.actor.rollCarac(caracName.toLowerCase());
+ });
+
+ this.html.find('a.competence-label').click(async event => {
+ this.actor.rollCompetence(RdDSheetUtility.getItemId(event));
+ });
+
+ this.html.find('.delete-active-effect').click(async event => {
+ if (game.user.isGM) {
+ let effect = this.html.find(event.currentTarget).parents(".active-effect").data('effect');
+ this.actor.removeEffect(effect);
+ }
+ });
+ this.html.find('.enlever-tous-effets').click(async event => {
+ if (game.user.isGM) {
+ await this.actor.removeEffects();
+ }
+ });
+
+ if (this.options.vueDetaillee) {
+ // On carac change
+ this.html.find('.carac-value').change(async event => {
+ let caracName = event.currentTarget.name.replace(".value", "").replace("system.carac.", "");
+ this.actor.updateCarac(caracName, parseInt(event.target.value));
+ });
+ // On competence change
+ this.html.find('.competence-value').change(async event => {
+ let compName = event.currentTarget.attributes.compname.value;
+ //console.log("Competence changed :", compName);
+ this.actor.updateCompetence(compName, parseInt(event.target.value));
+ });
+ }
+
+ this.html.find('.vue-detaillee').click(async event => {
+ this.options.vueDetaillee = !this.options.vueDetaillee;
+ this.render(true);
+ });
+
+ this.html.find('.endurance-plus').click(async event => {
+ this.actor.santeIncDec("endurance", 1);
+ });
+ this.html.find('.endurance-moins').click(async event => {
+ this.actor.santeIncDec("endurance", -1);
+ });
+ }
+
+}
diff --git a/module/actor/base-actor-reve.js b/module/actor/base-actor-reve.js
new file mode 100644
index 00000000..3409253f
--- /dev/null
+++ b/module/actor/base-actor-reve.js
@@ -0,0 +1,509 @@
+import { ChatUtility } from "../chat-utility.js";
+import { DialogValidationEncaissement } from "../dialog-validation-encaissement.js";
+import { Grammar } from "../grammar.js";
+import { RdDItemCompetence } from "../item-competence.js";
+import { Misc } from "../misc.js";
+import { RdDEmpoignade } from "../rdd-empoignade.js";
+import { RdDResolutionTable } from "../rdd-resolution-table.js";
+import { RdDEncaisser } from "../rdd-roll-encaisser.js";
+import { RdDRoll } from "../rdd-roll.js";
+import { RdDUtility } from "../rdd-utility.js";
+import { ReglesOptionnelles } from "../settings/regles-optionnelles.js";
+import { RdDBaseActor } from "./base-actor.js";
+import { RdDItemCompetenceCreature } from "../item-competencecreature.js";
+import { StatusEffects } from "../settings/status-effects.js";
+import { TYPES } from "../item.js";
+import { Targets } from "../targets.js";
+import { RdDPossession } from "../rdd-possession.js";
+import { RdDCombat } from "../rdd-combat.js";
+import { RdDConfirm } from "../rdd-confirm.js";
+import { ENTITE_INCARNE, SYSTEM_RDD } from "../constants.js";
+import { RdDItemArme } from "../item-arme.js";
+
+const POSSESSION_SANS_DRACONIC = {
+ img: 'systems/foundryvtt-reve-de-dragon/icons/entites/possession.webp',
+ name: 'Sans draconic',
+ system: {
+ niveau: 0,
+ defaut_carac: "reve-actuel",
+ }
+};
+
+/**
+ * Classe de base pour les acteurs disposant de rêve (donc, pas des objets)
+ * - Entités de rêve
+ * - Créatures de "sang": créatures et humanoides
+ */
+export class RdDBaseActorReve extends RdDBaseActor {
+
+ getCaracChanceActuelle() {
+ return {
+ label: 'Chance actuelle',
+ value: this.getChanceActuel(),
+ type: "number"
+ };
+ }
+
+ getCaracReveActuel() {
+ return {
+ label: 'Rêve actuel',
+ value: this.getReveActuel(),
+ type: "number"
+ };
+ }
+
+ getReveActuel() { return this.getReve() }
+ getChanceActuel() { return this.getChance() }
+
+ getReve() { return Number(this.system.carac.reve?.value ?? 0) }
+ getForce() { return this.getReve() }
+ getTaille() { return Number(this.system.carac.taille?.value ?? 0) }
+ getAgilite() { return this.getForce() }
+ getChance() { return this.getReve() }
+ getMoralTotal() { return 0 }
+ getBonusDegat() { return Number(this.system.attributs?.plusdom?.value ?? 0) }
+ getProtectionNaturelle() { return Number(this.system.attributs?.protection?.value ?? 0) }
+ getSConst() { return 0 }
+
+ /* -------------------------------------------- */
+ getEncombrementMax() { return 0 }
+ isSurenc() { return false }
+ computeMalusSurEncombrement() { return 0 }
+
+ ajustementAstrologique() { return 0 }
+ getMalusArmure() { return 0 }
+
+ getEnduranceActuelle() {
+ return Number(this.system.sante?.endurance?.value ?? 0);
+ }
+ async jetEndurance(resteEndurance = undefined) { return { jetEndurance: 0, sonne: false } }
+ isDead() { return false }
+ blessuresASoigner() { return [] }
+ getEtatGeneral(options = { ethylisme: false }) { return 0 }
+
+ async computeArmure(attackerRoll) { return this.getProtectionNaturelle() }
+ async remiseANeuf() { }
+ async appliquerAjoutExperience(rollData, hideChatMessage = 'show') { }
+
+ async santeIncDec(name, inc, isCritique = false) { }
+
+ async finDeRound(options = { terminer: false }) {
+ await this.$finDeRoundSuppressionEffetsTermines(options);
+ await this.finDeRoundBlessures();
+ await this.$finDeRoundSupprimerObsoletes();
+ await this.$finDeRoundEmpoignade();
+ }
+
+ async $finDeRoundSuppressionEffetsTermines(options) {
+ for (let effect of this.getEffects()) {
+ if (effect.duration.type !== 'none' && (effect.duration.remaining <= 0 || options.terminer)) {
+ await effect.delete();
+ ChatMessage.create({ content: `${this.name} n'est plus ${Misc.lowerFirst(game.i18n.localize(effect.system.label))} !` });
+ }
+ }
+ }
+
+ async finDeRoundBlessures() {
+ }
+
+ async $finDeRoundSupprimerObsoletes() {
+ const obsoletes = []
+ .concat(this.itemTypes[TYPES.empoignade].filter(it => it.system.pointsemp <= 0))
+ .concat(this.itemTypes[TYPES.possession].filter(it => it.system.compteur < -2 || it.system.compteur > 2))
+ .map(it => it.id);
+ await this.deleteEmbeddedDocuments('Item', obsoletes);
+ }
+
+ async $finDeRoundEmpoignade() {
+ const immobilisations = this.itemTypes[TYPES.empoignade].filter(it => it.system.pointsemp >= 2 && it.system.empoigneurid == this.id);
+ immobilisations.forEach(emp => RdDEmpoignade.onImmobilisation(this,
+ game.actors.get(emp.system.empoigneid),
+ emp
+ ))
+ }
+
+ async setSonne(sonne = true) { }
+
+ /* -------------------------------------------- */
+ getCompetence(idOrName, options = {}) {
+ if (idOrName instanceof Item) {
+ return idOrName.isCompetence() ? idOrName : undefined
+ }
+ return RdDItemCompetence.findCompetence(this.items, idOrName, options)
+ }
+ getCompetences(name) {
+ return RdDItemCompetence.findCompetences(this.items, name)
+ }
+ getCompetenceCorpsACorps(options = {}) {
+ return this.getCompetence("Corps à corps", options)
+ }
+ getCompetencesEsquive() {
+ return this.getCompetences("esquive")
+ }
+
+ getArmeParade(armeParadeId) {
+ const item = armeParadeId ? this.getEmbeddedDocument('Item', armeParadeId) : undefined;
+ return RdDItemArme.getArme(item);
+ }
+
+ getDraconicOuPossession() {
+ return POSSESSION_SANS_DRACONIC
+ }
+
+ getPossession(possessionId) {
+ return this.itemTypes[TYPES.possession].find(it => it.system.possessionid == possessionId);
+ }
+ getPossessions() {
+ return this.itemTypes[TYPES.possession];
+ }
+ getEmpoignades() {
+ return this.itemTypes[TYPES.empoignade];
+ }
+
+ /* -------------------------------------------- */
+ async updateCreatureCompetence(idOrName, fieldName, value) {
+ let competence = this.getCompetence(idOrName);
+ if (competence) {
+ function getFieldPath(fieldName) {
+ switch (fieldName) {
+ case "niveau": return 'system.niveau';
+ case "dommages": return 'system.dommages';
+ case "carac_value": return 'system.carac_value';
+ }
+ return undefined
+ }
+ const path = getFieldPath(fieldName);
+ if (path) {
+ await this.updateEmbeddedDocuments('Item', [{ _id: competence.id, [path]: value }]); // updates one EmbeddedEntity
+ }
+ }
+ }
+
+ /* -------------------------------------------- */
+ isEffectAllowed(statusId) { return true }
+
+ getEffects(filter = e => true) {
+ return this.getEmbeddedCollection("ActiveEffect").filter(filter);
+ }
+
+ getEffect(statusId) {
+ return this.getEmbeddedCollection("ActiveEffect").find(it => it.flags?.core?.statusId == statusId);
+ }
+
+ async setEffect(statusId, status) {
+ if (this.isEffectAllowed(statusId)) {
+ const effect = this.getEffect(statusId);
+ if (!status && effect) {
+ await this.deleteEmbeddedDocuments('ActiveEffect', [effect.id]);
+ }
+ if (status && !effect) {
+ await this.createEmbeddedDocuments("ActiveEffect", [StatusEffects.status(statusId)]);
+ }
+ }
+ }
+
+ async removeEffect(statusId) {
+ const effect = this.getEffect(statusId);
+ if (effect) {
+ await this.deleteEmbeddedDocuments('ActiveEffect', [effect.id]);
+ }
+ }
+
+ async removeEffects(filter = e => true) {
+ if (game.user.isGM) {
+ const ids = this.getEffects(filter).map(it => it.id);
+ await this.deleteEmbeddedDocuments('ActiveEffect', ids);
+ }
+ }
+
+ /* -------------------------------------------- */
+ getSurprise(isCombat = undefined) {
+ let niveauSurprise = this.getEffects()
+ .map(effect => StatusEffects.valeurSurprise(effect, isCombat))
+ .reduce(Misc.sum(), 0);
+ if (niveauSurprise > 1) {
+ return 'totale';
+ }
+ if (niveauSurprise == 1) {
+ return 'demi';
+ }
+ return '';
+ }
+
+ /* -------------------------------------------- */
+ async computeEtatGeneral() {
+ // Par défaut, on ne calcule pas d'état général, seuls les personnages/créatures sont affectés
+ this.system.compteurs.etat.value = 0;
+ }
+
+ /* -------------------------------------------- */
+ async openRollDialog({ name, label, template, rollData, callbackAction }) {
+ const dialog = await RdDRoll.create(this, rollData,
+ { html: template, close: async html => await this._onCloseRollDialog(html) },
+ {
+ name: name,
+ label: label,
+ callbacks: [
+ this.createCallbackExperience(),
+ this.createCallbackAppelAuMoral(),
+ { action: callbackAction }
+ ]
+ });
+ dialog.render(true);
+ return dialog
+ }
+
+ createEmptyCallback() {
+ return {
+ condition: r => false,
+ action: r => { }
+ };
+ }
+ createCallbackExperience() { return this.createEmptyCallback(); }
+ createCallbackAppelAuMoral() { return this.createEmptyCallback(); }
+ async _onCloseRollDialog(html) { }
+
+ /* -------------------------------------------- */
+ async roll() {
+ RdDEmpoignade.checkEmpoignadeEnCours(this)
+
+ const carac = this.getCarac()
+ const selectedCaracName = ['apparence', 'perception', 'force', 'reve'].find(it => carac[it] != undefined)
+
+ await this.openRollDialog({
+ name: `jet-${this.id}`,
+ label: `Jet de ${this.name}`,
+ template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll.html',
+ rollData: {
+ carac: carac,
+ selectedCarac: carac[selectedCaracName],
+ selectedCaracName: selectedCaracName,
+ competences: this.itemTypes['competence']
+ },
+ callbackAction: r => this.$onRollCaracResult(r)
+ });
+ }
+
+ getCarac() {
+ // TODO: le niveau d'une entité de cauchemar devrait être exclu...
+ const carac = mergeObject(duplicate(this.system.carac),
+ {
+ 'reve-actuel': this.getCaracReveActuel(),
+ 'chance-actuelle': this.getCaracChanceActuelle()
+ });
+ return carac;
+ }
+
+ /* -------------------------------------------- */
+ async rollCarac(caracName, jetResistance = undefined) {
+ RdDEmpoignade.checkEmpoignadeEnCours(this)
+ let selectedCarac = this.getCaracByName(caracName)
+ await this.openRollDialog({
+ name: 'jet-' + caracName,
+ label: 'Jet ' + Grammar.apostrophe('de', selectedCarac.label),
+ template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html',
+ rollData: {
+ selectedCarac: selectedCarac,
+ competences: this.itemTypes['competence'],
+ jetResistance: jetResistance ? caracName : undefined
+ },
+ callbackAction: r => this.$onRollCaracResult(r)
+ });
+ }
+
+ /* -------------------------------------------- */
+ async $onRollCaracResult(rollData) {
+ // Final chat message
+ await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-general.html');
+ }
+
+ /* -------------------------------------------- */
+ async rollCompetence(idOrName, options = { tryTarget: true }) {
+ RdDEmpoignade.checkEmpoignadeEnCours(this)
+ const competence = this.getCompetence(idOrName);
+ let rollData = { carac: this.system.carac, competence: competence }
+ if (competence.type == TYPES.competencecreature) {
+ const arme = RdDItemCompetenceCreature.armeCreature(competence)
+ if (arme && options.tryTarget && Targets.hasTargets()) {
+ Targets.selectOneToken(target => {
+ if (arme.action == "possession") {
+ RdDPossession.onAttaquePossession(target, this, competence)
+ }
+ else {
+ RdDCombat.rddCombatTarget(target, this).attaque(competence, arme)
+ }
+ });
+ return;
+ }
+ // Transformer la competence de créature
+ RdDItemCompetenceCreature.setRollDataCreature(rollData)
+ }
+
+ await this.openRollDialog({
+ name: 'jet-competence',
+ label: 'Jet ' + Grammar.apostrophe('de', competence.name),
+ template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html',
+ rollData: rollData,
+ callbackAction: r => this.$onRollCompetence(r, options)
+ });
+ }
+
+ async $onRollCompetence(rollData, options) {
+ await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-competence.html')
+ if (options?.onRollAutomate) {
+ options.onRollAutomate(rollData);
+ }
+ }
+
+ /** --------------------------------------------
+ * @param {*} arme item d'arme/compétence de créature
+ * @param {*} categorieArme catégorie d'attaque à utiliser: competence (== melee), lancer, tir; naturelle, possession
+ * @returns
+ */
+ rollArme(arme, categorieArme = "competence") {
+ let compToUse = this.$getCompetenceArme(arme, categorieArme)
+ if (!Targets.hasTargets()) {
+ RdDConfirm.confirmer({
+ settingConfirmer: "confirmer-combat-sans-cible",
+ content: `Voulez vous faire un jet de ${compToUse} sans choisir de cible valide?
+
Tous les jets de combats devront être gérés à la main
+
`,
+ title: 'Ne pas utiliser les automatisation de combat',
+ buttonLabel: "Pas d'automatisation",
+ onAction: async () => {
+ this.rollCompetence(compToUse, { tryTarget: false })
+ }
+ });
+ return;
+ }
+
+ Targets.selectOneToken(target => {
+ if (Targets.isTargetEntite(target)) {
+ ui.notifications.warn(`Vous ne pouvez pas attaquer une entité non incarnée avec votre ${arme.name}!!!!`);
+ return;
+ }
+
+ const competence = this.getCompetence(compToUse)
+ //console.log("RollArme", competence, arme)
+ if (competence.isCompetencePossession()) {
+ return RdDPossession.onAttaquePossession(target, this, competence);
+ }
+ RdDCombat.rddCombatTarget(target, this).attaque(competence, arme);
+ })
+ }
+
+ $getCompetenceArme(arme, competenceName) {
+ switch (arme.type) {
+ case TYPES.competencecreature:
+ return arme.name
+ case TYPES.arme:
+ switch (competenceName) {
+ case 'competence': return arme.system.competence;
+ case 'unemain': return RdDItemArme.competence1Mains(arme);
+ case 'deuxmains': return RdDItemArme.competence2Mains(arme);
+ case 'tir': return arme.system.tir;
+ case 'lancer': return arme.system.lancer;
+ }
+ }
+ return undefined
+ }
+
+ verifierForceMin(item) {
+ }
+ /* -------------------------------------------- */
+ async resetItemUse() { }
+ async incDecItemUse(itemId, inc = 1) { }
+ getItemUse(itemId) { return 0; }
+
+ /* -------------------------------------------- */
+ async encaisser() { await RdDEncaisser.encaisser(this) }
+
+ async encaisserDommages(rollData, attacker = undefined, show = undefined) {
+ if (attacker && !await attacker.accorder(this, 'avant-encaissement')) {
+ return;
+ }
+ const attackerId = attacker?.id;
+ if (ReglesOptionnelles.isUsing('validation-encaissement-gr') && !game.user.isGM) {
+ RdDBaseActor.remoteActorCall({
+ tokenId: this.token?.id,
+ actorId: this.id,
+ method: 'appliquerEncaissement',
+ args: [rollData, show, attackerId]
+ });
+ return;
+ }
+
+ const armure = await this.computeArmure(rollData);
+ if (ReglesOptionnelles.isUsing('validation-encaissement-gr')) {
+ DialogValidationEncaissement.validerEncaissement(this, rollData, armure,
+ jet => this.$onEncaissement(jet, show, attacker));
+ }
+ else {
+ const jet = await RdDUtility.jetEncaissement(rollData, armure, { showDice: SHOW_DICE });
+ await this.$onEncaissement(jet, show, attacker);
+ }
+ }
+
+ async $onEncaissement(jet, show, attacker) {
+ await this.onAppliquerJetEncaissement(jet, attacker);
+ await this.$afficherEncaissement(jet, show);
+ }
+
+ async onAppliquerJetEncaissement(encaissement, attacker) { }
+
+ async $afficherEncaissement(encaissement, show) {
+ mergeObject(encaissement, {
+ alias: this.name,
+ hasPlayerOwner: this.hasPlayerOwner,
+ show: show ?? {}
+ });
+
+ await ChatUtility.createChatWithRollMode(this.name, {
+ roll: encaissement.roll,
+ content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement)
+ });
+
+ if (!encaissement.hasPlayerOwner && encaissement.endurance != 0) {
+ encaissement = duplicate(encaissement);
+ encaissement.isGM = true;
+ ChatMessage.create({
+ whisper: ChatMessage.getWhisperRecipients("GM"),
+ content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement)
+ });
+ }
+ }
+
+ /* -------------------------------------------- */
+ async accorder(entite, when = 'avant-encaissement') {
+ if (when != game.settings.get(SYSTEM_RDD, "accorder-entite-cauchemar")
+ || entite == undefined
+ || !entite.isEntite([ENTITE_INCARNE])
+ || entite.isEntiteAccordee(this)) {
+ return true;
+ }
+ const rolled = await RdDResolutionTable.roll(this.getReveActuel(), - Number(entite.system.carac.niveau.value));
+ const rollData = {
+ alias: this.name,
+ rolled: rolled,
+ entite: entite.name,
+ selectedCarac: this.system.carac.reve
+ };
+
+ if (rolled.isSuccess) {
+ await entite.setEntiteReveAccordee(this);
+ }
+
+ await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-accorder-cauchemar.html');
+ if (rolled.isPart) {
+ await this.appliquerAjoutExperience(rollData, true);
+ }
+ return rolled.isSuccess;
+ }
+
+ isEntiteAccordee(attacker) { return true }
+
+ async setEntiteReveAccordee(attacker) {
+ ui.notifications.error("Impossible de s'accorder à " + this.name + ": ce n'est pas une entite de cauchemer/rêve");
+ }
+
+}
diff --git a/module/actor/base-actor-sang.js b/module/actor/base-actor-sang.js
new file mode 100644
index 00000000..b0a03669
--- /dev/null
+++ b/module/actor/base-actor-sang.js
@@ -0,0 +1,275 @@
+import { MAX_ENDURANCE_FATIGUE, RdDUtility } from "../rdd-utility.js";
+import { ReglesOptionnelles } from "../settings/regles-optionnelles.js";
+import { STATUSES } from "../settings/status-effects.js";
+import { TYPES } from "../item.js";
+import { RdDBaseActorReve } from "./base-actor-reve.js";
+import { RdDDice } from "../rdd-dice.js";
+import { RdDItemBlessure } from "../item/blessure.js";
+
+/**
+ * Classe de base pour les acteurs qui peuvent subir des blessures
+ * - créatures
+ * - humanoides
+ */
+export class RdDBaseActorSang extends RdDBaseActorReve {
+
+
+ getForce() { return Number(this.system.carac.force?.value ?? 0) }
+
+ getBonusDegat() { return Number(this.system.attributs?.plusdom?.value ?? 0) }
+ getProtectionNaturelle() { return Number(this.system.attributs?.protection?.value ?? 0) }
+ getSConst() { return 0 }
+
+ getEnduranceMax() {
+ return Math.max(1, Math.min(this.system.sante.endurance.max, MAX_ENDURANCE_FATIGUE));
+ }
+
+ getFatigueActuelle() {
+ if (ReglesOptionnelles.isUsing("appliquer-fatigue")) {
+ return Math.max(0, Math.min(this.getFatigueMax(), this.system.sante.fatigue?.value));
+ }
+ return 0;
+ }
+
+ getFatigueRestante() {
+ return this.getFatigueMax() - this.getFatigueActuelle();
+ }
+
+ getFatigueMin() {
+ return this.system.sante.endurance.max - this.system.sante.endurance.value;
+ }
+
+ getFatigueMax() { return this.getEnduranceMax() * 2 }
+
+ malusFatigue() {
+ if (ReglesOptionnelles.isUsing("appliquer-fatigue")) {
+ return RdDUtility.calculMalusFatigue(this.getFatigueActuelle(), this.getEnduranceMax())
+ }
+ return 0;
+ }
+
+ /* -------------------------------------------- */
+ getEncombrementMax() { return Number(this.system.attributs?.encombrement?.value ?? 0) }
+ isSurenc() { return this.computeMalusSurEncombrement() < 0 }
+
+ computeMalusSurEncombrement() {
+ return Math.min(0, Math.floor(this.getEncombrementMax() - this.encTotal));
+ }
+
+ isDead() {
+ return this.system.sante.vie.value < -this.getSConst()
+ }
+
+ /* -------------------------------------------- */
+ computeResumeBlessure() {
+ const blessures = this.filterItems(it => it.system.gravite > 0, 'blessure')
+
+ const nbLegeres = blessures.filter(it => it.isLegere()).length;
+ const nbGraves = blessures.filter(it => it.isGrave()).length;
+ const nbCritiques = blessures.filter(it => it.isCritique()).length;
+
+ if (nbLegeres + nbGraves + nbCritiques == 0) {
+ return "Aucune blessure";
+ }
+ let resume = "Blessures:";
+ if (nbLegeres > 0) {
+ resume += " " + nbLegeres + " légère" + (nbLegeres > 1 ? "s" : "");
+ }
+ if (nbGraves > 0) {
+ if (nbLegeres > 0)
+ resume += ",";
+ resume += " " + nbGraves + " grave" + (nbGraves > 1 ? "s" : "");
+ }
+ if (nbCritiques > 0) {
+ if (nbGraves > 0 || nbLegeres > 0)
+ resume += ",";
+ resume += " une CRITIQUE !";
+ }
+ return resume;
+ }
+
+ blessuresASoigner() { return [] }
+ getEtatGeneral(options = { ethylisme: false }) { return 0 }
+
+ async computeArmure(attackerRoll) { return this.getProtectionNaturelle() }
+ async remiseANeuf() { }
+ async appliquerAjoutExperience(rollData, hideChatMessage = 'show') { }
+
+ /* -------------------------------------------- */
+
+ async onAppliquerJetEncaissement(encaissement, attacker) {
+ const santeOrig = duplicate(this.system.sante);
+ const blessure = await this.ajouterBlessure(encaissement, attacker); // Will update the result table
+ const perteVie = await this.santeIncDec("vie", -encaissement.vie);
+ const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, blessure?.isCritique());
+
+ mergeObject(encaissement, {
+ resteEndurance: perteEndurance.newValue,
+ sonne: perteEndurance.sonne,
+ jetEndurance: perteEndurance.jetEndurance,
+ endurance: perteEndurance.perte,
+ vie: santeOrig.vie.value - perteVie.newValue,
+ blessure: blessure
+ });
+ }
+ /* -------------------------------------------- */
+ async santeIncDec(name, inc, isCritique = false) {
+ if (name == 'fatigue' && !ReglesOptionnelles.isUsing("appliquer-fatigue")) {
+ return;
+ }
+ const sante = duplicate(this.system.sante)
+ let compteur = sante[name];
+ if (!compteur) {
+ return;
+ }
+ let result = {
+ sonne: false,
+ };
+
+ let minValue = name == "vie" ? -this.getSConst() - 1 : 0;
+
+ result.newValue = Math.max(minValue, Math.min(compteur.value + inc, compteur.max));
+ //console.log("New value ", inc, minValue, result.newValue);
+ let fatigue = 0;
+ if (name == "endurance") {
+ if (result.newValue == 0 && inc < 0 && !isCritique) { // perte endurance et endurance devient 0 (sauf critique) -> -1 vie
+ sante.vie.value--;
+ result.perteVie = true;
+ }
+ result.newValue = Math.max(0, result.newValue);
+ if (inc > 0) { // le max d'endurance s'applique seulement à la récupération
+ result.newValue = Math.min(result.newValue, this._computeEnduranceMax())
+ }
+ const perte = compteur.value - result.newValue;
+ result.perte = perte;
+ if (perte > 1) {
+ // Peut-être sonné si 2 points d'endurance perdus d'un coup
+ mergeObject(result, await this.jetEndurance(result.newValue));
+ } else if (inc > 0) {
+ await this.setSonne(false);
+ }
+ if (sante.fatigue && inc < 0) { // Each endurance lost -> fatigue lost
+ fatigue = perte;
+ }
+ }
+ compteur.value = result.newValue;
+ // If endurance lost, then the same amount of fatigue cannot be recovered
+ if (ReglesOptionnelles.isUsing("appliquer-fatigue") && sante.fatigue && fatigue > 0) {
+ sante.fatigue.value = Math.max(sante.fatigue.value + fatigue, this.getFatigueMin());
+ }
+ await this.update({ "system.sante": sante })
+ if (this.isDead()) {
+ await this.setEffect(STATUSES.StatusComma, true);
+ }
+ return result
+ }
+
+ /* -------------------------------------------- */
+
+ /* -------------------------------------------- */
+ async ajouterBlessure(encaissement, attacker = undefined) {
+ if (encaissement.gravite < 0) return;
+ if (encaissement.gravite > 0) {
+ while (this.countBlessures(it => it.system.gravite == encaissement.gravite) >= RdDItemBlessure.maxBlessures(encaissement.gravite) && encaissement.gravite <= 6) {
+ // Aggravation
+ encaissement.gravite += 2
+ if (encaissement.gravite > 2) {
+ encaissement.vie += 2;
+ }
+ }
+ }
+ const endActuelle = this.getEnduranceActuelle();
+ const blessure = await RdDItemBlessure.createBlessure(this, encaissement.gravite, encaissement.dmg.loc.label, attacker);
+ if (blessure.isCritique()) {
+ encaissement.endurance = endActuelle;
+ }
+
+ if (blessure.isMort()) {
+ this.setEffect(STATUSES.StatusComma, true);
+ encaissement.mort = true;
+ ChatMessage.create({
+ content: `
+ ${this.name} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !`
+ });
+ }
+ return blessure;
+ }
+
+ async supprimerBlessures(filterToDelete) {
+ const toDelete = this.filterItems(filterToDelete, TYPES.blessure)
+ .map(it => it.id);
+ await this.deleteEmbeddedDocuments('Item', toDelete);
+ }
+
+ countBlessures(filter = it => !it.isContusion()) {
+ return this.filterItems(filter, 'blessure').length
+ }
+
+ /* -------------------------------------------- */
+ async jetVie() {
+ let roll = await RdDDice.roll("1d20");
+ let msgText = "Jet de Vie : " + roll.total + " / " + this.system.sante.vie.value + "
";
+ if (roll.total <= this.system.sante.vie.value) {
+ msgText += "Jet réussi, pas de perte de point de vie (prochain jet dans 1 round pour 1 critique, SC minutes pour une grave)";
+ if (roll.total == 1) {
+ msgText += "La durée entre 2 jets de vie est multipliée par 20 (20 rounds pour une critique, SCx20 minutes pour une grave)";
+ }
+ } else {
+ msgText += "Jet échoué, vous perdez 1 point de vie";
+ await this.santeIncDec("vie", -1);
+ if (roll.total == 20) {
+ msgText += "Votre personnage est mort !!!!!";
+ }
+ }
+ const message = {
+ content: msgText,
+ whisper: ChatMessage.getWhisperRecipients(this.name)
+ };
+ ChatMessage.create(message);
+ }
+
+ /* -------------------------------------------- */
+ async jetEndurance(resteEndurance = undefined) {
+ const jetEndurance = (await RdDDice.roll("1d20")).total;
+ const sonne = jetEndurance == 20 || jetEndurance > (resteEndurance ?? this.system.sante.endurance.value)
+ if (sonne) {
+ await this.setSonne();
+ }
+ return { jetEndurance, sonne }
+ }
+
+
+ async finDeRoundBlessures() {
+ const nbGraves = this.filterItems(it => it.isGrave(), 'blessure').length;
+ if (nbGraves > 0) {
+ // Gestion blessure graves : -1 pt endurance par blessure grave
+ await this.santeIncDec("endurance", -nbGraves);
+ }
+ }
+
+ async setSonne(sonne = true) {
+ if (!game.combat && sonne) {
+ ui.notifications.info(`${this.name} est hors combat, il ne reste donc pas sonné`);
+ return;
+ }
+ await this.setEffect(STATUSES.StatusStunned, sonne);
+ }
+
+ getSonne() {
+ return this.getEffect(STATUSES.StatusStunned);
+ }
+
+ /* -------------------------------------------- */
+ async computeEtatGeneral() {
+ this.system.compteurs.etat.value = this.malusVie() + this.malusFatigue() + this.malusEthylisme();
+ }
+
+ malusVie() {
+ return Math.min(this.system.sante.vie.value - this.system.sante.vie.max, 0);
+ }
+
+ malusEthylisme() { return 0 }
+ malusFatigue() { return 0 }
+
+
+}
diff --git a/module/actor/base-actor-sheet.js b/module/actor/base-actor-sheet.js
index 6411a415..ef137193 100644
--- a/module/actor/base-actor-sheet.js
+++ b/module/actor/base-actor-sheet.js
@@ -31,7 +31,7 @@ export class RdDBaseActorSheet extends ActorSheet {
async getData() {
Monnaie.validerMonnaies(this.actor.itemTypes['monnaie']);
- this.actor.recompute();
+ this.actor.computeEtatGeneral();
let formData = {
title: this.title,
id: this.actor.id,
diff --git a/module/actor/base-actor.js b/module/actor/base-actor.js
index f8521926..cc79030b 100644
--- a/module/actor/base-actor.js
+++ b/module/actor/base-actor.js
@@ -1,5 +1,6 @@
import { ChatUtility } from "../chat-utility.js";
import { SYSTEM_SOCKET_ID } from "../constants.js";
+import { Grammar } from "../grammar.js";
import { Monnaie } from "../item-monnaie.js";
import { Misc } from "../misc.js";
import { RdDAudio } from "../rdd-audio.js";
@@ -9,6 +10,33 @@ import { SystemCompendiums } from "../settings/system-compendiums.js";
import { APP_ASTROLOGIE_REFRESH } from "../sommeil/app-astrologie.js";
export class RdDBaseActor extends Actor {
+ /* -------------------------------------------- */
+ static _findCaracByName(carac, name) {
+ name = Grammar.toLowerCaseNoAccent(name);
+ switch (name) {
+ case 'reve-actuel': case 'reve actuel':
+ return carac.reve;
+ case 'chance-actuelle': case 'chance actuelle':
+ return carac.chance;
+ }
+
+ const caracList = Object.entries(carac);
+ let entry = Misc.findFirstLike(name, caracList, { mapper: it => it[0], description: 'caractéristique' });
+ if (!entry || entry.length == 0) {
+ entry = Misc.findFirstLike(name, caracList, { mapper: it => it[1].label, description: 'caractéristique' });
+ }
+ return entry && entry.length > 0 ? carac[entry[0]] : undefined;
+ }
+
+ getCaracByName(name) {
+ switch (Grammar.toLowerCaseNoAccent(name)) {
+ case 'reve-actuel': case 'reve actuel':
+ return this.getCaracReveActuel();
+ case 'chance-actuelle': case 'chance-actuelle':
+ return this.getCaracChanceActuelle();
+ }
+ return RdDBaseActor._findCaracByName(this.system.carac, name);
+ }
static getDefaultImg(itemType) {
return game.system.rdd.actorClasses[itemType]?.defaultIcon ?? defaultItemImg[itemType];
@@ -124,11 +152,26 @@ export class RdDBaseActor extends Actor {
}
/* -------------------------------------------- */
- isCreatureEntite() { return this.type == 'creature' || this.type == 'entite'; }
- isCreature() { return this.type == 'creature'; }
- isEntite() { return this.type == 'entite'; }
- isPersonnage() { return this.type == 'personnage'; }
- isVehicule() { return this.type == 'vehicule'; }
+ prepareData() {
+ super.prepareData()
+ this.prepareActorData()
+ this.cleanupConteneurs()
+ this.computeEtatGeneral()
+ this.computeEncTotal()
+ }
+
+ async prepareActorData() { }
+ async computeEtatGeneral() { }
+ /* -------------------------------------------- */
+ findPlayer() {
+ return game.users.players.find(player => player.active && player.character?.id == this.id);
+ }
+
+ isCreatureEntite() { return this.isCreature() || this.isEntite() }
+ isCreature() { return false }
+ isEntite(typeentite = []) { return false }
+ isVehicule() { return false }
+ isPersonnage() { return false }
getItem(id, type = undefined) {
const item = this.items.get(id);
if (type == undefined || (item?.type == type)) {
@@ -145,9 +188,7 @@ export class RdDBaseActor extends Actor {
}
getMonnaie(id) { return this.findItemLike(id, 'monnaie'); }
-
- recompute() { }
-
+ getEncombrementMax() { return 0 }
/* -------------------------------------------- */
async onPreUpdateItem(item, change, options, id) { }
@@ -176,6 +217,16 @@ export class RdDBaseActor extends Actor {
await this.createEmbeddedDocuments('Item', [object])
}
+ /* -------------------------------------------- */
+ async cleanupConteneurs() {
+ let updates = this.itemTypes['conteneur']
+ .filter(c => c.system.contenu.filter(id => this.getItem(id) == undefined).length > 0)
+ .map(c => { return { _id: c._id, 'system.contenu': c.system.contenu.filter(id => this.getItem(id) != undefined) } });
+ if (updates.length > 0) {
+ await this.updateEmbeddedDocuments("Item", updates)
+ }
+ }
+
/* -------------------------------------------- */
getFortune() {
return Monnaie.getFortune(this.itemTypes['monnaie']);
@@ -388,14 +439,6 @@ export class RdDBaseActor extends Actor {
}
/* -------------------------------------------- */
- computeMalusSurEncombrement() {
- return 0;
- }
-
- getEncombrementMax() {
- return 0;
- }
-
async computeEncTotal() {
if (!this.pack) {
this.encTotal = this.items.map(it => it.getEncTotal()).reduce(Misc.sum(), 0);
@@ -404,6 +447,10 @@ export class RdDBaseActor extends Actor {
return 0;
}
+ getEncTotal() {
+ return Math.floor(this.encTotal ?? 0);
+ }
+
async createItem(type, name = undefined) {
if (!name) {
name = 'Nouveau ' + Misc.typeName('Item', type);
@@ -632,5 +679,12 @@ export class RdDBaseActor extends Actor {
.then(html => ChatMessage.create(RdDUtility.chatDataSetup(html, modeOverride)));
}
+ actionImpossible(action) {
+ ui.notifications.info(`${this.name} ne peut pas faire cette action: ${action}`)
+ }
+ async roll() { this.actionImpossible("jet de caractéristiques") }
+ async jetEthylisme() { this.actionImpossible("jet d'éthylisme") }
+ async rollAppelChance() { this.actionImpossible("appel à la chance") }
+ async jetDeMoral() { this.actionImpossible("jet de moral") }
}
\ No newline at end of file
diff --git a/module/actor/commerce-sheet.js b/module/actor/commerce-sheet.js
index a342f2bf..8ade8124 100644
--- a/module/actor/commerce-sheet.js
+++ b/module/actor/commerce-sheet.js
@@ -1,9 +1,7 @@
import { DialogItemAchat } from "../dialog-item-achat.js";
import { RdDItem } from "../item.js";
-import { RdDSheetUtility } from "../rdd-sheet-utility.js";
import { RdDUtility } from "../rdd-utility.js";
import { RdDBaseActorSheet } from "./base-actor-sheet.js";
-import { RdDCommerce } from "./commerce.js";
/**
* Extend the basic ActorSheet with some very simple modifications
diff --git a/module/actor/commerce.js b/module/actor/commerce.js
index a03030fd..9f6cd289 100644
--- a/module/actor/commerce.js
+++ b/module/actor/commerce.js
@@ -7,18 +7,8 @@ export class RdDCommerce extends RdDBaseActor {
return "systems/foundryvtt-reve-de-dragon/icons/services/commerce.webp";
}
- prepareData() {
- super.prepareData();
- }
- prepareDerivedData() {
- super.prepareDerivedData();
- }
-
canReceive(item) {
- if (item.isInventaire('all')) {
- return true;
- }
- return super.canReceive(item);
+ return item.isInventaire('all');
}
getQuantiteDisponible(item) {
diff --git a/module/actor-creature-sheet.js b/module/actor/creature-sheet.js
similarity index 93%
rename from module/actor-creature-sheet.js
rename to module/actor/creature-sheet.js
index 47f5d3b7..c9306196 100644
--- a/module/actor-creature-sheet.js
+++ b/module/actor/creature-sheet.js
@@ -1,10 +1,10 @@
-import { RdDActorSheet } from "./actor-sheet.js";
+import { RdDActorSheet } from "../actor-sheet.js";
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
-export class RdDActorCreatureSheet extends RdDActorSheet {
+export class RdDCreatureSheet extends RdDActorSheet {
/** @override */
static get defaultOptions() {
diff --git a/module/actor/creature.js b/module/actor/creature.js
new file mode 100644
index 00000000..acf19110
--- /dev/null
+++ b/module/actor/creature.js
@@ -0,0 +1,65 @@
+import { ENTITE_INCARNE } from "../constants.js";
+import { STATUSES } from "../settings/status-effects.js";
+import { RdDBaseActorSang } from "./base-actor-sang.js";
+
+export class RdDCreature extends RdDBaseActorSang {
+
+ static get defaultIcon() {
+ return "systems/foundryvtt-reve-de-dragon/icons/creatures/bramart.svg";
+ }
+
+ isCreature() { return true }
+
+ canReceive(item) {
+ return item.type == TYPES.competencecreature || item.isInventaire();
+ }
+
+ async remiseANeuf() {
+ await this.removeEffects(e => true);
+ await this.supprimerBlessures(it => true);
+ const updates = {
+ 'system.sante.endurance.value': this.system.sante.endurance.max,
+ 'system.sante.vie.value': this.system.sante.vie.max,
+ 'system.sante.fatigue.value': 0
+ };
+ await this.update(updates);
+ }
+
+ async finDeRoundBlessures() {
+ const nbGraves = this.filterItems(it => it.isGrave(), 'blessure').length;
+ if (nbGraves > 0) {
+ // Gestion blessure graves : -1 pt endurance par blessure grave
+ await this.santeIncDec("endurance", -nbGraves);
+ }
+ }
+
+ isEffectAllowed(statusId) {
+ return [STATUSES.StatusComma].includes(statusId);
+ }
+
+ isEntiteAccordee(attacker) {
+ if (this.isEntite([ENTITE_INCARNE])) {
+ let resonnance = this.system.sante.resonnance
+ return (resonnance.actors.find(it => it == attacker.id))
+ }
+ return true
+ }
+
+ /* -------------------------------------------- */
+ async setEntiteReveAccordee(attacker) {
+ if (this.isEntite([ENTITE_INCARNE])) {
+ let resonnance = duplicate(this.system.sante.resonnance);
+ if (resonnance.actors.find(it => it == attacker.id)) {
+ // déjà accordé
+ return;
+ }
+ resonnance.actors.push(attacker.id);
+ await this.update({ "system.sante.resonnance": resonnance });
+ }
+ else {
+ super.setEntiteReveAccordee(attacker)
+ }
+ }
+
+
+}
diff --git a/module/actor-entite-sheet.js b/module/actor/entite-sheet.js
similarity index 91%
rename from module/actor-entite-sheet.js
rename to module/actor/entite-sheet.js
index 50f505bf..975a2d10 100644
--- a/module/actor-entite-sheet.js
+++ b/module/actor/entite-sheet.js
@@ -1,8 +1,8 @@
-import { RdDActorSheet } from "./actor-sheet.js";
-import { RdDSheetUtility } from "./rdd-sheet-utility.js";
-import { RdDUtility } from "./rdd-utility.js";
+import { RdDBaseActorReveSheet } from "./base-actor-reve-sheet.js";
+import { RdDSheetUtility } from "../rdd-sheet-utility.js";
+import { RdDUtility } from "../rdd-utility.js";
-export class RdDActorEntiteSheet extends RdDActorSheet {
+export class RdDActorEntiteSheet extends RdDBaseActorReveSheet {
/** @override */
static get defaultOptions() {
diff --git a/module/actor/entite.js b/module/actor/entite.js
new file mode 100644
index 00000000..7e971249
--- /dev/null
+++ b/module/actor/entite.js
@@ -0,0 +1,110 @@
+import { ENTITE_INCARNE, ENTITE_NONINCARNE } from "../constants.js";
+import { TYPES } from "../item.js";
+import { Misc } from "../misc.js";
+import { RdDEncaisser } from "../rdd-roll-encaisser.js";
+import { STATUSES } from "../settings/status-effects.js";
+import { RdDBaseActorReve } from "./base-actor-reve.js";
+
+export class RdDEntite extends RdDBaseActorReve {
+
+ static get defaultIcon() {
+ return "systems/foundryvtt-reve-de-dragon/icons/entites/darquoine.webp";
+ }
+
+ canReceive(item) {
+ return item.type == TYPES.competencecreature
+ }
+
+ isEntite(typeentite = []) {
+ return (typeentite.length == 0 || typeentite.includes(this.system.definition.typeentite));
+ }
+ isNonIncarnee() { return this.isEntite([ENTITE_NONINCARNE]) }
+
+ getReveActuel() {
+ return Misc.toInt(this.system.carac.reve?.value)
+ }
+
+ getForce() { return this.getReve() }
+ getAgilite() { return this.getReve() }
+ getChance() { return this.getReve() }
+
+ getDraconicOuPossession() {
+ return this.itemTypes[TYPES.competencecreature]
+ .filter(it => it.system.categorie == 'possession')
+ .sort(Misc.descending(it => it.system.niveau))
+ .find(it => true);
+ }
+
+ async remiseANeuf() {
+ await this.removeEffects(e => true);
+ if (!this.isNonIncarnee()) {
+ await this.update({
+ 'system.sante.endurance.value': this.system.sante.endurance.max
+ });
+ }
+ }
+
+ isDead() {
+ return this.isNonIncarnee() ? false : this.system.sante.endurance.value <= 0
+ }
+
+ async santeIncDec(name, inc, isCritique = false) {
+ if (name == 'endurance' && !this.isNonIncarnee()) {
+ const oldValue = this.system.sante.endurance.value;
+ const endurance = Math.max(0,
+ Math.min(oldValue + inc,
+ this.system.sante.endurance.max));
+ await this.update({ "system.sante.endurance.value": endurance })
+ await this.setEffect(STATUSES.StatusComma, endurance <= 0);
+ return {
+ perte: oldValue - endurance,
+ newValue: endurance
+ }
+ }
+ return {}
+ }
+ async encaisser() {
+ if (this.isNonIncarnee()) {
+ return
+ }
+ await RdDEncaisser.encaisser(this)
+ }
+
+ isEffectAllowed(statusId) {
+ return [STATUSES.StatusComma].includes(statusId);
+ }
+
+ async onAppliquerJetEncaissement(encaissement, attacker) {
+ const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance);
+ mergeObject(encaissement, {
+ resteEndurance: perteEndurance.newValue,
+ endurance: perteEndurance.perte
+ });
+ }
+
+ isEntiteAccordee(attacker) {
+ if (this.isEntite([ENTITE_INCARNE])) {
+ let resonnance = this.system.sante.resonnance
+ return (resonnance.actors.find(it => it == attacker.id))
+ }
+ return true
+ }
+
+ /* -------------------------------------------- */
+ async setEntiteReveAccordee(attacker) {
+ if (this.isEntite([ENTITE_INCARNE])) {
+ let resonnance = duplicate(this.system.sante.resonnance);
+ if (resonnance.actors.find(it => it == attacker.id)) {
+ // déjà accordé
+ return;
+ }
+ resonnance.actors.push(attacker.id);
+ await this.update({ "system.sante.resonnance": resonnance });
+ }
+ else {
+ super.setEntiteReveAccordee(attacker)
+ }
+ }
+
+
+}
diff --git a/module/actor-vehicule-sheet.js b/module/actor/vehicule-sheet.js
similarity index 64%
rename from module/actor-vehicule-sheet.js
rename to module/actor/vehicule-sheet.js
index 0124de0c..fcd4dcf6 100644
--- a/module/actor-vehicule-sheet.js
+++ b/module/actor/vehicule-sheet.js
@@ -1,8 +1,8 @@
-import { RdDUtility } from "./rdd-utility.js";
-import { RdDActorSheet } from "./actor-sheet.js";
+import { RdDUtility } from "../rdd-utility.js";
+import { RdDBaseActorSheet } from "./base-actor-sheet.js";
/* -------------------------------------------- */
-export class RdDActorVehiculeSheet extends RdDActorSheet {
+export class RdDActorVehiculeSheet extends RdDBaseActorSheet {
/** @override */
static get defaultOptions() {
@@ -18,6 +18,22 @@ export class RdDActorVehiculeSheet extends RdDActorSheet {
});
}
+ /* -------------------------------------------- */
+ async getData() {
+ let formData = await super.getData();
+ mergeObject(formData,
+ {
+ editable: this.isEditable,
+ cssClass: this.isEditable ? "editable" : "locked",
+ effects: this.actor.effects.map(e => foundry.utils.deepClone(e)),
+ limited: this.actor.limited,
+ owner: this.actor.isOwner,
+ });
+
+ this.timerRecherche = undefined;
+ return formData;
+ }
+
activateListeners(html) {
super.activateListeners(html);
if (!this.options.editable) return;
diff --git a/module/actor/vehicule.js b/module/actor/vehicule.js
new file mode 100644
index 00000000..4cd8edde
--- /dev/null
+++ b/module/actor/vehicule.js
@@ -0,0 +1,28 @@
+import { RdDBaseActor } from "./base-actor.js";
+
+export class RdDVehicule extends RdDBaseActor {
+
+ static get defaultIcon() {
+ return "systems/foundryvtt-reve-de-dragon/icons/vehicules/charette.webp";
+ }
+ isVehicule() { return true }
+
+ canReceive(item) {
+ return item.isInventaire();
+ }
+
+ getEncombrementMax() {
+ return this.system.capacite_encombrement;
+ }
+
+ async vehicleIncDec(name, inc) {
+ if (!['resistance', 'structure'].includes(name)) {
+ return
+ }
+ const newValue = this.system.etat[name].value + inc;
+ if (0 <= newValue && newValue <= this.system.etat[name].max) {
+ await this.update({ [`system.etat.${name}.value`]: newValue })
+ }
+ }
+
+}
diff --git a/module/dialog-create-signedraconique.js b/module/dialog-create-signedraconique.js
index 0620864d..3d38d29b 100644
--- a/module/dialog-create-signedraconique.js
+++ b/module/dialog-create-signedraconique.js
@@ -14,7 +14,7 @@ export class DialogCreateSigneDraconique extends Dialog {
.map(actor => ({
id: actor.id,
name: actor.name,
- selected: true
+ selected: false
}))
};
diff --git a/module/dialog-validation-encaissement.js b/module/dialog-validation-encaissement.js
index a8808466..d4c41f61 100644
--- a/module/dialog-validation-encaissement.js
+++ b/module/dialog-validation-encaissement.js
@@ -7,20 +7,19 @@ import { RdDUtility } from "./rdd-utility.js";
*/
export class DialogValidationEncaissement extends Dialog {
- static async validerEncaissement(actor, rollData, armure, show, attackerId, onEncaisser) {
+ static async validerEncaissement(actor, rollData, armure, onEncaisser) {
let encaissement = await RdDUtility.jetEncaissement(rollData, armure, { showDice: HIDE_DICE });
const html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-validation-encaissement.html', {
actor: actor,
rollData: rollData,
- encaissement: encaissement,
- show: show
+ encaissement: encaissement
});
- const dialog = new DialogValidationEncaissement(html, actor, rollData, armure, encaissement, show, attackerId, onEncaisser);
+ const dialog = new DialogValidationEncaissement(html, actor, rollData, armure, encaissement, onEncaisser);
dialog.render(true);
}
/* -------------------------------------------- */
- constructor(html, actor, rollData, armure, encaissement, show, attackerId, onEncaisser) {
+ constructor(html, actor, rollData, armure, encaissement, onEncaisser) {
// Common conf
let buttons = {
"valider": { label: "Valider", callback: html => this.onValider() },
@@ -47,8 +46,6 @@ export class DialogValidationEncaissement extends Dialog {
this.rollData = rollData;
this.armure = armure;
this.encaissement = encaissement;
- this.show = show;
- this.attackerId = attackerId;
this.onEncaisser = onEncaisser;
this.forceDiceResult = {total: encaissement.roll.result };
}
@@ -67,6 +64,6 @@ export class DialogValidationEncaissement extends Dialog {
async onValider() {
this.encaissement = await RdDUtility.jetEncaissement(this.rollData, this.armure, { showDice: SHOW_DICE, forceDiceResult: this.forceDiceResult});
- this.onEncaisser(this.encaissement, this.show, this.attackerId)
+ this.onEncaisser(this.encaissement)
}
}
diff --git a/module/item-arme.js b/module/item-arme.js
index bbc87102..ae7b688e 100644
--- a/module/item-arme.js
+++ b/module/item-arme.js
@@ -20,7 +20,7 @@ const nomCategorieParade = {
export class RdDItemArme extends Item {
static isArme(item) {
- return RdDItemCompetenceCreature.getCategorieAttaque(item) || item.type == TYPES.arme;
+ return item.type == TYPES.arme || RdDItemCompetenceCreature.getCategorieAttaque(item);
}
/* -------------------------------------------- */
diff --git a/module/item-competencecreature.js b/module/item-competencecreature.js
index 85f3f291..c5dae365 100644
--- a/module/item-competencecreature.js
+++ b/module/item-competencecreature.js
@@ -55,6 +55,20 @@ export class RdDItemCompetenceCreature extends Item {
}
/* -------------------------------------------- */
+ static isCompetenceAttaque(item) {
+ if (item.type == TYPES.competencecreature) {
+ switch (item.system.categorie) {
+ case "melee":
+ case "tir":
+ case "lancer":
+ case "naturelle":
+ case "possession":
+ return true
+ }
+ }
+ return undefined
+ }
+
static getCategorieAttaque(item) {
if (item.type == TYPES.competencecreature) {
switch (item.system.categorie) {
@@ -63,6 +77,7 @@ export class RdDItemCompetenceCreature extends Item {
case "lancer":
case "naturelle":
case "possession":
+ case "parade":
return item.system.categorie
}
}
diff --git a/module/misc.js b/module/misc.js
index f055f4a0..e7ce38fe 100644
--- a/module/misc.js
+++ b/module/misc.js
@@ -63,8 +63,8 @@ export class Misc {
static keepDecimals(num, decimals) {
if (decimals <= 0 || decimals > 6) return num;
- const decimal = Math.pow(10, parseInt(decimals));
- return Math.round(num * decimal) / decimal;
+ const power10n = Math.pow(10, parseInt(decimals));
+ return Math.round(num * power10n) / power10n;
}
static getFractionHtml(diviseur) {
diff --git a/module/rdd-carac.js b/module/rdd-carac.js
index 10a23e5b..1defe187 100644
--- a/module/rdd-carac.js
+++ b/module/rdd-carac.js
@@ -1,7 +1,7 @@
import { Grammar } from "./grammar.js";
import { Misc } from "./misc.js";
-const tableCaracDerivee = {
+const TABLE_CARACTERISTIQUES_DERIVEES = {
// xp: coût pour passer du niveau inférieur à ce niveau
1: { xp: 3, poids: "moins de 1kg", plusdom: -5, sconst: 0.5, sust: 0.1 },
2: { xp: 3, poids: "1-5", plusdom: -4, sconst: 0.5, sust: 0.3 },
@@ -58,6 +58,10 @@ export class RdDCarac {
selectedCarac?.label.match(/(Apparence|Force|Agilité|Dextérité|Vue|Ouïe|Odorat-Goût|Empathie|Dérobée|Mêlée|Tir|Lancer)/);
}
+ static getCaracDerivee(value) {
+ return TABLE_CARACTERISTIQUES_DERIVEES[Math.min(Math.max(Number(value), 1), 32)];
+ }
+
static computeTotal(carac, beaute = undefined) {
const total = Object.values(carac ?? {}).filter(c => !c.derivee)
.map(it => parseInt(it.value))
@@ -73,7 +77,7 @@ export class RdDCarac {
/* -------------------------------------------- */
static calculSConst(constitution) {
- return Number(tableCaracDerivee[Number(constitution)].sconst);
+ return RdDCarac.getCaracDerivee(constitution).sconst;
}
/* -------------------------------------------- */
@@ -84,7 +88,7 @@ export class RdDCarac {
}
static getCaracXp(targetValue) {
- return tableCaracDerivee[targetValue]?.xp ?? 200;
+ return RdDCarac.getCaracDerivee(targetValue)?.xp ?? 200;
}
@@ -97,37 +101,4 @@ export class RdDCarac {
return Grammar.toLowerCaseNoAccent(selectedCarac?.label)?.match(/(apparence|force|agilite|dexterite|vue|ouie|odorat|empathie|melee|tir|lancer|derobee)/);
}
- /* -------------------------------------------- */
- static computeCarac(system) {
- system.carac.force.value = Math.min(system.carac.force.value, parseInt(system.carac.taille.value) + 4);
-
- system.carac.derobee.value = Math.floor(parseInt(((21 - system.carac.taille.value)) + parseInt(system.carac.agilite.value)) / 2);
- let bonusDomKey = Math.floor((parseInt(system.carac.force.value) + parseInt(system.carac.taille.value)) / 2);
- bonusDomKey = Math.min(Math.max(bonusDomKey, 0), 32); // Clamp de securite
-
- let tailleData = tableCaracDerivee[bonusDomKey];
- system.attributs.plusdom.value = tailleData.plusdom;
-
- system.attributs.sconst.value = RdDCarac.calculSConst(system.carac.constitution.value);
- system.attributs.sust.value = tableCaracDerivee[Number(system.carac.taille.value)].sust;
-
- system.attributs.encombrement.value = (parseInt(system.carac.force.value) + parseInt(system.carac.taille.value)) / 2;
- system.carac.melee.value = Math.floor((parseInt(system.carac.force.value) + parseInt(system.carac.agilite.value)) / 2);
- system.carac.tir.value = Math.floor((parseInt(system.carac.vue.value) + parseInt(system.carac.dexterite.value)) / 2);
- system.carac.lancer.value = Math.floor((parseInt(system.carac.tir.value) + parseInt(system.carac.force.value)) / 2);
-
- system.sante.vie.max = Math.ceil((parseInt(system.carac.taille.value) + parseInt(system.carac.constitution.value)) / 2);
-
- system.sante.vie.value = Math.min(system.sante.vie.value, system.sante.vie.max)
- system.sante.endurance.max = Math.max(parseInt(system.carac.taille.value) + parseInt(system.carac.constitution.value), parseInt(system.sante.vie.max) + parseInt(system.carac.volonte.value));
- system.sante.endurance.value = Math.min(system.sante.endurance.value, system.sante.endurance.max);
- system.sante.fatigue.max = system.sante.endurance.max * 2;
- system.sante.fatigue.value = Math.min(system.sante.fatigue.value, system.sante.fatigue.max);
-
- //Compteurs
- system.reve.reve.max = system.carac.reve.value;
- system.compteurs.chance.max = system.carac.chance.value;
- }
-
-
}
diff --git a/module/rdd-combat.js b/module/rdd-combat.js
index c0eea94c..01924c4b 100644
--- a/module/rdd-combat.js
+++ b/module/rdd-combat.js
@@ -87,7 +87,7 @@ export class RdDCombatManager extends Combat {
let rollFormula = formula ?? RdDCombatManager.formuleInitiative(2, 10, 0, 0);
if (!formula) {
if (combatant.actor.type == 'creature' || combatant.actor.type == 'entite') {
- const competence = combatant.actor.items.find(it => RdDItemCompetenceCreature.getCategorieAttaque(it))
+ const competence = combatant.actor.items.find(it => RdDItemCompetenceCreature.isCompetenceAttaque(it))
if (competence) {
rollFormula = RdDCombatManager.formuleInitiative(2, competence.system.carac_value, competence.system.niveau, 0);
}
@@ -229,7 +229,9 @@ export class RdDCombatManager extends Combat {
}
static listActionsCreature(competences) {
- return competences.map(it => RdDItemCompetenceCreature.armeCreature(it))
+ return competences
+ .filter(it => RdDItemCompetenceCreature.isCompetenceAttaque(it))
+ .map(it => RdDItemCompetenceCreature.armeCreature(it))
.filter(it => it != undefined);
}
@@ -822,8 +824,8 @@ export class RdDCombat {
// finesse seulement en mélée, pour l'empoignade, ou si la difficulté libre est de -1 minimum
// rapidité seulement en mêlée, si l'arme le permet, et si la difficulté libre est de -1 minimum
const isForce = !rollData.arme.system.empoignade;
- const isFinesse = rollData.arme.system.empoignade || isMeleeDiffNegative;
- const isRapide = !rollData.arme.system.empoignade && isMeleeDiffNegative && rollData.arme.system.rapide;
+ const isFinesse = rollData.tactique != 'charge' && (rollData.arme.system.empoignade || isMeleeDiffNegative);
+ const isRapide = rollData.tactique != 'charge' && !rollData.arme.system.empoignade && isMeleeDiffNegative && rollData.arme.system.rapide;
// si un seul choix possible, le prendre
if (isForce && !isFinesse && !isRapide) {
return await this.choixParticuliere(rollData, "force");
diff --git a/module/rdd-main.js b/module/rdd-main.js
index b70dea0c..0d4d0175 100644
--- a/module/rdd-main.js
+++ b/module/rdd-main.js
@@ -29,11 +29,13 @@ import { Environnement } from "./environnement.js";
import { RdDActor } from "./actor.js";
import { RdDBaseActor } from "./actor/base-actor.js";
import { RdDCommerce } from "./actor/commerce.js";
+import { RdDEntite } from "./actor/entite.js";
+import { RdDVehicule } from "./actor/vehicule.js";
import { RdDActorSheet } from "./actor-sheet.js";
import { RdDCommerceSheet } from "./actor/commerce-sheet.js";
-import { RdDActorCreatureSheet } from "./actor-creature-sheet.js";
-import { RdDActorVehiculeSheet } from "./actor-vehicule-sheet.js";
-import { RdDActorEntiteSheet } from "./actor-entite-sheet.js";
+import { RdDCreatureSheet } from "./actor/creature-sheet.js";
+import { RdDActorEntiteSheet } from "./actor/entite-sheet.js";
+import { RdDActorVehiculeSheet } from "./actor/vehicule-sheet.js";
import { RdDItem } from "./item.js";
import { RdDItemBlessure } from "./item/blessure.js";
@@ -60,6 +62,7 @@ import { RdDItemInventaireSheet } from "./item/sheet-base-inventaire.js";
import { AppAstrologie } from "./sommeil/app-astrologie.js";
import { RdDItemArmure } from "./item/armure.js";
import { AutoAdjustDarkness as AutoAdjustDarkness } from "./time/auto-adjust-darkness.js";
+import { RdDCreature } from "./actor/creature.js";
/**
* RdD system
@@ -91,10 +94,10 @@ export class SystemReveDeDragon {
}
this.actorClasses = {
commerce: RdDCommerce,
- creature: RdDActor,
- entite: RdDActor,
+ creature: RdDCreature,
+ entite: RdDEntite,
personnage: RdDActor,
- vehicule: RdDActor,
+ vehicule: RdDVehicule,
}
}
@@ -150,7 +153,7 @@ export class SystemReveDeDragon {
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet(SYSTEM_RDD, RdDCommerceSheet, { types: ["commerce"], makeDefault: true });
Actors.registerSheet(SYSTEM_RDD, RdDActorSheet, { types: ["personnage"], makeDefault: true });
- Actors.registerSheet(SYSTEM_RDD, RdDActorCreatureSheet, { types: ["creature"], makeDefault: true });
+ Actors.registerSheet(SYSTEM_RDD, RdDCreatureSheet, { types: ["creature"], makeDefault: true });
Actors.registerSheet(SYSTEM_RDD, RdDActorVehiculeSheet, { types: ["vehicule"], makeDefault: true });
Actors.registerSheet(SYSTEM_RDD, RdDActorEntiteSheet, { types: ["entite"], makeDefault: true });
Items.unregisterSheet("core", ItemSheet);
diff --git a/module/rdd-resolution-table.js b/module/rdd-resolution-table.js
index 60ba2114..3bc94254 100644
--- a/module/rdd-resolution-table.js
+++ b/module/rdd-resolution-table.js
@@ -28,7 +28,7 @@ const reussites = [
const reussiteInsuffisante = { code: "notSign", isPart: false, isSign: false, isSuccess: false, isEchec: true, isEPart: false, isETotal: false, ptTache: 0, ptQualite: -2, quality: "Réussite insuffisante", condition: (target, roll) => false }
/* -------------------------------------------- */
-const caracMaximumResolution = 60;
+const CARAC_MAXIMUM_RESOLUTION = 40;
/* -------------------------------------------- */
export class RdDResolutionTable {
static resolutionTable = this.build()
@@ -36,7 +36,7 @@ export class RdDResolutionTable {
/* -------------------------------------------- */
static build() {
let table = []
- for (var caracValue = 0; caracValue <= caracMaximumResolution; caracValue++) {
+ for (var caracValue = 0; caracValue <= CARAC_MAXIMUM_RESOLUTION; caracValue++) {
table[caracValue] = this._computeRow(caracValue);
}
return table;
diff --git a/module/rdd-tmr-dialog.js b/module/rdd-tmr-dialog.js
index 37561dbb..78280820 100644
--- a/module/rdd-tmr-dialog.js
+++ b/module/rdd-tmr-dialog.js
@@ -56,7 +56,7 @@ export class RdDTMRDialog extends Dialog {
this.actor = actor;
this.actor.tmrApp = this; // reference this app in the actor structure
this.viewOnly = tmrData.mode == "visu"
- this.fatigueParCase = this.viewOnly || !ReglesOptionnelles.isUsing("appliquer-fatigue") ? 0 : this.actor.getTMRFatigue();
+ this.fatigueParCase = this.viewOnly ? 0 : this.actor.getCoutFatigueTMR();
this.cumulFatigue = 0;
this.loadRencontres();
this.loadCasesSpeciales();
@@ -262,10 +262,8 @@ export class RdDTMRDialog extends Dialog {
// Gestion du cout de montée en points de rêve
let reveCout = ((this.tmrdata.isRapide && !EffetsDraconiques.isDeplacementAccelere(this.actor)) ? -2 : -1) - this.actor.countMonteeLaborieuse();
- if (ReglesOptionnelles.isUsing("appliquer-fatigue")) {
- this.cumulFatigue += this.fatigueParCase;
- }
await this.actor.reveActuelIncDec(reveCout);
+ this.cumulFatigue += this.fatigueParCase;
// Le reste...
this.updateValuesDisplay();
let tmr = TMRUtility.getTMR(this._getActorCoord());
@@ -316,7 +314,8 @@ export class RdDTMRDialog extends Dialog {
await this.actor.setEffect(STATUSES.StatusDemiReve, false)
this._tellToGM(this.actor.name + " a quitté les terres médianes");
}
- await this.actor.santeIncDec("fatigue", this.cumulFatigue)
+ await this.actor.santeIncDec((ReglesOptionnelles.isUsing("appliquer-fatigue") ? "fatigue" : "endurance"),
+ this.cumulFatigue)
}
await super.close();
}
@@ -405,12 +404,16 @@ export class RdDTMRDialog extends Dialog {
this.close();
return true;
}
- const resteAvantInconscience = this.actor.getFatigueMax() - this.actor.getFatigueActuelle() - this.cumulFatigue;
- if (ReglesOptionnelles.isUsing("appliquer-fatigue") && resteAvantInconscience <= 0) {
+
+ if (ReglesOptionnelles.isUsing("appliquer-fatigue")
+ ? (this.actor.getFatigueRestante() <= this.cumulFatigue)
+ : (this.actor.getEnduranceActuelle() <= this.cumulFatigue)
+ ) {
this._tellToGM("Vous vous écroulez de fatigue : vous quittez les Terres médianes !");
this.quitterLesTMRInconscient();
return true;
}
+
if (this.actor.getReveActuel() == 0) {
this._tellToGM("Vos Points de Rêve sont à 0 : vous quittez les Terres médianes !");
this.quitterLesTMRInconscient();
diff --git a/module/rdd-utility.js b/module/rdd-utility.js
index b3b188a9..0f8880e3 100644
--- a/module/rdd-utility.js
+++ b/module/rdd-utility.js
@@ -28,7 +28,7 @@ const ajustementsEncaissement = Misc.intArray(-10, 26);
/* -------------------------------------------- */
function _buildAllSegmentsFatigue(max) {
const cycle = [5, 2, 4, 1, 3, 0];
- let fatigue = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
+ const fatigue = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
for (let i = 0; i <= max; i++) {
const ligneFatigue = duplicate(fatigue[i]);
const caseIncrementee = cycle[i % 6];
@@ -55,7 +55,8 @@ function _cumulSegmentsFatigue(matrix) {
}
/* -------------------------------------------- */
-const fatigueMatrix = _buildAllSegmentsFatigue(60);
+export const MAX_ENDURANCE_FATIGUE = 60;
+const fatigueMatrix = _buildAllSegmentsFatigue(MAX_ENDURANCE_FATIGUE);
const cumulFatigueMatrix = _cumulSegmentsFatigue(fatigueMatrix);
const fatigueMalus = [0, 0, 0, -1, -1, -1, -2, -3, -4, -5, -6, -7]; // Provides the malus for each segment of fatigue
@@ -460,18 +461,15 @@ export class RdDUtility {
}
/* -------------------------------------------- */
- static getSegmentsFatigue(maxEnd) {
- maxEnd = Math.max(maxEnd, 1);
- maxEnd = Math.min(maxEnd, fatigueMatrix.length);
- return fatigueMatrix[maxEnd];
+ static getSegmentsFatigue(maxEndurance) {
+ return fatigueMatrix[Math.min(Math.max(maxEndurance, 1), fatigueMatrix.length)];
}
/* -------------------------------------------- */
- static calculMalusFatigue(fatigue, maxEnd) {
- maxEnd = Math.max(maxEnd, 1);
- maxEnd = Math.min(maxEnd, cumulFatigueMatrix.length);
- let segments = cumulFatigueMatrix[maxEnd];
- for (let i = 0; i < 12; i++) {
+ static calculMalusFatigue(fatigue, endurance) {
+ endurance = Math.min(Math.max(endurance, 1), cumulFatigueMatrix.length);
+ let segments = cumulFatigueMatrix[endurance];
+ for (let i = 0; i < segments.length; i++) {
if (fatigue <= segments[i]) {
return fatigueMalus[i]
}
@@ -491,7 +489,7 @@ export class RdDUtility {
// Build the nice (?) html table used to manage fatigue.
// max should be the endurance max value
static makeHTMLfatigueMatrix(fatigue, maxEndurance) {
- let segments = this.getSegmentsFatigue(maxEndurance);
+ const segments = this.getSegmentsFatigue(maxEndurance);
return this.makeHTMLfatigueMatrixForSegment(fatigue, segments);
}
@@ -625,25 +623,6 @@ export class RdDUtility {
return perte.total;
}
- /* -------------------------------------------- */
- static currentFatigueMalus(value, max) {
- if (ReglesOptionnelles.isUsing("appliquer-fatigue")) {
- max = Math.max(1, Math.min(max, 60));
- value = Math.min(max * 2, Math.max(0, value));
-
- let fatigueTab = fatigueMatrix[max];
- let fatigueRem = value;
- for (let idx = 0; idx < fatigueTab.length; idx++) {
- fatigueRem -= fatigueTab[idx];
- if (fatigueRem <= 0) {
- return fatigueMalus[idx];
- }
- }
- return -7; // This is the max !
- }
- return 0;
- }
-
/* -------------------------------------------- */
static async responseNombreAstral(callData) {
let actor = game.actors.get(callData.id);
diff --git a/module/settings/regles-optionnelles.js b/module/settings/regles-optionnelles.js
index 8f3f3bf1..fa77bcd9 100644
--- a/module/settings/regles-optionnelles.js
+++ b/module/settings/regles-optionnelles.js
@@ -4,6 +4,12 @@ import { Misc } from "../misc.js";
const listeReglesOptionnelles = [
{ group: 'Règles générales', name: 'appliquer-fatigue', descr: "Appliquer les règles de fatigue"},
{ group: 'Règles générales', name: 'astrologie', descr: "Appliquer les ajustements astrologiques aux jets de chance et aux rituels"},
+ { group: 'Récupération', name: 'transformation-stress', descr: "Transformer le stress durant Château Dormant"},
+ { group: 'Récupération', name: 'recuperation-chance', descr: "Récupérer la chance durant Château Dormant"},
+ { group: 'Récupération', name: 'recuperation-ethylisme', descr: "Récupérer l'éthylisme"},
+ { group: 'Récupération', name: 'recuperation-reve', descr: "Récupérer le rêve pendant la nuit (les jets sont toujours faits pour les Rêves de Dragons)"},
+ { group: 'Récupération', name: 'recuperation-moral', descr: "Le moral revient vers 0 durant Château Dormant"},
+
{ group: 'Règles de combat', name: 'recul', descr: "Appliquer le recul en cas de particulière en force ou de charge" },
{ group: 'Règles de combat', name: 'resistanceArmeParade', descr: "Faire le jet de résistance des armes lors de parades pouvant les endommager" },
diff --git a/module/tmr/effets-rencontres.js b/module/tmr/effets-rencontres.js
index ec507768..72db974e 100644
--- a/module/tmr/effets-rencontres.js
+++ b/module/tmr/effets-rencontres.js
@@ -2,6 +2,7 @@ import { ExperienceLog, XP_TOPIC } from "../actor/experience-log.js";
import { ChatUtility } from "../chat-utility.js";
import { Poetique } from "../poetique.js";
import { RdDDice } from "../rdd-dice.js";
+import { ReglesOptionnelles } from "../settings/regles-optionnelles.js";
import { TMRUtility } from "../tmr-utility.js";
export class EffetsRencontre {
@@ -9,7 +10,7 @@ export class EffetsRencontre {
static messager = async (dialog, context) => {
dialog.setRencontreState('messager', TMRUtility.getTMRPortee(context.tmr.coord, context.rencontre.system.force));
}
-
+
static passeur = async (dialog, context) => {
dialog.setRencontreState('passeur', TMRUtility.getTMRPortee(context.tmr.coord, context.rencontre.system.force));
}
@@ -26,16 +27,25 @@ export class EffetsRencontre {
static reve_plus_1 = async (dialog, context) => { await EffetsRencontre.$reve_plus(context.actor, 1) }
static reve_moins_force = async (dialog, context) => { await EffetsRencontre.$reve_plus(context.actor, -context.rencontre.system.force) }
static reve_moins_1 = async (dialog, context) => { await EffetsRencontre.$reve_plus(context.actor, -1) }
- static $reve_plus = async (actor, valeur) => { await actor.reveActuelIncDec(valeur) }
+ static $reve_plus = async (actor, reve) => {
+ if (!ReglesOptionnelles.isUsing("recuperation-reve") && reve < 0) {
+ ChatMessage.create({
+ whisper: ChatUtility.getWhisperRecipientsAndGMs(actor.name),
+ content: `Pas de récupération de rêve (${reve} points ignorés)`
+ });
+ return
+ }
+ await actor.reveActuelIncDec(reve)
+ }
static vie_moins_1 = async (dialog, context) => { await EffetsRencontre.$vie_plus(context.actor, -1) }
static vie_moins_force = async (dialog, context) => { await EffetsRencontre.$vie_plus(context.actor, -context.rencontre.system.force) }
static $vie_plus = async (actor, valeur) => { await actor.santeIncDec("vie", valeur) }
-
+
static moral_plus_1 = async (dialog, context) => { await EffetsRencontre.$vie_plus(context.actor, 1) }
static moral_moins_1 = async (dialog, context) => { await EffetsRencontre.$vie_plus(context.actor, -1) }
static $moral_plus = async (actor, valeur) => { await actor.moralIncDec(valeur) }
-
+
static end_moins_1 = async (dialog, context) => { await EffetsRencontre.$vie_plus(context.actor, -1) }
static end_moins_force = async (dialog, context) => { await EffetsRencontre.$vie_plus(context.actor, -context.rencontre.system.force) }
static $end_plus = async (actor, valeur) => { await actor.santeIncDec("endurance", valeur) }
@@ -50,8 +60,8 @@ export class EffetsRencontre {
const perte = context.rolled.isETotal ? context.rencontre.system.force : 1;
await context.actor.chanceActuelleIncDec("fatigue", -perte);
}
-
- static xp_sort_force = async (dialog, context) => {
+
+ static xp_sort_force = async (dialog, context) => {
let competence = context.competence;
if (competence) {
const fromXpSort = Number(competence.system.xp_sort);
@@ -60,7 +70,7 @@ export class EffetsRencontre {
await ExperienceLog.add(this, XP_TOPIC.XPSORT, fromXpSort, toXpSort, `${competence.name} - ${context.rencontre.name} en TMR`);
}
}
-
+
static stress_plus_1 = async (dialog, context) => {
await context.actor.addCompteurValue('stress', 1, `Rencontre d'un ${context.rencontre.name} en TMR`);
}
@@ -75,7 +85,7 @@ export class EffetsRencontre {
static demireve_rompu = async (dialog, context) => {
dialog.close()
- }
+ }
static sort_aleatoire = async (dialog, context) => {
context.sortReserve = await RdDDice.rollOneOf(context.actor.itemTypes['sortreserve']);
@@ -128,7 +138,7 @@ export class EffetsRencontre {
static regain_seuil = async (dialog, context) => {
await context.actor.regainPointDeSeuil()
- }
+ }
static async $reinsertion(dialog, actor, filter) {
const newTMR = await TMRUtility.getTMRAleatoire(filter);
diff --git a/system.json b/system.json
index b0ff954c..f30e1b27 100644
--- a/system.json
+++ b/system.json
@@ -1,8 +1,8 @@
{
"id": "foundryvtt-reve-de-dragon",
"title": "Rêve de Dragon",
- "version": "11.0.28",
- "download": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/archive/foundryvtt-reve-de-dragon-11.0.28.zip",
+ "version": "11.1.0",
+ "download": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/archive/foundryvtt-reve-de-dragon-11.1.0.zip",
"manifest": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/raw/v11/system.json",
"changelog": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/raw/branch/v11/changelog.md",
"compatibility": {
diff --git a/templates/chat-resultat-encaissement.html b/templates/chat-resultat-encaissement.html
index 275ca74b..2e35925a 100644
--- a/templates/chat-resultat-encaissement.html
+++ b/templates/chat-resultat-encaissement.html
@@ -44,7 +44,7 @@
{{#if (ne dmg.mortalite 'entiteincarnee')}}
{{#if (gt endurance 1)}}et
{{#if sonne}}est sonné jusqu'à la fin du prochain round{{else}}n'est pas sonné{{/if}}!
- {{#if hasPlayerOwner}}Jet d'endurance : Jet d'endurance : {{jetEndurance}} / {{resteEndurance}}{{/if}}
+ {{#if hasPlayerOwner}}Jet d'endurance : {{jetEndurance}} / {{resteEndurance}}{{/if}}
{{/if}}
{{/if}}
{{/if}}