diff --git a/icons/sante/blessure-mort.webp b/icons/sante/blessure-mort.webp new file mode 100644 index 00000000..21294caf Binary files /dev/null and b/icons/sante/blessure-mort.webp differ diff --git a/icons/sante/blessure-soins.webp b/icons/sante/blessure-soins.webp new file mode 100644 index 00000000..a8c317d7 Binary files /dev/null and b/icons/sante/blessure-soins.webp differ diff --git a/icons/sante/blessure.webp b/icons/sante/blessure.webp new file mode 100644 index 00000000..f3901608 Binary files /dev/null and b/icons/sante/blessure.webp differ diff --git a/icons/sante/eraflure.webp b/icons/sante/eraflure.webp new file mode 100644 index 00000000..fc3b4a10 Binary files /dev/null and b/icons/sante/eraflure.webp differ diff --git a/icons/sante/mort.webp b/icons/sante/mort.webp new file mode 100644 index 00000000..7bbe0626 Binary files /dev/null and b/icons/sante/mort.webp differ diff --git a/lang/fr.json b/lang/fr.json index f8cf04e8..a671f680 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -7,47 +7,48 @@ "TypeVehicule": "Véhicule" }, "ITEM": { - "TypeObjet": "Objet", - "TypeGemme": "Gemme", - "TypeCompetence": "Compétence", - "TypeCompetencecreature": "Compétence de créature", - "TypeMaladie": "Maladie", - "TypePoison": "Poison", - "TypeNombreastral": "Nombre astral", - "TypeTarot": "Carte de tarot", - "TypeCasetmr": "TMR spéciale", - "TypeRencontre": "Rencontre TMR", - "TypeMunition": "Munition", - "TypeMonnaie": "Monnaie", - "TypeHerbe": "Herbe", - "TypePlante": "Plante", - "TypeIngredient": "Ingrédient", - "TypeFaune": "Faune", - "TypeLivre": "Livre", - "TypePotion": "Potion", "TypeArme": "Arme", "TypeArmure": "Armure", - "TypeConteneur": "Conteneur", - "TypeNourritureboisson": "Nourriture & boisson", - "TypeService": "Service", + "TypeBlessure": "Blessure", + "TypeCasetmr": "TMR spéciale", "TypeChant": "Chant", + "TypeCompetence": "Compétence", + "TypeCompetencecreature": "Compétence de créature", + "TypeConteneur": "Conteneur", "TypeDanse": "Danse", - "TypeMusique": "Musique", - "TypeOeuvre": "Oeuvre", - "TypeTache": "Tâche", + "TypeExtraitpoetique": "Extrait poetique", + "TypeFaune": "Faune", + "TypeGemme": "Gemme", + "TypeHerbe": "Herbe", + "TypeIngredient": "Ingrédient", "TypeJeu": "Jeu", + "TypeLivre": "Livre", + "TypeMaladie": "Maladie", + "TypeMeditation": "Méditation", + "TypeMonnaie": "Monnaie", + "TypeMunition": "Munition", + "TypeMusique": "Musique", + "TypeNombreastral": "Nombre astral", + "TypeNourritureboisson": "Nourriture & boisson", + "TypeObjet": "Objet", + "TypeOeuvre": "Oeuvre", + "TypeOmbre": "Ombre de Thanatos", + "TypePlante": "Plante", + "TypePoison": "Poison", + "TypePossession": "Possession", + "TypePotion": "Potion", + "TypeQueue": "Queue de Dragon", "TypeRecettealchimique": "Recette alchimique", "TypeRecettecuisine": "Recette de cuisine", - "TypeSort": "Sort", - "TypeMeditation": "Méditation", + "TypeRencontre": "Rencontre TMR", + "TypeService": "Service", "TypeSignedraconique": "Signe draconique", - "TypeQueue": "Queue de Dragon", - "TypeOmbre": "Ombre de Thanatos", - "TypeSouffle": "Souffle de Dragon", - "TypeTete": "Tête de Dragon", - "TypePossession": "Possession", + "TypeSort": "Sort", "TypeSortreserve": "Sort en réserve", - "TypeExtraitpoetique": "Extrait poetique" + "TypeSouffle": "Souffle de Dragon", + "TypeTache": "Tâche", + "TypeTarot": "Carte de tarot", + "TypeTete": "Tête de Dragon" }, "EFFECT": { "StatusStunned": "Sonné", diff --git a/module/actor-sheet.js b/module/actor-sheet.js index 7103aeff..00f2fa96 100644 --- a/module/actor-sheet.js +++ b/module/actor-sheet.js @@ -13,6 +13,7 @@ import { STATUSES } from "./settings/status-effects.js"; import { MAINS_DIRECTRICES } from "./actor.js"; import { RdDBaseActorSheet } from "./actor/base-actor-sheet.js"; import { RdDItem } from "./item.js"; +import { RdDItemBlessure } from "./item/blessure.js"; /* -------------------------------------------- */ /** @@ -151,42 +152,31 @@ export class RdDActorSheet extends RdDBaseActorSheet { this.html.find('.creer-tache').click(async event => { this.createEmptyTache(); }); - this.html.find('.creer-tache-blessure-legere').click(async event => { - this.actor.createTacheBlessure('legere'); - }); - this.html.find('.creer-tache-blessure-grave').click(async event => { - this.actor.createTacheBlessure('grave'); - }); - this.html.find('.creer-tache-blessure-critique').click(async event => { - this.actor.createTacheBlessure('critique'); - }); + this.html.find('.creer-tache-blessure-legere').click(async event => RdDItemBlessure.createTacheSoinBlessure(this.actor, 2)); + this.html.find('.creer-tache-blessure-grave').click(async event => RdDItemBlessure.createTacheSoinBlessure(this.actor, 4)); + this.html.find('.creer-tache-blessure-critique').click(async event => RdDItemBlessure.createTacheSoinBlessure(this.actor, 6)); + this.html.find('.creer-blessure-legere').click(async event => RdDItemBlessure.createBlessure(this.actor, 2)); + this.html.find('.creer-blessure-grave').click(async event => RdDItemBlessure.createBlessure(this.actor, 4)); + this.html.find('.creer-blessure-critique').click(async event => RdDItemBlessure.createBlessure(this.actor, 6)); this.html.find('.creer-une-oeuvre').click(async event => { this.selectTypeOeuvreToCreate(); }); - // Blessure control - this.html.find('.blessure-control').click(async event => { - const tr = this.html.find(event.currentTarget).parents(".item"); - let btype = tr.data("blessure-type"); - let index = tr.data('blessure-index'); - let active = this.html.find(event.currentTarget).data('blessure-active'); - //console.log(btype, index, active); - await this.actor.manageBlessureFromSheet(btype, index, active); + this.html.find('.blessure-premierssoins-done').change(async event => { + const blessure = this.getBlessure(event); + await blessure?.setSoinsBlessure({ premierssoins: { done: event.currentTarget.checked } }); }); - - // Blessure data - this.html.find('.blessure-soins').change(async event => { - const tr = this.html.find(event.currentTarget).parents(".item"); - let btype = tr.data('blessure-type'); - let index = tr.data('blessure-index'); - let psoins = tr.find('.blessure-premiers_soins').val(); - let pcomplets = tr.find('.blessure-soins_complets').val(); - let jours = tr.find('.blessure-jours').val(); - let loc = tr.find('.blessure-localisation').val(); - let psdone = tr.find('.blessure-psdone:checked').val(); - let scdone = tr.find('.blessure-scdone:checked').val(); - console.log(btype, index, psoins, pcomplets, jours, loc, psdone, scdone); - await this.actor.setDataBlessureFromSheet(btype, index, psoins, pcomplets, jours, loc, psdone, scdone); + this.html.find('.blessure-soinscomplets-done').change(async event => { + const blessure = this.getBlessure(event); + await blessure?.setSoinsBlessure({ soinscomplets: { done: event.currentTarget.checked } }) + }); + this.html.find('.blessure-premierssoins-bonus').change(async event => { + const blessure = this.getBlessure(event); + await blessure?.setSoinsBlessure({ premierssoins: { bonus: Number(event.currentTarget.value) } }) + }); + this.html.find('.blessure-soinscomplets-bonus').change(async event => { + const blessure = this.getBlessure(event); + await blessure?.setSoinsBlessure({ soinscomplets: { bonus: Number(event.currentTarget.value) } }) }); // Equip Inventory Item @@ -426,6 +416,12 @@ export class RdDActorSheet extends RdDBaseActorSheet { }); } + getBlessure(event) { + const itemId = this.html.find(event.currentTarget).parents(".item-blessure").data('item-id'); + const blessure = this.actor.getItem(itemId, 'blessure'); + return blessure; + } + isCompetenceAffichable(competence) { return !this.options.showCompNiveauBase || !RdDItemCompetence.isNiveauBase(competence); } diff --git a/module/actor.js b/module/actor.js index 0a005dc1..f51ae2aa 100644 --- a/module/actor.js +++ b/module/actor.js @@ -26,7 +26,7 @@ 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_BLURETTE, ENTITE_INCARNE, ENTITE_NONINCARNE, SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js"; +import { ENTITE_INCARNE, ENTITE_NONINCARNE, 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"; @@ -34,8 +34,8 @@ import { Targets } from "./targets.js"; import { DialogRepos } from "./sommeil/dialog-repos.js"; import { RdDBaseActor } from "./actor/base-actor.js"; import { RdDTimestamp } from "./rdd-timestamp.js"; -import { RdDItemTache } from "./item-tache.js"; -import { APP_ASTROLOGIE_REFRESH, AppAstrologie } from "./sommeil/app-astrologie.js"; +import { RdDItemBlessure } from "./item/blessure.js"; +import { AppAstrologie } from "./sommeil/app-astrologie.js"; const POSSESSION_SANS_DRACONIC = { img: 'systems/foundryvtt-reve-de-dragon/icons/entites/possession.webp', @@ -46,7 +46,6 @@ const POSSESSION_SANS_DRACONIC = { } }; -const PAS_DE_BLESSURE = { "active": false, "psdone": false, "scdone": false, "premiers_soins": 0, "soins_complets": 0, "jours": 0, "loc": "" }; export const MAINS_DIRECTRICES = ['Droitier', 'Gaucher', 'Ambidextre'] /* -------------------------------------------- */ @@ -332,34 +331,42 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async roll() { - const carac = mergeObject( - duplicate(this.system.carac), + const carac = mergeObject(duplicate(this.system.carac), { 'reve-actuel': this.getCaracReveActuel(), 'chance-actuelle': this.getCaracChanceActuelle() }); - let rollData = { - carac: carac, - selectedCarac: carac.apparence, - selectedCaracName: 'apparence', - competences: this.itemTypes['competence'] - }; + 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: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll.html' }, + { html: template }, { - name: `jet-${this.id}`, - label: `Jet de ${this.name}`, + name: name, + label: label, callbacks: [ this.createCallbackExperience(), this.createCallbackAppelAuMoral(), - { action: r => this._onRollCaracResult(r) } + { action: callbackAction } ] - } - ); + }); dialog.render(true); } + async prepareChateauDormant(consigne) { if (consigne.ignorer) { return; @@ -395,7 +402,7 @@ export class RdDActor extends RdDBaseActor { } await this._recupereChance(); await this.transformerStress(); - this.bonusRecuperationPotion = 0; // Reset potion + await this.setBonusPotionSoin(0); } await this.resetInfoSommeil() ChatMessage.create(message); @@ -406,11 +413,8 @@ export class RdDActor extends RdDBaseActor { const maladiesPoisons = this._maladiePoisons(message); const isMaladeEmpoisonne = maladiesPoisons.length > 0; this._messageRecuperationMaladiePoisons(maladiesPoisons, message); - const blessures = duplicate(this.system.blessures); - await this._recupererBlessures(message, "legere", blessures.legeres.liste.filter(b => b.active), [], isMaladeEmpoisonne); - await this._recupererBlessures(message, "grave", blessures.graves.liste.filter(b => b.active), blessures.legeres.liste, isMaladeEmpoisonne); - await this._recupererBlessures(message, "critique", blessures.critiques.liste.filter(b => b.active), blessures.graves.liste, isMaladeEmpoisonne); - await this.update({ "system.blessures": blessures }); + + await this._recuperationBlessures(message, isMaladeEmpoisonne); await this._recupererVie(message, isMaladeEmpoisonne); } @@ -452,7 +456,7 @@ export class RdDActor extends RdDBaseActor { await this._recupereChance(); await this.transformerStress(); await this.retourSeuilDeReve(message); - this.bonusRecuperationPotion = 0; // Reset potion + await this.setBonusPotionSoin(0); await this.retourSust(message); await this.verifierPotionsEnchantees(); if (message.content != "") { @@ -515,65 +519,36 @@ export class RdDActor extends RdDBaseActor { } /* -------------------------------------------- */ - async _recupererBlessures(message, type, liste, moindres, isMaladeEmpoisonne) { - if (!this.bonusRecuperationPotion) this.bonusRecuperationPotion = 0; - let count = 0; - const definitions = RdDUtility.getDefinitionsBlessures(); - let definition = definitions.find(d => d.type == type); - for (let blessure of liste) { - if (blessure.jours >= definition.facteur) { - let rolled = await this._jetRecuperationConstitution(Misc.toInt(blessure.soins_complets) + this.bonusRecuperationPotion, message); - blessure.soins_complets = 0; - if (!isMaladeEmpoisonne && rolled.isSuccess && this._retrograderBlessure(type, blessure, moindres)) { - message.content += ` -- une blessure ${type} cicatrise`; - count++; - } - else if (rolled.isETotal) { - message.content += ` -- une blessure ${type} s'infecte (temps de guérison augmenté de ${definition.facteur} jours, perte de vie)`; - blessure.jours = 0; - await this.santeIncDec("vie", -1); - } - else { - blessure.jours++; - message.content += ` -- une blessure ${type} reste stable`; - } - } - else { - blessure.jours++; - } - } + async _recuperationBlessures(message, isMaladeEmpoisonne) { + const timestamp = game.system.rdd.calendrier.getTimestamp() + const blessures = this.filterItems(it => it.gravite > 0, 'blessure').sort(Misc.ascending(it => it.system.gravite)) + + Promise.all(blessures.map(b => b.recuperationBlessure({ + actor: this, + timestamp, + message, + isMaladeEmpoisonne, + blessures + }))); + await this.supprimerBlessures(filterToDelete); } - /* -------------------------------------------- */ - _retrograderBlessure(type, blessure, blessuresMoindres) { - if (type != "legere") { - let retrograde = blessuresMoindres.find(b => !b.active); - if (!retrograde) { - return false; - } - mergeObject(retrograde, { "active": true, "psdone": blessure.psdone, "scdone": blessure.scdone, "premiers_soins": 0, "soins_complets": 0, "jours": 0, "loc": blessure.loc }); - } - this._supprimerBlessure(blessure); - return true; - } - - /* -------------------------------------------- */ - _supprimerBlessure(blessure) { - mergeObject(blessure, PAS_DE_BLESSURE); + async supprimerBlessures(filterToDelete) { + const toDelete = this.filterItems(filterToDelete, 'blessure') + .map(it => it.id); + await this.deleteEmbeddedDocuments('Item', toDelete); } /* -------------------------------------------- */ async _recupererVie(message, isMaladeEmpoisonne) { const tData = this.system - let blessures = [].concat(tData.blessures.legeres.liste).concat(tData.blessures.graves.liste).concat(tData.blessures.critiques.liste); - let nbBlessures = blessures.filter(b => b.active); + let blessures = this.filterItems(it => it.system.gravite > 0, 'blessure'); + if (blessures.length > 0) { + return + } let vieManquante = tData.sante.vie.max - tData.sante.vie.value; - if (nbBlessures == 0 && vieManquante > 0) { - let bonusSoins = 0; - for (let b of blessures) { - bonusSoins = Math.max(bonusSoins, Misc.toInt(b.soins_complets)); - } - let rolled = await this._jetRecuperationConstitution(bonusSoins, message) + if (vieManquante > 0) { + let rolled = await this.jetRecuperationConstitution(bonusSoins, message) if (!isMaladeEmpoisonne && rolled.isSuccess) { const gain = Math.min(rolled.isPart ? 2 : 1, vieManquante); message.content += " -- récupération de vie: " + gain; @@ -590,7 +565,7 @@ export class RdDActor extends RdDBaseActor { } /* -------------------------------------------- */ - async _jetRecuperationConstitution(bonusSoins, message = undefined) { + async jetRecuperationConstitution(bonusSoins, message = undefined) { let difficulte = Misc.toInt(bonusSoins) + Math.min(0, this.system.sante.vie.value - this.system.sante.vie.max); let rolled = await RdDResolutionTable.roll(this.system.carac.constitution.value, difficulte); if (message) { @@ -615,17 +590,13 @@ export class RdDActor extends RdDBaseActor { const updates = { 'system.sante.endurance.value': this.system.sante.endurance.max }; - if (!this.isEntite([ENTITE_INCARNE, ENTITE_BLURETTE])) { - if (this.system.blessures) { - updates['system.blessures.legeres.liste'] = [PAS_DE_BLESSURE, PAS_DE_BLESSURE, PAS_DE_BLESSURE, PAS_DE_BLESSURE, PAS_DE_BLESSURE]; - updates['system.blessures.graves.liste'] = [PAS_DE_BLESSURE, PAS_DE_BLESSURE]; - updates['system.blessures.critiques.liste'] = [PAS_DE_BLESSURE]; - } + 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 }; - } + } + if (this.isPersonnage()) { + updates['system.compteurs.ethylisme'] = { value: 1, nb_doses: 0, jet_moral: false }; } await this.update(updates); await this.removeEffects(e => e.flags.core.statusId !== STATUSES.StatusDemiReve); @@ -1139,35 +1110,31 @@ export class RdDActor extends RdDBaseActor { } /* -------------------------------------------- */ - computeResumeBlessure(blessures = undefined) { - blessures = blessures ?? this.system.blessures; - if (!blessures) { - return "Pas de blessures possibles"; - } - let nbLegeres = this.countBlessures(blessures.legeres.liste); - let nbGraves = this.countBlessures(blessures.graves.liste); - let nbCritiques = this.countBlessures(blessures.critiques.liste); + computeResumeBlessure() { + const blessures = this.filterItems(it => it.system.gravite > 0, 'blessure') - let resume = "Blessures:"; - if (nbCritiques > 0 || nbGraves > 0 || nbLegeres > 0) { - 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; - } - else { + 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() { @@ -1376,11 +1343,11 @@ export class RdDActor extends RdDBaseActor { ChatMessage.create({ content: `${this.name} n'est plus ${Misc.lowerFirst(game.i18n.localize(effect.system.label))} !` }); } } - if (this.type == 'personnage') { - // Gestion blessure graves : -1 pt endurance - let nbGraves = this.countBlessuresNonSoigneeByName('graves'); + if (this.isPersonnage() || this.isCreature()) { + const nbGraves = this.filterItems(it => it.isGrave(), 'blessure').length if (nbGraves > 0) { - await this.santeIncDec("endurance", -1); + // Gestion blessure graves : -1 pt endurance par blessure grave + await this.santeIncDec("endurance", - nbGraves); } } } @@ -1411,20 +1378,8 @@ export class RdDActor extends RdDBaseActor { } /* -------------------------------------------- */ - countBlessures(blessuresListe) { - return blessuresListe.filter(b => b.active).length - } - /* -------------------------------------------- */ - countBlessuresByName(name) { - return this.countBlessures(this.system.blessures[name].liste); - } - - countBlessuresNonSoigneeByName(name) { - if (this.system.blessures) { - let blessures = this.system.blessures[name].liste; - return blessures.filter(b => b.active && !b.psdone).length; - } - return 0; + countBlessures(filter = it => !it.isContusion()) { + return this.filterItems(filter, 'blessure').length } /* -------------------------------------------- */ @@ -1575,40 +1530,15 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ _computeEnduranceMax() { - let blessures = this.system.blessures; - let diffVie = this.system.sante.vie.max - this.system.sante.vie.value; - let maxEndVie = this.system.sante.endurance.max - (diffVie * 2); - let nbGraves = this.countBlessures(blessures.graves.liste); - let nbCritiques = this.countBlessures(blessures.critiques.liste); - let maxEndGraves = Math.floor(this.system.sante.endurance.max / (2 * nbGraves)); - let maxEndCritiques = nbCritiques > 0 ? 1 : this.system.sante.endurance.max; + const diffVie = this.system.sante.vie.max - this.system.sante.vie.value; + const maxEndVie = this.system.sante.endurance.max - (diffVie * 2); + const nbGraves = this.countBlessures(it => it.isGraves()) > 0 + const nbCritiques = this.countBlessures(it => it.isCritique()) > 0 + const maxEndGraves = Math.floor(this.system.sante.endurance.max / (2 * nbGraves)); + const maxEndCritiques = nbCritiques > 0 ? 1 : this.system.sante.endurance.max; return Math.max(0, Math.min(maxEndVie, maxEndGraves, maxEndCritiques)); } - /* -------------------------------------------- */ - async manageBlessureFromSheet(gravite, index) { - let listBlessures = duplicate(this.system.blessures); - let blessure = listBlessures[gravite + "s"].liste[index]; - blessure.active = !blessure.active; - if (!blessure.active) { - this._supprimerBlessure(blessure); - } - await this.update({ 'system.blessures': listBlessures }); - } - - /* -------------------------------------------- */ - async setDataBlessureFromSheet(gravite, index, psoins, pcomplets, jours, loc, psdone, scdone) { - let listBlessures = duplicate(this.system.blessures); - let blessure = listBlessures[gravite + "s"].liste[index]; - blessure.psdone = psdone; - blessure.scdone = scdone; - blessure.premiers_soins = psoins; - blessure.soins_complets = pcomplets; - blessure.jours = jours; - blessure.loc = loc; - await this.update({ 'system.blessures': listBlessures }); - } - /* -------------------------------------------- */ async jetDeMoral(situation, messageReussi = undefined, messageManque = undefined) { const jetMoral = await this._jetDeMoral(situation); @@ -1795,7 +1725,7 @@ export class RdDActor extends RdDBaseActor { async _surmonterExotisme(item) { const exotisme = Math.min(item.system.exotisme, item.system.qualite, 0); if (exotisme < 0) { - const rolled = await this.rollCaracCompetence('volonte', 'cuisine', exotisme, { title: `tente de surmonter l'exotisme de ${item.name}` }); + const rolled = await this.doRollCaracCompetence('volonte', 'cuisine', exotisme, { title: `tente de surmonter l'exotisme de ${item.name}` }); return rolled.isSuccess; } return true; @@ -1803,7 +1733,7 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async apprecier(carac, compName, qualite, title) { - const rolled = await this.rollCaracCompetence(carac, compName, qualite, { title: title, apprecier: true }); + const rolled = await this.doRollCaracCompetence(carac, compName, qualite, { title: title, apprecier: true }); if (rolled?.isSuccess) { await this.jetDeMoral('heureux'); } @@ -2089,22 +2019,21 @@ export class RdDActor extends RdDBaseActor { } /* -------------------------------------------- */ - filterSortList(sortList, coord) { + $filterSortList(sortList, coord) { let tmr = TMRUtility.getTMR(coord); - let letfilteredList = [] + let filtered = [] for (let sort of sortList) { if (sort.system.caseTMR.toLowerCase().includes('variable')) { - letfilteredList.push(sort); + filtered.push(sort); } else if (sort.system.caseTMRspeciale.toLowerCase().includes('variable')) { - letfilteredList.push(sort); + filtered.push(sort); } else if (sort.system.caseTMR.toLowerCase() == tmr.type) { - letfilteredList.push(sort); + filtered.push(sort); } else if (sort.system.caseTMR.toLowerCase().includes('special') && sort.system.caseTMRspeciale.toLowerCase().includes(coord.toLowerCase())) { - letfilteredList.push(sort); + filtered.push(sort); } } - - return letfilteredList; + return filtered; } /* -------------------------------------------- */ @@ -2136,52 +2065,39 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async rollUnSort(coord) { - let sortList = duplicate(this.getSortList()); // Duplication car les pts de reve sont modifiés dans le sort - if (!sortList || sortList.length == 0) { - ui.notifications.info("Aucun sort disponible!"); - return; - } - sortList = this.filterSortList(sortList, coord); - if (!sortList || sortList.length == 0) { - ui.notifications.info("Aucun sort disponible pour cette case !"); - return; - } if (EffetsDraconiques.isSortImpossible(this)) { ui.notifications.error("Une queue ou un souffle vous empèche de lancer de sort!"); return; } + // Duplication car les pts de reve sont modifiés dans le sort + let sorts = duplicate(this.$filterSortList(this.getSortList(), coord)); + if (sorts.length == 0) { + ui.notifications.info(`Aucun sort disponible en ${TMRUtility.getTMR(coord).label} !`); + return; + } if (this.currentTMR) this.currentTMR.minimize(); // Hide - let draconicList = this.computeDraconicAndSortIndex(sortList); + const draconicList = this.computeDraconicAndSortIndex(sorts); const reve = duplicate(this.system.carac.reve); - let rollData = { - carac: { 'reve': reve }, - forceCarac: { 'reve': reve }, - selectedCarac: reve, - draconicList: draconicList, - competence: draconicList[0], - sortList: sortList, - selectedSort: sortList[0], - tmr: TMRUtility.getTMR(coord), - diffLibre: RdDItemSort.getDifficulte(sortList[0], -7), // Per default at startup - coutreve: Array(30).fill().map((item, index) => 1 + index), - } - const dialog = await RdDRoll.create(this, rollData, - { - html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-sort.html', - close: html => { this.currentTMR.maximize() } // Re-display TMR + await this._openRollDialog({ + name: 'lancer-un-sort', + label: 'Lancer un sort', + template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-sort.html', + rollData: { + carac: { 'reve': reve }, + forceCarac: { 'reve': reve }, + selectedCarac: reve, + draconicList: draconicList, + competence: draconicList[0], + sortList: sorts, + selectedSort: sorts[0], + tmr: TMRUtility.getTMR(coord), + diffLibre: RdDItemSort.getDifficulte(sorts[0], -7), // Per default at startup + coutreve: Array(30).fill().map((item, index) => 1 + index), }, - { - name: 'lancer-un-sort', - label: 'Lancer un sort', - callbacks: [ - this.createCallbackExperience(), - { action: r => this._rollUnSortResult(r) } - ] - } - ); - dialog.render(true); + callbackAction: r => this._rollUnSortResult(r) + }); } /* -------------------------------------------- */ @@ -2288,34 +2204,34 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async rollCarac(caracName, jetResistance = undefined) { - let rollData = { - selectedCarac: this.getCaracByName(caracName), - competences: this.itemTypes['competence'], - jetResistance: jetResistance ? caracName : undefined - }; - - const dialog = await RdDRoll.create(this, rollData, - { html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html' }, - { - name: 'jet-' + caracName, - label: 'Jet ' + Grammar.apostrophe('de', rollData.selectedCarac.label), - callbacks: [ - this.createCallbackExperience(), - this.createCallbackAppelAuMoral(), - { action: r => this._onRollCaracResult(r) } - ] - } - ); - dialog.render(true); + await this._openRollDialog({ + name: 'jet-' + caracName, + label: 'Jet ' + Grammar.apostrophe('de', rollData.selectedCarac.label), + template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html', + rollData: { + selectedCarac: this.getCaracByName(caracName), + competences: this.itemTypes['competence'], + jetResistance: jetResistance ? caracName : undefined + }, + callbackAction: r => this.$onRollCaracResult(r) + }); } /* -------------------------------------------- */ - async _onRollCaracResult(rollData) { + async $onRollCaracResult(rollData) { // Final chat message await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-general.html'); } - async rollCaracCompetence(caracName, compName, diff, options = { title: "", apprecier: false }) { + /** + * Méthode pour faire un jet prédéterminer sans ouvrir la fenêtre de dialogue + * @param {*} caracName + * @param {*} compName + * @param {*} diff + * @param {*} options + * @returns + */ + async doRollCaracCompetence(caracName, compName, diff, options = { title: "", apprecier: false }) { const carac = this.getCaracByName(caracName); if (!carac) { ui.notifications.warn(`${this.name} n'a pas de caractéristique correspondant à ${caracName}`) @@ -2374,24 +2290,22 @@ export class RdDActor extends RdDBaseActor { // Transformer la competence de créature RdDItemCompetenceCreature.setRollDataCreature(rollData) } - console.log("rollCompetence !!!", rollData); - const dialog = await RdDRoll.create(this, rollData, - { html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html' }, - { - name: 'jet-competence', - label: 'Jet ' + Grammar.apostrophe('de', rollData.competence.name), - callbacks: [ - this.createCallbackExperience(), - this.createCallbackAppelAuMoral(), - { action: r => this.$onRollCompetence(r) } - ] - }); - dialog.render(true); + + await this._openRollDialog({ + name: 'jet-competence', + label: 'Jet ' + Grammar.apostrophe('de', rollData.competence.name), + template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html', + rollData: rollData, + callbackAction: r => this.$onRollCompetence(r, options) + }); } /* -------------------------------------------- */ - async $onRollCompetence(rollData) { + async $onRollCompetence(rollData, options) { await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-competence.html') + if (options?.onRollAutomate) { + options.onRollAutomate(rollData); + } } /* -------------------------------------------- */ @@ -2419,47 +2333,67 @@ export class RdDActor extends RdDBaseActor { return tachesExistantes.length > 0 ? tachesExistantes[0] : undefined; } - async createTacheBlessure(gravite) { - const tache = RdDItemTache.prepareTacheSoin(gravite) - if (tache) { - await this.createEmbeddedDocuments('Item', [tache], { renderSheet: false }); + 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') + } + + async getTacheBlessure(blesse, blessure) { + const gravite = blessure?.system.gravite ?? 0; + if (gravite > 0) { + const tache = this.listItems('tache').find(it => it.system.itemId == blessure.id) + ?? await RdDItemBlessure.createTacheSoinBlessure(this, gravite); + await blessure?.updateTacheSoinBlessure(tache); + return tache } + return undefined; + } + + async rollCaracCompetence(caracName, compName, diff, options = { title: "" }) { + const competence = this.getCompetence(compName); + 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: { + alias: this.name, + carac: this.system.carac, + selectedCarac: this.getCaracByName(caracName), + selectedCaracName: caracName, + diffLibre: diff, + competence: competence, + show: { title: options?.title ?? '' } + }, + callbackAction: r => this.$onRollCompetence(r, options) + }); } /* -------------------------------------------- */ - async rollTache(id) { + async rollTache(id, options = {}) { const tacheData = this.getTache(id) const compData = this.getCompetence(tacheData.system.competence) compData.system.defaut_carac = tacheData.system.carac; // Patch ! - let rollData = { - competence: compData, - tache: tacheData, - diffLibre: tacheData.system.difficulte, - diffConditions: 0, - use: { libre: false, conditions: true }, - carac: {} - }; - rollData.carac[tacheData.system.carac] = duplicate(this.system.carac[tacheData.system.carac]); // Single carac - - console.log("rollTache !!!", rollData); - - const dialog = await RdDRoll.create(this, rollData, - { html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html' }, - { - name: 'jet-competence', - label: 'Jet de Tâche ' + tacheData.name, - callbacks: [ - this.createCallbackExperience(), - this.createCallbackAppelAuMoral(), - { action: r => this._tacheResult(r) } - ] - }); - dialog.render(true); + await this._openRollDialog({ + name: 'jet-competence', + label: 'Jet de Tâche ' + tacheData.name, + template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html', + rollData: { + competence: compData, + tache: tacheData, + diffLibre: tacheData.system.difficulte, + diffConditions: 0, + use: { libre: false, conditions: true }, + carac: { + [tacheData.system.carac]: duplicate(this.system.carac[tacheData.system.carac]) + } + }, + callbackAction: r => this._tacheResult(r, options) + }); } /* -------------------------------------------- */ - async _tacheResult(rollData) { + async _tacheResult(rollData, options) { // Mise à jour de la tache rollData.appliquerFatigue = ReglesOptionelles.isUsing("appliquer-fatigue"); rollData.tache = duplicate(rollData.tache); @@ -2478,10 +2412,13 @@ export class RdDActor extends RdDBaseActor { this.santeIncDec("fatigue", rollData.tache.system.fatigue); await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-tache.html'); + if (options?.onRollAutomate) { + options.onRollAutomate(rollData); + } } /* -------------------------------------------- */ - async _rollArt(artData, selected, oeuvre, callBackResult = r => this._resultArt(r)) { + async _rollArt(artData, selected, oeuvre, callbackAction = r => this._resultArt(r)) { oeuvre.system.niveau = oeuvre.system.niveau ?? 0; mergeObject(artData, { @@ -2499,18 +2436,14 @@ export class RdDActor extends RdDBaseActor { artData.forceCarac = {}; artData.forceCarac[selected] = duplicate(this.system.carac[selected]); } - const dialog = await RdDRoll.create(this, artData, - { html: `systems/foundryvtt-reve-de-dragon/templates/dialog-roll-${oeuvre.type}.html` }, - { - name: `jet-${artData.art}`, - label: `${artData.verbe} ${oeuvre.name}`, - callbacks: [ - this.createCallbackExperience(), - this.createCallbackAppelAuMoral(), - { action: r => callBackResult(r) } - ] - }); - dialog.render(true); + + await this._openRollDialog({ + name: `jet-${artData.art}`, + label: `${artData.verbe} ${oeuvre.name}`, + template: `systems/foundryvtt-reve-de-dragon/templates/dialog-roll-${oeuvre.type}.html`, + rollData: artData, + callbackAction: callbackAction + }); } /* -------------------------------------------- */ @@ -2779,24 +2712,17 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async rollAppelChance(onSuccess = () => { }, onEchec = () => { }) { - // Stocke si utilisation de la chance - let rollData = { selectedCarac: this.getCaracByName('chance-actuelle'), surprise: '' }; - const dialog = await RdDRoll.create(this, rollData, - { html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html' }, - { - name: 'appelChance', - label: 'Appel à la chance', - callbacks: [ - this.createCallbackExperience(), - { action: r => this._appelChanceResult(r, onSuccess, onEchec) }, - ] - } - ); - dialog.render(true); + await this._openRollDialog({ + name: 'appelChance', + label: 'Appel à la chance', + template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html', + rollData: { selectedCarac: this.getCaracByName('chance-actuelle'), surprise: '' }, + callbackAction: r => this._appelChanceResult(r, onSuccess, onEchec) + }); } /* -------------------------------------------- */ - async _appelChanceResult(rollData, onSuccess = () => { }, onEchec = () => { }) { + async _appelChanceResult(rollData, onSuccess, onEchec) { await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-appelchance.html') if (rollData.rolled.isSuccess) { await this.setFlag(SYSTEM_RDD, 'utilisationChance', true); @@ -3106,7 +3032,77 @@ export class RdDActor extends RdDBaseActor { } RdDCombat.rddCombatTarget(target, this).attaque(competence, arme); }) + } + async rollSoins(blesse, blessureId) { + const blessure = blesse.blessuresASoigner().find(it => it.id == blessureId); + if (blessure) { + if (!blessure.system.premierssoins.done) { + const tache = await this.getTacheBlessure(blesse, blessure); + return await this.rollTache(tache.id, { + onRollAutomate: async r => blesse.onRollTachePremiersSoins(blessureId, r) + }); + } + if (!blessure.system.soinscomplets.done) { + const diff = blessure.system.difficulte + (blessure.system.premierssoins.bonus ?? 0); + return await this.rollCaracCompetence("dexterite", "Chirurgie", diff, { + title: "Soins complets", + onRollAutomate: r => blesse.onRollSoinsComplets(blessureId, r) + }) + } + } + } + + async onRollTachePremiersSoins(blessureId, rollData) { + if (!this.isOwner) { + return RdDBaseActor.remoteActorCall({ actorId: this.id, method: 'onRollTachePremiersSoins', args: [blessureId, rollData] }); + } + const blessure = this.getItem(blessureId, 'blessure') + console.log('TODO update blessure', this, blessureId, rollData, rollData.tache); + if (blessure && !blessure.system.premierssoins.done) { + const tache = rollData.tache; + if (rollData.rolled.isETotal) { + await blessure.update({ + 'system.difficulte': blessure.system.difficulte - 1, + 'system.premierssoins.tache': Math.max(0, tache.system.points_de_tache_courant) + }) + } + else { + const bonus = tache.system.points_de_tache_courant - tache.system.points_de_tache + await blessure.update({ + 'system.premierssoins': { + done: (bonus >= 0), + bonus: Math.max(0, bonus), + tache: Math.max(0, tache.system.points_de_tache_courant) + } + }) + if (bonus >= 0) { + await this.deleteEmbeddedDocuments('Item', [tache.id]) + } + } + } + } + + async onRollSoinsComplets(blessureId, rollData) { + if (!this.isOwner) { + return RdDBaseActor.remoteActorCall({ actorId: this.id, method: 'onRollSoinsComplets', args: [blessureId, rollData] }); + } + const blessure = this.getItem(blessureId, 'blessure') + if (blessure && blessure.system.premierssoins.done && !blessure.system.soinscomplets.done) { + // TODO: update de la blessure: passer par le MJ! + if (rollData.rolled.isETotal) { + await blessure.setSoinsBlessure({ + difficulte: blessure.system.difficulte - 1, + premierssoins: { done: false, bonus: 0 }, soinscomplets: { done: false, bonus: 0 }, + }) + } + else { + // soins complets finis + await blessure.setSoinsBlessure({ + soinscomplets: { done: true, bonus: Math.max(0, rollData.rolled.ptTache) }, + }) + } + } } /* -------------------------------------------- */ @@ -3236,11 +3232,11 @@ export class RdDActor extends RdDBaseActor { async _appliquerEncaissement(encaissement, show) { let santeOrig = duplicate(this.system.sante); - this.ajouterBlessure(encaissement); // Will upate the result table + const blessure = await this.ajouterBlessure(encaissement); // Will upate the result table const perteVie = this.isEntite() ? { newValue: 0 } : await this.santeIncDec("vie", -encaissement.vie); - const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, encaissement.critiques > 0); + const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, blessure?.isCritique()); mergeObject(encaissement, { alias: this.name, @@ -3250,6 +3246,7 @@ export class RdDActor extends RdDBaseActor { jetEndurance: perteEndurance.jetEndurance, endurance: santeOrig.endurance.value - perteEndurance.newValue, vie: this.isEntite() ? 0 : (santeOrig.vie.value - perteVie.newValue), + blessure: blessure, show: show ?? {} }); @@ -3269,73 +3266,33 @@ export class RdDActor extends RdDBaseActor { } /* -------------------------------------------- */ - ajouterBlessure(encaissement) { - if (this.type == 'entite') return; // Une entité n'a pas de blessures - if (encaissement.legeres + encaissement.graves + encaissement.critiques == 0) return; - + async ajouterBlessure(encaissement) { + 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); - let blessures = duplicate(this.system.blessures); - - let count = encaissement.legeres; - // Manage blessures - while (count > 0) { - let legere = blessures.legeres.liste.find(it => !it.active); - if (legere) { - this._setBlessure(legere, encaissement); - count--; - } - else { - encaissement.graves += count; - encaissement.legeres -= count; - break; - } + const blessure = await RdDItemBlessure.createBlessure(this, encaissement.gravite, encaissement.dmg.loc.label); + if (blessure.isCritique()) { + encaissement.endurance = endActuelle; } - count = encaissement.graves; - while (count > 0) { - let grave = blessures.graves.liste.find(it => !it.active); - if (grave) { - this._setBlessure(grave, encaissement); - count--; - } - else { - encaissement.critiques += count; - encaissement.graves -= count; - encaissement.endurance = endActuelle; - encaissement.vie = 4; - break; - } + if (blessure.isMort()) { + this.setEffect(STATUSES.StatusComma, true); + encaissement.mort = true; + ChatMessage.create({ + content: ` + ${this.name} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !` + }); } - - count = encaissement.critiques; - while (count > 0) { - let critique = blessures.critiques.liste[0]; - if (!critique.active) { - this._setBlessure(critique, encaissement); - count--; - } else { - // TODO: status effect dead - this.setEffect(STATUSES.StatusComma, true); - ChatMessage.create({ - content: ` - ${this.name} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !` - }); - encaissement.critiques -= count; - encaissement.mort = true; - break; - } - } - - encaissement.endurance = Math.max(encaissement.endurance, -endActuelle); - this.update({ "system.blessures": blessures }); - } - - /* -------------------------------------------- */ - _setBlessure(blessure, encaissement) { - blessure.active = true; - blessure.psdone = false; - blessure.scdone = false; - blessure.loc = encaissement.locName; + return blessure; } /* -------------------------------------------- */ @@ -3568,42 +3525,28 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ async buildPotionGuerisonList(pointsGuerison) { - let pointsGuerisonInitial = pointsGuerison; - let myData = this.system; - const blessures = duplicate(myData.blessures); - let guerisonData = { list: [], pointsConsommes: 0 } - - console.log(blessures); - for (let critique of blessures.critiques.liste) { - if (critique.active && pointsGuerison >= 6) { - pointsGuerison -= 6; - critique.active = false; - guerisonData.list.push("1 Blessure Critique (6 points)"); + const pointsGuerisonInitial = pointsGuerison; + const blessures = this.filterItems(it => it.isLegere() || it.isGrave() || it.isCritique()).sort(Misc.descending(it => it.system.gravite)) + const ids = [] + const guerisonData = { list: [], pointsConsommes: 0 } + for (let blessure of blessures) { + if (pointsGuerison >= blessure.system.gravite) { + pointsGuerison -= blessure.system.gravite; + guerisonData.list.push(`1 Blessure ${blessure.system.labelGravite} (${blessure.system.gravite} points)`); + ids.push(blessure.id) } } - for (let grave of blessures.graves.liste) { - if (grave.active && pointsGuerison >= 4) { - pointsGuerison -= 4; - grave.active = false; - guerisonData.list.push("1 Blessure Grave (4 points)"); - } + if (ids.length > 0) { + await this.supprimerBlessures(it => ids.includes(it.id)); } - for (let legere of blessures.legeres.liste) { - if (legere.active && pointsGuerison >= 2) { - pointsGuerison -= 2; - legere.active = false; - guerisonData.list.push("1 Blessure Légère (2 points)"); - } + if (blessures.length == ids.length) { + let pvManquants = this.system.sante.vie.max - this.system.sante.vie.value; + let pvSoignees = Math.min(pvManquants, Math.floor(pointsGuerison / 2)); + pointsGuerison -= pvSoignees * 2; + guerisonData.list.push(pvSoignees + " Points de Vie soignés"); + await this.santeIncDec('vie', +pvSoignees, false); } - await this.update({ "system.blessures": blessures }); - - let pvManquants = myData.sante.vie.max - myData.sante.vie.value; - let pvSoignees = Math.min(pvManquants, Math.floor(pointsGuerison / 2)); - pointsGuerison -= pvSoignees * 2; - guerisonData.list.push(pvSoignees + " Points de Vie soignés"); - await this.santeIncDec('vie', +pvSoignees, false); guerisonData.pointsConsommes = pointsGuerisonInitial - pointsGuerison; - return guerisonData; } @@ -3622,7 +3565,7 @@ export class RdDActor extends RdDBaseActor { } } if (!potionData.system.magique || potionData.rolled.isSuccess) { - this.bonusRecuperationPotion = potionData.system.herbeBonus; + await this.setBonusPotionSoin(potionData.system.herbeBonus); } ChatMessage.create({ whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name), @@ -3630,6 +3573,10 @@ export class RdDActor extends RdDBaseActor { }); } + async setBonusPotionSoin(bonus) { + await this.update({ 'sante.bonusPotion': bonus }); + } + /* -------------------------------------------- */ async consommerPotionRepos(potionData) { potionData.alias = this.name; diff --git a/module/actor/base-actor-sheet.js b/module/actor/base-actor-sheet.js index 9ac21339..11429c01 100644 --- a/module/actor/base-actor-sheet.js +++ b/module/actor/base-actor-sheet.js @@ -78,6 +78,7 @@ export class RdDBaseActorSheet extends ActorSheet { /* -------------------------------------------- */ static filterItemsPerTypeForSheet(formData, itemTypes) { + formData.blessures = Misc.arrayOrEmpty(itemTypes['blessure']); formData.recettescuisine = Misc.arrayOrEmpty(itemTypes['recettecuisine']); formData.recettesAlchimiques = Misc.arrayOrEmpty(itemTypes['recettealchimique']); formData.maladies = Misc.arrayOrEmpty(itemTypes['maladie']); diff --git a/module/actor/base-actor.js b/module/actor/base-actor.js index 6c838523..769bc76e 100644 --- a/module/actor/base-actor.js +++ b/module/actor/base-actor.js @@ -21,7 +21,6 @@ export class RdDBaseActor extends Actor { Hooks.on("updateActor", (actor, change, options, actorId) => actor.onUpdateActor(change, options, actorId)); } - static onSocketMessage(sockmsg) { switch (sockmsg.msg) { case "msg_remote_actor_call": diff --git a/module/item-tache.js b/module/item-tache.js deleted file mode 100644 index f17bb9af..00000000 --- a/module/item-tache.js +++ /dev/null @@ -1,18 +0,0 @@ -const BASE_TACHE_SOIN_BLESSURE = { type: "tache", img: 'systems/foundryvtt-reve-de-dragon/icons/competence_chirurgie.webp', system: { carac: "dexterite", competence: "Chirurgie", periodicite: "1 round", fatigue: 0, } } -const TACHES_SOIN_BLESSURE = { - 'critique': { name: 'Blessure critique', system: { difficulte: -6, points_de_tache: 6 } }, - 'grave': { name: 'Blessure grave', system: { difficulte: -4, points_de_tache: 4 } }, - 'legere': { name: 'Blessure légère', system: { difficulte: -2, points_de_tache: 2 } }, -} - -export class RdDItemTache extends Item { - - static prepareTacheSoin(gravite) { - const blessure = TACHES_SOIN_BLESSURE[gravite] - if (blessure) { - return mergeObject(duplicate(BASE_TACHE_SOIN_BLESSURE), blessure) - } - ui.notifications.warn(`Pas de tâche de soins pour une blessure ${gravite}`) - return undefined; - } -} \ No newline at end of file diff --git a/module/item.js b/module/item.js index 74b59594..bc1248a5 100644 --- a/module/item.js +++ b/module/item.js @@ -31,9 +31,9 @@ const typesInventaire = { const typesObjetsOeuvres = ["oeuvre", "recettecuisine", "musique", "chant", "danse", "jeu"] const typesObjetsDraconiques = ["queue", "ombre", "souffle", "tete", "signedraconique", "sortreserve", "rencontre"] const typesObjetsConnaissance = ["meditation", "recettealchimique", "sort"] -const typesObjetsEffet = ["possession", "poison", "maladie"] +const typesObjetsEffet = ["possession", "poison", "maladie", "blessure"] const typesObjetsCompetence = ["competence", "competencecreature"] -const typesObjetsTemporels = ["poison", "maladie", "queue", "ombre", "souffle", "signedraconique", "rencontre"] +const typesObjetsTemporels = ["blessure", "poison", "maladie", "queue", "ombre", "souffle", "signedraconique", "rencontre"] const typesEnvironnement = typesInventaireMateriel; const encBrin = 0.00005; // un brin = 1 décigramme = 1/10g = 1/10000kg = 1/20000 enc const encPepin = 0.0007; /* un pépin de gemme = 1/10 cm3 = 1/1000 l = 3.5/1000 kg = 7/2000 kg = 7/1000 enc @@ -176,6 +176,7 @@ export class RdDItem extends Item { isBoisson() { return this.isNourritureBoisson() && this.system.boisson; } isAlcool() { return this.isNourritureBoisson() && this.system.boisson && this.system.alcoolise; } isHerbeAPotion() { return this.type == 'herbe' && (this.system.categorie == 'Soin' || this.system.categorie == 'Repos'); } + isBlessure() { return this.type == 'blessure' } isPresentDansMilieux(milieux) { return this.getEnvironnements(milieux).length > 0 diff --git a/module/item/blessure.js b/module/item/blessure.js new file mode 100644 index 00000000..2c59a9be --- /dev/null +++ b/module/item/blessure.js @@ -0,0 +1,177 @@ +import { RdDItem } from "../item.js"; +import { Misc } from "../misc.js"; +import { RdDTimestamp } from "../rdd-timestamp.js"; + +const BASE_TACHE_SOIN_BLESSURE = { + type: "tache", + img: 'systems/foundryvtt-reve-de-dragon/icons/competence_chirurgie.webp', + system: { carac: "dexterite", competence: "Chirurgie", periodicite: "1 round", fatigue: 0, } +} +const TACHES_SOIN_BLESSURE = { + 6: { name: 'Blessure critique', system: { difficulte: -6, points_de_tache: 6 } }, + 4: { name: 'Blessure grave', system: { difficulte: -4, points_de_tache: 4 } }, + 2: { name: 'Blessure légère', system: { difficulte: -2, points_de_tache: 2 } }, +} + +const definitionsBlessures = [ + { type: "contusion", gravite: 0, labelGravite: 'Contusion/éraflure', max: 100, icon: "systems/foundryvtt-reve-de-dragon/icons/sante/eraflure.webp" }, + { type: "legere", gravite: 2, labelGravite: 'Légère', max: 5, icon: "systems/foundryvtt-reve-de-dragon/icons/sante/blessure.webp" }, + { type: "grave", gravite: 4, labelGravite: 'Grave', max: 2, icon: "systems/foundryvtt-reve-de-dragon/icons/sante/blessure.webp" }, + { type: "critique", gravite: 6, labelGravite: 'Critique', max: 1, icon: "systems/foundryvtt-reve-de-dragon/icons/sante/blessure.webp" }, + { type: "mort", gravite: 8, labelGravite: 'Mort', max: 1, icon: "systems/foundryvtt-reve-de-dragon/icons/sante/mort.webp" } +] + +export class RdDItemBlessure extends RdDItem { + + static get defaultIcon() { + return "systems/foundryvtt-reve-de-dragon/icons/sante/blessure.webp"; + } + + prepareDerivedData() { + super.prepareDerivedData(); + this.system.labelGravite = this.getLabelGravite() + } + + static prepareTacheSoin(gravite) { + const tache = TACHES_SOIN_BLESSURE[gravite] + if (!tache) { + ui.notifications.warn(`Pas de tâche de soins pour une blessure ${gravite}`) + return undefined; + } + return mergeObject(duplicate(BASE_TACHE_SOIN_BLESSURE), tache) + } + static async createBlessure(actor, gravite, localisation = '') { + const definition = RdDItemBlessure.getDefinition(gravite) + const blessure = { + name: definition.labelGravite, + type: 'blessure', + img: definition.icon, + system: { + gravite: gravite, + difficulte: - gravite, + localisation: localisation + } + } + + const blessures = await actor.createEmbeddedDocuments('Item', [blessure]) + return blessures[0] + } + + static async createTacheSoinBlessure(actor, gravite) { + const tache = RdDItemBlessure.prepareTacheSoin(gravite) + if (tache) { + const taches = await actor.createEmbeddedDocuments('Item', [tache], { renderSheet: false }); + return taches[0]; + } + return undefined + } + + async updateTacheSoinBlessure(tache) { + if (tache) { + await tache.update({ + system: { + itemId: this.id, + difficulte: Math.min(this.system.difficulte, tache.system.difficulte), + points_de_tache_courant: Math.max(0, this.system.premierssoins.tache) + } + }); + } + } + + async setSoinsBlessure(systemUpdate = {}) { + systemUpdate = mergeObject(systemUpdate, this.system, { overwrite: false }), + systemUpdate.soinscomplets.done = systemUpdate.premierssoins.done && systemUpdate.soinscomplets.done + await this.update({ + img: this.getImgSoins(systemUpdate.gravite, systemUpdate.soinscomplets.done), + system: systemUpdate + }); + } + + async recuperationBlessure({ actor, timestamp, message, isMaladeEmpoisonne, blessures }) { + if (this.parent != actor || actor == undefined) { + return; + } + if (new RdDTimestamp(this.system.fin).isAfterIndexDate(timestamp)) { + // attente periode + return + } + if (this.system.gravite > 0) { + const update = { premierssoins: { bonus: 0 }, soinscomplets: { bonus: 0 } } + const gravite = this.system.gravite; + const graviteMoindre = gravite - 2; + const moindres = blessures.filter(it => it.system.gravite == graviteMoindre, 'blessures').length + const labelGravite = RdDItemBlessure.getLabelGravite(gravite); + + let rolled = await actor.jetRecuperationConstitution(Misc.toInt(this.system.soinscomplets.bonus) + actor.system.sante.bonusPotion, message); + + if (rolled.isETotal) { + message.content += ` -- une blessure ${labelGravite} s'infecte (temps de guérison augmenté de ${definition.facteur} jours, perte de vie)`; + mergeObject(update, { fin: { indexDate: timestamp.addJours(gravite).indexDate } }); + await actor.santeIncDec("vie", -1); + } + else { + if (!isMaladeEmpoisonne && rolled.isSuccess && this.peutRetrograder(graviteMoindre, moindres)) { + message.content += ` -- une blessure ${labelGravite} cicatrise`; + mergeObject(update, { gravite: graviteMoindre, fin: { indexDate: timestamp.addJours(graviteMoindre).indexDate } }); + } + else { + message.content += ` -- une blessure ${labelGravite} reste stable`; + } + } + await this.update(update); + } + } + + peutRetrograder(graviteMoindre, moindres) { + return moindres < RdDItemBlessure.getDefinition(graviteMoindre).max + } + + async calculerFinPeriodeTemporel(debut) { + return await debut.nouveauJour().addJours(this.system.gravite); + } + + async onFinPeriode(oldTimestamp, newTimestamp) { + if (this.system.gravite <= 0) { + await super.onFinPeriode(oldTimestamp, newTimestamp) + } + } + + getImgSoins(gravite, soins) { + let img = 'blessure' + if (gravite > 6) { + img = 'mort' + } + if (gravite <= 0) { + img = 'eraflure' + } + return `systems/foundryvtt-reve-de-dragon/icons/sante/${soins ? 'blessure-soins' : img}.webp` + } + + getLabelGravite() { + return RdDItemBlessure.getDefinition(this.system.gravite).labelGravite + } + + static getDefinition(gravite) { + return definitionsBlessures.sort(Misc.ascending(it => it.gravite)) + .find(it => it.gravite >= gravite); + } + static maxBlessures(gravite) { + return RdDItemBlessure.getDefinition(gravite).max + } + + isContusion() { + return this.system.gravite <= 0 + } + isLegere() { + return this.system.gravite > 0 && this.system.gravite <= 2 + } + isGrave() { + return this.system.gravite > 2 && this.system.gravite <= 4 + } + isCritique() { + return this.system.gravite > 4 && this.system.gravite <= 6 + } + isMort() { + return this.system.gravite > 6 + } +} diff --git a/module/item/sheet-blessure.js b/module/item/sheet-blessure.js new file mode 100644 index 00000000..cbab7ad8 --- /dev/null +++ b/module/item/sheet-blessure.js @@ -0,0 +1,29 @@ +import { RdDItemSheet } from "../item-sheet.js"; + +export class RdDBlessureItemSheet extends RdDItemSheet { + + static get ITEM_TYPE() { return "blessure" }; + + async getData() { + const formData = await super.getData(); + formData.disabled = formData.options.isGM || formData.options.isOwned ? '' : 'disabled'; + return formData; + } + + activateListeners(html) { + super.activateListeners(html); + + if (!this.options.editable) return; + + this.html.find('[name="premierssoins-done"]').change(async event => { + await this.item.setSoinsBlessure({ premierssoins: { done: event.currentTarget.checked } }); + }); + this.html.find('[name="soinscomplets-done"]').change(async event => { + await this.item.setSoinsBlessure({ soinscomplets: { done: event.currentTarget.checked } }) + }); + this.html.find('[name="system-gravite"]').change(async event => { + const gravite = Number(event.currentTarget.value) + await this.item.setSoinsBlessure({ gravite: gravite, difficulte: - gravite }) + }); + } +} diff --git a/module/migrations.js b/module/migrations.js index 8b97047c..b1d3513f 100644 --- a/module/migrations.js +++ b/module/migrations.js @@ -416,6 +416,49 @@ class _10_5_0_UpdatePeriodicite extends Migration { } } +class _10_7_0_MigrationBlessures extends Migration { + get code() { return "migration-blessures"; } + get version() { return "10.7.0"; } + + async migrate() { + const timestamp = game.system.rdd.calendrier.getTimestamp() + await Promise.all(game.actors.filter(it => it.isPersonnage() || it.isCreature()) + .map(async (actor) => { + const legeres = actor.system.blessures?.legeres.liste.filter(it => it.active).map(it => this.creerBlessure(2, 'légère', it, timestamp)) ?? []; + const graves = actor.system.blessures?.graves.liste.filter(it => it.active).map(it => this.creerBlessure(4, 'grave', it, timestamp)) ?? []; + const critiques = actor.system.blessures?.critiques.liste.filter(it => it.active).map(it => this.creerBlessure(6, 'critique', it, timestamp)); + const blessures = legeres.concat(graves).concat(critiques); + if (blessures.length > 0) { + await actor.createEmbeddedDocuments("Item", blessures); + } + await actor.update({ + 'system.blessures.legeres.liste': [], + 'system.blessures.graves.liste': [], + 'system.blessures.critiques.liste': [] + }) + })); + } + creerBlessure(gravite, graviteTexte, blessure, timestamp) { + const dateBlessure = timestamp.addJours(-blessure.jours); + const datePremiereRecup = dateBlessure.addJours(gravite); + return { + name: `Blessure ${graviteTexte}`, + type: 'blessure', + img: `systems/foundryvtt-reve-de-dragon/icons/sante/blessure${blessure.psdone ? '-soins' : ''}.webp`, + system: { + gravite: gravite, + difficulte: -gravite, + debut: { indexDate: dateBlessure.indexDate, indexMinute: 0 }, + fin: { indexDate: datePremiereRecup.indexDate, indexMinute: 0 }, + premierssoins: { done: blessure.psdone, bonus: blessure.premiers_soins }, + soinscomplets: { done: blessure.scdone, bonus: blessure.soins_complets }, + localisation: blessure.localisation, + jours: blessure.jours + } + } + } +} + export class Migrations { static getMigrations() { return [ @@ -431,6 +474,7 @@ export class Migrations { new _10_3_17_Monnaies(), new _10_4_6_ServicesEnCommerces(), new _10_5_0_UpdatePeriodicite(), + new _10_7_0_MigrationBlessures(), ]; } @@ -447,7 +491,7 @@ export class Migrations { migrate() { const currentVersion = game.settings.get(SYSTEM_RDD, "systemMigrationVersion"); if (isNewerVersion(game.system.version, currentVersion)) { - //if (true) { /* comment previous and uncomment here to test before upgrade */ + //if (true) { /* comment previous and uncomment here to test before upgrade */ const migrations = Migrations.getMigrations().filter(m => isNewerVersion(m.version, currentVersion)); if (migrations.length > 0) { migrations.sort((a, b) => this.compareVersions(a, b)); diff --git a/module/rdd-bonus.js b/module/rdd-bonus.js index 25bbdb3e..2516b0ff 100644 --- a/module/rdd-bonus.js +++ b/module/rdd-bonus.js @@ -28,7 +28,7 @@ export class RdDBonus { } /* -------------------------------------------- */ - static dmg(rollData, dmgActor, isCauchemar = false) { + static dmg(rollData, dmgActor, isEntiteIncarnee = false) { let dmg = { total: 0 }; if (rollData.arme && rollData.arme.name.toLowerCase() == "esquive") { // Specific case management @@ -41,7 +41,7 @@ export class RdDBonus { dmg.dmgSurprise = RdDBonus.dmgBonus(rollData.ajustements?.attaqueDefenseurSurpris.used); dmg.dmgActor = rollData.selectedCarac ? RdDBonus._dmgPerso(dmgActor, rollData.selectedCarac.label, dmg.dmgArme) : 0; dmg.total = dmg.dmgSurprise + dmg.dmgTactique + dmg.dmgArme + dmg.dmgActor + dmg.dmgParticuliere; - dmg.mortalite = RdDBonus._calculMortalite(rollData, isCauchemar) + dmg.mortalite = RdDBonus._calculMortalite(rollData, isEntiteIncarnee) } return dmg; } @@ -62,11 +62,8 @@ export class RdDBonus { } /* -------------------------------------------- */ - static _calculMortalite(rollData, isCauchemar) { - if (isCauchemar) { - return "cauchemar"; - } - return isCauchemar ? "cauchemar" + static _calculMortalite(rollData, isEntiteIncarnee) { + return isEntiteIncarnee ? "entiteincarnee" : rollData.dmg?.mortalite ?? rollData.arme?.system.mortalite ?? "mortel"; diff --git a/module/rdd-calendrier.js b/module/rdd-calendrier.js index 0053d319..03fb26a5 100644 --- a/module/rdd-calendrier.js +++ b/module/rdd-calendrier.js @@ -45,6 +45,14 @@ export class RdDCalendrier extends Application { Hooks.on('updateSetting', async (setting, update, options, id) => this.onUpdateSetting(setting, update, options, id)); } + display() { + let templatePath = "systems/foundryvtt-reve-de-dragon/templates/calendar-template.html"; + renderTemplate(templatePath, {}).then(html => { + this.render(true); + }); + return this; + } + async onUpdateSetting(setting, update, options, id) { if (setting.key == SYSTEM_RDD + '.' + WORLD_TIMESTAMP_SETTING) { this.timestamp = RdDTimestamp.getWorldTime(); diff --git a/module/rdd-combat.js b/module/rdd-combat.js index 904384d6..435be56d 100644 --- a/module/rdd-combat.js +++ b/module/rdd-combat.js @@ -1295,13 +1295,8 @@ export class RdDCombat { blessuresStatus: actor.computeResumeBlessure(), SConst: actor.getSConst(), actorId: actor.id, - isGrave: false, - isCritique: false - } - if (actor.countBlessuresNonSoigneeByName("critiques") > 0) { // Pour éviter le cumul grave + critique - formData.isCritique = true; - } else if (actor.countBlessuresNonSoigneeByName("graves") > 0) { - formData.isGrave = true; + isGrave: actor.countBlessures(it => it.isGraves()) > 0, + isCritique: actor.countBlessures(it => it.isCritique()) > 0 } ChatUtility.createChatWithRollMode(actor.name, { diff --git a/module/rdd-commands.js b/module/rdd-commands.js index ebab970a..74dd1e61 100644 --- a/module/rdd-commands.js +++ b/module/rdd-commands.js @@ -332,7 +332,7 @@ export class RdDCommands { let competence = length > 1 ? actors[0].getCompetence(Misc.join(params.slice(1, length), ' ')) : { name: undefined }; if (competence) { for (let actor of actors) { - await actor.rollCaracCompetence(caracName, competence.name, diff); + await actor.doRollCaracCompetence(caracName, competence.name, diff); } } return; diff --git a/module/rdd-main.js b/module/rdd-main.js index 81f12111..73cf5d55 100644 --- a/module/rdd-main.js +++ b/module/rdd-main.js @@ -36,6 +36,7 @@ import { RdDActorVehiculeSheet } from "./actor-vehicule-sheet.js"; import { RdDActorEntiteSheet } from "./actor-entite-sheet.js"; import { RdDItem } from "./item.js"; +import { RdDItemBlessure } from "./item/blessure.js"; import { RdDItemService } from "./item/service.js"; import { RdDItemMaladie } from "./item/maladie.js"; import { RdDItemPoison } from "./item/poison.js"; @@ -46,6 +47,7 @@ import { RdDItemSouffle } from "./item/souffle.js"; import { RdDRencontre } from "./item/rencontre.js"; import { RdDItemSheet } from "./item-sheet.js"; +import { RdDBlessureItemSheet } from "./item/sheet-blessure.js"; import { RdDServiceItemSheet } from "./item/sheet-service.js"; import { RdDRencontreItemSheet } from "./item/sheet-rencontre.js"; import { RdDHerbeItemSheet } from "./item/sheet-herbe.js"; @@ -74,21 +76,22 @@ export class SystemReveDeDragon { this.RdDUtility = RdDUtility; this.RdDHotbar = RdDHotbar; this.itemClasses = { - service: RdDItemService, + blessure: RdDItemBlessure, maladie: RdDItemMaladie, + ombre: RdDItemOmbre, poison: RdDItemPoison, queue: RdDItemQueue, - ombre: RdDItemOmbre, - souffle: RdDItemSouffle, + rencontre: RdDRencontre, + service: RdDItemService, signedraconique: RdDItemSigneDraconique, - rencontre: RdDRencontre + souffle: RdDItemSouffle, } this.actorClasses = { + commerce: RdDCommerce, creature: RdDActor, entite: RdDActor, personnage: RdDActor, vehicule: RdDActor, - commerce: RdDCommerce, } } @@ -157,6 +160,7 @@ export class SystemReveDeDragon { RdDItemSheet.register(RdDPlanteItemSheet); RdDItemSheet.register(RdDIngredientItemSheet); RdDItemSheet.register(RdDServiceItemSheet); + RdDItemSheet.register(RdDBlessureItemSheet); Items.registerSheet(SYSTEM_RDD, RdDItemInventaireSheet, { types: [ @@ -276,8 +280,8 @@ export class SystemReveDeDragon { let sidebar = document.getElementById("sidebar"); sidebar.style.width = "min-content"; } - if (Misc.isUniqueConnectedGM()) { + game.system.rdd.calendrier = new RdDCalendrier(); new Migrations().migrate(); } @@ -286,13 +290,7 @@ export class SystemReveDeDragon { RdDDice.onReady(); /* -------------------------------------------- */ /* Affiche/Init le calendrier */ - let calendrier = new RdDCalendrier(); - let templatePath = "systems/foundryvtt-reve-de-dragon/templates/calendar-template.html"; - let templateData = {}; - renderTemplate(templatePath, templateData).then(html => { - calendrier.render(true); - }); - game.system.rdd.calendrier = calendrier; // Reference; + game.system.rdd.calendrier = new RdDCalendrier().display(); // Avertissement si joueur sans personnage if (!game.user.isGM && game.user.character == undefined) { diff --git a/module/rdd-roll-encaisser.js b/module/rdd-roll-encaisser.js index 4d98e413..a9fc2d84 100644 --- a/module/rdd-roll-encaisser.js +++ b/module/rdd-roll-encaisser.js @@ -30,9 +30,9 @@ export class RdDEncaisser extends Dialog { }; } else if (actor.isEntite([ENTITE_BLURETTE, ENTITE_INCARNE])) { - dialogConf.default = "cauchemar" + dialogConf.default = "entiteincarnee" dialogConf.buttons = { - "cauchemar": { label: "Cauchemar", callback: html => this.performEncaisser("cauchemar") } + "entiteincarnee": { label: "Entité incarnée", callback: html => this.performEncaisser("entiteincarnee") } } } @@ -70,7 +70,6 @@ export class RdDEncaisser extends Dialog { total: Number(this.modifier), ajustement: Number(this.modifier), encaisserSpecial: this.encaisserSpecial, - loc: { result: 0, label: "" }, mortalite: mortalite } }); diff --git a/module/rdd-sheet-utility.js b/module/rdd-sheet-utility.js index da9732cb..28bca225 100644 --- a/module/rdd-sheet-utility.js +++ b/module/rdd-sheet-utility.js @@ -9,7 +9,7 @@ export class RdDSheetUtility { : document.getUserLevel(game.user); mergeObject(options, { isGM: game.user.isGM, - isOwned: document.parent, + isOwned: document.parent ? true : false, editable: editable, cssClass: editable ? "editable" : "locked", isLimited: userRightLevel >= CONST.DOCUMENT_OWNERSHIP_LEVELS.LIMITED, diff --git a/module/rdd-token-hud.js b/module/rdd-token-hud.js index 6d29ecab..12dcaf4d 100644 --- a/module/rdd-token-hud.js +++ b/module/rdd-token-hud.js @@ -2,6 +2,7 @@ import { HtmlUtility } from "./html-utility.js"; import { Misc } from "./misc.js"; import { RdDCombatManager } from "./rdd-combat.js"; +import { Targets } from "./targets.js"; /* -------------------------------------------- */ export class RdDTokenHud { @@ -18,29 +19,39 @@ export class RdDTokenHud { } /* -------------------------------------------- */ - static async addExtensionHud(app, html, tokenId) { + static async addExtensionHud(app, html, tokenId, isCombat) { let token = canvas.tokens.get(tokenId); let actor = token.actor; - let combatant = game.combat.combatants.find(c => c.tokenId == tokenId); - if (! (combatant?.actor) ) { + app.hasExtension = true; + // soins + await RdDTokenHud.addExtensionHudSoins(html, actor); + + if (isCombat) { + let combatant = game.combat.combatants.find(c => c.tokenId == tokenId); + if (!(combatant?.actor)) { ui.notifications.warn(`Le combatant ${token.name} n'est pas associé à un acteur, impossible de déterminer ses actions de combat!`) return; + } + let actions = RdDCombatManager.listActionsCombat(combatant); + // initiative + await RdDTokenHud.addExtensionHudInit(html, combatant, actions); + // combat + await RdDTokenHud.addExtensionHudCombat(html, combatant, actions); } - app.hasExtension = true; - let actionsCombat = RdDCombatManager.listActionsCombat(combatant); + + } + + static async addExtensionHudInit(html, combatant, actions) { const hudData = { - combatant: combatant, - actions: actionsCombat, + combatant, actions, commandes: [ { name: "Autre action", command: 'autre' }, - { name: 'Initiative +1', command: 'inc', value: 0.01 }, + { name: 'Initiative +1', command: 'inc', value: 0.01 }, { name: 'Initiative -1', command: 'dec', value: -0.01 }] }; - const controlIconCombat = html.find('.control-icon[data-action=combat]'); - // initiative await RdDTokenHud._configureSubMenu(controlIconCombat, 'systems/foundryvtt-reve-de-dragon/templates/hud-actor-init.html', hudData, @@ -51,48 +62,70 @@ export class RdDTokenHud { RdDTokenHud._initiativeCommand(initCommand, combatantId); } else { let index = event.currentTarget.attributes['data-action-index'].value; - let action = actionsCombat[index]; + let action = hudData.actions[index]; RdDCombatManager.rollInitiativeAction(combatantId, action); - } + } }); + } + static async addExtensionHudCombat(html, combatant, actions) { + const hudData = { combatant, actions, commandes: [] }; const controlIconTarget = html.find('.control-icon[data-action=target]'); - // combat await RdDTokenHud._configureSubMenu(controlIconTarget, 'systems/foundryvtt-reve-de-dragon/templates/hud-actor-attaque.html', hudData, (event) => { const actionIndex = event.currentTarget.attributes['data-action-index']?.value; - const action = actionsCombat[actionIndex]; + const action = hudData.actions[actionIndex]; if (action.action == 'conjurer') { - actor.conjurerPossession(actor.getPossession(action.system.possessionid)); + const possession = combatant.actor.getPossession(action.system.possessionid); + combatant.actor.conjurerPossession(possession); } else { - actor.rollArme(action); + combatant.actor.rollArme(action); } }); } + static async addExtensionHudSoins(html, sourceActor) { + const target = Targets.getTarget({ warn: false }); + if (target?.actor) { + const hudSoins = { blessures: target.actor.blessuresASoigner() ?? [] }; + if (hudSoins.blessures.length > 0) { + // soins + const controlIconTarget = html.find('.control-icon[data-action=combat]'); + await RdDTokenHud._configureSubMenu(controlIconTarget, + 'systems/foundryvtt-reve-de-dragon/templates/hud-actor-soins.hbs', + hudSoins, + (event) => { + const blessureId = event.currentTarget.attributes['data-blessure-id']?.value; + sourceActor.rollSoins(target.actor, blessureId) + }); + } + } + } + static _initiativeCommand(initCommand, combatantId) { switch (initCommand) { case 'inc': return RdDCombatManager.incDecInit(combatantId, 0.01); case 'dec': return RdDCombatManager.incDecInit(combatantId, -0.01); - case 'autre': return RdDCombatManager.rollInitiativeAction(combatantId, + case 'autre': return RdDCombatManager.rollInitiativeAction(combatantId, { name: "Autre action", action: 'autre', system: { initOnly: true, competence: "Autre action" } }); } } /* -------------------------------------------- */ static async addTokenHudExtensions(app, html, tokenId) { - const controlIconCombat = html.find('.control-icon[data-action=combat]'); - controlIconCombat.click(event => { - if (event.currentTarget.className.includes('active')) { - RdDTokenHud.removeExtensionHud(app, html, tokenId); - } else { - setTimeout(function () { RdDTokenHud.addExtensionHud(app, html, tokenId) }, 200); - } - }); + const controlIconCombat = html.find('.control-icon[data-action=combat]'); + if (controlIconCombat.length > 0) { + controlIconCombat.click(event => { + if (event.currentTarget.className.includes('active')) { + RdDTokenHud.removeExtensionHud(app, html, tokenId); + } else { + setTimeout(() => RdDTokenHud.addExtensionHud(app, html, tokenId), 200); + } + }); - if (controlIconCombat.length>0 && controlIconCombat[0].className.includes('active')) { - RdDTokenHud.addExtensionHud(app, html, tokenId); + const isCombat = controlIconCombat[0].className.includes('active'); + RdDTokenHud.addExtensionHud(app, html, tokenId, isCombat); } } @@ -100,9 +133,9 @@ export class RdDTokenHud { static async _configureSubMenu(insertionPoint, template, hudData, onMenuItem) { const hud = $(await renderTemplate(template, hudData)); const list = hud.find('div.rdd-hud-list'); - + RdDTokenHud._toggleHudListActive(hud, list); - + hud.find('img.rdd-hud-togglebutton').click(event => RdDTokenHud._toggleHudListActive(hud, list)); list.find('.rdd-hud-menu').click(onMenuItem); diff --git a/module/rdd-utility.js b/module/rdd-utility.js index 460b96bb..55e968de 100644 --- a/module/rdd-utility.js +++ b/module/rdd-utility.js @@ -19,7 +19,7 @@ import { RdDRaretes } from "./item/raretes.js"; /* -------------------------------------------- */ // This table starts at 0 -> niveau -10 const carac_array = ["taille", "apparence", "constitution", "force", "agilite", "dexterite", "vue", "ouie", "odoratgout", "volonte", "intellect", "empathie", "reve", "chance", "melee", "tir", "lancer", "derobee"]; -const difficultesLibres = Misc.intArray(0, -11); +const difficultesLibres = Misc.intArray(0, -11); const ajustementsConditions = Misc.intArray(-10, 11); const ajustementsEncaissement = Misc.intArray(-10, 26); @@ -66,38 +66,31 @@ const fatigueMarche = { "tresdifficile": { "4": 4, "6": 6 } } -/* -------------------------------------------- */ -const definitionsBlessures = [ - { type: "legere", facteur: 2 }, - { type: "grave", facteur: 4 }, - { type: "critique", facteur: 6 } -] - /* -------------------------------------------- */ const nomEthylisme = ["Emeché", "Gris", "Pinté", "Pas frais", "Ivre", "Bu", "Complètement fait", "Ivre mort"]; /* -------------------------------------------- */ const definitionsEncaissement = { "mortel": [ - { minimum: undefined, maximum: 0, endurance: "0", vie: "0", eraflures: 0, legeres: 0, graves: 0, critiques: 0 }, - { minimum: 1, maximum: 10, endurance: "1d4", vie: "0", eraflures: 1, legeres: 0, graves: 0, critiques: 0 }, - { minimum: 11, maximum: 15, endurance: "1d6", vie: "0", eraflures: 0, legeres: 1, graves: 0, critiques: 0 }, - { minimum: 16, maximum: 19, endurance: "2d6", vie: "2", eraflures: 0, legeres: 0, graves: 1, critiques: 0 }, - { minimum: 20, maximum: undefined, endurance: "100", vie: "4 + @over20", eraflures: 0, legeres: 0, graves: 0, critiques: 1 }, + { minimum: undefined, maximum: 0, endurance: "0", vie: "0", gravite: -1}, + { minimum: 1, maximum: 10, endurance: "1d4", vie: "0", gravite: 0}, + { minimum: 11, maximum: 15, endurance: "1d6", vie: "0", gravite: 2}, + { minimum: 16, maximum: 19, endurance: "2d6", vie: "2", gravite: 4}, + { minimum: 20, maximum: undefined, endurance: "100", vie: "4 + @over20", gravite: 6}, ], "non-mortel": [ - { minimum: undefined, maximum: 0, endurance: "0", vie: "0", eraflures: 0, legeres: 0, graves: 0, critiques: 0 }, - { minimum: 1, maximum: 10, endurance: "1d4", vie: "0", eraflures: 1, legeres: 0, graves: 0, critiques: 0 }, - { minimum: 11, maximum: 15, endurance: "1d6", vie: "0", eraflures: 1, legeres: 0, graves: 0, critiques: 0 }, - { minimum: 16, maximum: 19, endurance: "2d6", vie: "0", eraflures: 0, legeres: 1, graves: 0, critiques: 0 }, - { minimum: 20, maximum: undefined, endurance: "100", vie: "0", eraflures: 0, legeres: 1, graves: 0, critiques: 0 }, + { minimum: undefined, maximum: 0, endurance: "0", vie: "0", gravite: -1}, + { minimum: 1, maximum: 10, endurance: "1d4", vie: "0", gravite: 0 }, + { minimum: 11, maximum: 15, endurance: "1d6", vie: "0", gravite: 0 }, + { minimum: 16, maximum: 19, endurance: "2d6", vie: "0", gravite: 2 }, + { minimum: 20, maximum: undefined, endurance: "100", vie: "0", gravite: 2 }, ], - "cauchemar": [ - { minimum: undefined, maximum: 0, endurance: "0", vie: "0", eraflures: 0, legeres: 0, graves: 0, critiques: 0 }, - { minimum: 1, maximum: 10, endurance: "1d4", vie: "0", eraflures: 1, legeres: 0, graves: 0, critiques: 0 }, - { minimum: 11, maximum: 15, endurance: "1d6", vie: "0", eraflures: 1, legeres: 0, graves: 0, critiques: 0 }, - { minimum: 16, maximum: 19, endurance: "2d6", vie: "0", eraflures: 1, legeres: 0, graves: 0, critiques: 0 }, - { minimum: 20, maximum: undefined, endurance: "3d6 + @over20", vie: "0", eraflures: 1, legeres: 0, graves: 0, critiques: 0 }, + "entiteincarnee": [ + { minimum: undefined, maximum: 0, endurance: "0", vie: "0", gravite: -1}, + { minimum: 1, maximum: 10, endurance: "1d4", vie: "0", gravite: 0}, + { minimum: 11, maximum: 15, endurance: "1d6", vie: "0", gravite: 0 }, + { minimum: 16, maximum: 19, endurance: "2d6", vie: "0", gravite: 0 }, + { minimum: 20, maximum: undefined, endurance: "3d6 + @over20", vie: "0", gravite: 0 }, ] }; @@ -139,7 +132,7 @@ export class RdDUtility { 'systems/foundryvtt-reve-de-dragon/templates/actor/xp-competences.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/combat.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/blessures.html', - 'systems/foundryvtt-reve-de-dragon/templates/actor/blessure.html', + 'systems/foundryvtt-reve-de-dragon/templates/actor/blessure.hbs', 'systems/foundryvtt-reve-de-dragon/templates/actor/maladies-poisons.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/possessions.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/taches.html', @@ -276,7 +269,8 @@ export class RdDUtility { Handlebars.registerHelper('computeResolutionChances', (row, col) => RdDResolutionTable.computeChances(row, col)); Handlebars.registerHelper('upperFirst', str => Misc.upperFirst(str ?? 'Null')); Handlebars.registerHelper('lowerFirst', str => Misc.lowerFirst(str ?? 'Null')); - Handlebars.registerHelper('upper', str => str?.toUpperCase() ?? 'NULL'); + Handlebars.registerHelper('upper', str => str?.toUpperCase() ?? ''); + Handlebars.registerHelper('lowercase', str => str?.toLowerCase() ?? ''); Handlebars.registerHelper('le', str => Grammar.articleDetermine(str)); Handlebars.registerHelper('apostrophe', (article, str) => Grammar.apostrophe(article, str)); Handlebars.registerHelper('un', str => Grammar.articleIndetermine(str)); @@ -472,10 +466,6 @@ export class RdDUtility { return ajustementsEncaissement; } - static getDefinitionsBlessures() { - return definitionsBlessures; - } - /* -------------------------------------------- */ static getSegmentsFatigue(maxEnd) { maxEnd = Math.max(maxEnd, 1); @@ -616,17 +606,10 @@ export class RdDUtility { encaissement.dmg.loc.label = encaissement.dmg.loc.label ?? 'Corps;'; encaissement.roll = roll; encaissement.armure = armure; + encaissement.penetration = rollData.arme?.system.penetration ?? 0; encaissement.total = jetTotal; encaissement.vie = await RdDUtility._evaluatePerte(encaissement.vie, over20); encaissement.endurance = await RdDUtility._evaluatePerte(encaissement.endurance, over20); - encaissement.penetration = rollData.arme?.system.penetration ?? 0; - encaissement.blessures = ( - encaissement.critiques > 0 ? "Critique" : - encaissement.graves > 0 ? "Grave" : - encaissement.legeres > 0 ? "Légère" : - encaissement.eraflures > 0 ? "Contusions/Eraflures" : - 'Aucune' - ); return encaissement; } diff --git a/module/targets.js b/module/targets.js index f975a60d..7217bea1 100644 --- a/module/targets.js +++ b/module/targets.js @@ -39,18 +39,18 @@ export class Targets { } } - static getTarget() { + static getTarget(options = { warn: true }) { const targets = Targets.listTargets(); switch (targets.length) { case 1: return targets[0]; case 0: - ui.notifications.warn("Vous devez choisir une cible à attaquer!"); + if (options.warn) ui.notifications.warn("Vous devez choisir une cible à attaquer!"); break; default: - ui.notifications.warn("Vous devez choisir une cible (et une seule) à attaquer!"); - return; + if (options.warn) ui.notifications.warn("Vous devez choisir une cible (et une seule) à attaquer!"); } + return undefined; } } \ No newline at end of file diff --git a/styles/simple.css b/styles/simple.css index 01c3c55b..f5144534 100644 --- a/styles/simple.css +++ b/styles/simple.css @@ -186,7 +186,7 @@ i:is(.fas, .far) { } .system-foundryvtt-reve-de-dragon .sheet-header :is(.header-compteurs,.header-etats,.profile-img, .profile-img-token){ - padding: 0 3%; + padding: 0 0.4rem; } .system-foundryvtt-reve-de-dragon .sheet-header :is(.profile-img, .profile-img-token) { @@ -213,11 +213,13 @@ i:is(.fas, .far) { } .system-foundryvtt-reve-de-dragon .sheet-header .header-compteurs { + width: calc(60% - 110px - 1rem); text-align: right; max-width: fit-content; } .system-foundryvtt-reve-de-dragon .sheet-header div.header-etats { + width: calc(40% - 32px - 1rem); height: 48px; max-width: fit-content; flex: initial; @@ -426,6 +428,7 @@ span.equipement-detail-buttons { justify-content: center; text-align: left; } + .blessure-control { flex-grow: 1; flex-direction: row; @@ -457,14 +460,17 @@ input:is(.blessure-premiers_soins, .blessure-soins_complets) { .blessure-inactive { color:rgba(150, 150, 150, 0.4); } +.blessure-active-2, .blessure-active-legere { color:rgba(60, 60, 60, 0.9); text-shadow: 1px 1px 4px rgba(60, 60, 60, 1); } +.blessure-active-4, .blessure-active-grave { color: rgba(218, 126, 21, 0.9); text-shadow: 1px 1px 4px rgba(60, 60, 60, 1); } +.blessure-active-6, .blessure-active-critique { color: rgba(173, 36, 26, 0.9); text-shadow: 1px 1px 4px rgba(60, 60, 60, 1); @@ -1386,27 +1392,36 @@ table.table-nombres-astraux tr:hover { } /* ======================================== */ -.tokenhudext { +.token-hud-ext { display: flex; flex: 0 !important; font-family: CaslonPro; font-weight: 600; } -.tokenhudext.left { +.token-hud-ext.left { justify-content: flex-start; flex-direction: column; position: absolute; top: 2.75rem; right: 4rem; } -.tokenhudext.right { +.token-hud-ext.soins { + justify-content: flex-start; + flex-direction: column; + position: absolute; + top: 13.2rem; + left: -5rem; + max-width: 8.5rem +} + +.token-hud-ext.right { justify-content: flex-start; flex-direction: column; position: absolute; top: 2.75rem; left: 4rem; } -.control-icon.tokenhudicon { +.control-icon.token-hud-icon { width: fit-content; height: fit-content; min-width: 6rem; @@ -1415,7 +1430,7 @@ table.table-nombres-astraux tr:hover { line-height: 1rem; margin: 0.2rem; } -.control-icon.tokenhudicon.right { +.control-icon.token-hud-icon.right { margin-left: 8px; } .rdd-hud-menu label { diff --git a/system.json b/system.json index 9d83c073..805704df 100644 --- a/system.json +++ b/system.json @@ -1,8 +1,8 @@ { "id": "foundryvtt-reve-de-dragon", "title": "Rêve de Dragon", - "version": "10.6.25", - "download": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/archive/foundryvtt-reve-de-dragon-10.6.25.zip", + "version": "10.7.0", + "download": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/archive/foundryvtt-reve-de-dragon-10.7.0.zip", "manifest": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/raw/v10/system.json", "compatibility": { "minimum": "10", diff --git a/template.json b/template.json index 52543e5f..a58c064c 100644 --- a/template.json +++ b/template.json @@ -179,23 +179,8 @@ "value": 10, "label": "Endurance", "derivee": false - } - }, - "blessures": { - "legeres": { - "liste": [ { "active": false, "psdone": false, "premiers_soins": -1, "scdone": false, "soins_complets": -1, "jours": 0, "localisation": "" }, - { "active": false, "psdone": false, "premiers_soins": -1, "scdone": false, "soins_complets": -1, "jours": 0, "localisation": "" }, - { "active": false, "psdone": false, "premiers_soins": -1, "scdone": false, "soins_complets": -1, "jours": 0, "localisation": "" }, - { "active": false, "psdone": false, "premiers_soins": -1, "scdone": false, "soins_complets": -1, "jours": 0, "localisation": "" }, - { "active": false, "psdone": false, "premiers_soins": -1, "scdone": false, "soins_complets": -1, "jours": 0, "localisation": "" } ] }, - "graves": { - "liste": [ { "active": false, "psdone": false, "premiers_soins": -1, "scdone": false, "soins_complets": -1, "jours": 0, "localisation": "" }, - { "active": false, "psdone": false, "premiers_soins": -1, "scdone": false, "soins_complets": -1, "jours": 0, "localisation": "" } ] - }, - "critiques": { - "liste": [ { "active": false, "psdone": false, "premiers_soins": -1, "scdone": false, "soins_complets": -1, "jours": 0, "localisation": "" } ] - } + "bonusPotion": 0 }, "attributs": { "plusdom": { @@ -380,23 +365,8 @@ "value": 0, "label": "Fatigue", "derivee": true - } - }, - "blessures": { - "legeres": { - "liste": [ { "active": false, "premiers_soins": 0, "soins_complets": 0, "jours": 0, "localisation": "" }, - { "active": false, "premiers_soins": 0, "soins_complets": 0, "jours": 0, "localisation": "" }, - { "active": false, "premiers_soins": 0, "soins_complets": 0, "jours": 0, "localisation": "" }, - { "active": false, "premiers_soins": 0, "soins_complets": 0, "jours": 0, "localisation": "" }, - { "active": false, "premiers_soins": 0, "soins_complets": 0, "jours": 0, "localisation": "" } ] }, - "graves": { - "liste": [ { "active": false, "premiers_soins": 0, "soins_complets": 0, "jours": 0, "localisation": "" }, - { "active": false, "premiers_soins": 0, "soins_complets": 0, "jours": 0, "localisation": "" } ] - }, - "critiques": { - "liste": [ { "active": false, "premiers_soins": 0, "soins_complets": 0, "jours": 0, "localisation": "" } ] - } + "bonusPotion": 0 }, "attributs": { "sconst": { @@ -567,7 +537,7 @@ "monnaie", "nourritureboisson", "gemme", "service", "meditation", "rencontre", "queue", "ombre", "souffle", "tete", "casetmr", "signedraconique", "sort", "sortreserve", - "nombreastral", "tache", "maladie", "poison", "possession", + "nombreastral", "tache", "blessure", "maladie", "poison", "possession", "tarot", "extraitpoetique" ], "templates": { @@ -636,6 +606,22 @@ "compteur": 0, "date": 0 }, + "blessure": { + "templates": ["temporel"], + "gravite": 0, + "difficulte": 0, + "premierssoins": { + "tache": 0, + "done": false, + "bonus": 0 + }, + "soinscomplets": { + "done": false, + "bonus": 0 + }, + "localisation": "", + "jours": 0 + }, "maladie": { "templates": ["description", "temporel"], "identifie": false, @@ -833,7 +819,8 @@ "points_de_tache_courant": 0, "nb_jet_echec": 0, "nb_jet_succes": 0, - "cacher_points_de_tache": false + "cacher_points_de_tache": false, + "itemId": "" }, "sort": { "templates": ["description"], diff --git a/templates/actor/blessure.hbs b/templates/actor/blessure.hbs new file mode 100644 index 00000000..16d9bc12 --- /dev/null +++ b/templates/actor/blessure.hbs @@ -0,0 +1,38 @@ +