diff --git a/module/actor.js b/module/actor.js index 387aaab7..e21d8d0c 100644 --- a/module/actor.js +++ b/module/actor.js @@ -33,8 +33,9 @@ import { RollDataAjustements } from "./rolldata-ajustements.js"; import { DialogItemAchat } from "./dialog-item-achat.js"; import { RdDItem } from "./item.js"; import { RdDPossession } from "./rdd-possession.js"; -import { ENTITE_BLURETTE, ENTITE_INCARNE, ENTITE_NONINCARNE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js"; +import { ENTITE_BLURETTE, ENTITE_INCARNE, ENTITE_NONINCARNE, HIDE_DICE, SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js"; import { RdDConfirm } from "./rdd-confirm.js"; +import { DialogValidationEncaissement } from "./dialog-validation-encaissement.js"; const POSSESSION_SANS_DRACONIC = { img: 'systems/foundryvtt-reve-de-dragon/icons/entites/possession.webp', @@ -120,7 +121,7 @@ export class RdDActor extends Actor { } if (isPersonnage) { - const competences = await RdDUtility.loadCompendium(RdDItemCompetence.actorCompendium(actorData.type)); + const competences = await RdDUtility.loadItems(it => it.isCompetencePersonnage(), 'foundryvtt-reve-de-dragon.competences'); actorData.items = competences.map(i => i.toObject()); actorData.items = actorData.items.concat(Monnaie.monnaiesStandard()); } @@ -343,7 +344,7 @@ export class RdDActor extends Actor { } /* -------------------------------------------- */ getDraconicList() { - return this.items.filter(it => it.type == 'competence' && it.system.categorie == 'draconic') + return this.items.filter(it => it.isCompetencePersonnage() && it.system.categorie == 'draconic') } /* -------------------------------------------- */ getBestDraconic() { @@ -2317,7 +2318,7 @@ export class RdDActor extends Actor { case "detection d'aura": return draconicList; case "annulation de magie": - return draconicList.filter(it => !Grammar.toLowerCaseNoAccent(it.name).includes('thanatos')); + return draconicList.filter(it => !RdDItemCompetence.isThanatos(it)); } return [RdDItemCompetence.getVoieDraconic(draconicList, sort.system.draconic)]; } @@ -3330,20 +3331,39 @@ export class RdDActor extends Actor { } /* -------------------------------------------- */ - async encaisserDommages(rollData, attacker = undefined, defenderRoll = undefined) { + async encaisserDommages(rollData, attacker = undefined, show = undefined) { if (attacker && !await attacker.accorder(this, 'avant-encaissement')) { return; } + this.validerEncaissement(rollData, show); + } - console.log("encaisserDommages", rollData) + async validerEncaissement(rollData, show) { + if (ReglesOptionelles.isUsing('validation-encaissement-gr') && !game.user.isGM) { + RdDActor.remoteActorCall({ + actorId: this.id, + method: 'validerEncaissement', + args: [rollData, show] + }); + return; + } + const armure = await this.computeArmure(rollData); + if (ReglesOptionelles.isUsing('validation-encaissement-gr')) { + DialogValidationEncaissement.validerEncaissement(this, rollData, armure, show, (encaissement, show) => this._appliquerEncaissement(encaissement, show)); + } + else { + let encaissement = await RdDUtility.jetEncaissement(rollData, armure, { showDice: SHOW_DICE }); + await this._appliquerEncaissement(encaissement, show) + } + } + async _appliquerEncaissement(encaissement, show) { let santeOrig = duplicate(this.system.sante); - let encaissement = await this.jetEncaissement(rollData); this.ajouterBlessure(encaissement); // Will upate the result table const perteVie = this.isEntite() ? { newValue: 0 } - : await this.santeIncDec("vie", - encaissement.vie); + : await this.santeIncDec("vie", -encaissement.vie); const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, encaissement.critiques > 0); this.computeEtatGeneral(); @@ -3356,7 +3376,7 @@ export class RdDActor extends Actor { jetEndurance: perteEndurance.jetEndurance, endurance: santeOrig.endurance.value - perteEndurance.newValue, vie: this.isEntite() ? 0 : (santeOrig.vie.value - perteVie.newValue), - show: defenderRoll?.show ?? {} + show: show ?? {} }); await ChatUtility.createChatWithRollMode(this.name, { @@ -3374,65 +3394,6 @@ export class RdDActor extends Actor { } } - /* -------------------------------------------- */ - async jetEncaissement(rollData) { - let formula = "2d10"; - - // Chaque dé fait au minmum la difficulté libre - if (ReglesOptionelles.isUsing('degat-minimum-malus-libre')) { - if (rollData.diffLibre < 0) { - let valeurMin = Math.abs(rollData.diffLibre); - formula += "min" + valeurMin; - } - } - // Chaque dé fait au minmum la difficulté libre - if (ReglesOptionelles.isUsing('degat-ajout-malus-libre')) { - if (rollData.diffLibre < 0) { - let valeurMin = Math.abs(rollData.diffLibre); - formula += "+" + valeurMin; - } - } - - let roll = await RdDDice.roll(formula); - - // 1 dé fait au minmum la difficulté libre - if (ReglesOptionelles.isUsing('degat-minimum-malus-libre-simple')) { - if (rollData.diffLibre < 0) { - let valeurMin = Math.abs(rollData.diffLibre); - if (roll.terms[0].results[0].result < valeurMin) { - roll.terms[0].results[0].result = valeurMin; - } else if (roll.terms[0].results[1].result < valeurMin) { - roll.terms[0].results[1].result = valeurMin; - } - roll._total = roll.terms[0].results[0].result + roll.terms[0].results[1].result; - } - } - - const armure = await this.computeArmure(rollData); - const jetTotal = roll.total + rollData.dmg.total - armure; - - let encaissement = RdDUtility.selectEncaissement(jetTotal, rollData.dmg.mortalite) - let over20 = Math.max(jetTotal - 20, 0); - encaissement.dmg = rollData.dmg; - encaissement.dmg.loc = rollData.dmg.loc ?? await RdDUtility.getLocalisation(this.type); - encaissement.dmg.loc.label = encaissement.dmg.loc.label ?? 'Corps;' - encaissement.roll = roll; - encaissement.armure = armure; - encaissement.total = jetTotal; - encaissement.vie = await RdDActor._evaluatePerte(encaissement.vie, over20); - encaissement.endurance = await RdDActor._evaluatePerte(encaissement.endurance, over20); - encaissement.penetration = rollData.arme?.system.penetration ?? 0; - - return encaissement; - } - - /* -------------------------------------------- */ - static async _evaluatePerte(formula, over20) { - let perte = new Roll(formula, { over20: over20 }); - await perte.evaluate({ async: true }); - return perte.total; - } - /* -------------------------------------------- */ ajouterBlessure(encaissement) { if (this.type == 'entite') return; // Une entité n'a pas de blessures @@ -3699,8 +3660,7 @@ export class RdDActor extends Actor { actorId: achat.vendeurId ?? achat.acheteurId, method: 'achatVente', args: [achat] - }, - ); + }); return; } @@ -4140,7 +4100,7 @@ export class RdDActor extends Actor { /* -------------------------------------------- */ async onPreUpdateItem(item, change, options, id) { - if (item.type == 'competence' && item.system.defaut_carac && item.system.xp) { + if (item.isCompetencePersonnage() && item.system.defaut_carac && item.system.xp) { await this.checkCompetenceXP(item.name, item.system.xp); } } diff --git a/module/chat-utility.js b/module/chat-utility.js index 86c2384e..cf707dfc 100644 --- a/module/chat-utility.js +++ b/module/chat-utility.js @@ -129,7 +129,7 @@ export class ChatUtility { /* -------------------------------------------- */ static getUsers(filter) { - return Misc.getUsers().filter(filter).map(user => user.id); + return game.users.filter(filter).map(user => user.id); } /* -------------------------------------------- */ diff --git a/module/dialog-validation-encaissement.js b/module/dialog-validation-encaissement.js new file mode 100644 index 00000000..addcf45c --- /dev/null +++ b/module/dialog-validation-encaissement.js @@ -0,0 +1,70 @@ +import { HIDE_DICE, SHOW_DICE } from "./constants.js"; +import { RdDUtility } from "./rdd-utility.js"; + +/** + * Extend the base Dialog entity by defining a custom window to perform roll. + * @extends {Dialog} + */ +export class DialogValidationEncaissement extends Dialog { + + static async validerEncaissement(actor, rollData, armure, show, onEncaisser) { + let encaissement = await RdDUtility.jetEncaissement(rollData, armure, { showDice: HIDE_DICE }); + const html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-validation-encaissement.html', { + actor: actor, + rollData: rollData, + encaissement: encaissement, + show: show + }); + const dialog = new DialogValidationEncaissement(html, actor, rollData, armure, encaissement, show, onEncaisser); + dialog.render(true); + } + + /* -------------------------------------------- */ + constructor(html, actor, rollData, armure, encaissement, show, onEncaisser) { + // Common conf + let buttons = { + "valider": { label: "Valider", callback: html => this.validerEncaissement() }, + "annuler": { label: "Annuler", callback: html => { } }, + }; + + let dialogConf = { + title: "Validation d'encaissement", + content: html, + buttons: buttons, + default: "valider" + } + + let dialogOptions = { + classes: ["rdddialog"], + width: 350, + height: 290 + } + + // Select proper roll dialog template and stuff + super(dialogConf, dialogOptions); + + this.actor = actor + this.rollData = rollData; + this.armure = armure; + this.encaissement = encaissement; + this.show = show; + this.onEncaisser = onEncaisser; + this.forceDiceResult = {total: encaissement.roll.result }; + } + + /* -------------------------------------------- */ + activateListeners(html) { + super.activateListeners(html); + html.find('input.encaissement-roll-result').keyup(async event => { + this.forceDiceResult.total = event.currentTarget.value; + this.encaissement = await RdDUtility.jetEncaissement(this.rollData, this.armure, { showDice: HIDE_DICE, forceDiceResult: this.forceDiceResult}); + $('label.encaissement-total').text(this.encaissement.total); + $('label.encaissement-blessure').text(this.encaissement.blessures) + }); + } + + async validerEncaissement() { + this.encaissement = await RdDUtility.jetEncaissement(this.rollData, this.armure, { showDice: SHOW_DICE, forceDiceResult: this.forceDiceResult}); + this.onEncaisser(this.encaissement, this.show) + } +} diff --git a/module/item-competence.js b/module/item-competence.js index 22766254..9530f0c9 100644 --- a/module/item-competence.js +++ b/module/item-competence.js @@ -57,8 +57,8 @@ const competence_xp_cumul = _buildCumulXP(); export class RdDItemCompetence extends Item { /* -------------------------------------------- */ - static actorCompendium(actorType) { - return compendiumCompetences[actorType]; + static actorCompendium(actorType = undefined) { + return compendiumCompetences[actorType ?? 'personnage']; } /* -------------------------------------------- */ @@ -89,22 +89,28 @@ export class RdDItemCompetence extends Item { /* -------------------------------------------- */ static isCompetenceArme(competence) { - switch (competence.system.categorie) { - case 'melee': - return competence.name != 'Esquive'; - case 'tir': - case 'lancer': - return true; + if (competence.isCompetence()) { + switch (competence.system.categorie) { + case 'melee': + return !Grammar.toLowerCaseNoAccent(competence.name).includes('esquive'); + case 'tir': + case 'lancer': + return true; + } } return false; } /* -------------------------------------------- */ static isArmeUneMain(competence) { - return competence.name.toLowerCase().includes("1 main"); + return RdDItemCompetence.isCompetenceArme(competence) && competence.name.toLowerCase().includes("1 main"); } static isArme2Main(competence) { - return competence.name.toLowerCase().includes("2 main"); + return RdDItemCompetence.isCompetenceArme(competence) && competence.name.toLowerCase().includes("2 main"); + } + + static isThanatos(competence) { + return competence.isCompetencePersonnage() && Grammar.toLowerCaseNoAccent(competence.name).includes('thanatos'); } /* -------------------------------------------- */ @@ -133,7 +139,7 @@ export class RdDItemCompetence extends Item { /* -------------------------------------------- */ static computeXP(competence) { - const factor = competence.name.includes('Thanatos') ? 2 : 1; // Thanatos compte double ! + const factor = RdDItemCompetence.isThanatos(competence) ? 2 : 1; // Thanatos compte double ! const xpNiveau = RdDItemCompetence.computeDeltaXP(competence.system.base, competence.system.niveau ?? competence.system.base); const xp = competence.system.xp ?? 0; const xpSort = competence.system.xp_sort ?? 0; @@ -213,21 +219,14 @@ export class RdDItemCompetence extends Item { if (idOrName == undefined) { return undefined; } - options = mergeObject(options, { - preFilter: it => RdDItemCompetence.isCompetence(it), - description: 'compétence', - }); - return list.find(it => it.id == idOrName && RdDItemCompetence.isCompetence(it)) + options = mergeObject(options, { preFilter: it => it.isCompetence(), description: 'compétence', }); + return list.find(it => it.id == idOrName && it.isCompetence()) ?? Misc.findFirstLike(idOrName, list, options); } /* -------------------------------------------- */ static findCompetences(list, name) { - return Misc.findAllLike(name, list, { filter: it => RdDItemCompetence.isCompetence(it), description: 'compétence' }); - } - - static isCompetence(item) { - return item.type == 'competence' || item.type == 'competencecreature'; + return Misc.findAllLike(name, list, { filter: it => it.isCompetence(), description: 'compétence' }); } /* -------------------------------------------- */ diff --git a/module/item-sheet.js b/module/item-sheet.js index 3e1d3429..1a92be79 100644 --- a/module/item-sheet.js +++ b/module/item-sheet.js @@ -90,11 +90,10 @@ export class RdDItemSheet extends ItemSheet { if (this.item.type == 'tache' || this.item.type == 'livre' || this.item.type == 'meditation' || this.item.type == 'oeuvre') { formData.caracList = duplicate(game.system.model.Actor.personnage.carac) formData.caracList["reve-actuel"] = duplicate(game.system.model.Actor.personnage.reve.reve) - formData.competences = await RdDUtility.loadCompendium('foundryvtt-reve-de-dragon.competences') + formData.competences = await RdDUtility.loadItems(it => it.isCompetencePersonnage(), RdDItemCompetence.actorCompendium(this.actor?.type)) } if (this.item.type == 'arme') { - formData.competences = await RdDUtility.loadCompendium('foundryvtt-reve-de-dragon.competences', it => RdDItemCompetence.isCompetenceArme(it)); - console.log(formData.competences) + formData.competences = await RdDUtility.loadItems(it => RdDItemCompetence.isCompetenceArme(it), RdDItemCompetence.actorCompendium(this.actor?.type)) } if (this.item.type == 'recettecuisine') { formData.ingredients = await TextEditor.enrichHTML(this.object.system.ingredients, {async: true}) @@ -115,7 +114,7 @@ export class RdDItemSheet extends ItemSheet { formData.system.prdate = this.dateUpdated; this.dateUpdated = undefined; } - RdDHerbes.updatePotionData(formData); + await RdDHerbes.updatePotionData(formData); } if (formData.isOwned && this.item.type == 'herbe' && (formData.system.categorie == 'Soin' || formData.system.categorie == 'Repos')) { formData.isIngredientPotionBase = true; @@ -160,7 +159,7 @@ export class RdDItemSheet extends ItemSheet { html.find(".categorie").change(event => this._onSelectCategorie(event)); html.find('.sheet-competence-xp').change((event) => { - if (this.item.type == 'competence') { + if (this.item.isCompetencePersonnage()) { RdDUtility.checkThanatosXP(this.item.name); } }); diff --git a/module/item.js b/module/item.js index 22e60a37..30bd3859 100644 --- a/module/item.js +++ b/module/item.js @@ -21,7 +21,7 @@ const typesObjetsOeuvres = ["oeuvre", "recettecuisine", "musique", "chant", "dan const typesObjetsDraconiques = ["queue", "ombre", "souffle", "tete", "signedraconique", "sortreserve"] const typesObjetsConnaissance = ["meditation", "recettealchimique", "sort"] const typesObjetsEffet = ["possession", "poison", "maladie"] -const typesObjetsCompetence = ["competence", "compcreature"] +const typesObjetsCompetence = ["competence", "competencecreature"] 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 densité 3.5 (~2.3 à 4, parfois plus) -- https://www.juwelo.fr/guide-des-pierres/faits-et-chiffres/ @@ -29,7 +29,7 @@ densité 3.5 (~2.3 à 4, parfois plus) -- https://www.juwelo.fr/guide-des-pierre export const defaultItemImg = { competence: "systems/foundryvtt-reve-de-dragon/icons/competence_defaut.webp", - compcreature: "systems/foundryvtt-reve-de-dragon/icons/competence_defaut.webp", + competencecreature: "systems/foundryvtt-reve-de-dragon/icons/competence_defaut.webp", arme: "systems/foundryvtt-reve-de-dragon/icons/armes_armures/epee_gnome.webp", armure: "systems/foundryvtt-reve-de-dragon/icons/armes_armures/armure_plaques.webp", conteneur: "systems/foundryvtt-reve-de-dragon/icons/objets/sac_a_dos.webp", @@ -77,6 +77,9 @@ export class RdDItem extends Item { return typesObjetsOeuvres } + isCompetencePersonnage() { + return this.type == 'competence' + } isCompetence() { return typesObjetsCompetence.includes(this.type) } @@ -180,8 +183,8 @@ export class RdDItem extends Item { this.system.magique = categorie.includes('enchante'); if (this.system.magique) { if (categorie.includes('soin') || categorie.includes('repos')) { - // TODO: utiliser calculePointsRepos / calculePointsGuerison - this.system.puissance = RdDHerbes.calculePuissancePotion(this); + // TODO: utiliser calculPointsRepos / calculPointsGuerison + this.system.puissance = RdDHerbes.calculPuissancePotion(this); } } } diff --git a/module/misc.js b/module/misc.js index 682c9aec..7f851799 100644 --- a/module/misc.js +++ b/module/misc.js @@ -112,16 +112,12 @@ export class Misc { return Misc.firstConnectedGM()?.id ?? game.user.id; } - static getUsers() { - return game.version ? game.users : game.users.entities; - } - static getActiveUser(id) { - return Misc.getUsers().find(u => u.id == id && u.active); + return game.users.find(u => u.id == id && u.active); } static firstConnectedGM() { - return Misc.getUsers().filter(u => u.isGM && u.active).sort(Misc.ascending(u => u.id)).find(u => u.isGM && u.active); + return game.users.filter(u => u.isGM && u.active).sort(Misc.ascending(u => u.id)).find(u => u.isGM && u.active); } @@ -142,7 +138,7 @@ export class Misc { /* -------------------------------------------- */ static findPlayer(name) { - return Misc.findFirstLike(name, Misc.getUsers(), { description: 'joueur' }); + return Misc.findFirstLike(name, game.users, { description: 'joueur' }); } /* -------------------------------------------- */ diff --git a/module/rdd-combat.js b/module/rdd-combat.js index 5108c363..af430b59 100644 --- a/module/rdd-combat.js +++ b/module/rdd-combat.js @@ -721,12 +721,12 @@ export class RdDCombat { } } - isVisible(token, defender) { - return canvas.effects.visibility.testVisibility(defender.center, { object: token }) + isVisible(token, defenderToken) { + return canvas.effects.visibility.testVisibility(defenderToken.center, { object: token }) } - distance(t, defenderToken) { - return Number(canvas.grid.measureDistances([{ ray: new Ray(t.center, defenderToken.center) }], { gridSpaces: false })).toFixed(1); + distance(token, defenderToken) { + return Number(canvas.grid.measureDistances([{ ray: new Ray(token.center, defenderToken.center) }], { gridSpaces: false })).toFixed(1); } _ajustementPortee(dist, arme) { @@ -878,7 +878,7 @@ export class RdDCombat { attackerRoll.dmg = RdDBonus.dmg(attackerRoll, this.attacker.getBonusDegat(), this.defender.isEntite()); let defenderRoll = { attackerRoll: attackerRoll, passeArme: attackerRoll.passeArme, show: {} } attackerRoll.show = { - cible: this.target ? this.defender.system.name : 'la cible', + cible: this.target ? this.defender.name : 'la cible', isRecul: (attackerRoll.particuliere == 'force' || attackerRoll.tactique == 'charge') } await RdDResolutionTable.displayRollData(attackerRoll, this.attacker, 'chat-resultat-attaque.html'); @@ -1303,7 +1303,7 @@ export class RdDCombat { attackerRoll.defenderTokenId = defenderTokenId; await this.computeRecul(defenderRoll); - this.defender.encaisserDommages(attackerRoll, this.attacker, defenderRoll); + this.defender.encaisserDommages(attackerRoll, this.attacker, defenderRoll?.show); } else { // envoi à un GM: les joueurs n'ont pas le droit de modifier les personnages qu'ils ne possèdent pas game.socket.emit(SYSTEM_SOCKET_ID, { diff --git a/module/rdd-dice.js b/module/rdd-dice.js index 9b2f8ded..cba20726 100644 --- a/module/rdd-dice.js +++ b/module/rdd-dice.js @@ -132,18 +132,15 @@ export class RdDDice { } } - static async roll(formula, options = { showDice: SHOW_DICE, rollMode: undefined }) { - const roll = new Roll(formula); - await roll.evaluate({ async: true }); - if (options.showDice != HIDE_DICE) { - await this.showDiceSoNice(roll, options.rollMode ?? game.settings.get("core", "rollMode")); - } - return roll; + static async rollTotal(formula, options = { showDice: HIDE_DICE }) { + return (await RdDDice.roll(formula, options)).total; } - static async rollTotal(formula, options = { showDice: HIDE_DICE}) { - const roll = await RdDDice.roll(formula, options); - return roll.total; + static async roll(formula, options = { showDice: SHOW_DICE, rollMode: undefined }) { + const roll = new Roll(RdDDice._formulaOrFake(formula, options)); + await roll.evaluate({ async: true }); + await this.showDiceSoNice(roll, options); + return roll; } static async rollOneOf(array) { @@ -160,27 +157,106 @@ export class RdDDice { } /* -------------------------------------------- */ - static async showDiceSoNice(roll, rollMode) { - if (game.modules.get("dice-so-nice")?.active) { - if (game.dice3d) { - let whisper = null; - let blind = false; - rollMode = rollMode ?? game.settings.get("core", "rollMode"); - switch (rollMode) { - case "blindroll": //GM only - blind = true; - case "gmroll": //GM + rolling player - whisper = ChatUtility.getUsers(user => user.isGM); - break; - case "roll": //everybody - whisper = ChatUtility.getUsers(user => user.active); - break; - case "selfroll": - whisper = [game.user.id]; - break; - } - await game.dice3d.showForRoll(roll, game.user, true, whisper, blind); + static async showDiceSoNice(roll, options) { + if (options.showDice == HIDE_DICE || !game.modules.get("dice-so-nice")?.active || !game.dice3d) { + return; + } + + let { whisper, blind } = RdDDice._getWhisperBlind(options); + if (options.forceDiceResult?.total) { + let terms = await RdDDice._getForcedTerms(options); + if (terms) { + await game.dice3d.show({ throws: [{ dice: terms }] }) + return; } } + await game.dice3d.showForRoll(roll, game.user, true, whisper, blind); + } + + static _formulaOrFake(formula, options) { + if (options?.forceDiceResult?.total) { + options.forceDiceResult.formula = formula; + return options.forceDiceResult.total.toString() + } + return formula; + } + + static async _getForcedTerms(options) { + const total = options.forceDiceResult.total; + switch (options.forceDiceResult.formula) { + case '1d100': + return terms1d100(total); + case "2d10": + return await terms2d10(total); + } + return undefined; + + function terms1d100(total) { + const unites = total % 10; + const dizaines = Math.floor(total / 10); + return [{ + resultLabel: dizaines * 10, + d100Result: total, + result: dizaines, + type: "d100", + vectors: [], + options: {} + }, + { + resultLabel: unites, + d100Result: total, + result: unites, + type: "d10", + vectors: [], + options: {} + }]; + } + + async function terms2d10(total) { + if (total>20 || total<2) { return undefined } + let first = await RdDDice.d10(); + let second = Math.min(total-first, 10); + first = Math.max(first, total-second); + return [{ + resultLabel:first, + result: first, + type: "d10", + vectors: [], + options: {} + }, + { + resultLabel: second, + result: second, + type: "d10", + vectors: [], + options: {} + }]; + } + } + + static async d10() { + let roll = new Roll('1d10'); + await roll.evaluate({ async: true }); + return roll.total; + } + + static _getWhisperBlind(options) { + let whisper = null; + let blind = false; + let rollMode = options.rollMode ?? game.settings.get("core", "rollMode"); + switch (rollMode) { + case "blindroll": //GM only + blind = true; + case "gmroll": //GM + rolling player + whisper = ChatUtility.getUsers(user => user.isGM); + break; + case "roll": //everybody + whisper = ChatUtility.getUsers(user => user.active); + break; + case "selfroll": + whisper = [game.user.id]; + break; + } + return { whisper, blind }; } } \ No newline at end of file diff --git a/module/rdd-herbes.js b/module/rdd-herbes.js index d11a38f2..32abaebc 100644 --- a/module/rdd-herbes.js +++ b/module/rdd-herbes.js @@ -1,76 +1,75 @@ import { RdDUtility } from "./rdd-utility.js"; import { RdDCalendrier } from "./rdd-calendrier.js"; +import { Grammar } from "./grammar.js"; /* -------------------------------------------- */ export class RdDHerbes extends Item { /* -------------------------------------------- */ - static isHerbeSoin( botaniqueItem ) { - return botaniqueItem.categorie == 'Soin'; - } - /* -------------------------------------------- */ - static isHerbeRepos( botaniqueItem ) { - return botaniqueItem.categorie == 'Repos'; + static async initializeHerbes() { + this.herbesSoins = await RdDHerbes.listCategorieHerbes('Soin'); + this.herbesRepos = await RdDHerbes.listCategorieHerbes('Repos'); } - /* -------------------------------------------- */ - static async initializeHerbes( ) { - this.herbesSoins = await RdDUtility.loadCompendium('foundryvtt-reve-de-dragon.botanique', item => this.isHerbeSoin(item)); - this.herbesRepos = await RdDUtility.loadCompendium('foundryvtt-reve-de-dragon.botanique', item => this.isHerbeRepos(item)); + static async listCategorieHerbes(categorie) { + return await RdDUtility.loadItems( + it => it.type == 'herbe' && it.system.categorie.toLowerCase() == categorie.toLowerCase(), + 'foundryvtt-reve-de-dragon.botanique'); } /* -------------------------------------------- */ static buildHerbesList(listeHerbes, max) { let list = {} - for ( let herbe of listeHerbes) { + for (let herbe of listeHerbes) { let brins = max - herbe.system.niveau; - list[herbe.system.name] = `${herbe.system.name} (Bonus: ${herbe.system.niveau}, Brins: ${brins})`; + list[herbe.name] = `${herbe.name} (Bonus: ${herbe.system.niveau}, Brins: ${brins})`; } list['Autre'] = 'Autre (Bonus: variable, Brins: variable)' return list; } /* -------------------------------------------- */ - static updatePotionData( formData ) { - formData.herbesSoins = this.buildHerbesList(this.herbesSoins, 12); - formData.herbesRepos = this.buildHerbesList(this.herbesRepos, 7); + static async updatePotionData(formData) { + formData.isSoins = formData.system.categorie.includes('Soin'); + formData.isRepos = formData.system.categorie.includes('Repos'); + if (formData.isSoins) { + RdDHerbes.calculBonusHerbe(formData, this.herbesSoins, 12); + } + if (formData.isRepos) { + RdDHerbes.calculBonusHerbe(formData, this.herbesRepos, 7); + } + formData.herbesSoins = RdDHerbes.buildHerbesList(this.herbesSoins, 12); + formData.herbesRepos = RdDHerbes.buildHerbesList(this.herbesRepos, 7); formData.jourMoisOptions = RdDCalendrier.buildJoursMois(); formData.dateActuelle = game.system.rdd.calendrier.getDateFromIndex(); formData.splitDate = game.system.rdd.calendrier.getNumericDateFromIndex(formData.system.prdate); - - if (formData.system.categorie.includes('Soin') ) { - formData.isHerbe = true; - this.computeHerbeBonus(formData, this.herbesSoins, 12); - } else if (formData.system.categorie.includes('Repos')) { - formData.isRepos = true; - this.computeHerbeBonus(formData, this.herbesRepos, 7); - } } /* -------------------------------------------- */ - static calculePuissancePotion( potion ) { + static calculPuissancePotion(potion) { return potion.system.herbebonus * potion.system.pr; } /* -------------------------------------------- */ - static calculePointsRepos( potion ) { + static calculPointsRepos(potion) { return potion.system.herbebonus * potion.system.pr; } /* -------------------------------------------- */ - static calculePointsGuerison( potion ){ + static calculPointsGuerison(potion) { return potion.system.herbebonus * potion.system.pr; } /* -------------------------------------------- */ - static computeHerbeBonus( formData, herbesList, max) { - if ( Number(formData.system.herbebrins) ) { - let herbe = herbesList.find(item => item.name.toLowerCase() == formData.system.herbe.toLowerCase() ); - if( herbe ) { - let brinsBase = max - herbe.system.niveau; - formData.system.herbebonus = Math.max(herbe.system.niveau - Math.max(brinsBase - formData.system.herbebrins, 0), 0); + static calculBonusHerbe(formData, herbesList, max) { + if (Number(formData.system.herbebrins)) { + let herbe = herbesList.find(item => item.name.toLowerCase() == formData.system.herbe.toLowerCase()); + if (herbe) { + const brinsRequis = max - herbe.system.niveau; + const brinsManquants = Math.max(brinsRequis - formData.system.herbebrins, 0); + formData.system.herbebonus = Math.max(herbe.system.niveau - brinsManquants, 0); } } - } + } } \ No newline at end of file diff --git a/module/rdd-resolution-table.js b/module/rdd-resolution-table.js index 88389a9c..37eac10a 100644 --- a/module/rdd-resolution-table.js +++ b/module/rdd-resolution-table.js @@ -142,10 +142,8 @@ export class RdDResolutionTable { /* -------------------------------------------- */ static async rollChances(chances, diviseur, forceDiceResult = -1) { - if (forceDiceResult <= 0 || forceDiceResult > 100) { - forceDiceResult = -1; - } - chances.roll = await RdDDice.rollTotal((forceDiceResult == -1) ? "1d100" : `${forceDiceResult}`, chances); + chances.forceDiceResult = forceDiceResult <= 0 || forceDiceResult > 100 ? undefined : {total: forceDiceResult}; + chances.roll = await RdDDice.rollTotal( "1d100", chances); mergeObject(chances, this.computeReussite(chances, chances.roll, diviseur), { overwrite: true }); return chances; } diff --git a/module/rdd-roll-encaisser.js b/module/rdd-roll-encaisser.js index 5f2fa08a..109e6fbf 100644 --- a/module/rdd-roll-encaisser.js +++ b/module/rdd-roll-encaisser.js @@ -1,4 +1,4 @@ -import { ENTITE_BLURETTE, ENTITE_INCARNE, ENTITE_NONINCARNE } from "./constants.js"; +import { ENTITE_BLURETTE, ENTITE_INCARNE} from "./constants.js"; /** * Extend the base Dialog entity by defining a custom window to perform roll. @@ -33,7 +33,7 @@ export class RdDEncaisser extends Dialog { let dialogOptions = { classes: ["rdddialog"], width: 320, - height: 240 + height: 260 } // Select proper roll dialog template and stuff diff --git a/module/rdd-roll.js b/module/rdd-roll.js index 063a9c5a..fece5d69 100644 --- a/module/rdd-roll.js +++ b/module/rdd-roll.js @@ -144,6 +144,7 @@ export class RdDRoll extends Dialog { /* -------------------------------------------- */ async onAction(action, html) { + this.rollData.forceDiceResult = Number.parseInt($('#force-dice-result').val()) ?? -1; await RdDResolutionTable.rollData(this.rollData); console.log("RdDRoll -=>", this.rollData, this.rollData.rolled); this.actor.setRollWindowsOpened(false); diff --git a/module/rdd-rolltables.js b/module/rdd-rolltables.js index 950a50af..3f90a5bd 100644 --- a/module/rdd-rolltables.js +++ b/module/rdd-rolltables.js @@ -2,16 +2,22 @@ export class RdDRollTables { /* -------------------------------------------- */ static async genericGetTableResult(tableName, toChat) { - let table = game.tables.find(table => table.name.toLowerCase() == tableName.toLowerCase()) - if ( !table) { - const pack = game.packs.get("foundryvtt-reve-de-dragon.tables-diverses"); - const index = await pack.getIndex(); - const entry = index.find(e => e.name === tableName); - table = await pack.getDocument(entry._id); - } + let table = RdDRollTables.getWorldTable() ?? (await RdDRollTables.getSystemTable(tableName)); const draw = await table.draw({ displayChat: toChat, rollMode: "gmroll"}); //console.log("RdDRollTables", tableName, toChat, ":", draw); return draw.results.length > 0 ? draw.results[0] : undefined; + + } + + static getWorldTable() { + return game.tables.find(table => table.name.toLowerCase() == tableName.toLowerCase()); + } + + static async getSystemTable(tableName) { + const pack = game.packs.get("foundryvtt-reve-de-dragon.tables-diverses"); + const index = await pack.getIndex(); + const entry = index.find(e => e.name === tableName); + return await pack.getDocument(entry._id); } /* -------------------------------------------- */ diff --git a/module/rdd-utility.js b/module/rdd-utility.js index 80be7814..1e14f75c 100644 --- a/module/rdd-utility.js +++ b/module/rdd-utility.js @@ -13,6 +13,7 @@ import { Monnaie } from "./item-monnaie.js"; import { RdDPossession } from "./rdd-possession.js"; import { RdDNameGen } from "./rdd-namegen.js"; import { RdDConfirm } from "./rdd-confirm.js"; +import { RdDActor } from "./actor.js"; /* -------------------------------------------- */ // This table starts at 0 -> niveau -10 @@ -213,6 +214,7 @@ export class RdDUtility { 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html', 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-sort.html', 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-encaisser.html', + 'systems/foundryvtt-reve-de-dragon/templates/dialog-validation-encaissement.html', 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-meditation.html', 'systems/foundryvtt-reve-de-dragon/templates/dialog-tmr.html', 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-alchimie.html', @@ -652,7 +654,68 @@ export class RdDUtility { } /* -------------------------------------------- */ - static selectEncaissement(degats, mortalite) { + static async jetEncaissement(rollData, armure, options = { showDice: HIDE_DICE }) { + let formula = "2d10"; + + // Chaque dé fait au minmum la difficulté libre + if (ReglesOptionelles.isUsing('degat-minimum-malus-libre')) { + if (rollData.diffLibre < 0) { + let valeurMin = Math.abs(rollData.diffLibre); + formula += "min" + valeurMin; + } + } + // Chaque dé fait au minmum la difficulté libre + if (ReglesOptionelles.isUsing('degat-ajout-malus-libre')) { + if (rollData.diffLibre < 0) { + let valeurMin = Math.abs(rollData.diffLibre); + formula += "+" + valeurMin; + } + } + + let roll = await RdDDice.roll(formula, options); + + // 1 dé fait au minmum la difficulté libre + if (ReglesOptionelles.isUsing('degat-minimum-malus-libre-simple')) { + if (rollData.diffLibre < 0) { + let valeurMin = Math.abs(rollData.diffLibre); + if (roll.terms[0].results[0].result < valeurMin) { + roll.terms[0].results[0].result = valeurMin; + } else if (roll.terms[0].results[1].result < valeurMin) { + roll.terms[0].results[1].result = valeurMin; + } + roll._total = roll.terms[0].results[0].result + roll.terms[0].results[1].result; + } + } + + return await RdDUtility.prepareEncaissement(rollData, roll, armure); + } + + /* -------------------------------------------- */ + static async prepareEncaissement(rollData, roll, armure) { + const jetTotal = roll.total + rollData.dmg.total - armure; + let encaissement = RdDUtility._selectEncaissement(jetTotal, rollData.dmg.mortalite); + let over20 = Math.max(jetTotal - 20, 0); + encaissement.dmg = rollData.dmg; + encaissement.dmg.loc = rollData.dmg.loc ?? await RdDUtility.getLocalisation(this.type); + encaissement.dmg.loc.label = encaissement.dmg.loc.label ?? 'Corps;'; + encaissement.roll = roll; + encaissement.armure = armure; + 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; + } + + /* -------------------------------------------- */ + static _selectEncaissement(degats, mortalite) { const table = definitionsEncaissement[mortalite] === undefined ? definitionsEncaissement["mortel"] : definitionsEncaissement[mortalite]; for (let encaissement of table) { if ((encaissement.minimum === undefined || encaissement.minimum <= degats) @@ -663,6 +726,13 @@ export class RdDUtility { return duplicate(table[0]); } + /* -------------------------------------------- */ + static async _evaluatePerte(formula, over20) { + let perte = new Roll(formula, { over20: over20 }); + await perte.evaluate({ async: true }); + return perte.total; + } + /* -------------------------------------------- */ static currentFatigueMalus(value, max) { if (ReglesOptionelles.isUsing("appliquer-fatigue")) { @@ -683,17 +753,28 @@ export class RdDUtility { } /* -------------------------------------------- */ - static async loadCompendiumData(compendium) { - const pack = game.packs.get(compendium); - return await pack?.getDocuments() ?? []; + static async loadItems(filter, compendium) { + let items = game.items.filter(filter); + if (compendium) { + const ids = items.map(it => it.id); + const names = items.map(it => it.name.toLowerCase()); + items = items.concat(await RdDUtility.loadCompendium(compendium, it => !ids.includes(it.id) && !names.includes(it.name.toLowerCase()) && filter(it))); + } + return items; } /* -------------------------------------------- */ - static async loadCompendium(compendium, filter = item => true) { + static async loadCompendium(compendium, filter = it => true) { let compendiumData = await RdDUtility.loadCompendiumData(compendium); return compendiumData.filter(filter); } + /* -------------------------------------------- */ + static async loadCompendiumData(compendium) { + const pack = game.packs.get(compendium); + return await pack?.getDocuments() ?? []; + } + /* -------------------------------------------- */ static async responseNombreAstral(callData) { let actor = game.actors.get(callData.id); diff --git a/module/regles-optionelles.js b/module/regles-optionelles.js index 9ba22044..ec93b239 100644 --- a/module/regles-optionelles.js +++ b/module/regles-optionelles.js @@ -2,29 +2,32 @@ import { SYSTEM_RDD } from "./constants.js"; import { Misc } from "./misc.js"; const listeReglesOptionelles = [ - { name: 'recul', group: 'Règles de combat', descr: "Appliquer le recul en cas de particulière en force ou de charge" }, - { name: 'resistanceArmeParade', group: 'Règles de combat', descr: "Faire le jet de résistance des armes lors de parades pouvant les endommager" }, - { name: 'deteriorationArmure', group: 'Règles de combat', descr: "Tenir compte de la détérioration des armures" }, - { name: 'defenseurDesarme', group: 'Règles de combat', descr: "Le défenseur peut être désarmé en parant une particulière en force ou une charge avec une arme autre qu'un bouclier" }, - { name: 'categorieParade', group: 'Règles de combat', descr: "Le défenseur doit obtenir une significative en cas de parade avec des armes de catégories différentes" }, - { name: 'tripleSignificative', group: 'Règles de combat', descr: "En cas de demi-surprise, d'attaque particulière en finesse, et de catégories d'armes différentes, le défenseur doit obtenir 1/8 des chances de succès" }, - { name: 'degat-minimum-malus-libre-simple', group: 'Règles de combat', descr: "Le malus libre d'attaque remplace une des valeurs de dés d'encaissement si elle est plus petite. Exemple : la difficulté libre de l'attaquant est de -4. Sur le jet d'encaissement, si 1 résultat est inférieur à 4, alors il devient 4.", default: false }, - { name: 'degat-minimum-malus-libre', group: 'Règles de combat', descr: "Le malus libre d'attaque remplace une valeur de dés d'encaissement si elle est plus petite. Exemple : la difficulté libre de l'attaquant est de -4. Sur le jet d'encaissement, tout résultat inférieur à 4 devient 4.", default: false }, - { name: 'degat-ajout-malus-libre', group: 'Règles de combat', descr: "Le malus libre d'attaque s'ajoute au jet d'encaissement et aux autres bonus. Exemple : la difficulté libre de l'attaquant est de -4. Le jet d'encaissement est effectué à 2d10+4, plus les bonus de situation et d'armes.", default: false }, - { name: 'astrologie', group: 'Règles générales', descr: "Appliquer les ajustements astrologiques aux jets de chance et aux rituels"}, - { name: 'afficher-prix-joueurs', group: 'Règles générales', descr: "Afficher le prix de l'équipement des joueurs", uniquementJoueur: true}, - { name: 'appliquer-fatigue', group: 'Règles générales', descr: "Appliquer les règles de fatigue"}, - { name: 'afficher-colonnes-reussite', group: 'Règles générales', descr: "Afficher le nombre de colonnes de réussite ou d'échec", default: false }, - { name: 'confirmation-tmr', group: 'Confirmations', descr: "Confirmer pour monter dans les TMR", scope: "client"}, - { name: 'confirmation-vider', group: 'Confirmations', descr: "Confirmer pour vider l'équipement", scope: "client"}, - { name: 'confirmation-supprimer-lien-acteur', group: 'Confirmations', descr: "Confirmer pour détacher un animal/suivant/véhicule", scope: "client"}, - { name: 'confirmation-supprimer-equipement', group: 'Confirmations', descr: "Confirmer la suppression des équipements", scope: "client"}, - { name: 'confirmation-supprimer-oeuvre', group: 'Confirmations', descr: "Confirmer la suppression des oeuvres", scope: "client"}, - { name: 'confirmation-supprimer-connaissance', group: 'Confirmations', descr: "Confirmer la suppression des connaissances", scope: "client"}, - { name: 'confirmation-supprimer-draconique', group: 'Confirmations', descr: "Confirmer la suppression des queues, souffles, têtes", scope: "client"}, - { name: 'confirmation-supprimer-effet', group: 'Confirmations', descr: "Confirmer la suppression des effets", scope: "client"}, - { name: 'confirmation-supprimer-competence', group: 'Confirmations', descr: "Confirmer la suppression des compétences", scope: "client"}, - { name: 'confirmation-supprimer-autres', group: 'Confirmations', descr: "Confirmer la suppression des autres types d'Objets", scope: "client"}, + { group: 'Règles de combat', name: 'recul', descr: "Appliquer le recul en cas de particulière en force ou de charge" }, + { group: 'Règles de combat', name: 'resistanceArmeParade', descr: "Faire le jet de résistance des armes lors de parades pouvant les endommager" }, + { group: 'Règles de combat', name: 'deteriorationArmure', descr: "Tenir compte de la détérioration des armures" }, + { group: 'Règles de combat', name: 'defenseurDesarme', descr: "Le défenseur peut être désarmé en parant une particulière en force ou une charge avec une arme autre qu'un bouclier" }, + { group: 'Règles de combat', name: 'categorieParade', descr: "Le défenseur doit obtenir une significative en cas de parade avec des armes de catégories différentes" }, + { group: 'Règles de combat', name: 'tripleSignificative', descr: "En cas de demi-surprise, d'attaque particulière en finesse, et de catégories d'armes différentes, le défenseur doit obtenir 1/8 des chances de succès" }, + { group: 'Règles de combat', name: 'degat-minimum-malus-libre-simple', descr: "Le malus libre d'attaque remplace une des valeurs de dés d'encaissement si elle est plus petite. Exemple : la difficulté libre de l'attaquant est de -4. Sur le jet d'encaissement, si le plus petit dé est inférieur à 4, alors il devient 4.", default: false }, + { group: 'Règles de combat', name: 'degat-minimum-malus-libre', descr: "Le malus libre d'attaque remplace une valeur de dés d'encaissement si elle est plus petite. Exemple : la difficulté libre de l'attaquant est de -4. Sur le jet d'encaissement, tout résultat inférieur à 4 devient 4.", default: false }, + { group: 'Règles de combat', name: 'degat-ajout-malus-libre', descr: "Le malus libre d'attaque s'ajoute au jet d'encaissement et aux autres bonus. Exemple : la difficulté libre de l'attaquant est de -4. Le jet d'encaissement est effectué à 2d10+4, plus les bonus de situation et d'armes.", default: false }, + { group: 'Règles de combat', name: 'validation-encaissement-gr', descr: "Le Gardien des Rêves doit valider les jets d'encaissement et peut les changer.", default: false }, + + { group: 'Règles générales', name: 'astrologie', descr: "Appliquer les ajustements astrologiques aux jets de chance et aux rituels"}, + { group: 'Règles générales', name: 'afficher-prix-joueurs', descr: "Afficher le prix de l'équipement des joueurs", uniquementJoueur: true}, + { group: 'Règles générales', name: 'appliquer-fatigue', descr: "Appliquer les règles de fatigue"}, + { group: 'Règles générales', name: 'afficher-colonnes-reussite', descr: "Afficher le nombre de colonnes de réussite ou d'échec", default: false }, + + { group: 'Confirmations', name: 'confirmation-tmr', descr: "Confirmer pour monter dans les TMR", scope: "client"}, + { group: 'Confirmations', name: 'confirmation-vider', descr: "Confirmer pour vider l'équipement", scope: "client"}, + { group: 'Confirmations', name: 'confirmation-supprimer-lien-acteur', descr: "Confirmer pour détacher un animal/suivant/véhicule", scope: "client"}, + { group: 'Confirmations', name: 'confirmation-supprimer-equipement', descr: "Confirmer la suppression des équipements", scope: "client"}, + { group: 'Confirmations', name: 'confirmation-supprimer-oeuvre', descr: "Confirmer la suppression des oeuvres", scope: "client"}, + { group: 'Confirmations', name: 'confirmation-supprimer-connaissance', descr: "Confirmer la suppression des connaissances", scope: "client"}, + { group: 'Confirmations', name: 'confirmation-supprimer-draconique', descr: "Confirmer la suppression des queues, souffles, têtes", scope: "client"}, + { group: 'Confirmations', name: 'confirmation-supprimer-effet', descr: "Confirmer la suppression des effets", scope: "client"}, + { group: 'Confirmations', name: 'confirmation-supprimer-competence', descr: "Confirmer la suppression des compétences", scope: "client"}, + { group: 'Confirmations', name: 'confirmation-supprimer-autres', descr: "Confirmer la suppression des autres types d'Objets", scope: "client"}, ]; const uniquementJoueur = listeReglesOptionelles.filter(it => it.uniquementJoueur).map(it=>it.name); @@ -73,7 +76,7 @@ export class ReglesOptionelles extends FormApplication { const regles = listeReglesOptionelles.filter(it => game.user.isGM || it.scope == "client").map(it => { it = duplicate(it); it.id = ReglesOptionelles._getIdRegle(it.name); - it.active = ReglesOptionelles.isUsing(it.name); + it.active = ReglesOptionelles.isSet(it.name); return it; }); formData.regles = regles; @@ -85,11 +88,11 @@ export class ReglesOptionelles extends FormApplication { if (game.user.isGM && uniquementJoueur.includes(name)) { return true; } - return game.settings.get(SYSTEM_RDD, ReglesOptionelles._getIdRegle(name)); + return ReglesOptionelles.isSet(name); } static isSet(name) { - return ReglesOptionelles.isUsing(name); + return game.settings.get(SYSTEM_RDD, ReglesOptionelles._getIdRegle(name)); } static set(name, value) { diff --git a/templates/dialog-roll-encaisser.html b/templates/dialog-roll-encaisser.html index 73244852..a794c67a 100644 --- a/templates/dialog-roll-encaisser.html +++ b/templates/dialog-roll-encaisser.html @@ -1,8 +1,8 @@