diff --git a/module/actor.js b/module/actor.js
index cf6b7268..fa0312b9 100644
--- a/module/actor.js
+++ b/module/actor.js
@@ -12,7 +12,7 @@ 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 { RdDCombat, RdDCombatSettings } from "./rdd-combat.js";
import { DeDraconique } from "./de-draconique.js";
import { RdDAudio } from "./rdd-audio.js";
import { RdDItemCompetence } from "./item-competence.js";
@@ -32,6 +32,7 @@ export class RdDActor extends Actor {
static init() {
Hooks.on("deleteActiveEffect", (actor, effect, options) => actor.onDeleteActiveEffect(effect, options));
Hooks.on("createActiveEffect", (actor, effect, options) => actor.onCreateActiveEffect(effect, options));
+ Hooks.on("updateActor", (actor, update, options, actorId) => actor.onUpdateActor(update, options, actorId));
}
/* -------------------------------------------- */
@@ -99,18 +100,18 @@ export class RdDActor extends Actor {
// Make separate methods for each Actor type (character, npc, etc.) to keep
// things organized.
if (actorData.type === 'personnage') this._prepareCharacterData(actorData);
- if (actorData.type === 'creature') this.prepareCreatureData(actorData);
- if (actorData.type === 'vehicule') this.prepareVehiculeData(actorData);
+ if (actorData.type === 'creature') this._prepareCreatureData(actorData);
+ if (actorData.type === 'vehicule') this._prepareVehiculeData(actorData);
}
/* -------------------------------------------- */
- prepareCreatureData(actorData) {
+ _prepareCreatureData(actorData) {
this.computeEncombrementTotalEtMalusArmure();
this.computeEtatGeneral();
}
/* -------------------------------------------- */
- prepareVehiculeData( actorData ) {
+ _prepareVehiculeData( actorData ) {
this.computeEncombrementTotalEtMalusArmure();
}
@@ -1105,7 +1106,7 @@ export class RdDActor extends Actor {
if (this.isEntiteCauchemar()) {
return 0;
}
- return this.data.data.attributs?.sconst?.value ?? 0;
+ return RdDUtility.calculSConst(this.data.data.carac.constitution.value);
}
/* -------------------------------------------- */
@@ -1177,18 +1178,14 @@ export class RdDActor extends Actor {
sonne: false,
};
- let minValue = 0;
- if (this.type == 'personnage') {
- // TODO: les animaux/humanoïdes on théoriquement aussi un sconst, mais la SPA n'est pas passé par là
- minValue = name == "vie" ? -Number(this.data.data.attributs.sconst.value) : 0;
- }
+ 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.data.type != 'entite') {
- if (!isCritique && result.newValue == 0 && inc < 0) { // perte endurance et endurance devient 0 -> -1 vie sauf si coup critique
- sante.vie.value = sante.vie.value - 1;
+ if (name == "endurance" && !this.isEntiteCauchemar()) {
+ if (result.newValue == 0 && inc < 0 && !isCritique) { // perte endurance et endurance devient 0 (sauf critique) -> -1 vie
+ sante.vie.value --;
}
result.newValue = Math.max(0, result.newValue);
if (inc > 0) { // le max d'endurance s'applique seulement à la récupération
@@ -1202,6 +1199,7 @@ export class RdDActor extends Actor {
result.jetEndurance = testIsSonne.roll.total;
} else if (inc > 0) {
await this.setSonne(false);
+ sante.sonne.value = false;
}
if (sante.fatigue && inc < 0) { // Each endurance lost -> fatigue lost
fatigue = perte;
@@ -1213,6 +1211,9 @@ export class RdDActor extends Actor {
if (sante.fatigue && fatigue > 0) {
sante.fatigue.value = Math.max(sante.fatigue.value + fatigue, this._computeFatigueMin());
}
+ if (!this.isEntiteCauchemar() && sante.vie.value<-this.getSConst()) {
+ await this.addStatusEffectById('dead');
+ }
await this.update({ "data.sante": sante });
return result;
@@ -2221,6 +2222,9 @@ export class RdDActor extends Actor {
}
_deteriorerArmure(item, dmg) {
+ if (!RdDCombatSettings.isUsingDeteriorationArmure()) {
+ return;
+ }
let update = duplicate(item);
update.data.deterioration = (update.data.deterioration ?? 0) + dmg;
if (update.data.deterioration >= 10) {
@@ -2366,8 +2370,11 @@ export class RdDActor extends Actor {
count--;
} else {
// TODO: status effect dead
- ChatMessage.create({ content: `${this.name} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !` });
+ this.addStatusEffectById('dead');
+ ChatMessage.create({ content: `
+ ${this.name} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !` });
encaissement.critiques -= count;
+ encaissement.mort = true;
break;
}
}
@@ -2639,6 +2646,13 @@ export class RdDActor extends Actor {
await this.update( { 'data.subacteurs.montures': newMontures });
}
/* -------------------------------------------- */
+ async onUpdateActor(update, options, actorId) {
+ const updatedEndurance = update?.data?.sante?.endurance;
+ if (updatedEndurance && options.diff) {
+ this.forceStatusEffectId('unconscious', updatedEndurance.value == 0);
+ }
+ }
+ /* -------------------------------------------- */
async onCreateActiveEffect(effect, options) {
switch (StatusEffects.statusId(effect)) {
case 'sonne':
@@ -2646,7 +2660,7 @@ export class RdDActor extends Actor {
return;
}
}
-
+
/* -------------------------------------------- */
async onDeleteActiveEffect(effect, options) {
switch (StatusEffects.statusId(effect)) {
@@ -2655,80 +2669,81 @@ export class RdDActor extends Actor {
return;
}
}
-
+
/* -------------------------------------------- */
enleverTousLesEffets() {
this.deleteEmbeddedEntity('ActiveEffect', Array.from(this.effects?.keys() ?? []));
}
-
+
/* -------------------------------------------- */
listeEffets(matching = it => true) {
const all = Array.from(this.effects?.values() ?? []);
const filtered = all.filter(it => matching(it.data));
return filtered;
}
-
+
/* -------------------------------------------- */
async setStatusDemiReve(status) {
- const options = { renderSheet: true/*, noHook: from == 'hook' */ };
if (status) {
- await this.addEffect(StatusEffects.demiReve(), options)
+ await this.addStatusEffect(StatusEffects.demiReve())
}
else {
- this.deleteEffect(StatusEffects.demiReve(), options)
+ this.deleteStatusEffect(StatusEffects.demiReve())
}
}
-
+
/* -------------------------------------------- */
async setStatusSonne(sonne) {
if (this.isEntiteCauchemar()) {
return;
}
- const id = 'sonne';
- const options = { renderSheet: true/*, noHook: from == 'hook' */ };
-
+ await this.forceStatusEffectId('sonne', sonne);
+ }
+
+ /* -------------------------------------------- */
+ async forceStatusEffectId(statusId, sonne) {
if (sonne) {
- await this.addById(id, options);
+ await this.addStatusEffectById(statusId);
}
- else /* if (!sonne)*/ {
- this.deleteById(id, options)
+ else {
+ this.deleteStatusEffectById(statusId);
}
}
/* -------------------------------------------- */
- deleteById(id, options) {
+ deleteStatusEffectById(id, options = { renderSheet: true}) {
const effects = Array.from(this.effects?.values())
.filter(it => it.data.flags.core?.statusId == id);
- this._deleteAll(effects, options);
+ this._deleteStatusEffects(effects, options);
}
/* -------------------------------------------- */
- deleteEffect(effect, options) {
+ deleteStatusEffect(effect, options = { renderSheet: true}) {
const toDelete = Array.from(this.effects?.values())
.filter(it => StatusEffects.statusId(it.data) == StatusEffects.statusId(effect));
- this._deleteAll(toDelete, options);
+ this._deleteStatusEffects(toDelete, options);
}
/* -------------------------------------------- */
- _deleteAll(effects, options) {
- this._deleteAllIds(effects.map(it => it.id), options);
+ _deleteStatusEffects(effects, options) {
+ this._deleteStatusEffectsByIds(effects.map(it => it.id), options);
}
/* -------------------------------------------- */
- _deleteAllIds(effectIds, options) {
+ _deleteStatusEffectsByIds(effectIds, options) {
this.deleteEmbeddedEntity('ActiveEffect', effectIds, options);
this.applyActiveEffects();
}
/* -------------------------------------------- */
- async addById(id, options) {
+ async addStatusEffectById(id, options = { renderSheet: true}) {
const statusEffect = CONFIG.statusEffects.find(it => it.id == id);
- await this.addEffect(statusEffect, options);
+ await this.addStatusEffect(statusEffect, options);
}
/* -------------------------------------------- */
- async addEffect(statusEffect, options) {
- this.deleteById(statusEffect.id, options);
+ async addStatusEffect(statusEffect, options = { renderSheet: true}) {
+ this.deleteStatusEffectById(statusEffect.id, options);
const effet = duplicate(statusEffect);
effet["flags.core.statusId"] = effet.id;
await this.createEmbeddedEntity('ActiveEffect', effet, options);
diff --git a/module/rdd-combat.js b/module/rdd-combat.js
index 4191f302..99fd9402 100644
--- a/module/rdd-combat.js
+++ b/module/rdd-combat.js
@@ -11,6 +11,7 @@ import { RdDRollTables } from "./rdd-rolltables.js";
export class RdDCombat {
static init() {
+ RdDCombatSettings.onInit();
this.initStorePasseArmes();
Hooks.on("updateCombat", (combat, data) => { RdDCombat.onUpdateCombat(combat, data) });
Hooks.on("preDeleteCombat", (combat, options) => { RdDCombat.onPreDeleteCombat(combat, options); });
@@ -562,7 +563,7 @@ export class RdDCombat {
/* -------------------------------------------- */
async choixParticuliere(rollData, choix) {
console.log("RdDCombat.choixParticuliere >>>", rollData, choix);
-
+
this.removeChatMessageActionsPasseArme(rollData.passeArme);
rollData.particuliere = choix;
await this._onAttaqueNormale(rollData);
@@ -594,7 +595,6 @@ export class RdDCombat {
dialog.render(true);
}
-
_prepareParade(attackerRoll, armeParade) {
const compName = armeParade.data.competence;
const armeAttaque = attackerRoll.arme;
@@ -606,7 +606,7 @@ export class RdDCombat {
competence: this.defender.getCompetence(compName),
arme: armeParade,
surprise: this.defender.getSurprise(true),
- needParadeSignificative: RdDItemArme.needParadeSignificative(armeAttaque, armeParade),
+ needParadeSignificative: RdDCombatSettings.isUsingCategorieParade() && RdDItemArme.needParadeSignificative(armeAttaque, armeParade),
needResist: RdDItemArme.needArmeResist(armeAttaque, armeParade),
carac: this.defender.data.data.carac,
show: {}
@@ -632,6 +632,9 @@ export class RdDCombat {
if (RdDBonus.isDefenseAttaqueFinesse(defenderRoll)) {
facteurSign *= 2;
}
+ if (!RdDCombatSettings.isUsingTripleSignificative()) {
+ facteurSign = Math.min(facteurSign, 4);
+ }
return facteurSign;
}
@@ -741,6 +744,9 @@ export class RdDCombat {
/* -------------------------------------------- */
async computeDeteriorationArme(rollData) {
+ if (!RdDCombatSettings.isUsingResistanceArmeParade()) {
+ return;
+ }
const attackerRoll = rollData.attackerRoll;
// Est-ce une parade normale?
if (rollData.arme && attackerRoll && !rollData.rolled.isPart) {
@@ -772,7 +778,7 @@ export class RdDCombat {
}
}
// Si l'arme de parade n'est pas un bouclier, jet de désarmement (p.132)
- if (resistance > 0 && RdDItemArme.getCategorieParade(rollData.arme) != 'boucliers') {
+ if (RdDCombatSettings.isUsingDefenseurDesarme() && resistance > 0 && RdDItemArme.getCategorieParade(rollData.arme) != 'boucliers') {
let desarme = await RdDResolutionTable.rollData({
caracValue: this.defender.getForce(),
finalLevel: Misc.toInt(rollData.competence.data.niveau) - dmg,
@@ -783,31 +789,38 @@ export class RdDCombat {
}
}
}
+
/* -------------------------------------------- */
async computeRecul(defenderRoll) { // Calcul du recul (p. 132)
const attackerRoll = defenderRoll.attackerRoll;
- if (this._isAttaqueCauseRecul(attackerRoll)) {
-
+ if (RdDCombatSettings.isUsingRecul() && this._isAttaqueCauseRecul(attackerRoll)) {
const impact = this._computeImpactRecul(attackerRoll);
const rollRecul = await RdDResolutionTable.rollData({ caracValue: 10, finalLevel: impact });
-
if (rollRecul.rolled.isSuccess) {
defenderRoll.show.recul = 'encaisse';
- } else if (rollRecul.rolled.isETotal) {
+ } else if (rollRecul.rolled.isETotal || this._isReculCauseChute(impact)) {
defenderRoll.show.recul = 'chute';
+ await this.defender.addStatusEffectById('prone');
}
else {
- const agilite = this.defender.getAgilite();
- const chute = await RdDResolutionTable.rollData({ caracValue: agilite, finalLevel: impact });
- defenderRoll.show.recul = (chute.rolled.isSuccess) ? 'recul' : 'chute';
+ defenderRoll.show.recul = 'recul';
}
}
}
+ /* -------------------------------------------- */
+ async _isReculCauseChute(impact) {
+ const agilite = this.defender.getAgilite();
+ const chute = await RdDResolutionTable.rollData({ caracValue: agilite, finalLevel: impact });
+ return chute.rolled.isEchec;
+ }
+
+ /* -------------------------------------------- */
_isAttaqueCauseRecul(attaque) {
return attaque.particuliere == 'force' || attaque.tactique == 'charge';
}
+ /* -------------------------------------------- */
_computeImpactRecul(attaque) {
const taille = this.defender.getTaille();
const force = this.attacker.getForce();
@@ -902,5 +915,92 @@ export class RdDCombat {
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-turn-summary.html`, data)
});
}
+}
+
+export class RdDCombatSettings extends FormApplication {
+ static onInit() {
+ game.settings.register("foundryvtt-reve-de-dragon", "rdd-combat-recul", { name: "rdd-combat-recul", scope: "world", config: false, default: true, type: Boolean });
+ game.settings.register("foundryvtt-reve-de-dragon", "rdd-combat-resistanceArmeParade", { name: "rdd-combat-resistanceArmeParade", scope: "world", config: false, default: true, type: Boolean });
+ game.settings.register("foundryvtt-reve-de-dragon", "rdd-combat-deteriorationArmure", { name: "rdd-combat-deteriorationArmure", scope: "world", config: false, default: true, type: Boolean });
+ game.settings.register("foundryvtt-reve-de-dragon", "rdd-combat-defenseurDesarme", { name: "rdd-combat-defenseurDesarme", scope: "world", config: false, default: true, type: Boolean });
+ game.settings.register("foundryvtt-reve-de-dragon", "rdd-combat-categorieParade", { name: "rdd-combat-categorieParade", scope: "world", config: false, default: true, type: Boolean });
+ game.settings.register("foundryvtt-reve-de-dragon", "rdd-combat-tripleSignificative", { name: "rdd-combat-tripleSignificative", scope: "world", config: false, default: true, type: Boolean });
+
+ game.settings.registerMenu("foundryvtt-reve-de-dragon", "rdd-combat-options", {
+ name: "Choisir les options de combat",
+ label: "Choix des options de combat",
+ hint: "Ouvre la fenêtre de sélection des options de combats pour désactiver certaines règles",
+ icon: "fas fa-bars",
+ type: RdDCombatSettings,
+ restricted: true
+ });
+ }
+
+ constructor(...args) {
+ super(...args);
+ }
+
+ static get defaultOptions() {
+ const options = super.defaultOptions;
+ mergeObject(options, {
+ id: "combat-settings",
+ template: "systems/foundryvtt-reve-de-dragon/templates/combat-settings.html",
+ height: 600,
+ width: 350,
+ minimizable: false,
+ closeOnSubmit: true,
+ title: "Options de combat"
+ });
+ return options;
+ }
+
+ getData() {
+ let data = super.getData();
+ data.recul = RdDCombatSettings.isUsingRecul();
+ data.resistanceArmeParade = RdDCombatSettings.isUsingResistanceArmeParade();
+ data.deteriorationArmure = RdDCombatSettings.isUsingDeteriorationArmure();
+ data.defenseurDesarme = RdDCombatSettings.isUsingDefenseurDesarme();
+ data.categorieParade = RdDCombatSettings.isUsingCategorieParade();
+ data.tripleSignificative = RdDCombatSettings.isUsingTripleSignificative();
+ return data;
+ }
+
+ static isUsingRecul() {
+ return game.settings.get("foundryvtt-reve-de-dragon", "rdd-combat-recul");
+ }
+
+ static isUsingResistanceArmeParade() {
+ return game.settings.get("foundryvtt-reve-de-dragon", "rdd-combat-resistanceArmeParade");
+ }
+
+ static isUsingDeteriorationArmure() {
+ return game.settings.get("foundryvtt-reve-de-dragon", "rdd-combat-deteriorationArmure");
+ }
+
+ static isUsingDefenseurDesarme() {
+ return game.settings.get("foundryvtt-reve-de-dragon", "rdd-combat-defenseurDesarme");
+ }
+
+ static isUsingCategorieParade() {
+ return game.settings.get("foundryvtt-reve-de-dragon", "rdd-combat-categorieParade");
+ }
+
+ static isUsingTripleSignificative() {
+ return game.settings.get("foundryvtt-reve-de-dragon", "rdd-combat-tripleSignificative");
+ }
+
+ activateListeners(html) {
+ html.find(".select-option").click((event) => {
+ if (event.currentTarget.attributes.name) {
+ let id = event.currentTarget.attributes.name.value;
+ let isChecked = event.currentTarget.checked;
+ game.settings.set("foundryvtt-reve-de-dragon", id, isChecked);
+ }
+ });
+ }
+
+ async _updateObject(event, formData) {
+ this.close();
+ }
+}
-}
\ No newline at end of file
diff --git a/module/rdd-roll.js b/module/rdd-roll.js
index f10e285c..51c0478a 100644
--- a/module/rdd-roll.js
+++ b/module/rdd-roll.js
@@ -42,8 +42,6 @@ export class RdDRoll extends Dialog {
finalLevel: 0,
diffConditions: 0,
diffLibre: rollData.competence?.data.default_diffLibre ?? 0,
- editLibre: true,
- editConditions: true,
malusArmureValue: actor.getMalusArmure(),
surencMalusFlag: actor.isPersonnage() ? (actor.data.data.compteurs.surenc.value < 0) : false,
surencMalusValue: actor.getSurenc(),
diff --git a/module/rdd-utility.js b/module/rdd-utility.js
index da714c1f..0e11e0bb 100644
--- a/module/rdd-utility.js
+++ b/module/rdd-utility.js
@@ -468,7 +468,7 @@ export class RdDUtility {
let tailleData = tableCaracDerivee[bonusDomKey];
data.attributs.plusdom.value = tailleData.plusdom;
- data.attributs.sconst.value = tableCaracDerivee[Number(data.carac.constitution.value)].sconst;
+ data.attributs.sconst.value = RdDUtility.calculSConst(data.carac.constitution.value);
data.attributs.sust.value = tableCaracDerivee[Number(data.carac.taille.value)].sust;
data.attributs.encombrement.value = (parseInt(data.carac.force.value) + parseInt(data.carac.taille.value)) / 2;
@@ -489,6 +489,10 @@ export class RdDUtility {
data.compteurs.chance.max = data.carac.chance.value;
}
+ static calculSConst(constitution) {
+ return Number(tableCaracDerivee[Number(constitution)].sconst);
+ }
+
/* -------------------------------------------- */
static getSegmentsFatigue(maxEnd) {
maxEnd = Math.max(maxEnd, 1);
diff --git a/module/status-effects.js b/module/status-effects.js
index 57d2b315..a5efcf6e 100644
--- a/module/status-effects.js
+++ b/module/status-effects.js
@@ -5,7 +5,7 @@ const rddStatusEffects = [
demiReveStatusEffect
];
const statusDemiSurprise = new Set(['sonne', 'prone', 'restrain']);
-const statusSurpriseTotale = new Set(['unconscious', 'blind']);
+const statusSurpriseTotale = new Set(['unconscious', 'blind', 'dead']);
export class StatusEffects {
static onReady() {
diff --git a/templates/actor-sheet.html b/templates/actor-sheet.html
index 5597990d..e6d3a159 100644
--- a/templates/actor-sheet.html
+++ b/templates/actor-sheet.html
@@ -41,7 +41,7 @@