diff --git a/changelog.md b/changelog.md index 0d2488df..fb32e08b 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,7 @@ - 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) - Séparation des véhicules dans leur propre acteur +- Séparation des entités dans leur propre acteur ## v11.0.28 - les fractures de Khrachtchoum - La gravité de la blessure est affichée dans le résumé de l'encaissement diff --git a/module/actor-sheet.js b/module/actor-sheet.js index b1716797..57ee0e72 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(); @@ -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); }); diff --git a/module/actor.js b/module/actor.js index e0b43328..547e55d5 100644 --- a/module/actor.js +++ b/module/actor.js @@ -1,4 +1,4 @@ -import { RdDUtility } from "./rdd-utility.js"; +import { MAX_ENDURANCE_FATIGUE, RdDUtility } from "./rdd-utility.js"; import { TMRUtility } from "./tmr-utility.js"; import { RdDRollDialogEthylisme } from "./rdd-roll-ethylisme.js"; import { RdDRoll } from "./rdd-roll.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 { RdDBaseActorVivant } from "./actor/base-actor-vivant.js"; const POSSESSION_SANS_DRACONIC = { img: 'systems/foundryvtt-reve-de-dragon/icons/entites/possession.webp', @@ -56,57 +51,59 @@ 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) - this.recompute(); - } - - /* -------------------------------------------- */ - _prepareCreatureData(actorData) { - this.computeEncTotal(); - } +export class RdDActor extends RdDBaseActorVivant { /* -------------------------------------------- */ /** * Prepare Character type specific data */ - async _prepareCharacterData(actorData) { + prepareActorData() { + // TODO: separate derived/base data preparation + // TODO: split by actor class + if (this.isPersonnage()) this.$prepareCharacterData() + } + + $prepareCharacterData() { // Initialize empty items - RdDCarac.computeCarac(actorData.system) + this.$computeCaracDerivee() this.computeIsHautRevant(); - await this.cleanupConteneurs(); - await this.computeEncTotal(); + this.cleanupConteneurs(); } /* -------------------------------------------- */ - 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.isPersonnage()) { switch (item.type) { case 'competencecreature': case 'tarot': case 'service': @@ -121,27 +118,13 @@ export class RdDActor extends RdDBaseActor { 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); - } + /* -------------------------------------------- */ 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) default: return 0; @@ -158,9 +141,6 @@ export class RdDActor extends RdDBaseActor { } /* -------------------------------------------- */ getForce() { - if (this.isEntite()) { - return Misc.toInt(this.system.carac.reve?.value); - } return Misc.toInt(this.system.carac.force?.value); } /* -------------------------------------------- */ @@ -168,7 +148,6 @@ export class RdDActor extends RdDBaseActor { 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; } @@ -214,28 +193,6 @@ export class RdDActor extends RdDBaseActor { return 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'); @@ -285,15 +242,6 @@ export class RdDActor extends RdDBaseActor { return draconics[0]; } - 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; } @@ -322,20 +270,6 @@ 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 != "") @@ -351,7 +285,7 @@ export class RdDActor extends RdDBaseActor { 'chance-actuelle': this.getCaracChanceActuelle() }); - await this._openRollDialog({ + await this.openRollDialog({ name: `jet-${this.id}`, label: `Jet de ${this.name}`, template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll.html', @@ -365,23 +299,6 @@ export class RdDActor extends RdDBaseActor { }); } - 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; @@ -606,9 +523,6 @@ 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 @@ -701,7 +615,7 @@ export class RdDActor extends RdDBaseActor { return 'eveil'; } else { - if (!ReglesOptionnelles.isUsing("recuperation-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)` @@ -751,7 +665,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; } @@ -878,7 +792,7 @@ export class RdDActor extends RdDBaseActor { this.setPointsDeChance(to); } } - let selectedCarac = RdDActor._findCaracByName(this.system.carac, caracName); + let selectedCarac = RdDBaseActorVivant._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); @@ -889,7 +803,7 @@ export class RdDActor extends RdDBaseActor { if (caracName == 'Taille') { return; } - let selectedCarac = RdDActor._findCaracByName(this.system.carac, caracName); + let selectedCarac = RdDBaseActorVivant._findCaracByName(this.system.carac, caracName); if (!selectedCarac.derivee) { const from = Number(selectedCarac.xp); await this.update({ [`system.carac.${caracName}.xp`]: to }); @@ -903,7 +817,7 @@ export class RdDActor extends RdDBaseActor { if (caracName == 'Taille') { return; } - let carac = RdDActor._findCaracByName(this.system.carac, caracName); + let carac = RdDBaseActorVivant._findCaracByName(this.system.carac, caracName); if (carac) { carac = duplicate(carac); const fromXp = Number(carac.xp); @@ -1117,18 +1031,12 @@ 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': - return 0; - } return Math.min(0, Math.floor(this.getEncombrementMax() - this.encTotal)); } @@ -1138,12 +1046,7 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ getEncombrementMax() { - switch (this.type) { - case 'entite': - return 0; - default: - return this.system.attributs.encombrement.value - } + return this.system.attributs.encombrement.value } /* -------------------------------------------- */ @@ -1184,12 +1087,7 @@ export class RdDActor extends RdDBaseActor { } /* -------------------------------------------- */ - recompute() { - if (this.type == 'entite') { - // Pas d'état général pour les entités forçage à 0 - this.system.compteurs.etat.value = 0; - return - } + async computeEtatGeneral() { this.system.compteurs.etat.value = this.$malusVie() + this.$malusFatigue() + this.$malusEthylisme(); } @@ -1202,19 +1100,42 @@ export class RdDActor extends RdDBaseActor { } /* -------------------------------------------- */ - $malusFatigue() { - if (ReglesOptionnelles.isUsing("appliquer-fatigue") && this.system.sante.fatigue) { - const max = Math.max(1, Math.min(this.system.sante.endurance.max, 60)); + getEnduranceActuelle() { + return Number(this.system.sante.endurance.value); + } - let fatigueTab = RdDUtility.getSegmentsFatigue(max); - let reste = Math.min(max * 2, Math.max(0, this.system.sante.fatigue.value)); - for (let idx = 0; idx < fatigueTab.length; idx++) { - reste -= fatigueTab[idx]; - if (reste <= 0) { - return fatigueMalus[idx]; - } - } - return -7; // This is the max ! + getFatigueActuelle() { + if (ReglesOptionnelles.isUsing("appliquer-fatigue") && this.isPersonnage()) { + return Math.max(0, Math.min(this.$getFatigueMax(), this.system.sante.fatigue?.value)); + } + return 0; + } + + getFatigueRestante() { + return this.$getFatigueMax() - this.getFatigueActuelle(); + } + + getFatigueMax() { + return this.isPersonnage() ? this.$getFatigueMax() : 1; + } + + $getFatigueMin() { + return this.system.sante.endurance.max - this.system.sante.endurance.value; + } + + $getFatigueMax() { + return this.$getEnduranceMax() * 2; + } + + $getEnduranceMax() { + return Math.max(1, Math.min(this.system.sante.endurance.max, MAX_ENDURANCE_FATIGUE)); + } + + $malusFatigue() { + if (ReglesOptionnelles.isUsing("appliquer-fatigue") && this.isPersonnage()) { + const fatigueMax = this.$getFatigueMax(); + const fatigue = this.getFatigueActuelle(); + return RdDUtility.calculMalusFatigue(fatigue, this.$getEnduranceMax()) } return 0; } @@ -1436,9 +1357,6 @@ export class RdDActor extends RdDBaseActor { } /* -------------------------------------------- */ 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; @@ -1448,9 +1366,6 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ getSConst() { - if (this.isEntite()) { - return 0; - } return RdDCarac.calculSConst(this.system.carac.constitution.value) } @@ -1554,7 +1469,7 @@ export class RdDActor extends RdDBaseActor { 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 (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; @@ -1580,23 +1495,18 @@ export class RdDActor extends RdDBaseActor { 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()); + 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; + return result } isDead() { - return !this.isEntite() && this.system.sante.vie.value < -this.getSConst() - } - - /* -------------------------------------------- */ - _computeFatigueMin() { - return this.system.sante.endurance.max - this.system.sante.endurance.value; + return this.system.sante.vie.value < -this.getSConst() } /* -------------------------------------------- */ @@ -2003,7 +1913,7 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async checkCaracXP(caracName, display = true) { - let carac = RdDActor._findCaracByName(this.system.carac, caracName); + let carac = RdDBaseActorVivant._findCaracByName(this.system.carac, caracName); if (carac && carac.xp > 0) { const niveauSuivant = Number(carac.value) + 1; let xpNeeded = RdDCarac.getCaracNextXp(niveauSuivant); @@ -2152,7 +2062,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 +2106,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 +2182,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 +2227,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 +2253,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 +2270,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 +2294,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 +2357,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 +2597,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 +2612,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 +2637,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', @@ -2838,7 +2689,7 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ ajustementAstrologique() { - if (this.isCreatureEntite()) { + if (this.isCreature()) { return 0; } // selon l'heure de naissance... @@ -2904,7 +2755,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 = RdDBaseActorVivant._findCaracByName(carac, xpData.caracName); if (!selectedCarac.derivee) { const from = Number(selectedCarac.xp); const to = from + xpData.xpCarac; @@ -2957,51 +2808,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 +2882,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 +2958,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 +2974,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); } @@ -3264,79 +3009,24 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ - 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); - + async onAppliquerJetEncaissement(encaissement, attacker) { + const 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 perteVie = 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, + resteEndurance: perteEndurance.newValue, 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 ?? {} + endurance: perteEndurance.perte, + vie: santeOrig.vie.value - perteVie.newValue, + blessure: blessure }); - - 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) { @@ -3347,7 +3037,7 @@ export class RdDActor extends RdDBaseActor { } } } - const endActuelle = Number(this.system.sante.endurance.value); + const endActuelle = this.getEnduranceActuelle(); const blessure = await RdDItemBlessure.createBlessure(this, encaissement.gravite, encaissement.dmg.loc.label, attacker); if (blessure.isCritique()) { encaissement.endurance = endActuelle; @@ -3392,71 +3082,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 +3385,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()) { - 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-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 c1fda4b4..c3bad622 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,24 @@ export class RdDBaseActor extends Actor { } /* -------------------------------------------- */ + prepareData() { + super.prepareData() + this.prepareActorData() + this.cleanupConteneurs() + this.computeEtatGeneral() + this.computeEncTotal() + } + + async prepareActorData() { } + async computeEtatGeneral() { } + /* -------------------------------------------- */ + isCreatureEntite() { return this.type == 'creature' || this.type == 'entite'; } isCreature() { return this.type == 'creature'; } - isEntite() { return this.type == 'entite'; } + isEntite(typeentite = []) { return false } isPersonnage() { return this.type == 'personnage'; } isVehicule() { return this.type == 'vehicule'; } + getItem(id, type = undefined) { const item = this.items.get(id); if (type == undefined || (item?.type == type)) { @@ -145,12 +186,7 @@ export class RdDBaseActor extends Actor { } getMonnaie(id) { return this.findItemLike(id, 'monnaie'); } - getReveActuel() { return 0 } - computeMalusSurEncombrement() { return 0 } getEncombrementMax() { return 0 } - recompute() { } - async setEffect(statusId, status) { } - /* -------------------------------------------- */ async onPreUpdateItem(item, change, options, id) { } @@ -179,6 +215,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']); @@ -399,6 +445,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); @@ -627,5 +677,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-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/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..363a0f43 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); } diff --git a/module/rdd-main.js b/module/rdd-main.js index 3f016727..56ba18a9 100644 --- a/module/rdd-main.js +++ b/module/rdd-main.js @@ -29,11 +29,12 @@ 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 { RdDActorEntiteSheet } from "./actor-entite-sheet.js"; +import { RdDActorEntiteSheet } from "./actor/entite-sheet.js"; import { RdDActorVehiculeSheet } from "./actor/vehicule-sheet.js"; import { RdDItem } from "./item.js"; @@ -93,7 +94,7 @@ export class SystemReveDeDragon { this.actorClasses = { commerce: RdDCommerce, creature: RdDActor, - entite: RdDActor, + entite: RdDEntite, personnage: RdDActor, vehicule: RdDVehicule, } 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-utility.js b/module/rdd-utility.js index 4d625da0..0f8880e3 100644 --- a/module/rdd-utility.js +++ b/module/rdd-utility.js @@ -55,7 +55,8 @@ function _cumulSegmentsFatigue(matrix) { } /* -------------------------------------------- */ -export 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 @@ -465,11 +466,10 @@ export class RdDUtility { } /* -------------------------------------------- */ - 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] }