diff --git a/changelog.md b/changelog.md index 0b1aead6..d448882a 100644 --- a/changelog.md +++ b/changelog.md @@ -7,8 +7,10 @@ - La récupération de rêve (y compris fleurs de rêve et Rêves de Dragon: la rencontre a lieu, mais ne donne pas de rêve) - Séparation des véhicules dans leur propre acteur - Séparation des entités dans leur propre acteur +- Séparation des créatures dans leur propre acteur - 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 ## v11.0.28 - les fractures de Khrachtchoum - La gravité de la blessure est affichée dans le résumé de l'encaissement diff --git a/module/actor-sheet.js b/module/actor-sheet.js index 57ee0e72..750c6896 100644 --- a/module/actor-sheet.js +++ b/module/actor-sheet.js @@ -363,7 +363,7 @@ export class RdDActorSheet extends RdDBaseActorReveSheet { 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 => { @@ -386,6 +386,16 @@ export class RdDActorSheet extends RdDBaseActorReveSheet { }); } + 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 547e55d5..8359f245 100644 --- a/module/actor.js +++ b/module/actor.js @@ -1,4 +1,4 @@ -import { MAX_ENDURANCE_FATIGUE, RdDUtility } from "./rdd-utility.js"; +import { RdDUtility } from "./rdd-utility.js"; import { TMRUtility } from "./tmr-utility.js"; import { RdDRollDialogEthylisme } from "./rdd-roll-ethylisme.js"; import { RdDRoll } from "./rdd-roll.js"; @@ -33,7 +33,7 @@ import { AppAstrologie } from "./sommeil/app-astrologie.js"; import { RdDEmpoignade } from "./rdd-empoignade.js"; import { ExperienceLog, XP_TOPIC } from "./actor/experience-log.js"; import { TYPES } from "./item.js"; -import { RdDBaseActorVivant } from "./actor/base-actor-vivant.js"; +import { RdDBaseActorSang } from "./actor/base-actor-sang.js"; const POSSESSION_SANS_DRACONIC = { img: 'systems/foundryvtt-reve-de-dragon/icons/entites/possession.webp', @@ -51,23 +51,15 @@ 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 RdDBaseActorVivant { +export class RdDActor extends RdDBaseActorSang { /* -------------------------------------------- */ /** * Prepare Character type specific data */ prepareActorData() { - // TODO: separate derived/base data preparation - // TODO: split by actor class - if (this.isPersonnage()) this.$prepareCharacterData() - } - - $prepareCharacterData() { - // Initialize empty items this.$computeCaracDerivee() - this.computeIsHautRevant(); - this.cleanupConteneurs(); + this.$computeIsHautRevant() } /* -------------------------------------------- */ @@ -92,7 +84,7 @@ export class RdDActor extends RdDBaseActorVivant { 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.max = this.getFatigueMax(); this.system.sante.fatigue.value = Math.min(this.system.sante.fatigue.value, this.system.sante.fatigue.max); //Compteurs @@ -101,72 +93,28 @@ export class RdDActor extends RdDBaseActorVivant { } canReceive(item) { - if (this.isCreature()) { - return item.type == TYPES.competencecreature || 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 != "" - } + 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': - return Misc.toInt(this.system.carac.reve?.value) - 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() { - 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); - } - 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 }) { @@ -185,12 +133,9 @@ export class RdDActor extends RdDBaseActorVivant { /* -------------------------------------------- */ 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); } /* -------------------------------------------- */ @@ -230,16 +175,10 @@ export class RdDActor extends RdDBaseActorVivant { } 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) } getDemiReve() { @@ -275,30 +214,6 @@ export class RdDActor extends RdDBaseActorVivant { 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 prepareChateauDormant(consigne) { if (consigne.ignorer) { return; @@ -313,10 +228,6 @@ export class RdDActor extends RdDBaseActorVivant { } } - 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(); @@ -476,12 +387,6 @@ export class RdDActor extends RdDBaseActorVivant { 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 @@ -527,19 +432,15 @@ export class RdDActor extends RdDBaseActorVivant { 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); } /* -------------------------------------------- */ @@ -665,7 +566,7 @@ export class RdDActor extends RdDBaseActorVivant { async recupererFatigue(message) { if (ReglesOptionnelles.isUsing("appliquer-fatigue")) { let fatigue = this.system.sante.fatigue.value; - const fatigueMin = this.$getFatigueMin(); + const fatigueMin = this.getFatigueMin(); if (fatigue <= fatigueMin) { return; } @@ -792,7 +693,7 @@ export class RdDActor extends RdDBaseActorVivant { this.setPointsDeChance(to); } } - let selectedCarac = RdDBaseActorVivant._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); @@ -803,7 +704,7 @@ export class RdDActor extends RdDBaseActorVivant { if (caracName == 'Taille') { return; } - let selectedCarac = RdDBaseActorVivant._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 }); @@ -817,7 +718,7 @@ export class RdDActor extends RdDBaseActorVivant { if (caracName == 'Taille') { return; } - let carac = RdDBaseActorVivant._findCaracByName(this.system.carac, caracName); + let carac = RdDBaseActor._findCaracByName(this.system.carac, caracName); if (carac) { carac = duplicate(carac); const fromXp = Number(carac.xp); @@ -891,25 +792,6 @@ export class RdDActor extends RdDBaseActorVivant { 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); @@ -1014,7 +896,7 @@ export class RdDActor extends RdDBaseActorVivant { /* -------------------------------------------- */ 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); @@ -1031,114 +913,17 @@ export class RdDActor extends RdDBaseActorVivant { await this.update({ [`system.attributs.${fieldName}.value`]: fieldValue }); } - isSurenc() { - return this.isPersonnage() ? (this.computeMalusSurEncombrement() < 0) : false - } - /* -------------------------------------------- */ - computeMalusSurEncombrement() { - 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!" : ""; - } - - /* -------------------------------------------- */ - getEncombrementMax() { - 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; - } - - /* -------------------------------------------- */ - 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() { + malusEthylisme() { return Math.min(0, (this.system.compteurs.ethylisme?.value ?? 0)); } - /* -------------------------------------------- */ - getEnduranceActuelle() { - return Number(this.system.sante.endurance.value); - } - - getFatigueActuelle() { - if (ReglesOptionnelles.isUsing("appliquer-fatigue") && this.isPersonnage()) { - return Math.max(0, Math.min(this.$getFatigueMax(), this.system.sante.fatigue?.value)); - } - return 0; - } - - getFatigueRestante() { - return this.$getFatigueMax() - this.getFatigueActuelle(); - } - - getFatigueMax() { - return this.isPersonnage() ? this.$getFatigueMax() : 1; - } - - $getFatigueMin() { - return this.system.sante.endurance.max - this.system.sante.endurance.value; - } - - $getFatigueMax() { - return this.$getEnduranceMax() * 2; - } - - $getEnduranceMax() { - return Math.max(1, Math.min(this.system.sante.endurance.max, MAX_ENDURANCE_FATIGUE)); - } - - $malusFatigue() { - if (ReglesOptionnelles.isUsing("appliquer-fatigue") && this.isPersonnage()) { - const fatigueMax = this.$getFatigueMax(); - const fatigue = this.getFatigueActuelle(); - return RdDUtility.calculMalusFatigue(fatigue, this.$getEnduranceMax()) - } - return 0; - } /* -------------------------------------------- */ async actionRefoulement(item) { @@ -1308,60 +1093,12 @@ export class RdDActor extends RdDBaseActorVivant { 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 jetEndurance(resteEndurance = undefined) { + const result = super.jetEndurance(resteEndurance); + if (result.jetEndurance == 1) { + ChatMessage.create({ content: await this._gainXpConstitutionJetEndurance() }); } - } - - 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 (!game.combat && sonne) { - ui.notifications.info("Le personnage est hors combat, il ne reste donc pas sonné"); - return; - } - await this.setEffect(STATUSES.StatusStunned, sonne); + return result; } /* -------------------------------------------- */ @@ -1369,146 +1106,15 @@ export class RdDActor extends RdDBaseActorVivant { 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) { - ChatMessage.create({ content: await this._gainXpConstitutionJetEndurance() }); - } - return result; - } - - /* -------------------------------------------- */ - async jetEndurance() { - const endurance = this.system.sante.endurance.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 _gainXpConstitutionJetEndurance() { await this.ajoutXpConstitution(1); // +1 XP ! 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") { - 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.$getFatigueMin()); - } - await this.update({ "system.sante": sante }) - if (this.isDead()) { - await this.setEffect(STATUSES.StatusComma, true); - } - return result - } - - - isDead() { - return this.system.sante.vie.value < -this.getSConst() - } - /* -------------------------------------------- */ _computeEnduranceMax() { const diffVie = this.system.sante.vie.max - this.system.sante.vie.value; @@ -1610,7 +1216,7 @@ export class RdDActor extends RdDBaseActorVivant { 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) } @@ -1913,7 +1519,7 @@ export class RdDActor extends RdDBaseActorVivant { /* -------------------------------------------- */ async checkCaracXP(caracName, display = true) { - let carac = RdDBaseActorVivant._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); @@ -1972,7 +1578,6 @@ export class RdDActor extends RdDBaseActorVivant { /* -------------------------------------------- */ 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) { @@ -1991,7 +1596,6 @@ export class RdDActor extends RdDBaseActorVivant { /* -------------------------------------------- */ 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))) { @@ -2614,7 +2218,7 @@ export class RdDActor extends RdDBaseActorVivant { async _onCloseRollDialog(html) { this.tmrApp?.restoreTMRAfterAction() - } + } /* -------------------------------------------- */ async _rollLireSigneDraconique(rollData) { @@ -2681,20 +2285,15 @@ export class RdDActor extends RdDBaseActorVivant { /* -------------------------------------------- */ getHeureNaissance() { - if (this.isPersonnage()) { - return this.system.heure; - } - return 0; + return this.system.heure ?? 0; } /* -------------------------------------------- */ ajustementAstrologique() { - if (this.isCreature()) { - 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') @@ -2704,7 +2303,6 @@ export class RdDActor extends RdDBaseActorVivant { /* -------------------------------------------- */ async _appliquerExperience(rolled, caracName, competence, jetResistance) { - if (!this.isPersonnage()) return; // Pas d'XP if (!rolled.isPart || rolled.finalLevel >= 0) { return undefined; @@ -2755,7 +2353,7 @@ export class RdDActor extends RdDBaseActorVivant { async _xpCarac(xpData) { if (xpData.xpCarac > 0) { let carac = duplicate(this.system.carac); - let selectedCarac = RdDBaseActorVivant._findCaracByName(carac, xpData.caracName); + let selectedCarac = RdDBaseActor._findCaracByName(carac, xpData.caracName); if (!selectedCarac.derivee) { const from = Number(selectedCarac.xp); const to = from + xpData.xpCarac; @@ -3007,53 +2605,6 @@ export class RdDActor extends RdDBaseActorVivant { return protection; } - - /* -------------------------------------------- */ - 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 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; - } - /* -------------------------------------------- */ /** @override */ getRollData() { 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.js b/module/actor/base-actor.js index c3bad622..01dded16 100644 --- a/module/actor/base-actor.js +++ b/module/actor/base-actor.js @@ -163,13 +163,15 @@ export class RdDBaseActor extends Actor { async prepareActorData() { } async computeEtatGeneral() { } /* -------------------------------------------- */ + findPlayer() { + return game.users.players.find(player => player.active && player.character?.id == this.id); + } - isCreatureEntite() { return this.type == 'creature' || this.type == 'entite'; } - isCreature() { return this.type == 'creature'; } + isCreatureEntite() { return this.isCreature() || this.isEntite() } + isCreature() { return false } isEntite(typeentite = []) { return false } - isPersonnage() { return this.type == 'personnage'; } isVehicule() { return this.type == 'vehicule'; } - + isPersonnage() { return false } getItem(id, type = undefined) { const item = this.items.get(id); if (type == undefined || (item?.type == type)) { 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/dialog-create-signedraconique.js b/module/dialog-create-signedraconique.js index 0620864d..d141d08a 100644 --- a/module/dialog-create-signedraconique.js +++ b/module/dialog-create-signedraconique.js @@ -10,7 +10,7 @@ export class DialogCreateSigneDraconique extends Dialog { let dialogData = { signe: signe, tmrs: TMRUtility.buildSelectionTypesTMR(signe.system.typesTMR), - actors: game.actors.filter(actor => actor.isPersonnage() && actor.isHautRevant()) + actors: game.actors.filter(actor => actor.isHautRevant()) .map(actor => ({ id: actor.id, name: actor.name, diff --git a/module/rdd-main.js b/module/rdd-main.js index 56ba18a9..0d4d0175 100644 --- a/module/rdd-main.js +++ b/module/rdd-main.js @@ -33,7 +33,7 @@ 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 { RdDCreatureSheet } from "./actor/creature-sheet.js"; import { RdDActorEntiteSheet } from "./actor/entite-sheet.js"; import { RdDActorVehiculeSheet } from "./actor/vehicule-sheet.js"; @@ -62,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 @@ -93,7 +94,7 @@ export class SystemReveDeDragon { } this.actorClasses = { commerce: RdDCommerce, - creature: RdDActor, + creature: RdDCreature, entite: RdDEntite, personnage: RdDActor, vehicule: RdDVehicule, @@ -152,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);