diff --git a/changelog.md b/changelog.md index 1f36d362..b07eda4d 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,21 @@ # v11.0 +## v11.1.0 - Les choix de Werther de Zloth +- Les options suivantes peuvent être désactivées: + - La transformation de stress à Château Dormant + - La récuperation de chance à Château Dormant + - La récupération d'éthylisme + - La récupération de rêve (y compris fleurs de rêve et Rêves de Dragon: la rencontre a lieu, mais ne donne pas de rêve) + - Le jet de moral de Château Dormant +- Séparation des véhicules dans leur propre acteur +- Séparation des entités dans leur propre acteur +- Séparation des créatures dans leur propre acteur +- La fenêtre de signes draconiques ne sélectionne plus tout les haut-rêvants par défaut +- Un nouveau personnage a automatiquement son token relié +- corrections de bugs + - si on n'utilise pas les règles de fatigues, un reflet de rêve pouvait garder le Haut-rêvant dans les TMRs pour toujours + - certaines macros ne marchaient pas pour les créatures/entités/véhicules/commerces + - en cas de charge, les particulières sont toujours en force (p125) + ## v11.0.28 - les fractures de Khrachtchoum - La gravité de la blessure est affichée dans le résumé de l'encaissement - Lors du changement d'acteur pendant le round diff --git a/module/actor-sheet.js b/module/actor-sheet.js index b1716797..750c6896 100644 --- a/module/actor-sheet.js +++ b/module/actor-sheet.js @@ -11,17 +11,18 @@ import { ReglesOptionnelles } from "./settings/regles-optionnelles.js"; import { RdDSheetUtility } from "./rdd-sheet-utility.js"; import { STATUSES } from "./settings/status-effects.js"; import { MAINS_DIRECTRICES } from "./actor.js"; -import { RdDBaseActorSheet } from "./actor/base-actor-sheet.js"; +import { RdDBaseActorReveSheet } from "./actor/base-actor-reve-sheet.js"; import { RdDItem } from "./item.js"; import { RdDItemBlessure } from "./item/blessure.js"; import { RdDEmpoignade } from "./rdd-empoignade.js"; +import { ChatUtility } from "./chat-utility.js"; /* -------------------------------------------- */ /** * Extend the basic ActorSheet with some very simple modifications * @extends {ActorSheet} */ -export class RdDActorSheet extends RdDBaseActorSheet { +export class RdDActorSheet extends RdDBaseActorReveSheet { /** @override */ static get defaultOptions() { @@ -56,7 +57,7 @@ export class RdDActorSheet extends RdDBaseActorSheet { surprise: RdDBonus.find(this.actor.getSurprise(false)).descr, resumeBlessures: this.actor.computeResumeBlessure(this.actor.system.blessures), caracTotal: RdDCarac.computeTotal(this.actor.system.carac, this.actor.system.beaute), - surEncombrementMessage: this.actor.getMessageSurEncombrement(), + surEncombrementMessage: this.actor.isSurenc() ? "Sur-Encombrement!" : "", malusArmure: this.actor.getMalusArmure() }) @@ -115,7 +116,8 @@ export class RdDActorSheet extends RdDBaseActorSheet { return formData; } - /* -------------------------------------------- */ /** @override */ + /* -------------------------------------------- */ + /** @override */ activateListeners(html) { super.activateListeners(html); @@ -124,10 +126,10 @@ export class RdDActorSheet extends RdDBaseActorSheet { // Everything below here is only needed if the sheet is editable if (!this.options.editable) return; - this.html.find('.item-action').click(async event => { - const item = RdDSheetUtility.getItem(event, this.actor); - item?.actionPrincipale(this.actor, async () => this.render()) - }); + this.html.find('.sheet-possession-attack').click(async event => { + const poss = RdDSheetUtility.getItem(event, this.actor) + this.actor.conjurerPossession(poss) + }) this.html.find('.subacteur-delete').click(async event => { const li = RdDSheetUtility.getEventElement(event); @@ -158,18 +160,6 @@ export class RdDActorSheet extends RdDBaseActorSheet { this.actor.updateCompteurValue("experience", parseInt(event.target.value)); }); - this.html.find('.encaisser-direct').click(async event => { - this.actor.encaisser(); - }) - this.html.find('.sheet-possession-attack').click(async event => { - const poss = RdDSheetUtility.getItem(event, this.actor) - this.actor.conjurerPossession(poss) - }) - this.html.find('.remise-a-neuf').click(async event => { - if (game.user.isGM) { - this.actor.remiseANeuf(); - } - }); this.html.find('.creer-tache').click(async event => { this.createEmptyTache(); }); @@ -206,11 +196,6 @@ export class RdDActorSheet extends RdDBaseActorSheet { }); // Roll Carac - this.html.find('.carac-label a').click(async event => { - let caracName = event.currentTarget.attributes.name.value; - this.actor.rollCarac(caracName.toLowerCase()); - }); - this.html.find('.chance-actuelle').click(async event => { this.actor.rollCarac('chance-actuelle'); }); @@ -219,14 +204,10 @@ export class RdDActorSheet extends RdDBaseActorSheet { this.actor.rollAppelChance(); }); + // Roll Skill this.html.find('[name="jet-astrologie"]').click(async event => { this.actor.astrologieNombresAstraux(); }); - - // Roll Skill - this.html.find('a.competence-label').click(async event => { - this.actor.rollCompetence(RdDSheetUtility.getItemId(event)); - }); this.html.find('.tache-label a').click(async event => { this.actor.rollTache(RdDSheetUtility.getItemId(event)); }); @@ -292,35 +273,12 @@ export class RdDActorSheet extends RdDBaseActorSheet { ui.notifications.info("Impossible de lancer l'initiative sans être dans un combat."); } }); - // Display TMR, visualisation - this.html.find('.visu-tmr').click(async event => { - this.actor.displayTMR("visu"); - }); + // Display TMR + this.html.find('.visu-tmr').click(async event => { this.actor.displayTMR("visu") }) + this.html.find('.monte-tmr').click(async event => { this.actor.displayTMR("normal") }) + this.html.find('.monte-tmr-rapide').click(async event => { this.actor.displayTMR("rapide") }) - // Display TMR, normal - this.html.find('.monte-tmr').click(async event => { - this.actor.displayTMR("normal"); - }); - - // Display TMR, fast - this.html.find('.monte-tmr-rapide').click(async event => { - this.actor.displayTMR("rapide"); - }); - - this.html.find('.repos').click(async event => { - await this.actor.repos(); - }); - this.html.find('.delete-active-effect').click(async event => { - if (game.user.isGM) { - let effect = this.html.find(event.currentTarget).parents(".active-effect").data('effect'); - this.actor.removeEffect(effect); - } - }); - this.html.find('.enlever-tous-effets').click(async event => { - if (game.user.isGM) { - await this.actor.removeEffects(); - } - }); + this.html.find('.repos').click(async event => { await this.actor.repos() }) this.html.find('.carac-xp-augmenter').click(async event => { let caracName = event.currentTarget.name.replace("augmenter.", ""); this.actor.updateCaracXPAuto(caracName); @@ -334,30 +292,20 @@ export class RdDActorSheet extends RdDBaseActorSheet { if (this.options.vueDetaillee) { // On carac change - this.html.find('.carac-value').change(async event => { - let caracName = event.currentTarget.name.replace(".value", "").replace("system.carac.", ""); - this.actor.updateCarac(caracName, parseInt(event.target.value)); - }); this.html.find('input.carac-xp').change(async event => { let caracName = event.currentTarget.name.replace(".xp", "").replace("system.carac.", ""); this.actor.updateCaracXP(caracName, parseInt(event.target.value)); }); - // On competence change - this.html.find('.competence-value').change(async event => { - let compName = event.currentTarget.attributes.compname.value; - //console.log("Competence changed :", compName); - this.actor.updateCompetence(compName, parseInt(event.target.value)); - }); // On competence xp change this.html.find('input.competence-xp').change(async event => { let compName = event.currentTarget.attributes.compname.value; this.actor.updateCompetenceXP(compName, parseInt(event.target.value)); }); - // On competence xp change this.html.find('input.competence-xp-sort').change(async event => { let compName = event.currentTarget.attributes.compname.value; this.actor.updateCompetenceXPSort(compName, parseInt(event.target.value)); }); + this.html.find('.toggle-archetype').click(async event => { this.options.vueArchetype = !this.options.vueArchetype; this.render(true); @@ -394,11 +342,6 @@ export class RdDActorSheet extends RdDBaseActorSheet { this.actor.setPointsDeSeuil(event.currentTarget.value); }); - // On stress change - this.html.find('.compteur-edit').change(async event => { - let fieldName = event.currentTarget.attributes.name.value; - this.actor.updateCompteurValue(fieldName, parseInt(event.target.value)); - }); this.html.find('.stress-test').click(async event => { this.actor.transformerStress(); @@ -420,7 +363,7 @@ export class RdDActorSheet extends RdDBaseActorSheet { this.actor.jetVie(); }); this.html.find('.jet-endurance').click(async event => { - this.actor.jetEndurance(); + await this.jetEndurance(); }); this.html.find('.vie-plus').click(async event => { @@ -429,12 +372,6 @@ export class RdDActorSheet extends RdDBaseActorSheet { this.html.find('.vie-moins').click(async event => { this.actor.santeIncDec("vie", -1); }); - this.html.find('.endurance-plus').click(async event => { - this.actor.santeIncDec("endurance", 1); - }); - this.html.find('.endurance-moins').click(async event => { - this.actor.santeIncDec("endurance", -1); - }); this.html.find('.ptreve-actuel-plus').click(async event => { this.actor.reveActuelIncDec(1); }); @@ -449,6 +386,16 @@ export class RdDActorSheet extends RdDBaseActorSheet { }); } + async jetEndurance() { + const endurance = this.actor.getEnduranceActuelle() + const result = await this.actor.jetEndurance(endurance); + ChatMessage.create({ + content: `Jet d'Endurance : ${result.jetEndurance} / ${endurance} +
${this.actor.name} a ${result.sonne ? 'échoué' : 'réussi'} son Jet d'Endurance ${result.sonne ? 'et devient Sonné' : ''}`, + whisper: ChatUtility.getWhisperRecipientsAndGMs(this.actor.name) + }); + } + getBlessure(event) { const itemId = this.html.find(event.currentTarget).parents(".item-blessure").data('item-id'); const blessure = this.actor.getItem(itemId, 'blessure'); diff --git a/module/actor.js b/module/actor.js index 7b41567c..c0e7f15a 100644 --- a/module/actor.js +++ b/module/actor.js @@ -10,13 +10,9 @@ import { RdDRollTables } from "./rdd-rolltables.js"; import { ChatUtility } from "./chat-utility.js"; import { RdDItemSort } from "./item-sort.js"; import { Grammar } from "./grammar.js"; -import { RdDEncaisser } from "./rdd-roll-encaisser.js"; -import { RdDCombat } from "./rdd-combat.js"; import { RdDItemCompetence } from "./item-competence.js"; -import { RdDItemArme } from "./item-arme.js"; import { RdDAlchimie } from "./rdd-alchimie.js"; -import { STATUSES, StatusEffects } from "./settings/status-effects.js"; -import { RdDItemCompetenceCreature } from "./item-competencecreature.js"; +import { STATUSES } from "./settings/status-effects.js"; import { RdDItemSigneDraconique } from "./item/signedraconique.js"; import { ReglesOptionnelles } from "./settings/regles-optionnelles.js"; import { EffetsDraconiques } from "./tmr/effets-draconiques.js"; @@ -26,11 +22,9 @@ import { DialogConsommer } from "./dialog-item-consommer.js"; import { DialogFabriquerPotion } from "./dialog-fabriquer-potion.js"; import { RollDataAjustements } from "./rolldata-ajustements.js"; import { RdDPossession } from "./rdd-possession.js"; -import { ENTITE_INCARNE, ENTITE_NONINCARNE, SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js"; +import { SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js"; import { RdDConfirm } from "./rdd-confirm.js"; -import { DialogValidationEncaissement } from "./dialog-validation-encaissement.js"; import { RdDRencontre } from "./item/rencontre.js"; -import { Targets } from "./targets.js"; import { DialogRepos } from "./sommeil/dialog-repos.js"; import { RdDBaseActor } from "./actor/base-actor.js"; import { RdDTimestamp } from "./time/rdd-timestamp.js"; @@ -39,6 +33,7 @@ import { AppAstrologie } from "./sommeil/app-astrologie.js"; import { RdDEmpoignade } from "./rdd-empoignade.js"; import { ExperienceLog, XP_TOPIC } from "./actor/experience-log.js"; import { TYPES } from "./item.js"; +import { RdDBaseActorSang } from "./actor/base-actor-sang.js"; const POSSESSION_SANS_DRACONIC = { img: 'systems/foundryvtt-reve-de-dragon/icons/entites/possession.webp', @@ -56,149 +51,70 @@ export const MAINS_DIRECTRICES = ['Droitier', 'Gaucher', 'Ambidextre'] * Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system. * @extends {Actor} */ -export class RdDActor extends RdDBaseActor { - - /* -------------------------------------------- */ - prepareData() { - super.prepareData(); - - // Dynamic computing fields - this.encTotal = 0; - // TODO: separate derived/base data preparation - // TODO: split by actor class - - // Make separate methods for each Actor type (character, npc, etc.) to keep - // things organized. - if (this.isPersonnage()) this._prepareCharacterData(this) - if (this.isCreatureEntite()) this._prepareCreatureData(this) - if (this.isVehicule()) this._prepareVehiculeData(this) - this.computeEtatGeneral(); - } - - /* -------------------------------------------- */ - _prepareCreatureData(actorData) { - this.computeEncTotal(); - } - - /* -------------------------------------------- */ - _prepareVehiculeData(actorData) { - this.computeEncTotal(); - } +export class RdDActor extends RdDBaseActorSang { /* -------------------------------------------- */ /** * Prepare Character type specific data */ - async _prepareCharacterData(actorData) { - // Initialize empty items - RdDCarac.computeCarac(actorData.system) - this.computeIsHautRevant(); - await this.cleanupConteneurs(); - await this.computeEncTotal(); + prepareActorData() { + this.$computeCaracDerivee() + this.$computeIsHautRevant() } /* -------------------------------------------- */ - async cleanupConteneurs() { - let updates = this.itemTypes['conteneur'] - .filter(c => c.system.contenu.filter(id => this.getItem(id) == undefined).length > 0) - .map(c => { return { _id: c._id, 'system.contenu': c.system.contenu.filter(id => this.getItem(id) != undefined) } }); - if (updates.length > 0) { - await this.updateEmbeddedDocuments("Item", updates) - } + $computeCaracDerivee() { + this.system.carac.force.value = Math.min(this.system.carac.force.value, parseInt(this.system.carac.taille.value) + 4); + + this.system.carac.derobee.value = Math.floor(parseInt(((21 - this.system.carac.taille.value)) + parseInt(this.system.carac.agilite.value)) / 2); + let bonusDomKey = Math.floor((parseInt(this.system.carac.force.value) + parseInt(this.system.carac.taille.value)) / 2); + let tailleData = RdDCarac.getCaracDerivee(bonusDomKey); + this.system.attributs.plusdom.value = tailleData.plusdom; + + this.system.attributs.sconst.value = RdDCarac.calculSConst(this.system.carac.constitution.value); + this.system.attributs.sust.value = RdDCarac.getCaracDerivee(this.system.carac.taille.value).sust; + + this.system.attributs.encombrement.value = (parseInt(this.system.carac.force.value) + parseInt(this.system.carac.taille.value)) / 2; + this.system.carac.melee.value = Math.floor((parseInt(this.system.carac.force.value) + parseInt(this.system.carac.agilite.value)) / 2); + this.system.carac.tir.value = Math.floor((parseInt(this.system.carac.vue.value) + parseInt(this.system.carac.dexterite.value)) / 2); + this.system.carac.lancer.value = Math.floor((parseInt(this.system.carac.tir.value) + parseInt(this.system.carac.force.value)) / 2); + + this.system.sante.vie.max = Math.ceil((parseInt(this.system.carac.taille.value) + parseInt(this.system.carac.constitution.value)) / 2); + + this.system.sante.vie.value = Math.min(this.system.sante.vie.value, this.system.sante.vie.max) + this.system.sante.endurance.max = Math.max(parseInt(this.system.carac.taille.value) + parseInt(this.system.carac.constitution.value), parseInt(this.system.sante.vie.max) + parseInt(this.system.carac.volonte.value)); + this.system.sante.endurance.value = Math.min(this.system.sante.endurance.value, this.system.sante.endurance.max); + this.system.sante.fatigue.max = this.getFatigueMax(); + this.system.sante.fatigue.value = Math.min(this.system.sante.fatigue.value, this.system.sante.fatigue.max); + + //Compteurs + this.system.reve.reve.max = this.system.carac.reve.value; + this.system.compteurs.chance.max = this.system.carac.chance.value; } canReceive(item) { - if (this.isCreature()) { - return item.type == TYPES.competencecreature || item.isInventaire(); - } - if (this.isEntite()) { - return item.type == 'competencecreature'; - } - if (this.isVehicule()) { - return item.isInventaire(); - } - if (this.isPersonnage()) { - switch (item.type) { - case 'competencecreature': case 'tarot': case 'service': - return false; - } - return true; - } - return false; + return ![TYPES.competencecreature, TYPES.tarot, TYPES.service].includes(item.type) } /* -------------------------------------------- */ - isHautRevant() { - return this.isPersonnage() && this.system.attributs.hautrevant.value != "" - } - /* -------------------------------------------- */ - getFatigueActuelle() { - if (ReglesOptionnelles.isUsing("appliquer-fatigue") && this.isPersonnage()) { - return this.system.sante.fatigue?.value; - } - return 0; - } - /* -------------------------------------------- */ - getFatigueMax() { - if (!this.isPersonnage()) { - return 1; - } - return Misc.toInt(this.system.sante.fatigue?.max); - } - /* -------------------------------------------- */ + isPersonnage() { return true } + isHautRevant() { return this.system.attributs.hautrevant.value != "" } + getReveActuel() { - switch (this.type) { - case 'personnage': - return Misc.toInt(this.system.reve?.reve?.value ?? this.carac.reve.value); - case 'creature': - case 'entite': - return Misc.toInt(this.system.carac.reve?.value) - case 'vehicule': - default: - return 0; - } + return Misc.toInt(this.system.reve?.reve?.value ?? this.carac.reve.value); } - /* -------------------------------------------- */ getChanceActuel() { return Misc.toInt(this.system.compteurs.chance?.value ?? 10); } + + getAgilite() { return Number(this.system.carac.agilite?.value ?? 0) } + getChance() { return Number(this.system.carac.chance?.value ?? 0) } + /* -------------------------------------------- */ - getTaille() { - return Misc.toInt(this.system.carac.taille?.value); - } - /* -------------------------------------------- */ - getForce() { - if (this.isEntite()) { - return Misc.toInt(this.system.carac.reve?.value); - } - return Misc.toInt(this.system.carac.force?.value); - } - /* -------------------------------------------- */ - getAgilite() { - switch (this.type) { - case 'personnage': return Misc.toInt(this.system.carac.agilite?.value); - case 'creature': return Misc.toInt(this.system.carac.force?.value); - case 'entite': return Misc.toInt(this.system.carac.reve?.value); - } - return 10; - } - /* -------------------------------------------- */ - getChance() { - return Number(this.system.carac.chance?.value ?? 10); - } getMoralTotal() { return Number(this.system.compteurs.moral?.value ?? 0); } - /* -------------------------------------------- */ - getBonusDegat() { - // TODO: gérer séparation et +dom créature/entité indépendament de la compétence - return Number(this.system.attributs.plusdom.value ?? 0); - } - /* -------------------------------------------- */ - getProtectionNaturelle() { - return Number(this.system.attributs.protection.value ?? 0); - } /* -------------------------------------------- */ getEtatGeneral(options = { ethylisme: false }) { @@ -217,36 +133,11 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ getMalusArmure() { - if (this.isPersonnage()) { - return this.itemTypes[TYPES.armure].filter(it => it.system.equipe) - .map(it => it.system.malus) - .reduce(Misc.sum(), 0); - } - return 0; + return this.itemTypes[TYPES.armure].filter(it => it.system.equipe) + .map(it => it.system.malus) + .reduce(Misc.sum(), 0); } - /* -------------------------------------------- */ - getEncTotal() { - return Math.floor(this.encTotal ?? 0); - } - - /* -------------------------------------------- */ - getCompetence(idOrName, options = {}) { - if (idOrName instanceof Item) { - return idOrName.isCompetence() ? idOrName : undefined - } - return RdDItemCompetence.findCompetence(this.items, idOrName, options) - } - - getCompetences(name) { - return RdDItemCompetence.findCompetences(this.items, name) - } - getCompetenceCorpsACorps(options = {}) { - return this.getCompetence("Corps à corps", options) - } - getCompetencesEsquive() { - return this.getCompetences("esquive") - } /* -------------------------------------------- */ getTache(id) { return this.findItemLike(id, 'tache'); @@ -284,27 +175,12 @@ export class RdDActor extends RdDBaseActor { } getDraconicOuPossession() { - const possession = this.itemTypes[TYPES.competencecreature].filter(it => it.system.categorie == 'possession') + return [...this.getDraconicList().filter(it => it.system.niveau >= 0), + super.getDraconicOuPossession()] .sort(Misc.descending(it => it.system.niveau)) - .find(it => true); - if (possession) { - return possession; - } - const draconics = [...this.getDraconicList().filter(it => it.system.niveau >= 0), - POSSESSION_SANS_DRACONIC] - .sort(Misc.descending(it => it.system.niveau)); - return draconics[0]; + .find(it => true) } - getPossession(possessionId) { - return this.itemTypes[TYPES.possession].find(it => it.system.possessionid == possessionId); - } - getPossessions() { - return this.itemTypes[TYPES.possession]; - } - getEmpoignades() { - return this.itemTypes[TYPES.empoignade]; - } getDemiReve() { return this.system.reve.tmrpos.coord; } @@ -333,66 +209,11 @@ export class RdDActor extends RdDBaseActor { } } - /* -------------------------------------------- */ - getSurprise(isCombat = undefined) { - let niveauSurprise = this.getEffects() - .map(effect => StatusEffects.valeurSurprise(effect, isCombat)) - .reduce(Misc.sum(), 0); - if (niveauSurprise > 1) { - return 'totale'; - } - if (niveauSurprise == 1) { - return 'demi'; - } - return ''; - } - /* -------------------------------------------- */ hasArmeeMeleeEquipee() { // Return true si l'acteur possède au moins 1 arme de mêlée équipée return this.itemTypes['arme'].find(it => it.system.equipe && it.system.competence != "") } - /* -------------------------------------------- */ - async roll() { - RdDEmpoignade.checkEmpoignadeEnCours(this) - - const carac = mergeObject(duplicate(this.system.carac), - { - 'reve-actuel': this.getCaracReveActuel(), - 'chance-actuelle': this.getCaracChanceActuelle() - }); - - await this._openRollDialog({ - name: `jet-${this.id}`, - label: `Jet de ${this.name}`, - template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll.html', - rollData: { - carac: carac, - selectedCarac: carac.apparence, - selectedCaracName: 'apparence', - competences: this.itemTypes['competence'] - }, - callbackAction: r => this.$onRollCaracResult(r) - }); - } - - async _openRollDialog({ name, label, template, rollData, callbackAction }) { - const dialog = await RdDRoll.create(this, rollData, - { html: template, close: html => { this.tmrApp?.restoreTMRAfterAction() } }, - { - name: name, - label: label, - callbacks: [ - this.createCallbackExperience(), - this.createCallbackAppelAuMoral(), - { action: callbackAction } - ] - }); - dialog.render(true); - return dialog - } - - async prepareChateauDormant(consigne) { if (consigne.ignorer) { return; @@ -407,10 +228,6 @@ export class RdDActor extends RdDBaseActor { } } - findPlayer() { - return game.users.players.find(player => player.active && player.character?.id == this.id); - } - async onTimeChanging(oldTimestamp, newTimestamp) { await super.onTimeChanging(oldTimestamp, newTimestamp); await this.setInfoSommeilInsomnie(); @@ -486,7 +303,7 @@ export class RdDActor extends RdDBaseActor { }; await this._recuperationSante(message) - await this._jetDeMoralChateauDormant(message); + await this._recupereMoralChateauDormant(message); await this._recupereChance(); await this.transformerStress(); await this.retourSeuilDeReve(message); @@ -524,6 +341,8 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async _recupereChance() { + if (!ReglesOptionnelles.isUsing("recuperation-chance")) { return } + // On ne récupère un point de chance que si aucun appel à la chance dans la journée if (this.getChanceActuel() < this.getChance() && !this.getFlag(SYSTEM_RDD, 'utilisationChance')) { await this.chanceActuelleIncDec(1); @@ -532,7 +351,9 @@ export class RdDActor extends RdDBaseActor { await this.unsetFlag(SYSTEM_RDD, 'utilisationChance'); } - async _jetDeMoralChateauDormant(message) { + async _recupereMoralChateauDormant(message) { + if (!ReglesOptionnelles.isUsing("recuperation-moral")) { return } + const etatMoral = this.system.sommeil?.moral ?? 'neutre'; const jetMoral = await this._jetDeMoral(etatMoral); message.content += ` -- le jet de moral est ${etatMoral}, le moral ` + this._messageAjustementMoral(jetMoral.ajustement); @@ -568,12 +389,6 @@ export class RdDActor extends RdDBaseActor { await this.supprimerBlessures(it => it.system.gravite <= 0); } - async supprimerBlessures(filterToDelete) { - const toDelete = this.filterItems(filterToDelete, TYPES.blessure) - .map(it => it.id); - await this.deleteEmbeddedDocuments('Item', toDelete); - } - /* -------------------------------------------- */ async _recupererVie(message, isMaladeEmpoisonne) { const tData = this.system @@ -615,26 +430,19 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async remiseANeuf() { - if (this.isEntite([ENTITE_NONINCARNE])) { - return; - } ChatMessage.create({ whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name), content: 'Remise à neuf de ' + this.name }); - const updates = { - 'system.sante.endurance.value': this.system.sante.endurance.max - }; - if (this.isPersonnage() || this.isCreature()) { - await this.supprimerBlessures(it => true); - updates['system.sante.vie.value'] = this.system.sante.vie.max; - updates['system.sante.fatigue.value'] = 0; - } - if (this.isPersonnage()) { - updates['system.compteurs.ethylisme'] = { value: 1, nb_doses: 0, jet_moral: false }; - } - await this.update(updates); + await this.supprimerBlessures(it => true); await this.removeEffects(e => e.flags.core.statusId !== STATUSES.StatusDemiReve); + const updates = { + 'system.sante.endurance.value': this.system.sante.endurance.max, + 'system.sante.vie.value': this.system.sante.vie.max, + 'system.sante.fatigue.value': 0, + 'system.compteurs.ethylisme': { value: 1, nb_doses: 0, jet_moral: false } + }; + await this.update(updates); } /* -------------------------------------------- */ @@ -710,8 +518,17 @@ export class RdDActor extends RdDBaseActor { return 'eveil'; } else { - await this.reveActuelIncDec(reve); - jetsReve.push(reve); + if (!ReglesOptionnelles.isUsing("recuperation-reve")) { + ChatMessage.create({ + whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name), + content: `Pas de récupération de rêve (${reve} points ignorés)` + }); + jetsReve.push(0); + } + else { + await this.reveActuelIncDec(reve); + jetsReve.push(reve); + } } } return 'dort'; @@ -719,6 +536,7 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async _recupererEthylisme(message) { + if (!ReglesOptionnelles.isUsing("recuperation-ethylisme")) { return; } let value = Math.min(Number.parseInt(this.system.compteurs.ethylisme.value) + 1, 1); if (value <= 0) { message.content += `Vous dégrisez un peu (${RdDUtility.getNomEthylisme(value)}). `; @@ -750,7 +568,7 @@ export class RdDActor extends RdDBaseActor { async recupererFatigue(message) { if (ReglesOptionnelles.isUsing("appliquer-fatigue")) { let fatigue = this.system.sante.fatigue.value; - const fatigueMin = this._computeFatigueMin(); + const fatigueMin = this.getFatigueMin(); if (fatigue <= fatigueMin) { return; } @@ -877,7 +695,7 @@ export class RdDActor extends RdDBaseActor { this.setPointsDeChance(to); } } - let selectedCarac = RdDActor._findCaracByName(this.system.carac, caracName); + let selectedCarac = RdDBaseActor._findCaracByName(this.system.carac, caracName); const from = selectedCarac.value await this.update({ [`system.carac.${caracName}.value`]: to }); await ExperienceLog.add(this, XP_TOPIC.CARAC, from, to, caracName); @@ -888,7 +706,7 @@ export class RdDActor extends RdDBaseActor { if (caracName == 'Taille') { return; } - let selectedCarac = RdDActor._findCaracByName(this.system.carac, caracName); + let selectedCarac = RdDBaseActor._findCaracByName(this.system.carac, caracName); if (!selectedCarac.derivee) { const from = Number(selectedCarac.xp); await this.update({ [`system.carac.${caracName}.xp`]: to }); @@ -902,7 +720,7 @@ export class RdDActor extends RdDBaseActor { if (caracName == 'Taille') { return; } - let carac = RdDActor._findCaracByName(this.system.carac, caracName); + let carac = RdDBaseActor._findCaracByName(this.system.carac, caracName); if (carac) { carac = duplicate(carac); const fromXp = Number(carac.xp); @@ -976,25 +794,6 @@ export class RdDActor extends RdDBaseActor { await ExperienceLog.add(this, XP_TOPIC.NIVEAU, fromNiveau, toNiveau, competence.name); } - /* -------------------------------------------- */ - async updateCreatureCompetence(idOrName, fieldName, value) { - let competence = this.getCompetence(idOrName); - if (competence) { - function getPath(fieldName) { - switch (fieldName) { - case "niveau": return 'system.niveau'; - case "dommages": return 'system.dommages'; - case "carac_value": return 'system.carac_value'; - } - return undefined - } - const path = getPath(fieldName); - if (path) { - await this.updateEmbeddedDocuments('Item', [{ _id: competence.id, [path]: value }]); // updates one EmbeddedEntity - } - } - } - /* -------------------------------------------- */ async updateCompetence(idOrName, compValue) { const competence = this.getCompetence(idOrName); @@ -1099,7 +898,7 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async distribuerStress(compteur, stress, motif) { - if (game.user.isGM && this.hasPlayerOwner && this.isPersonnage()) { + if (game.user.isGM && this.hasPlayerOwner) { switch (compteur) { case 'stress': case 'experience': await this.addCompteurValue(compteur, stress, motif); @@ -1116,98 +915,17 @@ export class RdDActor extends RdDBaseActor { await this.update({ [`system.attributs.${fieldName}.value`]: fieldValue }); } - - - isSurenc() { - return this.isPersonnage() ? (this.computeMalusSurEncombrement() < 0) : false - } - /* -------------------------------------------- */ - computeMalusSurEncombrement() { - switch (this.type) { - case 'entite': case 'vehicule': - return 0; - } - return Math.min(0, Math.floor(this.getEncombrementMax() - this.encTotal)); + $computeIsHautRevant() { + this.system.attributs.hautrevant.value = this.itemTypes['tete'].find(it => Grammar.equalsInsensitive(it.name, 'don de haut-reve')) + ? "Haut rêvant" + : ""; } - getMessageSurEncombrement() { - return this.computeMalusSurEncombrement() < 0 ? "Sur-Encombrement!" : ""; + malusEthylisme() { + return Math.min(0, (this.system.compteurs.ethylisme?.value ?? 0)); } - /* -------------------------------------------- */ - getEncombrementMax() { - switch (this.type) { - case 'vehicule': - return this.system.capacite_encombrement; - case 'entite': - return 0; - default: - return this.system.attributs.encombrement.value - } - } - - /* -------------------------------------------- */ - computeIsHautRevant() { - if (this.isPersonnage()) { - this.system.attributs.hautrevant.value = this.itemTypes['tete'].find(it => Grammar.equalsInsensitive(it.name, 'don de haut-reve')) - ? "Haut rêvant" - : ""; - } - } - - /* -------------------------------------------- */ - computeResumeBlessure() { - const blessures = this.filterItems(it => it.system.gravite > 0, 'blessure') - - const nbLegeres = blessures.filter(it => it.isLegere()).length; - const nbGraves = blessures.filter(it => it.isGrave()).length; - const nbCritiques = blessures.filter(it => it.isCritique()).length; - - if (nbLegeres + nbGraves + nbCritiques == 0) { - return "Aucune blessure"; - } - let resume = "Blessures:"; - if (nbLegeres > 0) { - resume += " " + nbLegeres + " légère" + (nbLegeres > 1 ? "s" : ""); - } - if (nbGraves > 0) { - if (nbLegeres > 0) - resume += ","; - resume += " " + nbGraves + " grave" + (nbGraves > 1 ? "s" : ""); - } - if (nbCritiques > 0) { - if (nbGraves > 0 || nbLegeres > 0) - resume += ","; - resume += " une CRITIQUE !"; - } - return resume; - } - - recompute() { - this.computeEtatGeneral(); - } - - /* -------------------------------------------- */ - computeEtatGeneral() { - // Pas d'état général pour les entités forçage à 0 - if (this.type == 'vehicule') { - return - } - if (this.type == 'entite') { - this.system.compteurs.etat.value = 0; - return - } - // Pour les autres - let state = Math.min(this.system.sante.vie.value - this.system.sante.vie.max, 0); - if (ReglesOptionnelles.isUsing("appliquer-fatigue") && this.system.sante.fatigue) { - state += RdDUtility.currentFatigueMalus(this.system.sante.fatigue.value, this.system.sante.endurance.max); - } - // Ajout de l'éthylisme - state += Math.min(0, (this.system.compteurs.ethylisme?.value ?? 0)); - - this.system.compteurs.etat.value = state; - } /* -------------------------------------------- */ async actionRefoulement(item) { @@ -1352,7 +1070,7 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async reveActuelIncDec(value) { - let reve = Math.max(this.system.reve.reve.value + value, 0); + const reve = Math.max(this.system.reve.reve.value + value, 0); await this.update({ "system.reve.reve.value": reve }); } @@ -1377,112 +1095,21 @@ export class RdDActor extends RdDBaseActor { await this.updateCompteurValue("chance", chance); } - /* -------------------------------------------- */ - getSonne() { - return this.getEffect(STATUSES.StatusStunned); - } - - /* -------------------------------------------- */ - async finDeRound(options = { terminer: false }) { - await this.$finDeRoundSuppressionEffetsTermines(options); - await this.$finDeRoundBlessuresGraves(); - await this.$finDeRoundSupprimerObsoletes(); - await this.$finDeRoundEmpoignade(); - } - - async $finDeRoundSuppressionEffetsTermines(options) { - for (let effect of this.getEffects()) { - if (effect.duration.type !== 'none' && (effect.duration.remaining <= 0 || options.terminer)) { - await effect.delete(); - ChatMessage.create({ content: `${this.name} n'est plus ${Misc.lowerFirst(game.i18n.localize(effect.system.label))} !` }); - } - } - } - - async $finDeRoundBlessuresGraves() { - if (this.isPersonnage() || this.isCreature()) { - const nbGraves = this.filterItems(it => it.isGrave(), 'blessure').length; - if (nbGraves > 0) { - // Gestion blessure graves : -1 pt endurance par blessure grave - await this.santeIncDec("endurance", -nbGraves); - } - } - } - - async $finDeRoundSupprimerObsoletes() { - const obsoletes = [] - .concat(this.itemTypes[TYPES.empoignade].filter(it => it.system.pointsemp <= 0)) - .concat(this.itemTypes[TYPES.possession].filter(it => it.system.compteur < -2 || it.system.compteur > 2)) - .map(it => it.id); - await this.deleteEmbeddedDocuments('Item', obsoletes); - } - - async $finDeRoundEmpoignade() { - const immobilisations = this.itemTypes[TYPES.empoignade].filter(it => it.system.pointsemp >= 2 && it.system.empoigneurid == this.id); - immobilisations.forEach(emp => RdDEmpoignade.onImmobilisation(this, - game.actors.get(emp.system.empoigneid), - emp - )) - } - /* -------------------------------------------- */ - async setSonne(sonne = true) { - if (this.isEntite()) { - return; - } - if (!game.combat && sonne) { - ui.notifications.info("Le personnage est hors combat, il ne reste donc pas sonné"); - return; - } - await this.setEffect(STATUSES.StatusStunned, sonne); - } - - /* -------------------------------------------- */ - getSConst() { - if (this.isEntite()) { - return 0; - } - return RdDCarac.calculSConst(this.system.carac.constitution.value) - } - - - async ajoutXpConstitution(xp) { - await this.update({ "system.carac.constitution.xp": Misc.toInt(this.system.carac.constitution.xp) + xp }); - } - - /* -------------------------------------------- */ - countBlessures(filter = it => !it.isContusion()) { - return this.filterItems(filter, 'blessure').length - } - - /* -------------------------------------------- */ - async testSiSonne(endurance) { - const result = await this._jetEndurance(endurance); - if (result.roll.total == 1) { + async jetEndurance(resteEndurance = undefined) { + const result = super.jetEndurance(resteEndurance); + if (result.jetEndurance == 1) { ChatMessage.create({ content: await this._gainXpConstitutionJetEndurance() }); } return result; } /* -------------------------------------------- */ - async jetEndurance() { - const endurance = this.system.sante.endurance.value; + getSConst() { + return RdDCarac.calculSConst(this.system.carac.constitution.value) + } - const result = await this._jetEndurance(this.system.sante.endurance.value) - const message = { - content: "Jet d'Endurance : " + result.roll.total + " / " + endurance + "
", - whisper: ChatMessage.getWhisperRecipients(this.name) - }; - if (result.sonne) { - message.content += `${this.name} a échoué son Jet d'Endurance et devient Sonné`; - } - else if (result.roll.total == 1) { - message.content += await this._gainXpConstitutionJetEndurance(); - } - else { - message.content += `${this.name} a réussi son Jet d'Endurance !`; - } - - ChatMessage.create(message); + async ajoutXpConstitution(xp) { + await this.update({ "system.carac.constitution.xp": Misc.toInt(this.system.carac.constitution.xp) + xp }); } async _gainXpConstitutionJetEndurance() { @@ -1490,116 +1117,6 @@ export class RdDActor extends RdDBaseActor { return `${this.name} a obtenu 1 sur son Jet d'Endurance et a gagné 1 point d'Expérience en Constitution. Ce point d'XP a été ajouté automatiquement.`; } - async _jetEndurance(endurance) { - const roll = await RdDDice.roll("1d20"); - let result = { - roll: roll, - sonne: roll.total > endurance || roll.total == 20 // 20 is always a failure - } - if (result.sonne) { - await this.setSonne(); - } - return result; - } - - /* -------------------------------------------- */ - async jetVie() { - let roll = await RdDDice.roll("1d20"); - let msgText = "Jet de Vie : " + roll.total + " / " + this.system.sante.vie.value + "
"; - if (roll.total <= this.system.sante.vie.value) { - msgText += "Jet réussi, pas de perte de point de vie (prochain jet dans 1 round pour 1 critique, SC minutes pour une grave)"; - if (roll.total == 1) { - msgText += "La durée entre 2 jets de vie est multipliée par 20 (20 rounds pour une critique, SCx20 minutes pour une grave)"; - } - } else { - msgText += "Jet échoué, vous perdez 1 point de vie"; - await this.santeIncDec("vie", -1); - if (roll.total == 20) { - msgText += "Votre personnage est mort !!!!!"; - } - } - const message = { - content: msgText, - whisper: ChatMessage.getWhisperRecipients(this.name) - }; - ChatMessage.create(message); - } - - /* -------------------------------------------- */ - async santeIncDec(name, inc, isCritique = false) { - if (name == 'fatigue' && !ReglesOptionnelles.isUsing("appliquer-fatigue")) { - return; - } - const sante = duplicate(this.system.sante) - let compteur = sante[name]; - if (!compteur) { - return; - } - let result = { - sonne: false, - }; - - let minValue = name == "vie" ? -this.getSConst() - 1 : 0; - - result.newValue = Math.max(minValue, Math.min(compteur.value + inc, compteur.max)); - //console.log("New value ", inc, minValue, result.newValue); - let fatigue = 0; - if (name == "endurance" && !this.isEntite()) { - if (result.newValue == 0 && inc < 0 && !isCritique) { // perte endurance et endurance devient 0 (sauf critique) -> -1 vie - sante.vie.value--; - result.perteVie = true; - } - result.newValue = Math.max(0, result.newValue); - if (inc > 0) { // le max d'endurance s'applique seulement à la récupération - result.newValue = Math.min(result.newValue, this._computeEnduranceMax()) - } - const perte = compteur.value - result.newValue; - result.perte = perte; - if (perte > 1) { - // Peut-être sonné si 2 points d'endurance perdus d'un coup - const testIsSonne = await this.testSiSonne(result.newValue); - result.sonne = testIsSonne.sonne; - result.jetEndurance = testIsSonne.roll.total; - } else if (inc > 0) { - await this.setSonne(false); - } - if (sante.fatigue && inc < 0) { // Each endurance lost -> fatigue lost - fatigue = perte; - } - } - compteur.value = result.newValue; - // If endurance lost, then the same amount of fatigue cannot be recovered - if (ReglesOptionnelles.isUsing("appliquer-fatigue") && sante.fatigue && fatigue > 0) { - sante.fatigue.value = Math.max(sante.fatigue.value + fatigue, this._computeFatigueMin()); - } - await this.update({ "system.sante": sante }) - if (this.isDead()) { - await this.setEffect(STATUSES.StatusComma, true); - } - return result; - } - - async vehicleIncDec(name, inc) { - if (!this.isVehicule() || !['resistance', 'structure'].includes(name)) { - return - } - const value = this.system.etat[name].value; - const max = this.system.etat[name].max; - const newValue = value + inc; - if (0 <= newValue && newValue <= max) { - await this.update({ [`system.etat.${name}.value`]: newValue }) - } - } - - isDead() { - return !this.isEntite() && this.system.sante.vie.value < -this.getSConst() - } - - /* -------------------------------------------- */ - _computeFatigueMin() { - return this.system.sante.endurance.max - this.system.sante.endurance.value; - } - /* -------------------------------------------- */ _computeEnduranceMax() { const diffVie = this.system.sante.vie.max - this.system.sante.vie.value; @@ -1701,7 +1218,7 @@ export class RdDActor extends RdDBaseActor { case 'brut': { let d = new Dialog({ title: "Nourriture brute", - content: `Que faire de votre ${item.name}`, + content: `Que faire de votre ${item.name}`, buttons: { 'cuisiner': { icon: '', label: 'Cuisiner', callback: async () => await this.preparerNourriture(item) }, 'manger': { icon: '', label: 'Manger cru', callback: async () => await this.mangerNourriture(item, onActionItem) } @@ -1911,6 +1428,7 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async transformerStress() { + if (!ReglesOptionnelles.isUsing("transformation-stress")) { return } const fromStress = Number(this.system.compteurs.stress.value); if (this.system.sommeil?.insomnie || fromStress <= 0) { return; @@ -2003,7 +1521,7 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async checkCaracXP(caracName, display = true) { - let carac = RdDActor._findCaracByName(this.system.carac, caracName); + let carac = RdDBaseActor._findCaracByName(this.system.carac, caracName); if (carac && carac.xp > 0) { const niveauSuivant = Number(carac.value) + 1; let xpNeeded = RdDCarac.getCaracNextXp(niveauSuivant); @@ -2062,7 +1580,6 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async appliquerAjoutExperience(rollData, hideChatMessage = 'show') { - if (!this.isPersonnage()) return; hideChatMessage = hideChatMessage == 'hide' || (Misc.isRollModeHiddenToPlayer() && !game.user.isGM) let xpData = await this._appliquerExperience(rollData.rolled, rollData.selectedCarac.label, rollData.competence, rollData.jetResistance); if (xpData) { @@ -2081,7 +1598,6 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async _appliquerAppelMoral(rollData) { - if (!this.isPersonnage()) return; if (!rollData.use.moral) return; if (rollData.rolled.isEchec || (rollData.ajustements.diviseurSignificative && (rollData.rolled.roll * rollData.ajustements.diviseurSignificative > rollData.score))) { @@ -2152,7 +1668,7 @@ export class RdDActor extends RdDBaseActor { const draconicList = this.computeDraconicAndSortIndex(sorts); const reve = duplicate(this.system.carac.reve); - const dialog = await this._openRollDialog({ + const dialog = await this.openRollDialog({ name: 'lancer-un-sort', label: 'Lancer un sort', template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-sort.html', @@ -2196,7 +1712,7 @@ export class RdDActor extends RdDBaseActor { } /* -------------------------------------------- */ - getTMRFatigue() { // Pour l'instant uniquement Inertie Draconique + getCoutFatigueTMR() { // Pour l'instant uniquement Inertie Draconique let countInertieDraconique = EffetsDraconiques.countInertieDraconique(this); if (countInertieDraconique > 0) { ChatMessage.create({ @@ -2272,29 +1788,6 @@ export class RdDActor extends RdDBaseActor { } } - /* -------------------------------------------- */ - async rollCarac(caracName, jetResistance = undefined) { - RdDEmpoignade.checkEmpoignadeEnCours(this) - let selectedCarac = this.getCaracByName(caracName) - await this._openRollDialog({ - name: 'jet-' + caracName, - label: 'Jet ' + Grammar.apostrophe('de', selectedCarac.label), - template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html', - rollData: { - selectedCarac: selectedCarac, - competences: this.itemTypes['competence'], - jetResistance: jetResistance ? caracName : undefined - }, - callbackAction: r => this.$onRollCaracResult(r) - }); - } - - /* -------------------------------------------- */ - async $onRollCaracResult(rollData) { - // Final chat message - await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-general.html'); - } - /** * Méthode pour faire un jet prédéterminer sans ouvrir la fenêtre de dialogue * @param {*} caracName @@ -2340,45 +1833,6 @@ export class RdDActor extends RdDBaseActor { } } - /* -------------------------------------------- */ - async rollCompetence(idOrName, options = { tryTarget: true }) { - RdDEmpoignade.checkEmpoignadeEnCours(this) - const competence = this.getCompetence(idOrName); - let rollData = { carac: this.system.carac, competence: competence } - if (competence.type == TYPES.competencecreature) { - const arme = RdDItemCompetenceCreature.armeCreature(competence) - if (arme && options.tryTarget && Targets.hasTargets()) { - Targets.selectOneToken(target => { - if (arme.action == "possession") { - RdDPossession.onAttaquePossession(target, this, competence) - } - else { - RdDCombat.rddCombatTarget(target, this).attaque(competence, arme) - } - }); - return; - } - // Transformer la competence de créature - RdDItemCompetenceCreature.setRollDataCreature(rollData) - } - - await this._openRollDialog({ - name: 'jet-competence', - label: 'Jet ' + Grammar.apostrophe('de', competence.name), - template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html', - rollData: rollData, - callbackAction: r => this.$onRollCompetence(r, options) - }); - } - - /* -------------------------------------------- */ - async $onRollCompetence(rollData, options) { - await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-competence.html') - if (options?.onRollAutomate) { - options.onRollAutomate(rollData); - } - } - /* -------------------------------------------- */ async creerTacheDepuisLivre(item, options = { renderSheet: true }) { const nomTache = "Lire " + item.name; @@ -2405,7 +1859,6 @@ export class RdDActor extends RdDBaseActor { } blessuresASoigner() { - // TODO or not TODO: filtrer les blessures poour lesquels on ne peut plus faire de premiers soins? return this.filterItems(it => it.system.gravite > 0 && it.system.gravite <= 6 && !(it.system.premierssoins.done && it.system.soinscomplets.done), 'blessure') } @@ -2423,7 +1876,7 @@ export class RdDActor extends RdDBaseActor { async rollCaracCompetence(caracName, compName, diff, options = { title: "" }) { RdDEmpoignade.checkEmpoignadeEnCours(this) const competence = this.getCompetence(compName); - await this._openRollDialog({ + await this.openRollDialog({ name: 'jet-competence', label: 'Jet ' + Grammar.apostrophe('de', competence.name), template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html', @@ -2447,7 +1900,7 @@ export class RdDActor extends RdDBaseActor { const compData = this.getCompetence(tacheData.system.competence) compData.system.defaut_carac = tacheData.system.carac; // Patch ! - await this._openRollDialog({ + await this.openRollDialog({ name: 'jet-competence', label: 'Jet de Tâche ' + tacheData.name, template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html', @@ -2510,7 +1963,7 @@ export class RdDActor extends RdDBaseActor { artData.forceCarac[selected] = duplicate(this.system.carac[selected]); } - await this._openRollDialog({ + await this.openRollDialog({ name: `jet-${artData.art}`, label: `${artData.verbe} ${oeuvre.name}`, template: `systems/foundryvtt-reve-de-dragon/templates/dialog-roll-${oeuvre.type}.html`, @@ -2750,7 +2203,7 @@ export class RdDActor extends RdDBaseActor { const dialog = await RdDRoll.create(this, rollData, { html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-signedraconique.html', - close: html => { this.tmrApp?.restoreTMRAfterAction() } + close: async html => await this._onCloseRollDialog(html) }, { name: 'lire-signe-draconique', @@ -2765,6 +2218,10 @@ export class RdDActor extends RdDBaseActor { this.tmrApp?.setTMRPendingAction(dialog); } + async _onCloseRollDialog(html) { + this.tmrApp?.restoreTMRAfterAction() + } + /* -------------------------------------------- */ async _rollLireSigneDraconique(rollData) { const compData = rollData.competence; @@ -2786,7 +2243,7 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async rollAppelChance(onSuccess = () => { }, onEchec = () => { }) { - await this._openRollDialog({ + await this.openRollDialog({ name: 'appelChance', label: 'Appel à la chance', template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html', @@ -2830,20 +2287,15 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ getHeureNaissance() { - if (this.isPersonnage()) { - return this.system.heure; - } - return 0; + return this.system.heure ?? 0; } /* -------------------------------------------- */ ajustementAstrologique() { - if (this.isCreatureEntite()) { - return 0; - } // selon l'heure de naissance... return game.system.rdd.calendrier.getAjustementAstrologique(this.system.heure, this.name) } + /* -------------------------------------------- */ checkDesirLancinant() { let queue = this.filterItems(it => it.type == 'queue' || it.type == 'ombre') @@ -2853,7 +2305,6 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async _appliquerExperience(rolled, caracName, competence, jetResistance) { - if (!this.isPersonnage()) return; // Pas d'XP if (!rolled.isPart || rolled.finalLevel >= 0) { return undefined; @@ -2904,7 +2355,7 @@ export class RdDActor extends RdDBaseActor { async _xpCarac(xpData) { if (xpData.xpCarac > 0) { let carac = duplicate(this.system.carac); - let selectedCarac = RdDActor._findCaracByName(carac, xpData.caracName); + let selectedCarac = RdDBaseActor._findCaracByName(carac, xpData.caracName); if (!selectedCarac.derivee) { const from = Number(selectedCarac.xp); const to = from + xpData.xpCarac; @@ -2957,51 +2408,6 @@ export class RdDActor extends RdDBaseActor { await AppAstrologie.create(this); } - /* -------------------------------------------- */ - getCaracByName(name) { - switch (Grammar.toLowerCaseNoAccent(name)) { - case 'reve-actuel': case 'reve actuel': - return this.getCaracReveActuel(); - case 'chance-actuelle': case 'chance-actuelle': - return this.getCaracChanceActuelle(); - } - return RdDActor._findCaracByName(this.system.carac, name); - } - - getCaracChanceActuelle() { - return { - label: 'Chance actuelle', - value: this.getChanceActuel(), - type: "number" - }; - } - - getCaracReveActuel() { - return { - label: 'Rêve actuel', - value: this.getReveActuel(), - type: "number" - }; - } - - /* -------------------------------------------- */ - static _findCaracByName(carac, name) { - name = Grammar.toLowerCaseNoAccent(name); - switch (name) { - case 'reve-actuel': case 'reve actuel': - return carac.reve; - case 'chance-actuelle': case 'chance actuelle': - return carac.chance; - } - - const caracList = Object.entries(carac); - let entry = Misc.findFirstLike(name, caracList, { mapper: it => it[0], description: 'caractéristique' }); - if (!entry || entry.length == 0) { - entry = Misc.findFirstLike(name, caracList, { mapper: it => it[1].label, description: 'caractéristique' }); - } - return entry && entry.length > 0 ? carac[entry[0]] : undefined; - } - /* -------------------------------------------- */ countMonteeLaborieuse() { // Return +1 par queue/ombre/souffle Montée Laborieuse présente let countMonteeLaborieuse = EffetsDraconiques.countMonteeLaborieuse(this); @@ -3076,61 +2482,6 @@ export class RdDActor extends RdDBaseActor { } /* -------------------------------------------- */ - getCompetenceArme(arme, competenceName) { - switch (arme.type) { - case TYPES.competencecreature: - return arme.name - case TYPES.arme: - switch (competenceName) { - case 'competence': return arme.system.competence; - case 'unemain': return RdDItemArme.competence1Mains(arme); - case 'deuxmains': return RdDItemArme.competence2Mains(arme); - case 'tir': return arme.system.tir; - case 'lancer': return arme.system.lancer; - } - } - return undefined - } - - /* -------------------------------------------- */ - /** - * - * @param {*} arme item d'arme/compétence de créature - * @param {*} categorieArme catégorie d'attaque à utiliser: competence (== melee), lancer, tir; naturelle, possession - * @returns - */ - rollArme(arme, categorieArme = "competence") { - let compToUse = this.getCompetenceArme(arme, categorieArme) - if (!Targets.hasTargets()) { - RdDConfirm.confirmer({ - settingConfirmer: "confirmer-combat-sans-cible", - content: `

Voulez vous faire un jet de ${compToUse} sans choisir de cible valide? -
Tous les jets de combats devront être gérés à la main -

`, - title: 'Ne pas utiliser les automatisation de combat', - buttonLabel: "Pas d'automatisation", - onAction: async () => { - this.rollCompetence(compToUse, { tryTarget: false }) - } - }); - return; - } - - Targets.selectOneToken(target => { - if (Targets.isTargetEntite(target)) { - ui.notifications.warn(`Vous ne pouvez pas attaquer une entité non incarnée avec votre ${arme.name}!!!!`); - return; - } - - const competence = this.getCompetence(compToUse) - //console.log("RollArme", competence, arme) - if (competence.isCompetencePossession()) { - return RdDPossession.onAttaquePossession(target, this, competence); - } - RdDCombat.rddCombatTarget(target, this).attaque(competence, arme); - }) - } - async rollSoins(blesse, blessureId) { const blessure = blesse.blessuresASoigner().find(it => it.id == blessureId); if (blessure) { @@ -3207,12 +2558,6 @@ export class RdDActor extends RdDBaseActor { RdDPossession.onConjurerPossession(this, possession) } - /* -------------------------------------------- */ - getArmeParade(armeParadeId) { - const item = armeParadeId ? this.getEmbeddedDocument('Item', armeParadeId) : undefined; - return RdDItemArme.getArme(item); - } - /* -------------------------------------------- */ verifierForceMin(item) { if (item.type == 'arme' && item.system.force > this.system.carac.force.value) { @@ -3229,7 +2574,7 @@ export class RdDActor extends RdDBaseActor { if (item?.isEquipable()) { const isEquipe = !item.system.equipe; await this.updateEmbeddedDocuments('Item', [{ _id: item.id, "system.equipe": isEquipe }]); - this.computeEncTotal(); // Mise à jour encombrement + this.computeEncTotal(); if (isEquipe) this.verifierForceMin(item); } @@ -3262,108 +2607,6 @@ export class RdDActor extends RdDBaseActor { return protection; } - - /* -------------------------------------------- */ - async encaisser() { - await RdDEncaisser.encaisser(this); - } - - /* -------------------------------------------- */ - async encaisserDommages(rollData, attacker = undefined, show = undefined) { - if (attacker && !await attacker.accorder(this, 'avant-encaissement')) { - return; - } - const attackerId = attacker?.id; - if (ReglesOptionnelles.isUsing('validation-encaissement-gr') && !game.user.isGM) { - RdDBaseActor.remoteActorCall({ - tokenId: this.token?.id, - actorId: this.id, - method: 'appliquerEncaissement', - args: [rollData, show, attackerId] - }); - return; - } - await this.appliquerEncaissement(rollData, show, attackerId); - } - - async appliquerEncaissement(rollData, show, attackerId) { - const armure = await this.computeArmure(rollData); - if (ReglesOptionnelles.isUsing('validation-encaissement-gr')) { - DialogValidationEncaissement.validerEncaissement(this, rollData, armure, show, attackerId, (encaissement, show, attackerId) => this._appliquerEncaissement(encaissement, show, attackerId)); - } - else { - let encaissement = await RdDUtility.jetEncaissement(rollData, armure, { showDice: SHOW_DICE }); - await this._appliquerEncaissement(encaissement, show, attackerId); - } - } - - async _appliquerEncaissement(encaissement, show, attackedId) { - const attacker = attackedId ? game.actors.get(attackedId) : undefined - let santeOrig = duplicate(this.system.sante); - - const blessure = await this.ajouterBlessure(encaissement, attacker); // Will update the result table - const perteVie = this.isEntite() - ? { newValue: 0 } - : await this.santeIncDec("vie", -encaissement.vie); - const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, blessure?.isCritique()); - - mergeObject(encaissement, { - alias: this.name, - hasPlayerOwner: this.hasPlayerOwner, - resteEndurance: this.system.sante.endurance.value, - sonne: perteEndurance.sonne, - jetEndurance: perteEndurance.jetEndurance, - endurance: santeOrig.endurance.value - perteEndurance.newValue, - vie: this.isEntite() ? 0 : (santeOrig.vie.value - perteVie.newValue), - blessure: blessure, - show: show ?? {} - }); - - await ChatUtility.createChatWithRollMode(this.name, { - roll: encaissement.roll, - content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement) - }); - - if (!encaissement.hasPlayerOwner && encaissement.endurance != 0) { - encaissement = duplicate(encaissement); - encaissement.isGM = true; - ChatMessage.create({ - whisper: ChatMessage.getWhisperRecipients("GM"), - content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement) - }); - } - } - - /* -------------------------------------------- */ - async ajouterBlessure(encaissement, attacker = undefined) { - if (this.isEntite()) return; // Une entité n'a pas de blessures - if (encaissement.gravite < 0) return; - if (encaissement.gravite > 0) { - while (this.countBlessures(it => it.system.gravite == encaissement.gravite) >= RdDItemBlessure.maxBlessures(encaissement.gravite) && encaissement.gravite <= 6) { - // Aggravation - encaissement.gravite += 2 - if (encaissement.gravite > 2) { - encaissement.vie += 2; - } - } - } - const endActuelle = Number(this.system.sante.endurance.value); - const blessure = await RdDItemBlessure.createBlessure(this, encaissement.gravite, encaissement.dmg.loc.label, attacker); - if (blessure.isCritique()) { - encaissement.endurance = endActuelle; - } - - if (blessure.isMort()) { - this.setEffect(STATUSES.StatusComma, true); - encaissement.mort = true; - ChatMessage.create({ - content: `charge - ${this.name} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !` - }); - } - return blessure; - } - /* -------------------------------------------- */ /** @override */ getRollData() { @@ -3392,71 +2635,6 @@ export class RdDActor extends RdDBaseActor { return itemUse[itemId] ?? 0; } - /* -------------------------------------------- */ - /* -- entites -- */ - /* retourne true si on peut continuer, false si on ne peut pas continuer */ - async targetEntiteNonAccordee(target, when = 'avant-encaissement') { - if (target) { - return !await this.accorder(target.actor, when); - } - return false; - } - - /* -------------------------------------------- */ - async accorder(entite, when = 'avant-encaissement') { - if (when != game.settings.get(SYSTEM_RDD, "accorder-entite-cauchemar") - || entite == undefined - || !entite.isEntite([ENTITE_INCARNE]) - || entite.isEntiteAccordee(this)) { - return true; - } - const rolled = await RdDResolutionTable.roll(this.getReveActuel(), - Number(entite.system.carac.niveau.value)); - const rollData = { - alias: this.name, - rolled: rolled, - entite: entite.name, - selectedCarac: this.system.carac.reve - }; - - if (rolled.isSuccess) { - await entite.setEntiteReveAccordee(this); - } - - await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-accorder-cauchemar.html'); - if (rolled.isPart) { - await this.appliquerAjoutExperience(rollData, true); - } - return rolled.isSuccess; - } - - /* -------------------------------------------- */ - isEntite(typeentite = []) { - return this.type == 'entite' && (typeentite.length == 0 || typeentite.includes(this.system.definition.typeentite)); - } - - /* -------------------------------------------- */ - isEntiteAccordee(attaquant) { - if (!this.isEntite([ENTITE_INCARNE])) { return true; } - let resonnance = this.system.sante.resonnance; - return (resonnance.actors.find(it => it == attaquant.id)); - } - - /* -------------------------------------------- */ - async setEntiteReveAccordee(attaquant) { - if (!this.isEntite([ENTITE_INCARNE])) { - ui.notifications.error("Impossible de s'accorder à " + this.name + ": ce n'est pas une entite de cauchemer/rêve"); - return; - } - let resonnance = duplicate(this.system.sante.resonnance); - if (resonnance.actors.find(it => it == attaquant.id)) { - // déjà accordé - return; - } - resonnance.actors.push(attaquant.id); - await this.update({ "system.sante.resonnance": resonnance }); - return; - } - /* -------------------------------------------- */ async effectuerTacheAlchimie(recetteId, tacheAlchimie, texteTache) { let recetteData = this.findItemLike(recetteId, 'recettealchimique'); @@ -3760,43 +2938,8 @@ export class RdDActor extends RdDBaseActor { } /* -------------------------------------------- */ - getEffects(filter = e => true) { - return this.getEmbeddedCollection("ActiveEffect").filter(filter); - } - - /* -------------------------------------------- */ - getEffect(statusId) { - return this.getEmbeddedCollection("ActiveEffect").find(it => it.flags?.core?.statusId == statusId); - } - - /* -------------------------------------------- */ - async setEffect(statusId, status) { - if (this.isEntite() || this.isVehicule()) { - return; - } - console.log("setEffect", statusId, status) - const effect = this.getEffect(statusId); - if (!status && effect) { - await this.deleteEmbeddedDocuments('ActiveEffect', [effect.id]); - } - if (status && !effect) { - await this.createEmbeddedDocuments("ActiveEffect", [StatusEffects.status(statusId)]); - } - } - - async removeEffect(statusId) { - const effect = this.getEffect(statusId); - if (effect) { - await this.deleteEmbeddedDocuments('ActiveEffect', [effect.id]); - } - } - - /* -------------------------------------------- */ - async removeEffects(filter = e => true) { - if (game.user.isGM) { - const ids = this.getEffects(filter).map(it => it.id); - await this.deleteEmbeddedDocuments('ActiveEffect', ids); - } + isEffectAllowed(statusId) { + return true } /* -------------------------------------------- */ diff --git a/module/actor/base-actor-reve-sheet.js b/module/actor/base-actor-reve-sheet.js new file mode 100644 index 00000000..ea074ff1 --- /dev/null +++ b/module/actor/base-actor-reve-sheet.js @@ -0,0 +1,81 @@ +import { RdDSheetUtility } from "../rdd-sheet-utility.js"; +import { RdDBaseActorSheet } from "./base-actor-sheet.js"; + +/* -------------------------------------------- */ +/** + * Extend the basic ActorSheet with some very simple modifications + * @extends {ActorSheet} + */ +export class RdDBaseActorReveSheet extends RdDBaseActorSheet { + + /* -------------------------------------------- */ + /** @override */ + activateListeners(html) { + super.activateListeners(html); + + // Everything below here is only needed if the sheet is editable + if (!this.options.editable) return; + + this.html.find('.item-action').click(async event => { + const item = RdDSheetUtility.getItem(event, this.actor); + item?.actionPrincipale(this.actor, async () => this.render()) + }); + + this.html.find('.encaisser-direct').click(async event => { + this.actor.encaisser(); + }) + this.html.find('.remise-a-neuf').click(async event => { + if (game.user.isGM) { + this.actor.remiseANeuf(); + } + }); + + this.html.find('.carac-label a').click(async event => { + let caracName = event.currentTarget.attributes.name.value; + this.actor.rollCarac(caracName.toLowerCase()); + }); + + this.html.find('a.competence-label').click(async event => { + this.actor.rollCompetence(RdDSheetUtility.getItemId(event)); + }); + + this.html.find('.delete-active-effect').click(async event => { + if (game.user.isGM) { + let effect = this.html.find(event.currentTarget).parents(".active-effect").data('effect'); + this.actor.removeEffect(effect); + } + }); + this.html.find('.enlever-tous-effets').click(async event => { + if (game.user.isGM) { + await this.actor.removeEffects(); + } + }); + + if (this.options.vueDetaillee) { + // On carac change + this.html.find('.carac-value').change(async event => { + let caracName = event.currentTarget.name.replace(".value", "").replace("system.carac.", ""); + this.actor.updateCarac(caracName, parseInt(event.target.value)); + }); + // On competence change + this.html.find('.competence-value').change(async event => { + let compName = event.currentTarget.attributes.compname.value; + //console.log("Competence changed :", compName); + this.actor.updateCompetence(compName, parseInt(event.target.value)); + }); + } + + this.html.find('.vue-detaillee').click(async event => { + this.options.vueDetaillee = !this.options.vueDetaillee; + this.render(true); + }); + + this.html.find('.endurance-plus').click(async event => { + this.actor.santeIncDec("endurance", 1); + }); + this.html.find('.endurance-moins').click(async event => { + this.actor.santeIncDec("endurance", -1); + }); + } + +} diff --git a/module/actor/base-actor-reve.js b/module/actor/base-actor-reve.js new file mode 100644 index 00000000..3409253f --- /dev/null +++ b/module/actor/base-actor-reve.js @@ -0,0 +1,509 @@ +import { ChatUtility } from "../chat-utility.js"; +import { DialogValidationEncaissement } from "../dialog-validation-encaissement.js"; +import { Grammar } from "../grammar.js"; +import { RdDItemCompetence } from "../item-competence.js"; +import { Misc } from "../misc.js"; +import { RdDEmpoignade } from "../rdd-empoignade.js"; +import { RdDResolutionTable } from "../rdd-resolution-table.js"; +import { RdDEncaisser } from "../rdd-roll-encaisser.js"; +import { RdDRoll } from "../rdd-roll.js"; +import { RdDUtility } from "../rdd-utility.js"; +import { ReglesOptionnelles } from "../settings/regles-optionnelles.js"; +import { RdDBaseActor } from "./base-actor.js"; +import { RdDItemCompetenceCreature } from "../item-competencecreature.js"; +import { StatusEffects } from "../settings/status-effects.js"; +import { TYPES } from "../item.js"; +import { Targets } from "../targets.js"; +import { RdDPossession } from "../rdd-possession.js"; +import { RdDCombat } from "../rdd-combat.js"; +import { RdDConfirm } from "../rdd-confirm.js"; +import { ENTITE_INCARNE, SYSTEM_RDD } from "../constants.js"; +import { RdDItemArme } from "../item-arme.js"; + +const POSSESSION_SANS_DRACONIC = { + img: 'systems/foundryvtt-reve-de-dragon/icons/entites/possession.webp', + name: 'Sans draconic', + system: { + niveau: 0, + defaut_carac: "reve-actuel", + } +}; + +/** + * Classe de base pour les acteurs disposant de rêve (donc, pas des objets) + * - Entités de rêve + * - Créatures de "sang": créatures et humanoides + */ +export class RdDBaseActorReve extends RdDBaseActor { + + getCaracChanceActuelle() { + return { + label: 'Chance actuelle', + value: this.getChanceActuel(), + type: "number" + }; + } + + getCaracReveActuel() { + return { + label: 'Rêve actuel', + value: this.getReveActuel(), + type: "number" + }; + } + + getReveActuel() { return this.getReve() } + getChanceActuel() { return this.getChance() } + + getReve() { return Number(this.system.carac.reve?.value ?? 0) } + getForce() { return this.getReve() } + getTaille() { return Number(this.system.carac.taille?.value ?? 0) } + getAgilite() { return this.getForce() } + getChance() { return this.getReve() } + getMoralTotal() { return 0 } + getBonusDegat() { return Number(this.system.attributs?.plusdom?.value ?? 0) } + getProtectionNaturelle() { return Number(this.system.attributs?.protection?.value ?? 0) } + getSConst() { return 0 } + + /* -------------------------------------------- */ + getEncombrementMax() { return 0 } + isSurenc() { return false } + computeMalusSurEncombrement() { return 0 } + + ajustementAstrologique() { return 0 } + getMalusArmure() { return 0 } + + getEnduranceActuelle() { + return Number(this.system.sante?.endurance?.value ?? 0); + } + async jetEndurance(resteEndurance = undefined) { return { jetEndurance: 0, sonne: false } } + isDead() { return false } + blessuresASoigner() { return [] } + getEtatGeneral(options = { ethylisme: false }) { return 0 } + + async computeArmure(attackerRoll) { return this.getProtectionNaturelle() } + async remiseANeuf() { } + async appliquerAjoutExperience(rollData, hideChatMessage = 'show') { } + + async santeIncDec(name, inc, isCritique = false) { } + + async finDeRound(options = { terminer: false }) { + await this.$finDeRoundSuppressionEffetsTermines(options); + await this.finDeRoundBlessures(); + await this.$finDeRoundSupprimerObsoletes(); + await this.$finDeRoundEmpoignade(); + } + + async $finDeRoundSuppressionEffetsTermines(options) { + for (let effect of this.getEffects()) { + if (effect.duration.type !== 'none' && (effect.duration.remaining <= 0 || options.terminer)) { + await effect.delete(); + ChatMessage.create({ content: `${this.name} n'est plus ${Misc.lowerFirst(game.i18n.localize(effect.system.label))} !` }); + } + } + } + + async finDeRoundBlessures() { + } + + async $finDeRoundSupprimerObsoletes() { + const obsoletes = [] + .concat(this.itemTypes[TYPES.empoignade].filter(it => it.system.pointsemp <= 0)) + .concat(this.itemTypes[TYPES.possession].filter(it => it.system.compteur < -2 || it.system.compteur > 2)) + .map(it => it.id); + await this.deleteEmbeddedDocuments('Item', obsoletes); + } + + async $finDeRoundEmpoignade() { + const immobilisations = this.itemTypes[TYPES.empoignade].filter(it => it.system.pointsemp >= 2 && it.system.empoigneurid == this.id); + immobilisations.forEach(emp => RdDEmpoignade.onImmobilisation(this, + game.actors.get(emp.system.empoigneid), + emp + )) + } + + async setSonne(sonne = true) { } + + /* -------------------------------------------- */ + getCompetence(idOrName, options = {}) { + if (idOrName instanceof Item) { + return idOrName.isCompetence() ? idOrName : undefined + } + return RdDItemCompetence.findCompetence(this.items, idOrName, options) + } + getCompetences(name) { + return RdDItemCompetence.findCompetences(this.items, name) + } + getCompetenceCorpsACorps(options = {}) { + return this.getCompetence("Corps à corps", options) + } + getCompetencesEsquive() { + return this.getCompetences("esquive") + } + + getArmeParade(armeParadeId) { + const item = armeParadeId ? this.getEmbeddedDocument('Item', armeParadeId) : undefined; + return RdDItemArme.getArme(item); + } + + getDraconicOuPossession() { + return POSSESSION_SANS_DRACONIC + } + + getPossession(possessionId) { + return this.itemTypes[TYPES.possession].find(it => it.system.possessionid == possessionId); + } + getPossessions() { + return this.itemTypes[TYPES.possession]; + } + getEmpoignades() { + return this.itemTypes[TYPES.empoignade]; + } + + /* -------------------------------------------- */ + async updateCreatureCompetence(idOrName, fieldName, value) { + let competence = this.getCompetence(idOrName); + if (competence) { + function getFieldPath(fieldName) { + switch (fieldName) { + case "niveau": return 'system.niveau'; + case "dommages": return 'system.dommages'; + case "carac_value": return 'system.carac_value'; + } + return undefined + } + const path = getFieldPath(fieldName); + if (path) { + await this.updateEmbeddedDocuments('Item', [{ _id: competence.id, [path]: value }]); // updates one EmbeddedEntity + } + } + } + + /* -------------------------------------------- */ + isEffectAllowed(statusId) { return true } + + getEffects(filter = e => true) { + return this.getEmbeddedCollection("ActiveEffect").filter(filter); + } + + getEffect(statusId) { + return this.getEmbeddedCollection("ActiveEffect").find(it => it.flags?.core?.statusId == statusId); + } + + async setEffect(statusId, status) { + if (this.isEffectAllowed(statusId)) { + const effect = this.getEffect(statusId); + if (!status && effect) { + await this.deleteEmbeddedDocuments('ActiveEffect', [effect.id]); + } + if (status && !effect) { + await this.createEmbeddedDocuments("ActiveEffect", [StatusEffects.status(statusId)]); + } + } + } + + async removeEffect(statusId) { + const effect = this.getEffect(statusId); + if (effect) { + await this.deleteEmbeddedDocuments('ActiveEffect', [effect.id]); + } + } + + async removeEffects(filter = e => true) { + if (game.user.isGM) { + const ids = this.getEffects(filter).map(it => it.id); + await this.deleteEmbeddedDocuments('ActiveEffect', ids); + } + } + + /* -------------------------------------------- */ + getSurprise(isCombat = undefined) { + let niveauSurprise = this.getEffects() + .map(effect => StatusEffects.valeurSurprise(effect, isCombat)) + .reduce(Misc.sum(), 0); + if (niveauSurprise > 1) { + return 'totale'; + } + if (niveauSurprise == 1) { + return 'demi'; + } + return ''; + } + + /* -------------------------------------------- */ + async computeEtatGeneral() { + // Par défaut, on ne calcule pas d'état général, seuls les personnages/créatures sont affectés + this.system.compteurs.etat.value = 0; + } + + /* -------------------------------------------- */ + async openRollDialog({ name, label, template, rollData, callbackAction }) { + const dialog = await RdDRoll.create(this, rollData, + { html: template, close: async html => await this._onCloseRollDialog(html) }, + { + name: name, + label: label, + callbacks: [ + this.createCallbackExperience(), + this.createCallbackAppelAuMoral(), + { action: callbackAction } + ] + }); + dialog.render(true); + return dialog + } + + createEmptyCallback() { + return { + condition: r => false, + action: r => { } + }; + } + createCallbackExperience() { return this.createEmptyCallback(); } + createCallbackAppelAuMoral() { return this.createEmptyCallback(); } + async _onCloseRollDialog(html) { } + + /* -------------------------------------------- */ + async roll() { + RdDEmpoignade.checkEmpoignadeEnCours(this) + + const carac = this.getCarac() + const selectedCaracName = ['apparence', 'perception', 'force', 'reve'].find(it => carac[it] != undefined) + + await this.openRollDialog({ + name: `jet-${this.id}`, + label: `Jet de ${this.name}`, + template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll.html', + rollData: { + carac: carac, + selectedCarac: carac[selectedCaracName], + selectedCaracName: selectedCaracName, + competences: this.itemTypes['competence'] + }, + callbackAction: r => this.$onRollCaracResult(r) + }); + } + + getCarac() { + // TODO: le niveau d'une entité de cauchemar devrait être exclu... + const carac = mergeObject(duplicate(this.system.carac), + { + 'reve-actuel': this.getCaracReveActuel(), + 'chance-actuelle': this.getCaracChanceActuelle() + }); + return carac; + } + + /* -------------------------------------------- */ + async rollCarac(caracName, jetResistance = undefined) { + RdDEmpoignade.checkEmpoignadeEnCours(this) + let selectedCarac = this.getCaracByName(caracName) + await this.openRollDialog({ + name: 'jet-' + caracName, + label: 'Jet ' + Grammar.apostrophe('de', selectedCarac.label), + template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html', + rollData: { + selectedCarac: selectedCarac, + competences: this.itemTypes['competence'], + jetResistance: jetResistance ? caracName : undefined + }, + callbackAction: r => this.$onRollCaracResult(r) + }); + } + + /* -------------------------------------------- */ + async $onRollCaracResult(rollData) { + // Final chat message + await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-general.html'); + } + + /* -------------------------------------------- */ + async rollCompetence(idOrName, options = { tryTarget: true }) { + RdDEmpoignade.checkEmpoignadeEnCours(this) + const competence = this.getCompetence(idOrName); + let rollData = { carac: this.system.carac, competence: competence } + if (competence.type == TYPES.competencecreature) { + const arme = RdDItemCompetenceCreature.armeCreature(competence) + if (arme && options.tryTarget && Targets.hasTargets()) { + Targets.selectOneToken(target => { + if (arme.action == "possession") { + RdDPossession.onAttaquePossession(target, this, competence) + } + else { + RdDCombat.rddCombatTarget(target, this).attaque(competence, arme) + } + }); + return; + } + // Transformer la competence de créature + RdDItemCompetenceCreature.setRollDataCreature(rollData) + } + + await this.openRollDialog({ + name: 'jet-competence', + label: 'Jet ' + Grammar.apostrophe('de', competence.name), + template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html', + rollData: rollData, + callbackAction: r => this.$onRollCompetence(r, options) + }); + } + + async $onRollCompetence(rollData, options) { + await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-competence.html') + if (options?.onRollAutomate) { + options.onRollAutomate(rollData); + } + } + + /** -------------------------------------------- + * @param {*} arme item d'arme/compétence de créature + * @param {*} categorieArme catégorie d'attaque à utiliser: competence (== melee), lancer, tir; naturelle, possession + * @returns + */ + rollArme(arme, categorieArme = "competence") { + let compToUse = this.$getCompetenceArme(arme, categorieArme) + if (!Targets.hasTargets()) { + RdDConfirm.confirmer({ + settingConfirmer: "confirmer-combat-sans-cible", + content: `

Voulez vous faire un jet de ${compToUse} sans choisir de cible valide? +
Tous les jets de combats devront être gérés à la main +

`, + title: 'Ne pas utiliser les automatisation de combat', + buttonLabel: "Pas d'automatisation", + onAction: async () => { + this.rollCompetence(compToUse, { tryTarget: false }) + } + }); + return; + } + + Targets.selectOneToken(target => { + if (Targets.isTargetEntite(target)) { + ui.notifications.warn(`Vous ne pouvez pas attaquer une entité non incarnée avec votre ${arme.name}!!!!`); + return; + } + + const competence = this.getCompetence(compToUse) + //console.log("RollArme", competence, arme) + if (competence.isCompetencePossession()) { + return RdDPossession.onAttaquePossession(target, this, competence); + } + RdDCombat.rddCombatTarget(target, this).attaque(competence, arme); + }) + } + + $getCompetenceArme(arme, competenceName) { + switch (arme.type) { + case TYPES.competencecreature: + return arme.name + case TYPES.arme: + switch (competenceName) { + case 'competence': return arme.system.competence; + case 'unemain': return RdDItemArme.competence1Mains(arme); + case 'deuxmains': return RdDItemArme.competence2Mains(arme); + case 'tir': return arme.system.tir; + case 'lancer': return arme.system.lancer; + } + } + return undefined + } + + verifierForceMin(item) { + } + /* -------------------------------------------- */ + async resetItemUse() { } + async incDecItemUse(itemId, inc = 1) { } + getItemUse(itemId) { return 0; } + + /* -------------------------------------------- */ + async encaisser() { await RdDEncaisser.encaisser(this) } + + async encaisserDommages(rollData, attacker = undefined, show = undefined) { + if (attacker && !await attacker.accorder(this, 'avant-encaissement')) { + return; + } + const attackerId = attacker?.id; + if (ReglesOptionnelles.isUsing('validation-encaissement-gr') && !game.user.isGM) { + RdDBaseActor.remoteActorCall({ + tokenId: this.token?.id, + actorId: this.id, + method: 'appliquerEncaissement', + args: [rollData, show, attackerId] + }); + return; + } + + const armure = await this.computeArmure(rollData); + if (ReglesOptionnelles.isUsing('validation-encaissement-gr')) { + DialogValidationEncaissement.validerEncaissement(this, rollData, armure, + jet => this.$onEncaissement(jet, show, attacker)); + } + else { + const jet = await RdDUtility.jetEncaissement(rollData, armure, { showDice: SHOW_DICE }); + await this.$onEncaissement(jet, show, attacker); + } + } + + async $onEncaissement(jet, show, attacker) { + await this.onAppliquerJetEncaissement(jet, attacker); + await this.$afficherEncaissement(jet, show); + } + + async onAppliquerJetEncaissement(encaissement, attacker) { } + + async $afficherEncaissement(encaissement, show) { + mergeObject(encaissement, { + alias: this.name, + hasPlayerOwner: this.hasPlayerOwner, + show: show ?? {} + }); + + await ChatUtility.createChatWithRollMode(this.name, { + roll: encaissement.roll, + content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement) + }); + + if (!encaissement.hasPlayerOwner && encaissement.endurance != 0) { + encaissement = duplicate(encaissement); + encaissement.isGM = true; + ChatMessage.create({ + whisper: ChatMessage.getWhisperRecipients("GM"), + content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement) + }); + } + } + + /* -------------------------------------------- */ + async accorder(entite, when = 'avant-encaissement') { + if (when != game.settings.get(SYSTEM_RDD, "accorder-entite-cauchemar") + || entite == undefined + || !entite.isEntite([ENTITE_INCARNE]) + || entite.isEntiteAccordee(this)) { + return true; + } + const rolled = await RdDResolutionTable.roll(this.getReveActuel(), - Number(entite.system.carac.niveau.value)); + const rollData = { + alias: this.name, + rolled: rolled, + entite: entite.name, + selectedCarac: this.system.carac.reve + }; + + if (rolled.isSuccess) { + await entite.setEntiteReveAccordee(this); + } + + await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-accorder-cauchemar.html'); + if (rolled.isPart) { + await this.appliquerAjoutExperience(rollData, true); + } + return rolled.isSuccess; + } + + isEntiteAccordee(attacker) { return true } + + async setEntiteReveAccordee(attacker) { + ui.notifications.error("Impossible de s'accorder à " + this.name + ": ce n'est pas une entite de cauchemer/rêve"); + } + +} diff --git a/module/actor/base-actor-sang.js b/module/actor/base-actor-sang.js new file mode 100644 index 00000000..b0a03669 --- /dev/null +++ b/module/actor/base-actor-sang.js @@ -0,0 +1,275 @@ +import { MAX_ENDURANCE_FATIGUE, RdDUtility } from "../rdd-utility.js"; +import { ReglesOptionnelles } from "../settings/regles-optionnelles.js"; +import { STATUSES } from "../settings/status-effects.js"; +import { TYPES } from "../item.js"; +import { RdDBaseActorReve } from "./base-actor-reve.js"; +import { RdDDice } from "../rdd-dice.js"; +import { RdDItemBlessure } from "../item/blessure.js"; + +/** + * Classe de base pour les acteurs qui peuvent subir des blessures + * - créatures + * - humanoides + */ +export class RdDBaseActorSang extends RdDBaseActorReve { + + + getForce() { return Number(this.system.carac.force?.value ?? 0) } + + getBonusDegat() { return Number(this.system.attributs?.plusdom?.value ?? 0) } + getProtectionNaturelle() { return Number(this.system.attributs?.protection?.value ?? 0) } + getSConst() { return 0 } + + getEnduranceMax() { + return Math.max(1, Math.min(this.system.sante.endurance.max, MAX_ENDURANCE_FATIGUE)); + } + + getFatigueActuelle() { + if (ReglesOptionnelles.isUsing("appliquer-fatigue")) { + return Math.max(0, Math.min(this.getFatigueMax(), this.system.sante.fatigue?.value)); + } + return 0; + } + + getFatigueRestante() { + return this.getFatigueMax() - this.getFatigueActuelle(); + } + + getFatigueMin() { + return this.system.sante.endurance.max - this.system.sante.endurance.value; + } + + getFatigueMax() { return this.getEnduranceMax() * 2 } + + malusFatigue() { + if (ReglesOptionnelles.isUsing("appliquer-fatigue")) { + return RdDUtility.calculMalusFatigue(this.getFatigueActuelle(), this.getEnduranceMax()) + } + return 0; + } + + /* -------------------------------------------- */ + getEncombrementMax() { return Number(this.system.attributs?.encombrement?.value ?? 0) } + isSurenc() { return this.computeMalusSurEncombrement() < 0 } + + computeMalusSurEncombrement() { + return Math.min(0, Math.floor(this.getEncombrementMax() - this.encTotal)); + } + + isDead() { + return this.system.sante.vie.value < -this.getSConst() + } + + /* -------------------------------------------- */ + computeResumeBlessure() { + const blessures = this.filterItems(it => it.system.gravite > 0, 'blessure') + + const nbLegeres = blessures.filter(it => it.isLegere()).length; + const nbGraves = blessures.filter(it => it.isGrave()).length; + const nbCritiques = blessures.filter(it => it.isCritique()).length; + + if (nbLegeres + nbGraves + nbCritiques == 0) { + return "Aucune blessure"; + } + let resume = "Blessures:"; + if (nbLegeres > 0) { + resume += " " + nbLegeres + " légère" + (nbLegeres > 1 ? "s" : ""); + } + if (nbGraves > 0) { + if (nbLegeres > 0) + resume += ","; + resume += " " + nbGraves + " grave" + (nbGraves > 1 ? "s" : ""); + } + if (nbCritiques > 0) { + if (nbGraves > 0 || nbLegeres > 0) + resume += ","; + resume += " une CRITIQUE !"; + } + return resume; + } + + blessuresASoigner() { return [] } + getEtatGeneral(options = { ethylisme: false }) { return 0 } + + async computeArmure(attackerRoll) { return this.getProtectionNaturelle() } + async remiseANeuf() { } + async appliquerAjoutExperience(rollData, hideChatMessage = 'show') { } + + /* -------------------------------------------- */ + + async onAppliquerJetEncaissement(encaissement, attacker) { + const santeOrig = duplicate(this.system.sante); + const blessure = await this.ajouterBlessure(encaissement, attacker); // Will update the result table + const perteVie = await this.santeIncDec("vie", -encaissement.vie); + const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, blessure?.isCritique()); + + mergeObject(encaissement, { + resteEndurance: perteEndurance.newValue, + sonne: perteEndurance.sonne, + jetEndurance: perteEndurance.jetEndurance, + endurance: perteEndurance.perte, + vie: santeOrig.vie.value - perteVie.newValue, + blessure: blessure + }); + } + /* -------------------------------------------- */ + async santeIncDec(name, inc, isCritique = false) { + if (name == 'fatigue' && !ReglesOptionnelles.isUsing("appliquer-fatigue")) { + return; + } + const sante = duplicate(this.system.sante) + let compteur = sante[name]; + if (!compteur) { + return; + } + let result = { + sonne: false, + }; + + let minValue = name == "vie" ? -this.getSConst() - 1 : 0; + + result.newValue = Math.max(minValue, Math.min(compteur.value + inc, compteur.max)); + //console.log("New value ", inc, minValue, result.newValue); + let fatigue = 0; + if (name == "endurance") { + if (result.newValue == 0 && inc < 0 && !isCritique) { // perte endurance et endurance devient 0 (sauf critique) -> -1 vie + sante.vie.value--; + result.perteVie = true; + } + result.newValue = Math.max(0, result.newValue); + if (inc > 0) { // le max d'endurance s'applique seulement à la récupération + result.newValue = Math.min(result.newValue, this._computeEnduranceMax()) + } + const perte = compteur.value - result.newValue; + result.perte = perte; + if (perte > 1) { + // Peut-être sonné si 2 points d'endurance perdus d'un coup + mergeObject(result, await this.jetEndurance(result.newValue)); + } else if (inc > 0) { + await this.setSonne(false); + } + if (sante.fatigue && inc < 0) { // Each endurance lost -> fatigue lost + fatigue = perte; + } + } + compteur.value = result.newValue; + // If endurance lost, then the same amount of fatigue cannot be recovered + if (ReglesOptionnelles.isUsing("appliquer-fatigue") && sante.fatigue && fatigue > 0) { + sante.fatigue.value = Math.max(sante.fatigue.value + fatigue, this.getFatigueMin()); + } + await this.update({ "system.sante": sante }) + if (this.isDead()) { + await this.setEffect(STATUSES.StatusComma, true); + } + return result + } + + /* -------------------------------------------- */ + + /* -------------------------------------------- */ + async ajouterBlessure(encaissement, attacker = undefined) { + if (encaissement.gravite < 0) return; + if (encaissement.gravite > 0) { + while (this.countBlessures(it => it.system.gravite == encaissement.gravite) >= RdDItemBlessure.maxBlessures(encaissement.gravite) && encaissement.gravite <= 6) { + // Aggravation + encaissement.gravite += 2 + if (encaissement.gravite > 2) { + encaissement.vie += 2; + } + } + } + const endActuelle = this.getEnduranceActuelle(); + const blessure = await RdDItemBlessure.createBlessure(this, encaissement.gravite, encaissement.dmg.loc.label, attacker); + if (blessure.isCritique()) { + encaissement.endurance = endActuelle; + } + + if (blessure.isMort()) { + this.setEffect(STATUSES.StatusComma, true); + encaissement.mort = true; + ChatMessage.create({ + content: `charge + ${this.name} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !` + }); + } + return blessure; + } + + async supprimerBlessures(filterToDelete) { + const toDelete = this.filterItems(filterToDelete, TYPES.blessure) + .map(it => it.id); + await this.deleteEmbeddedDocuments('Item', toDelete); + } + + countBlessures(filter = it => !it.isContusion()) { + return this.filterItems(filter, 'blessure').length + } + + /* -------------------------------------------- */ + async jetVie() { + let roll = await RdDDice.roll("1d20"); + let msgText = "Jet de Vie : " + roll.total + " / " + this.system.sante.vie.value + "
"; + if (roll.total <= this.system.sante.vie.value) { + msgText += "Jet réussi, pas de perte de point de vie (prochain jet dans 1 round pour 1 critique, SC minutes pour une grave)"; + if (roll.total == 1) { + msgText += "La durée entre 2 jets de vie est multipliée par 20 (20 rounds pour une critique, SCx20 minutes pour une grave)"; + } + } else { + msgText += "Jet échoué, vous perdez 1 point de vie"; + await this.santeIncDec("vie", -1); + if (roll.total == 20) { + msgText += "Votre personnage est mort !!!!!"; + } + } + const message = { + content: msgText, + whisper: ChatMessage.getWhisperRecipients(this.name) + }; + ChatMessage.create(message); + } + + /* -------------------------------------------- */ + async jetEndurance(resteEndurance = undefined) { + const jetEndurance = (await RdDDice.roll("1d20")).total; + const sonne = jetEndurance == 20 || jetEndurance > (resteEndurance ?? this.system.sante.endurance.value) + if (sonne) { + await this.setSonne(); + } + return { jetEndurance, sonne } + } + + + async finDeRoundBlessures() { + const nbGraves = this.filterItems(it => it.isGrave(), 'blessure').length; + if (nbGraves > 0) { + // Gestion blessure graves : -1 pt endurance par blessure grave + await this.santeIncDec("endurance", -nbGraves); + } + } + + async setSonne(sonne = true) { + if (!game.combat && sonne) { + ui.notifications.info(`${this.name} est hors combat, il ne reste donc pas sonné`); + return; + } + await this.setEffect(STATUSES.StatusStunned, sonne); + } + + getSonne() { + return this.getEffect(STATUSES.StatusStunned); + } + + /* -------------------------------------------- */ + async computeEtatGeneral() { + this.system.compteurs.etat.value = this.malusVie() + this.malusFatigue() + this.malusEthylisme(); + } + + malusVie() { + return Math.min(this.system.sante.vie.value - this.system.sante.vie.max, 0); + } + + malusEthylisme() { return 0 } + malusFatigue() { return 0 } + + +} diff --git a/module/actor/base-actor-sheet.js b/module/actor/base-actor-sheet.js index 6411a415..ef137193 100644 --- a/module/actor/base-actor-sheet.js +++ b/module/actor/base-actor-sheet.js @@ -31,7 +31,7 @@ export class RdDBaseActorSheet extends ActorSheet { async getData() { Monnaie.validerMonnaies(this.actor.itemTypes['monnaie']); - this.actor.recompute(); + this.actor.computeEtatGeneral(); let formData = { title: this.title, id: this.actor.id, diff --git a/module/actor/base-actor.js b/module/actor/base-actor.js index f8521926..cc79030b 100644 --- a/module/actor/base-actor.js +++ b/module/actor/base-actor.js @@ -1,5 +1,6 @@ import { ChatUtility } from "../chat-utility.js"; import { SYSTEM_SOCKET_ID } from "../constants.js"; +import { Grammar } from "../grammar.js"; import { Monnaie } from "../item-monnaie.js"; import { Misc } from "../misc.js"; import { RdDAudio } from "../rdd-audio.js"; @@ -9,6 +10,33 @@ import { SystemCompendiums } from "../settings/system-compendiums.js"; import { APP_ASTROLOGIE_REFRESH } from "../sommeil/app-astrologie.js"; export class RdDBaseActor extends Actor { + /* -------------------------------------------- */ + static _findCaracByName(carac, name) { + name = Grammar.toLowerCaseNoAccent(name); + switch (name) { + case 'reve-actuel': case 'reve actuel': + return carac.reve; + case 'chance-actuelle': case 'chance actuelle': + return carac.chance; + } + + const caracList = Object.entries(carac); + let entry = Misc.findFirstLike(name, caracList, { mapper: it => it[0], description: 'caractéristique' }); + if (!entry || entry.length == 0) { + entry = Misc.findFirstLike(name, caracList, { mapper: it => it[1].label, description: 'caractéristique' }); + } + return entry && entry.length > 0 ? carac[entry[0]] : undefined; + } + + getCaracByName(name) { + switch (Grammar.toLowerCaseNoAccent(name)) { + case 'reve-actuel': case 'reve actuel': + return this.getCaracReveActuel(); + case 'chance-actuelle': case 'chance-actuelle': + return this.getCaracChanceActuelle(); + } + return RdDBaseActor._findCaracByName(this.system.carac, name); + } static getDefaultImg(itemType) { return game.system.rdd.actorClasses[itemType]?.defaultIcon ?? defaultItemImg[itemType]; @@ -124,11 +152,26 @@ export class RdDBaseActor extends Actor { } /* -------------------------------------------- */ - isCreatureEntite() { return this.type == 'creature' || this.type == 'entite'; } - isCreature() { return this.type == 'creature'; } - isEntite() { return this.type == 'entite'; } - isPersonnage() { return this.type == 'personnage'; } - isVehicule() { return this.type == 'vehicule'; } + prepareData() { + super.prepareData() + this.prepareActorData() + this.cleanupConteneurs() + this.computeEtatGeneral() + this.computeEncTotal() + } + + async prepareActorData() { } + async computeEtatGeneral() { } + /* -------------------------------------------- */ + findPlayer() { + return game.users.players.find(player => player.active && player.character?.id == this.id); + } + + isCreatureEntite() { return this.isCreature() || this.isEntite() } + isCreature() { return false } + isEntite(typeentite = []) { return false } + isVehicule() { return false } + isPersonnage() { return false } getItem(id, type = undefined) { const item = this.items.get(id); if (type == undefined || (item?.type == type)) { @@ -145,9 +188,7 @@ export class RdDBaseActor extends Actor { } getMonnaie(id) { return this.findItemLike(id, 'monnaie'); } - - recompute() { } - + getEncombrementMax() { return 0 } /* -------------------------------------------- */ async onPreUpdateItem(item, change, options, id) { } @@ -176,6 +217,16 @@ export class RdDBaseActor extends Actor { await this.createEmbeddedDocuments('Item', [object]) } + /* -------------------------------------------- */ + async cleanupConteneurs() { + let updates = this.itemTypes['conteneur'] + .filter(c => c.system.contenu.filter(id => this.getItem(id) == undefined).length > 0) + .map(c => { return { _id: c._id, 'system.contenu': c.system.contenu.filter(id => this.getItem(id) != undefined) } }); + if (updates.length > 0) { + await this.updateEmbeddedDocuments("Item", updates) + } + } + /* -------------------------------------------- */ getFortune() { return Monnaie.getFortune(this.itemTypes['monnaie']); @@ -388,14 +439,6 @@ export class RdDBaseActor extends Actor { } /* -------------------------------------------- */ - computeMalusSurEncombrement() { - return 0; - } - - getEncombrementMax() { - return 0; - } - async computeEncTotal() { if (!this.pack) { this.encTotal = this.items.map(it => it.getEncTotal()).reduce(Misc.sum(), 0); @@ -404,6 +447,10 @@ export class RdDBaseActor extends Actor { return 0; } + getEncTotal() { + return Math.floor(this.encTotal ?? 0); + } + async createItem(type, name = undefined) { if (!name) { name = 'Nouveau ' + Misc.typeName('Item', type); @@ -632,5 +679,12 @@ export class RdDBaseActor extends Actor { .then(html => ChatMessage.create(RdDUtility.chatDataSetup(html, modeOverride))); } + actionImpossible(action) { + ui.notifications.info(`${this.name} ne peut pas faire cette action: ${action}`) + } + async roll() { this.actionImpossible("jet de caractéristiques") } + async jetEthylisme() { this.actionImpossible("jet d'éthylisme") } + async rollAppelChance() { this.actionImpossible("appel à la chance") } + async jetDeMoral() { this.actionImpossible("jet de moral") } } \ No newline at end of file diff --git a/module/actor/commerce-sheet.js b/module/actor/commerce-sheet.js index a342f2bf..8ade8124 100644 --- a/module/actor/commerce-sheet.js +++ b/module/actor/commerce-sheet.js @@ -1,9 +1,7 @@ import { DialogItemAchat } from "../dialog-item-achat.js"; import { RdDItem } from "../item.js"; -import { RdDSheetUtility } from "../rdd-sheet-utility.js"; import { RdDUtility } from "../rdd-utility.js"; import { RdDBaseActorSheet } from "./base-actor-sheet.js"; -import { RdDCommerce } from "./commerce.js"; /** * Extend the basic ActorSheet with some very simple modifications diff --git a/module/actor/commerce.js b/module/actor/commerce.js index a03030fd..9f6cd289 100644 --- a/module/actor/commerce.js +++ b/module/actor/commerce.js @@ -7,18 +7,8 @@ export class RdDCommerce extends RdDBaseActor { return "systems/foundryvtt-reve-de-dragon/icons/services/commerce.webp"; } - prepareData() { - super.prepareData(); - } - prepareDerivedData() { - super.prepareDerivedData(); - } - canReceive(item) { - if (item.isInventaire('all')) { - return true; - } - return super.canReceive(item); + return item.isInventaire('all'); } getQuantiteDisponible(item) { diff --git a/module/actor-creature-sheet.js b/module/actor/creature-sheet.js similarity index 93% rename from module/actor-creature-sheet.js rename to module/actor/creature-sheet.js index 47f5d3b7..c9306196 100644 --- a/module/actor-creature-sheet.js +++ b/module/actor/creature-sheet.js @@ -1,10 +1,10 @@ -import { RdDActorSheet } from "./actor-sheet.js"; +import { RdDActorSheet } from "../actor-sheet.js"; /** * Extend the basic ActorSheet with some very simple modifications * @extends {ActorSheet} */ -export class RdDActorCreatureSheet extends RdDActorSheet { +export class RdDCreatureSheet extends RdDActorSheet { /** @override */ static get defaultOptions() { diff --git a/module/actor/creature.js b/module/actor/creature.js new file mode 100644 index 00000000..acf19110 --- /dev/null +++ b/module/actor/creature.js @@ -0,0 +1,65 @@ +import { ENTITE_INCARNE } from "../constants.js"; +import { STATUSES } from "../settings/status-effects.js"; +import { RdDBaseActorSang } from "./base-actor-sang.js"; + +export class RdDCreature extends RdDBaseActorSang { + + static get defaultIcon() { + return "systems/foundryvtt-reve-de-dragon/icons/creatures/bramart.svg"; + } + + isCreature() { return true } + + canReceive(item) { + return item.type == TYPES.competencecreature || item.isInventaire(); + } + + async remiseANeuf() { + await this.removeEffects(e => true); + await this.supprimerBlessures(it => true); + const updates = { + 'system.sante.endurance.value': this.system.sante.endurance.max, + 'system.sante.vie.value': this.system.sante.vie.max, + 'system.sante.fatigue.value': 0 + }; + await this.update(updates); + } + + async finDeRoundBlessures() { + const nbGraves = this.filterItems(it => it.isGrave(), 'blessure').length; + if (nbGraves > 0) { + // Gestion blessure graves : -1 pt endurance par blessure grave + await this.santeIncDec("endurance", -nbGraves); + } + } + + isEffectAllowed(statusId) { + return [STATUSES.StatusComma].includes(statusId); + } + + isEntiteAccordee(attacker) { + if (this.isEntite([ENTITE_INCARNE])) { + let resonnance = this.system.sante.resonnance + return (resonnance.actors.find(it => it == attacker.id)) + } + return true + } + + /* -------------------------------------------- */ + async setEntiteReveAccordee(attacker) { + if (this.isEntite([ENTITE_INCARNE])) { + let resonnance = duplicate(this.system.sante.resonnance); + if (resonnance.actors.find(it => it == attacker.id)) { + // déjà accordé + return; + } + resonnance.actors.push(attacker.id); + await this.update({ "system.sante.resonnance": resonnance }); + } + else { + super.setEntiteReveAccordee(attacker) + } + } + + +} diff --git a/module/actor-entite-sheet.js b/module/actor/entite-sheet.js similarity index 91% rename from module/actor-entite-sheet.js rename to module/actor/entite-sheet.js index 50f505bf..975a2d10 100644 --- a/module/actor-entite-sheet.js +++ b/module/actor/entite-sheet.js @@ -1,8 +1,8 @@ -import { RdDActorSheet } from "./actor-sheet.js"; -import { RdDSheetUtility } from "./rdd-sheet-utility.js"; -import { RdDUtility } from "./rdd-utility.js"; +import { RdDBaseActorReveSheet } from "./base-actor-reve-sheet.js"; +import { RdDSheetUtility } from "../rdd-sheet-utility.js"; +import { RdDUtility } from "../rdd-utility.js"; -export class RdDActorEntiteSheet extends RdDActorSheet { +export class RdDActorEntiteSheet extends RdDBaseActorReveSheet { /** @override */ static get defaultOptions() { diff --git a/module/actor/entite.js b/module/actor/entite.js new file mode 100644 index 00000000..7e971249 --- /dev/null +++ b/module/actor/entite.js @@ -0,0 +1,110 @@ +import { ENTITE_INCARNE, ENTITE_NONINCARNE } from "../constants.js"; +import { TYPES } from "../item.js"; +import { Misc } from "../misc.js"; +import { RdDEncaisser } from "../rdd-roll-encaisser.js"; +import { STATUSES } from "../settings/status-effects.js"; +import { RdDBaseActorReve } from "./base-actor-reve.js"; + +export class RdDEntite extends RdDBaseActorReve { + + static get defaultIcon() { + return "systems/foundryvtt-reve-de-dragon/icons/entites/darquoine.webp"; + } + + canReceive(item) { + return item.type == TYPES.competencecreature + } + + isEntite(typeentite = []) { + return (typeentite.length == 0 || typeentite.includes(this.system.definition.typeentite)); + } + isNonIncarnee() { return this.isEntite([ENTITE_NONINCARNE]) } + + getReveActuel() { + return Misc.toInt(this.system.carac.reve?.value) + } + + getForce() { return this.getReve() } + getAgilite() { return this.getReve() } + getChance() { return this.getReve() } + + getDraconicOuPossession() { + return this.itemTypes[TYPES.competencecreature] + .filter(it => it.system.categorie == 'possession') + .sort(Misc.descending(it => it.system.niveau)) + .find(it => true); + } + + async remiseANeuf() { + await this.removeEffects(e => true); + if (!this.isNonIncarnee()) { + await this.update({ + 'system.sante.endurance.value': this.system.sante.endurance.max + }); + } + } + + isDead() { + return this.isNonIncarnee() ? false : this.system.sante.endurance.value <= 0 + } + + async santeIncDec(name, inc, isCritique = false) { + if (name == 'endurance' && !this.isNonIncarnee()) { + const oldValue = this.system.sante.endurance.value; + const endurance = Math.max(0, + Math.min(oldValue + inc, + this.system.sante.endurance.max)); + await this.update({ "system.sante.endurance.value": endurance }) + await this.setEffect(STATUSES.StatusComma, endurance <= 0); + return { + perte: oldValue - endurance, + newValue: endurance + } + } + return {} + } + async encaisser() { + if (this.isNonIncarnee()) { + return + } + await RdDEncaisser.encaisser(this) + } + + isEffectAllowed(statusId) { + return [STATUSES.StatusComma].includes(statusId); + } + + async onAppliquerJetEncaissement(encaissement, attacker) { + const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance); + mergeObject(encaissement, { + resteEndurance: perteEndurance.newValue, + endurance: perteEndurance.perte + }); + } + + isEntiteAccordee(attacker) { + if (this.isEntite([ENTITE_INCARNE])) { + let resonnance = this.system.sante.resonnance + return (resonnance.actors.find(it => it == attacker.id)) + } + return true + } + + /* -------------------------------------------- */ + async setEntiteReveAccordee(attacker) { + if (this.isEntite([ENTITE_INCARNE])) { + let resonnance = duplicate(this.system.sante.resonnance); + if (resonnance.actors.find(it => it == attacker.id)) { + // déjà accordé + return; + } + resonnance.actors.push(attacker.id); + await this.update({ "system.sante.resonnance": resonnance }); + } + else { + super.setEntiteReveAccordee(attacker) + } + } + + +} diff --git a/module/actor-vehicule-sheet.js b/module/actor/vehicule-sheet.js similarity index 64% rename from module/actor-vehicule-sheet.js rename to module/actor/vehicule-sheet.js index 0124de0c..fcd4dcf6 100644 --- a/module/actor-vehicule-sheet.js +++ b/module/actor/vehicule-sheet.js @@ -1,8 +1,8 @@ -import { RdDUtility } from "./rdd-utility.js"; -import { RdDActorSheet } from "./actor-sheet.js"; +import { RdDUtility } from "../rdd-utility.js"; +import { RdDBaseActorSheet } from "./base-actor-sheet.js"; /* -------------------------------------------- */ -export class RdDActorVehiculeSheet extends RdDActorSheet { +export class RdDActorVehiculeSheet extends RdDBaseActorSheet { /** @override */ static get defaultOptions() { @@ -18,6 +18,22 @@ export class RdDActorVehiculeSheet extends RdDActorSheet { }); } + /* -------------------------------------------- */ + async getData() { + let formData = await super.getData(); + mergeObject(formData, + { + editable: this.isEditable, + cssClass: this.isEditable ? "editable" : "locked", + effects: this.actor.effects.map(e => foundry.utils.deepClone(e)), + limited: this.actor.limited, + owner: this.actor.isOwner, + }); + + this.timerRecherche = undefined; + return formData; + } + activateListeners(html) { super.activateListeners(html); if (!this.options.editable) return; diff --git a/module/actor/vehicule.js b/module/actor/vehicule.js new file mode 100644 index 00000000..4cd8edde --- /dev/null +++ b/module/actor/vehicule.js @@ -0,0 +1,28 @@ +import { RdDBaseActor } from "./base-actor.js"; + +export class RdDVehicule extends RdDBaseActor { + + static get defaultIcon() { + return "systems/foundryvtt-reve-de-dragon/icons/vehicules/charette.webp"; + } + isVehicule() { return true } + + canReceive(item) { + return item.isInventaire(); + } + + getEncombrementMax() { + return this.system.capacite_encombrement; + } + + async vehicleIncDec(name, inc) { + if (!['resistance', 'structure'].includes(name)) { + return + } + const newValue = this.system.etat[name].value + inc; + if (0 <= newValue && newValue <= this.system.etat[name].max) { + await this.update({ [`system.etat.${name}.value`]: newValue }) + } + } + +} diff --git a/module/dialog-create-signedraconique.js b/module/dialog-create-signedraconique.js index 0620864d..3d38d29b 100644 --- a/module/dialog-create-signedraconique.js +++ b/module/dialog-create-signedraconique.js @@ -14,7 +14,7 @@ export class DialogCreateSigneDraconique extends Dialog { .map(actor => ({ id: actor.id, name: actor.name, - selected: true + selected: false })) }; diff --git a/module/dialog-validation-encaissement.js b/module/dialog-validation-encaissement.js index a8808466..d4c41f61 100644 --- a/module/dialog-validation-encaissement.js +++ b/module/dialog-validation-encaissement.js @@ -7,20 +7,19 @@ import { RdDUtility } from "./rdd-utility.js"; */ export class DialogValidationEncaissement extends Dialog { - static async validerEncaissement(actor, rollData, armure, show, attackerId, onEncaisser) { + static async validerEncaissement(actor, rollData, armure, onEncaisser) { let encaissement = await RdDUtility.jetEncaissement(rollData, armure, { showDice: HIDE_DICE }); const html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-validation-encaissement.html', { actor: actor, rollData: rollData, - encaissement: encaissement, - show: show + encaissement: encaissement }); - const dialog = new DialogValidationEncaissement(html, actor, rollData, armure, encaissement, show, attackerId, onEncaisser); + const dialog = new DialogValidationEncaissement(html, actor, rollData, armure, encaissement, onEncaisser); dialog.render(true); } /* -------------------------------------------- */ - constructor(html, actor, rollData, armure, encaissement, show, attackerId, onEncaisser) { + constructor(html, actor, rollData, armure, encaissement, onEncaisser) { // Common conf let buttons = { "valider": { label: "Valider", callback: html => this.onValider() }, @@ -47,8 +46,6 @@ export class DialogValidationEncaissement extends Dialog { this.rollData = rollData; this.armure = armure; this.encaissement = encaissement; - this.show = show; - this.attackerId = attackerId; this.onEncaisser = onEncaisser; this.forceDiceResult = {total: encaissement.roll.result }; } @@ -67,6 +64,6 @@ export class DialogValidationEncaissement extends Dialog { async onValider() { this.encaissement = await RdDUtility.jetEncaissement(this.rollData, this.armure, { showDice: SHOW_DICE, forceDiceResult: this.forceDiceResult}); - this.onEncaisser(this.encaissement, this.show, this.attackerId) + this.onEncaisser(this.encaissement) } } diff --git a/module/item-arme.js b/module/item-arme.js index bbc87102..ae7b688e 100644 --- a/module/item-arme.js +++ b/module/item-arme.js @@ -20,7 +20,7 @@ const nomCategorieParade = { export class RdDItemArme extends Item { static isArme(item) { - return RdDItemCompetenceCreature.getCategorieAttaque(item) || item.type == TYPES.arme; + return item.type == TYPES.arme || RdDItemCompetenceCreature.getCategorieAttaque(item); } /* -------------------------------------------- */ diff --git a/module/item-competencecreature.js b/module/item-competencecreature.js index 85f3f291..c5dae365 100644 --- a/module/item-competencecreature.js +++ b/module/item-competencecreature.js @@ -55,6 +55,20 @@ export class RdDItemCompetenceCreature extends Item { } /* -------------------------------------------- */ + static isCompetenceAttaque(item) { + if (item.type == TYPES.competencecreature) { + switch (item.system.categorie) { + case "melee": + case "tir": + case "lancer": + case "naturelle": + case "possession": + return true + } + } + return undefined + } + static getCategorieAttaque(item) { if (item.type == TYPES.competencecreature) { switch (item.system.categorie) { @@ -63,6 +77,7 @@ export class RdDItemCompetenceCreature extends Item { case "lancer": case "naturelle": case "possession": + case "parade": return item.system.categorie } } diff --git a/module/misc.js b/module/misc.js index f055f4a0..e7ce38fe 100644 --- a/module/misc.js +++ b/module/misc.js @@ -63,8 +63,8 @@ export class Misc { static keepDecimals(num, decimals) { if (decimals <= 0 || decimals > 6) return num; - const decimal = Math.pow(10, parseInt(decimals)); - return Math.round(num * decimal) / decimal; + const power10n = Math.pow(10, parseInt(decimals)); + return Math.round(num * power10n) / power10n; } static getFractionHtml(diviseur) { diff --git a/module/rdd-carac.js b/module/rdd-carac.js index 10a23e5b..1defe187 100644 --- a/module/rdd-carac.js +++ b/module/rdd-carac.js @@ -1,7 +1,7 @@ import { Grammar } from "./grammar.js"; import { Misc } from "./misc.js"; -const tableCaracDerivee = { +const TABLE_CARACTERISTIQUES_DERIVEES = { // xp: coût pour passer du niveau inférieur à ce niveau 1: { xp: 3, poids: "moins de 1kg", plusdom: -5, sconst: 0.5, sust: 0.1 }, 2: { xp: 3, poids: "1-5", plusdom: -4, sconst: 0.5, sust: 0.3 }, @@ -58,6 +58,10 @@ export class RdDCarac { selectedCarac?.label.match(/(Apparence|Force|Agilité|Dextérité|Vue|Ouïe|Odorat-Goût|Empathie|Dérobée|Mêlée|Tir|Lancer)/); } + static getCaracDerivee(value) { + return TABLE_CARACTERISTIQUES_DERIVEES[Math.min(Math.max(Number(value), 1), 32)]; + } + static computeTotal(carac, beaute = undefined) { const total = Object.values(carac ?? {}).filter(c => !c.derivee) .map(it => parseInt(it.value)) @@ -73,7 +77,7 @@ export class RdDCarac { /* -------------------------------------------- */ static calculSConst(constitution) { - return Number(tableCaracDerivee[Number(constitution)].sconst); + return RdDCarac.getCaracDerivee(constitution).sconst; } /* -------------------------------------------- */ @@ -84,7 +88,7 @@ export class RdDCarac { } static getCaracXp(targetValue) { - return tableCaracDerivee[targetValue]?.xp ?? 200; + return RdDCarac.getCaracDerivee(targetValue)?.xp ?? 200; } @@ -97,37 +101,4 @@ export class RdDCarac { return Grammar.toLowerCaseNoAccent(selectedCarac?.label)?.match(/(apparence|force|agilite|dexterite|vue|ouie|odorat|empathie|melee|tir|lancer|derobee)/); } - /* -------------------------------------------- */ - static computeCarac(system) { - system.carac.force.value = Math.min(system.carac.force.value, parseInt(system.carac.taille.value) + 4); - - system.carac.derobee.value = Math.floor(parseInt(((21 - system.carac.taille.value)) + parseInt(system.carac.agilite.value)) / 2); - let bonusDomKey = Math.floor((parseInt(system.carac.force.value) + parseInt(system.carac.taille.value)) / 2); - bonusDomKey = Math.min(Math.max(bonusDomKey, 0), 32); // Clamp de securite - - let tailleData = tableCaracDerivee[bonusDomKey]; - system.attributs.plusdom.value = tailleData.plusdom; - - system.attributs.sconst.value = RdDCarac.calculSConst(system.carac.constitution.value); - system.attributs.sust.value = tableCaracDerivee[Number(system.carac.taille.value)].sust; - - system.attributs.encombrement.value = (parseInt(system.carac.force.value) + parseInt(system.carac.taille.value)) / 2; - system.carac.melee.value = Math.floor((parseInt(system.carac.force.value) + parseInt(system.carac.agilite.value)) / 2); - system.carac.tir.value = Math.floor((parseInt(system.carac.vue.value) + parseInt(system.carac.dexterite.value)) / 2); - system.carac.lancer.value = Math.floor((parseInt(system.carac.tir.value) + parseInt(system.carac.force.value)) / 2); - - system.sante.vie.max = Math.ceil((parseInt(system.carac.taille.value) + parseInt(system.carac.constitution.value)) / 2); - - system.sante.vie.value = Math.min(system.sante.vie.value, system.sante.vie.max) - system.sante.endurance.max = Math.max(parseInt(system.carac.taille.value) + parseInt(system.carac.constitution.value), parseInt(system.sante.vie.max) + parseInt(system.carac.volonte.value)); - system.sante.endurance.value = Math.min(system.sante.endurance.value, system.sante.endurance.max); - system.sante.fatigue.max = system.sante.endurance.max * 2; - system.sante.fatigue.value = Math.min(system.sante.fatigue.value, system.sante.fatigue.max); - - //Compteurs - system.reve.reve.max = system.carac.reve.value; - system.compteurs.chance.max = system.carac.chance.value; - } - - } diff --git a/module/rdd-combat.js b/module/rdd-combat.js index c0eea94c..01924c4b 100644 --- a/module/rdd-combat.js +++ b/module/rdd-combat.js @@ -87,7 +87,7 @@ export class RdDCombatManager extends Combat { let rollFormula = formula ?? RdDCombatManager.formuleInitiative(2, 10, 0, 0); if (!formula) { if (combatant.actor.type == 'creature' || combatant.actor.type == 'entite') { - const competence = combatant.actor.items.find(it => RdDItemCompetenceCreature.getCategorieAttaque(it)) + const competence = combatant.actor.items.find(it => RdDItemCompetenceCreature.isCompetenceAttaque(it)) if (competence) { rollFormula = RdDCombatManager.formuleInitiative(2, competence.system.carac_value, competence.system.niveau, 0); } @@ -229,7 +229,9 @@ export class RdDCombatManager extends Combat { } static listActionsCreature(competences) { - return competences.map(it => RdDItemCompetenceCreature.armeCreature(it)) + return competences + .filter(it => RdDItemCompetenceCreature.isCompetenceAttaque(it)) + .map(it => RdDItemCompetenceCreature.armeCreature(it)) .filter(it => it != undefined); } @@ -822,8 +824,8 @@ export class RdDCombat { // finesse seulement en mélée, pour l'empoignade, ou si la difficulté libre est de -1 minimum // rapidité seulement en mêlée, si l'arme le permet, et si la difficulté libre est de -1 minimum const isForce = !rollData.arme.system.empoignade; - const isFinesse = rollData.arme.system.empoignade || isMeleeDiffNegative; - const isRapide = !rollData.arme.system.empoignade && isMeleeDiffNegative && rollData.arme.system.rapide; + const isFinesse = rollData.tactique != 'charge' && (rollData.arme.system.empoignade || isMeleeDiffNegative); + const isRapide = rollData.tactique != 'charge' && !rollData.arme.system.empoignade && isMeleeDiffNegative && rollData.arme.system.rapide; // si un seul choix possible, le prendre if (isForce && !isFinesse && !isRapide) { return await this.choixParticuliere(rollData, "force"); diff --git a/module/rdd-main.js b/module/rdd-main.js index b70dea0c..0d4d0175 100644 --- a/module/rdd-main.js +++ b/module/rdd-main.js @@ -29,11 +29,13 @@ import { Environnement } from "./environnement.js"; import { RdDActor } from "./actor.js"; import { RdDBaseActor } from "./actor/base-actor.js"; import { RdDCommerce } from "./actor/commerce.js"; +import { RdDEntite } from "./actor/entite.js"; +import { RdDVehicule } from "./actor/vehicule.js"; import { RdDActorSheet } from "./actor-sheet.js"; import { RdDCommerceSheet } from "./actor/commerce-sheet.js"; -import { RdDActorCreatureSheet } from "./actor-creature-sheet.js"; -import { RdDActorVehiculeSheet } from "./actor-vehicule-sheet.js"; -import { RdDActorEntiteSheet } from "./actor-entite-sheet.js"; +import { RdDCreatureSheet } from "./actor/creature-sheet.js"; +import { RdDActorEntiteSheet } from "./actor/entite-sheet.js"; +import { RdDActorVehiculeSheet } from "./actor/vehicule-sheet.js"; import { RdDItem } from "./item.js"; import { RdDItemBlessure } from "./item/blessure.js"; @@ -60,6 +62,7 @@ import { RdDItemInventaireSheet } from "./item/sheet-base-inventaire.js"; import { AppAstrologie } from "./sommeil/app-astrologie.js"; import { RdDItemArmure } from "./item/armure.js"; import { AutoAdjustDarkness as AutoAdjustDarkness } from "./time/auto-adjust-darkness.js"; +import { RdDCreature } from "./actor/creature.js"; /** * RdD system @@ -91,10 +94,10 @@ export class SystemReveDeDragon { } this.actorClasses = { commerce: RdDCommerce, - creature: RdDActor, - entite: RdDActor, + creature: RdDCreature, + entite: RdDEntite, personnage: RdDActor, - vehicule: RdDActor, + vehicule: RdDVehicule, } } @@ -150,7 +153,7 @@ export class SystemReveDeDragon { Actors.unregisterSheet("core", ActorSheet); Actors.registerSheet(SYSTEM_RDD, RdDCommerceSheet, { types: ["commerce"], makeDefault: true }); Actors.registerSheet(SYSTEM_RDD, RdDActorSheet, { types: ["personnage"], makeDefault: true }); - Actors.registerSheet(SYSTEM_RDD, RdDActorCreatureSheet, { types: ["creature"], makeDefault: true }); + Actors.registerSheet(SYSTEM_RDD, RdDCreatureSheet, { types: ["creature"], makeDefault: true }); Actors.registerSheet(SYSTEM_RDD, RdDActorVehiculeSheet, { types: ["vehicule"], makeDefault: true }); Actors.registerSheet(SYSTEM_RDD, RdDActorEntiteSheet, { types: ["entite"], makeDefault: true }); Items.unregisterSheet("core", ItemSheet); diff --git a/module/rdd-resolution-table.js b/module/rdd-resolution-table.js index 60ba2114..3bc94254 100644 --- a/module/rdd-resolution-table.js +++ b/module/rdd-resolution-table.js @@ -28,7 +28,7 @@ const reussites = [ const reussiteInsuffisante = { code: "notSign", isPart: false, isSign: false, isSuccess: false, isEchec: true, isEPart: false, isETotal: false, ptTache: 0, ptQualite: -2, quality: "Réussite insuffisante", condition: (target, roll) => false } /* -------------------------------------------- */ -const caracMaximumResolution = 60; +const CARAC_MAXIMUM_RESOLUTION = 40; /* -------------------------------------------- */ export class RdDResolutionTable { static resolutionTable = this.build() @@ -36,7 +36,7 @@ export class RdDResolutionTable { /* -------------------------------------------- */ static build() { let table = [] - for (var caracValue = 0; caracValue <= caracMaximumResolution; caracValue++) { + for (var caracValue = 0; caracValue <= CARAC_MAXIMUM_RESOLUTION; caracValue++) { table[caracValue] = this._computeRow(caracValue); } return table; diff --git a/module/rdd-tmr-dialog.js b/module/rdd-tmr-dialog.js index 37561dbb..78280820 100644 --- a/module/rdd-tmr-dialog.js +++ b/module/rdd-tmr-dialog.js @@ -56,7 +56,7 @@ export class RdDTMRDialog extends Dialog { this.actor = actor; this.actor.tmrApp = this; // reference this app in the actor structure this.viewOnly = tmrData.mode == "visu" - this.fatigueParCase = this.viewOnly || !ReglesOptionnelles.isUsing("appliquer-fatigue") ? 0 : this.actor.getTMRFatigue(); + this.fatigueParCase = this.viewOnly ? 0 : this.actor.getCoutFatigueTMR(); this.cumulFatigue = 0; this.loadRencontres(); this.loadCasesSpeciales(); @@ -262,10 +262,8 @@ export class RdDTMRDialog extends Dialog { // Gestion du cout de montée en points de rêve let reveCout = ((this.tmrdata.isRapide && !EffetsDraconiques.isDeplacementAccelere(this.actor)) ? -2 : -1) - this.actor.countMonteeLaborieuse(); - if (ReglesOptionnelles.isUsing("appliquer-fatigue")) { - this.cumulFatigue += this.fatigueParCase; - } await this.actor.reveActuelIncDec(reveCout); + this.cumulFatigue += this.fatigueParCase; // Le reste... this.updateValuesDisplay(); let tmr = TMRUtility.getTMR(this._getActorCoord()); @@ -316,7 +314,8 @@ export class RdDTMRDialog extends Dialog { await this.actor.setEffect(STATUSES.StatusDemiReve, false) this._tellToGM(this.actor.name + " a quitté les terres médianes"); } - await this.actor.santeIncDec("fatigue", this.cumulFatigue) + await this.actor.santeIncDec((ReglesOptionnelles.isUsing("appliquer-fatigue") ? "fatigue" : "endurance"), + this.cumulFatigue) } await super.close(); } @@ -405,12 +404,16 @@ export class RdDTMRDialog extends Dialog { this.close(); return true; } - const resteAvantInconscience = this.actor.getFatigueMax() - this.actor.getFatigueActuelle() - this.cumulFatigue; - if (ReglesOptionnelles.isUsing("appliquer-fatigue") && resteAvantInconscience <= 0) { + + if (ReglesOptionnelles.isUsing("appliquer-fatigue") + ? (this.actor.getFatigueRestante() <= this.cumulFatigue) + : (this.actor.getEnduranceActuelle() <= this.cumulFatigue) + ) { this._tellToGM("Vous vous écroulez de fatigue : vous quittez les Terres médianes !"); this.quitterLesTMRInconscient(); return true; } + if (this.actor.getReveActuel() == 0) { this._tellToGM("Vos Points de Rêve sont à 0 : vous quittez les Terres médianes !"); this.quitterLesTMRInconscient(); diff --git a/module/rdd-utility.js b/module/rdd-utility.js index b3b188a9..0f8880e3 100644 --- a/module/rdd-utility.js +++ b/module/rdd-utility.js @@ -28,7 +28,7 @@ const ajustementsEncaissement = Misc.intArray(-10, 26); /* -------------------------------------------- */ function _buildAllSegmentsFatigue(max) { const cycle = [5, 2, 4, 1, 3, 0]; - let fatigue = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]; + const fatigue = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]; for (let i = 0; i <= max; i++) { const ligneFatigue = duplicate(fatigue[i]); const caseIncrementee = cycle[i % 6]; @@ -55,7 +55,8 @@ function _cumulSegmentsFatigue(matrix) { } /* -------------------------------------------- */ -const fatigueMatrix = _buildAllSegmentsFatigue(60); +export const MAX_ENDURANCE_FATIGUE = 60; +const fatigueMatrix = _buildAllSegmentsFatigue(MAX_ENDURANCE_FATIGUE); const cumulFatigueMatrix = _cumulSegmentsFatigue(fatigueMatrix); const fatigueMalus = [0, 0, 0, -1, -1, -1, -2, -3, -4, -5, -6, -7]; // Provides the malus for each segment of fatigue @@ -460,18 +461,15 @@ export class RdDUtility { } /* -------------------------------------------- */ - static getSegmentsFatigue(maxEnd) { - maxEnd = Math.max(maxEnd, 1); - maxEnd = Math.min(maxEnd, fatigueMatrix.length); - return fatigueMatrix[maxEnd]; + static getSegmentsFatigue(maxEndurance) { + return fatigueMatrix[Math.min(Math.max(maxEndurance, 1), fatigueMatrix.length)]; } /* -------------------------------------------- */ - static calculMalusFatigue(fatigue, maxEnd) { - maxEnd = Math.max(maxEnd, 1); - maxEnd = Math.min(maxEnd, cumulFatigueMatrix.length); - let segments = cumulFatigueMatrix[maxEnd]; - for (let i = 0; i < 12; i++) { + static calculMalusFatigue(fatigue, endurance) { + endurance = Math.min(Math.max(endurance, 1), cumulFatigueMatrix.length); + let segments = cumulFatigueMatrix[endurance]; + for (let i = 0; i < segments.length; i++) { if (fatigue <= segments[i]) { return fatigueMalus[i] } @@ -491,7 +489,7 @@ export class RdDUtility { // Build the nice (?) html table used to manage fatigue. // max should be the endurance max value static makeHTMLfatigueMatrix(fatigue, maxEndurance) { - let segments = this.getSegmentsFatigue(maxEndurance); + const segments = this.getSegmentsFatigue(maxEndurance); return this.makeHTMLfatigueMatrixForSegment(fatigue, segments); } @@ -625,25 +623,6 @@ export class RdDUtility { return perte.total; } - /* -------------------------------------------- */ - static currentFatigueMalus(value, max) { - if (ReglesOptionnelles.isUsing("appliquer-fatigue")) { - max = Math.max(1, Math.min(max, 60)); - value = Math.min(max * 2, Math.max(0, value)); - - let fatigueTab = fatigueMatrix[max]; - let fatigueRem = value; - for (let idx = 0; idx < fatigueTab.length; idx++) { - fatigueRem -= fatigueTab[idx]; - if (fatigueRem <= 0) { - return fatigueMalus[idx]; - } - } - return -7; // This is the max ! - } - return 0; - } - /* -------------------------------------------- */ static async responseNombreAstral(callData) { let actor = game.actors.get(callData.id); diff --git a/module/settings/regles-optionnelles.js b/module/settings/regles-optionnelles.js index 8f3f3bf1..fa77bcd9 100644 --- a/module/settings/regles-optionnelles.js +++ b/module/settings/regles-optionnelles.js @@ -4,6 +4,12 @@ import { Misc } from "../misc.js"; const listeReglesOptionnelles = [ { group: 'Règles générales', name: 'appliquer-fatigue', descr: "Appliquer les règles de fatigue"}, { group: 'Règles générales', name: 'astrologie', descr: "Appliquer les ajustements astrologiques aux jets de chance et aux rituels"}, + { group: 'Récupération', name: 'transformation-stress', descr: "Transformer le stress durant Château Dormant"}, + { group: 'Récupération', name: 'recuperation-chance', descr: "Récupérer la chance durant Château Dormant"}, + { group: 'Récupération', name: 'recuperation-ethylisme', descr: "Récupérer l'éthylisme"}, + { group: 'Récupération', name: 'recuperation-reve', descr: "Récupérer le rêve pendant la nuit (les jets sont toujours faits pour les Rêves de Dragons)"}, + { group: 'Récupération', name: 'recuperation-moral', descr: "Le moral revient vers 0 durant Château Dormant"}, + { group: 'Règles de combat', name: 'recul', descr: "Appliquer le recul en cas de particulière en force ou de charge" }, { group: 'Règles de combat', name: 'resistanceArmeParade', descr: "Faire le jet de résistance des armes lors de parades pouvant les endommager" }, diff --git a/module/tmr/effets-rencontres.js b/module/tmr/effets-rencontres.js index ec507768..72db974e 100644 --- a/module/tmr/effets-rencontres.js +++ b/module/tmr/effets-rencontres.js @@ -2,6 +2,7 @@ import { ExperienceLog, XP_TOPIC } from "../actor/experience-log.js"; import { ChatUtility } from "../chat-utility.js"; import { Poetique } from "../poetique.js"; import { RdDDice } from "../rdd-dice.js"; +import { ReglesOptionnelles } from "../settings/regles-optionnelles.js"; import { TMRUtility } from "../tmr-utility.js"; export class EffetsRencontre { @@ -9,7 +10,7 @@ export class EffetsRencontre { static messager = async (dialog, context) => { dialog.setRencontreState('messager', TMRUtility.getTMRPortee(context.tmr.coord, context.rencontre.system.force)); } - + static passeur = async (dialog, context) => { dialog.setRencontreState('passeur', TMRUtility.getTMRPortee(context.tmr.coord, context.rencontre.system.force)); } @@ -26,16 +27,25 @@ export class EffetsRencontre { static reve_plus_1 = async (dialog, context) => { await EffetsRencontre.$reve_plus(context.actor, 1) } static reve_moins_force = async (dialog, context) => { await EffetsRencontre.$reve_plus(context.actor, -context.rencontre.system.force) } static reve_moins_1 = async (dialog, context) => { await EffetsRencontre.$reve_plus(context.actor, -1) } - static $reve_plus = async (actor, valeur) => { await actor.reveActuelIncDec(valeur) } + static $reve_plus = async (actor, reve) => { + if (!ReglesOptionnelles.isUsing("recuperation-reve") && reve < 0) { + ChatMessage.create({ + whisper: ChatUtility.getWhisperRecipientsAndGMs(actor.name), + content: `Pas de récupération de rêve (${reve} points ignorés)` + }); + return + } + await actor.reveActuelIncDec(reve) + } static vie_moins_1 = async (dialog, context) => { await EffetsRencontre.$vie_plus(context.actor, -1) } static vie_moins_force = async (dialog, context) => { await EffetsRencontre.$vie_plus(context.actor, -context.rencontre.system.force) } static $vie_plus = async (actor, valeur) => { await actor.santeIncDec("vie", valeur) } - + static moral_plus_1 = async (dialog, context) => { await EffetsRencontre.$vie_plus(context.actor, 1) } static moral_moins_1 = async (dialog, context) => { await EffetsRencontre.$vie_plus(context.actor, -1) } static $moral_plus = async (actor, valeur) => { await actor.moralIncDec(valeur) } - + static end_moins_1 = async (dialog, context) => { await EffetsRencontre.$vie_plus(context.actor, -1) } static end_moins_force = async (dialog, context) => { await EffetsRencontre.$vie_plus(context.actor, -context.rencontre.system.force) } static $end_plus = async (actor, valeur) => { await actor.santeIncDec("endurance", valeur) } @@ -50,8 +60,8 @@ export class EffetsRencontre { const perte = context.rolled.isETotal ? context.rencontre.system.force : 1; await context.actor.chanceActuelleIncDec("fatigue", -perte); } - - static xp_sort_force = async (dialog, context) => { + + static xp_sort_force = async (dialog, context) => { let competence = context.competence; if (competence) { const fromXpSort = Number(competence.system.xp_sort); @@ -60,7 +70,7 @@ export class EffetsRencontre { await ExperienceLog.add(this, XP_TOPIC.XPSORT, fromXpSort, toXpSort, `${competence.name} - ${context.rencontre.name} en TMR`); } } - + static stress_plus_1 = async (dialog, context) => { await context.actor.addCompteurValue('stress', 1, `Rencontre d'un ${context.rencontre.name} en TMR`); } @@ -75,7 +85,7 @@ export class EffetsRencontre { static demireve_rompu = async (dialog, context) => { dialog.close() - } + } static sort_aleatoire = async (dialog, context) => { context.sortReserve = await RdDDice.rollOneOf(context.actor.itemTypes['sortreserve']); @@ -128,7 +138,7 @@ export class EffetsRencontre { static regain_seuil = async (dialog, context) => { await context.actor.regainPointDeSeuil() - } + } static async $reinsertion(dialog, actor, filter) { const newTMR = await TMRUtility.getTMRAleatoire(filter); diff --git a/system.json b/system.json index b0ff954c..f30e1b27 100644 --- a/system.json +++ b/system.json @@ -1,8 +1,8 @@ { "id": "foundryvtt-reve-de-dragon", "title": "Rêve de Dragon", - "version": "11.0.28", - "download": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/archive/foundryvtt-reve-de-dragon-11.0.28.zip", + "version": "11.1.0", + "download": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/archive/foundryvtt-reve-de-dragon-11.1.0.zip", "manifest": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/raw/v11/system.json", "changelog": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/raw/branch/v11/changelog.md", "compatibility": { diff --git a/templates/chat-resultat-encaissement.html b/templates/chat-resultat-encaissement.html index 275ca74b..2e35925a 100644 --- a/templates/chat-resultat-encaissement.html +++ b/templates/chat-resultat-encaissement.html @@ -44,7 +44,7 @@ {{#if (ne dmg.mortalite 'entiteincarnee')}} {{#if (gt endurance 1)}}et {{#if sonne}}est sonnécharge jusqu'à la fin du prochain round{{else}}n'est pas sonné{{/if}}! - {{#if hasPlayerOwner}}Jet d'endurance : Jet d'endurance : {{jetEndurance}} / {{resteEndurance}}{{/if}} + {{#if hasPlayerOwner}}Jet d'endurance : {{jetEndurance}} / {{resteEndurance}}{{/if}} {{/if}} {{/if}} {{/if}}