diff --git a/module/actor-sheet.js b/module/actor-sheet.js index 22abd450..19837d4e 100644 --- a/module/actor-sheet.js +++ b/module/actor-sheet.js @@ -9,6 +9,7 @@ import { RdDItemArme } from "./item-arme.js"; import { RdDItemCompetence } from "./item-competence.js"; import { RdDBonus } from "./rdd-bonus.js"; import { Misc } from "./misc.js"; +import { RdDCombatManager } from "./rdd-combat.js"; /* -------------------------------------------- */ export class RdDActorSheet extends ActorSheet { @@ -48,7 +49,10 @@ export class RdDActorSheet extends ActorSheet { item => item.data.categorie, item => { let archetypeKey = (item.data.niveau_archetype < 0) ? 0 : item.data.niveau_archetype; - data.data.comptageArchetype[archetypeKey].nombre = data.data.comptageArchetype[archetypeKey].nombre + 1; //Comptage archetype + if (data.data.comptageArchetype[archetypeKey] == undefined) { + data.data.comptageArchetype[archetypeKey] = { "niveau": archetypeKey, "nombreMax": 0, "nombre": 0}; + } + data.data.comptageArchetype[archetypeKey].nombre = (data.data.comptageArchetype[archetypeKey]?.nombre??0) + 1; //Comptage archetype item.data.xpNext = RdDItemCompetence.getCompetenceNextXp(item.data.niveau); item.data.isLevelUp = item.data.xp >= item.data.xpNext; // Flag de niveau à MAJ //this.actor.checkCompetenceXP(item.name); // Petite vérification experience @@ -92,12 +96,12 @@ export class RdDActorSheet extends ActorSheet { // To avoid armour and so on... data.data.combat = duplicate(RdDUtility.checkNull(data.itemsByType['arme'])); - data.data.combat = RdDUtility._finalizeArmeList(data.data.combat, data.itemsByType.competence, data.data.carac); + data.data.combat = RdDCombatManager.finalizeArmeList(data.data.combat, data.itemsByType.competence, data.data.carac); data.esquive = { name: "Esquive", niveau: data.competenceByCategory?.melee.find(it => it.name == 'Esquive')?.data.niveau ?? -6}; let corpsACorps = data.competenceByCategory?.melee.find(it => it.name == 'Corps à corps'); if (corpsACorps) { - let cc_init = RdDUtility.calculInitiative(corpsACorps.data.niveau, data.data.carac['melee'].value); + let cc_init = RdDCombatManager.calculInitiative(corpsACorps.data.niveau, data.data.carac['melee'].value); data.data.combat.push(RdDItemArme.mainsNues({ niveau: corpsACorps.data.niveau, initiative: cc_init })); } this.armesList = duplicate(data.data.combat); @@ -354,7 +358,7 @@ export class RdDActorSheet extends ActorSheet { if (combatant) { let armeName = event.currentTarget.attributes['data-arme-name'].value; let arme = this.armesList.find(a => a.name == armeName); - RdDUtility.rollInitiativeCompetence(combatant._id, arme); + RdDCombatManager.rollInitiativeCompetence(combatant._id, arme); } else { ui.notifications.info("Impossible de lancer l'initiative sans être dans un combat."); } diff --git a/module/grammar.js b/module/grammar.js index df4162a2..e75bdc77 100644 --- a/module/grammar.js +++ b/module/grammar.js @@ -1,11 +1,12 @@ const articlesApostrophes = { - 'de' : 'd\'', - 'le' : 'l\'', - 'la' : 'l\'' + 'de': 'd\'', + 'le': 'l\'', + 'la': 'l\'' } export class Grammar { + /* -------------------------------------------- */ static apostrophe(article, word) { if (articlesApostrophes[article] && Grammar.startsWithVoyel(word)) { return articlesApostrophes[article] + word @@ -13,28 +14,58 @@ export class Grammar { return article + ' ' + word; } + /* -------------------------------------------- */ static startsWithVoyel(word) { return word.match(/^[aeiouy]/i) } + /* -------------------------------------------- */ static toLowerCaseNoAccent(words) { return words?.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "") ?? words; } + + /* -------------------------------------------- */ static articleDetermine(genre) { - switch (genre?.toLowerCase()) { + switch (toLowerCaseNoAccent(genre)) { case 'f': case 'feminin': return 'la'; - case 'p': case 'pluriel': return 'les'; + case 'p': case 'mp': case 'fp': case 'pluriel': return 'les'; default: case 'm': case 'masculin': return 'le'; } } + + /* -------------------------------------------- */ static articleIndétermine(genre) { - switch (genre?.toLowerCase()) { + switch (toLowerCaseNoAccent(genre)) { case 'f': case 'feminin': return 'une'; - case 'p': case 'pluriel': return 'des'; + case 'p': case 'fp': case 'mp': case 'pluriel': return 'des'; case 'n': case 'neutre': return 'du' default: case 'm': case 'masculin': return 'un'; } } + + /* -------------------------------------------- */ + /** + * renvoie un des mots en fonction du genre: + * + * - masculin/neutre/m/n : mots[0] + * - feminin/f : mots[1] + * - pluriel/mp/p : mots[2] + * - fp : mots[3] + * + * @param {*} genre + * @param {...any} mots + */ + static accord(genre, ...mots) { + switch (toLowerCaseNoAccent(genre)) { + default: + case 'n': case 'neutre': + case 'm': case 'masculin': return mots[0]; + case 'f': case 'feminin': return mots[1]; + case 'p': case 'mp': case 'pluriel': return mots[2] + case 'fp': return mots[3]; + } + } + } \ No newline at end of file diff --git a/module/item-arme.js b/module/item-arme.js index 048543f0..68e3cafb 100644 --- a/module/item-arme.js +++ b/module/item-arme.js @@ -145,7 +145,7 @@ export class RdDItemArme extends Item { } static isArmeUtilisable(item) { - return item.type == 'arme' && item.data.resistance > 0; + return item.type == 'arme' && (item.data.resistance > 0 || item.data.portee_courte>0); } static mainsNues(actorData={}) { diff --git a/module/rdd-combat.js b/module/rdd-combat.js index bc641c0a..b84c8d6a 100644 --- a/module/rdd-combat.js +++ b/module/rdd-combat.js @@ -10,19 +10,26 @@ import { RdDRollTables } from "./rdd-rolltables.js"; import { ReglesOptionelles } from "./regles-optionelles.js"; /* -------------------------------------------- */ -export class RdDCombatManager extends Combat { +export class RdDCombatManager extends Combat { + + static init() { + /* -------------------------------------------- */ + Hooks.on("getCombatTrackerEntryContext", (html, options) => { + RdDCombatManager.pushInitiativeOptions(html, options); + }); + } /* -------------------------------------------- */ cleanItemUse() { - for(let turn of this.turns) { + for (let turn of this.turns) { turn.actor.resetItemUse() } } - + /* -------------------------------------------- */ - cleanSonne( ) { + cleanSonne() { for (let combatant of this.data.combatants) { - combatant.actor.verifierSonneRound( this.current.round ); + combatant.actor.verifierSonneRound(this.current.round); } } @@ -31,7 +38,268 @@ export class RdDCombatManager extends Combat { //console.log('New round !');s this.cleanItemUse(); this.cleanSonne(); + return super.nextRound(); } + + /************************************************************************************/ + async rollInitiative(ids, formula = undefined, messageOptions = {}) { + console.log(`${game.data.system.data.title} | Combat.rollInitiative()`, ids, formula, messageOptions); + // Structure input data + ids = typeof ids === "string" ? [ids] : ids; + const currentId = this.combatant._id; + // calculate initiative + for (let cId = 0; cId < ids.length; cId++) { + const c = this.getCombatant(ids[cId]); + //if (!c) return results; + + let rollFormula = formula; // Init per default + if (!rollFormula) { + let armeCombat, competence; + if (c.actor.data.type == 'creature' || c.actor.data.type == 'entite') { + for (const competenceItem of c.actor.data.items) { + if (competenceItem.data.iscombat) { + competence = duplicate(competenceItem); + } + } + rollFormula = "2+( (" + RdDCombatManager.calculInitiative(competence.data.niveau, competence.data.carac_value) + ")/100)"; + } else { + for (const item of c.actor.data.items) { + if (item.type == "arme" && item.data.equipe) { + armeCombat = duplicate(item); + } + } + let compName = (armeCombat == undefined) ? "Corps à corps" : armeCombat.data.competence; + competence = RdDItemCompetence.findCompetence(c.actor.data.items, compName); + let bonusEcaille = (armeCombat && armeCombat.data.magique) ? armeCombat.data.ecaille_efficacite : 0; + rollFormula = "2+( (" + RdDCombatManager.calculInitiative(competence.data.niveau, c.actor.data.data.carac[competence.data.defaut_carac].value, bonusEcaille) + ")/100)"; + } + } + //console.log("Combatat", c); + const roll = super._getInitiativeRoll(c, rollFormula); + if (roll.total <= 0) roll.total = 0.00; + console.log("Compute init for", rollFormula, roll.total); + await this.updateEmbeddedEntity("Combatant", { _id: c._id, initiative: roll.total }); + + // Send a chat message + let rollMode = messageOptions.rollMode || game.settings.get("core", "rollMode"); + let messageData = mergeObject( + { + speaker: { + scene: canvas.scene._id, + actor: c.actor ? c.actor._id : null, + token: c.token._id, + alias: c.token.name, + sound: CONFIG.sounds.dice, + }, + flavor: `${c.token.name} a fait son jet d'Initiative (${messageOptions.initInfo}) +
+ `, + }, + messageOptions + ); + roll.toMessage(messageData, { rollMode, create: true }); + + RdDCombatManager.processPremierRoundInit(); + } + return this; + }; + + /* -------------------------------------------- */ + static calculInitiative(niveau, caracValue, bonusEcaille = 0) { + let base = niveau + Math.floor(caracValue / 2); + base += bonusEcaille; + return "1d6" + (base >= 0 ? "+" : "") + base; + } + + /* -------------------------------------------- */ + /** Retourne une liste triée d'armes avec le split arme1 main / arme 2 main */ + static finalizeArmeList(armes, competences, carac) { + // Gestion des armes 1/2 mains + let armesEquipe = []; + for (const arme of armes) { + if (arme.data.equipe) { + armesEquipe.push(arme); + let comp = competences.find(c => c.name == arme.data.competence); + arme.data.initiative = RdDCombatManager.calculInitiative(arme.data.niveau, carac[comp.data.defaut_carac].value); + // Dupliquer les armes pouvant être à 1 main et 2 mains en patchant la compétence + if (arme.data.unemain && !arme.data.deuxmains) { + arme.data.mainInfo = "(1m)"; + } else if (!arme.data.unemain && arme.data.deuxmains) { + arme.data.mainInfo = "(2m)"; + } else if (arme.data.unemain && arme.data.deuxmains) { + arme.data.mainInfo = "(1m)"; + let arme2main = duplicate(arme); + arme2main.data.mainInfo = "(2m)"; + arme2main.data.dommages = arme2main.data.dommages.split("/")[1]; // Existence temporaire uniquement dans la liste des armes, donc OK + arme2main.data.competence = arme2main.data.competence.replace(" 1 main", " 2 mains"); // Replace ! + let comp = competences.find(c => c.name == arme2main.data.competence); + arme2main.data.niveau = comp.data.niveau; + arme2main.data.initiative = RdDCombatManager.calculInitiative(arme2main.data.niveau, carac[comp.data.defaut_carac].value); + armesEquipe.push(arme2main); + } + } + } + return armesEquipe.sort((a, b) => { + const nameA = a.name + (a.data.mainInfo ?? ''); + const nameB = b.name + (b.data.mainInfo ?? ''); + if (nameA > nameB) return 1; + if (nameA < nameB) return -1; + return 0; + }); + } + + /* -------------------------------------------- */ + static buildListeActionsCombat(combatant) { + const actor = combatant.actor; // Easy access + let items = actor.data.items; + let actions = [] + if (actor.isCreature()) { + actions = actions.concat(items.filter(it => RdDItemCompetenceCreature.isCompetenceAttaque(it)) + .map(competence => RdDItemCompetenceCreature.toArme(competence))); + } else { + // Recupération des items 'arme' + let armes = items.filter(it => RdDItemArme.isArmeUtilisable(it)) + .map(arme => duplicate(arme)) /* pas de changements aux armes d'origine */ + .concat(RdDItemArme.mainsNues()); + + let competences = items.filter(it => it.type == 'competence'); + actions = actions.concat(RdDCombatManager.finalizeArmeList(armes, competences, actor.data.data.carac)); + + actions.push({ name: "Draconic", data: { initOnly: true, competence: "Draconic" } }); + } + + actions.push({ name: "Autre action", data: { initOnly: true, competence: "Autre action" } }); + for (let index = 0; index < actions.length; index++) { + actions[index].index = index; + } + return actions; + } + + /* -------------------------------------------- */ + static processPremierRoundInit() { + // Check if we have the whole init ! + if (game.user.isGM && game.combat.current.round == 1) { + let initMissing = game.combat.data.combatants.find(it => !it.initiative); + if (!initMissing) { // Premier round ! + for (let combatant of game.combat.data.combatants) { + let arme = combatant.initiativeData?.arme; + //console.log("Parsed !!!", combatant, initDone, game.combat.current, arme); + if (arme && arme.type == "arme") { + for (let initData of premierRoundInit) { + if (arme.data.initpremierround.toLowerCase().includes(initData.pattern)) { + let msg = `

L'initiative de ${combatant.actor.name} a été modifiée !

+
+
+ Etant donné son ${arme.name}, son initative pour ce premier round est désormais de ${initData.init}. +
` + ChatMessage.create({ content: msg }); + game.combat.setInitiative(combatant._id, initData.init); + } + } + } + } + } + } + } + + /* -------------------------------------------- */ + static incDecInit(combatantId, incDecValue) { + const combatant = game.combat.getCombatant(combatantId); + let initValue = combatant.initiative + incDecValue; + game.combat.setInitiative(combatantId, initValue); + } + + /* -------------------------------------------- */ + static pushInitiativeOptions(html, options) { + for (let i = 0; i < options.length; i++) { + let option = options[i]; + if (option.name == 'COMBAT.CombatantReroll') { // Replace ! + option.name = "Sélectionner l'initiative..."; + option.condition = true; + option.icon = ''; + option.callback = target => { + RdDCombatManager.displayInitiativeMenu(html, target.data('combatant-id')); + } + } + } + options = [ + { name: "Incrémenter initiative", condition: true, icon: '', callback: target => { RdDCombatManager.incDecInit(target.data('combatant-id'), +0.01); } }, + { name: "Décrémenter initiative", condition: true, icon: '', callback: target => { RdDCombatManager.incDecInit(target.data('combatant-id'), -0.01); } } + ].concat(options); + } + /* -------------------------------------------- */ + static rollInitiativeCompetence(combatantId, arme) { + const combatant = game.combat.getCombatant(combatantId); + const actor = combatant.actor; + + let initInfo = ""; + let initOffset = 0; + let caracForInit = 0; + let compNiveau = 0; + let competence = { name: "Aucune" }; + if (actor.getSurprise() == "totale") { + initOffset = -1; // To force 0 + initInfo = "Surprise Totale" + } else if (actor.getSurprise() == "demi") { + initOffset = 0; + initInfo = "Demi Surprise" + } else if (arme.name == "Autre action") { + initOffset = 2; + initInfo = "Autre Action" + } else if (arme.name == "Draconic") { + initOffset = 7; + initInfo = "Draconic" + } else { + initOffset = 3; // Melée = 3.XX + competence = RdDItemCompetence.findCompetence(combatant.actor.data.items, arme.data.competence); + compNiveau = competence.data.niveau; + initInfo = arme.name + " / " + arme.data.competence; + + if (actor.data.type == 'creature' || actor.data.type == 'entite') { + caracForInit = competence.data.carac_value; + if (competence.data.categorie == "lancer") { + initOffset = 5; + } + } else { + caracForInit = actor.data.data.carac[competence.data.defaut_carac].value; + if (competence.data.categorie == "lancer") { // Offset de principe pour les armes de jet + initOffset = 4; + } + if (competence.data.categorie == "tir") { // Offset de principe pour les armes de jet + initOffset = 5; + } + if (competence.data.categorie == "melee") { // Offset de principe pour les armes de jet + initOffset = 3; + } + } + } + let malus = actor.getEtatGeneral(); // Prise en compte état général + // Cas des créatures et entités vs personnages + let rollFormula = initOffset + "+ ( (" + RdDCombatManager.calculInitiative(compNiveau, caracForInit) + " + " + malus + ") /100)"; + // Garder la trace de l'arme/compétence utilisée pour l'iniative + combatant.initiativeData = { arme: arme } // pour reclasser l'init au round 0 + game.combat.rollInitiative(combatantId, rollFormula, { initInfo: initInfo }); + } + + /* -------------------------------------------- */ + static displayInitiativeMenu(html, combatantId) { + const combatant = game.combat.getCombatant(combatantId); + let armesList = RdDCombatManager.buildListeActionsCombat(combatant); + + // Build the relevant submenu + if (armesList) { + let menuItems = []; + for (let arme of armesList) { + menuItems.push({ + name: arme.data.competence, + icon: "", + callback: target => { RdDCombatManager.rollInitiativeCompetence(combatantId, arme) } + }); + } + new ContextMenu(html, ".directory-list", menuItems).render(); + } + } + } /* -------------------------------------------- */ @@ -384,8 +652,8 @@ export class RdDCombat { let rollData = this._prepareAttaque(competence, arme); console.log("RdDCombat.attaque >>>", rollData); - this.attacker.incItemUse( arme._id ); // Usage - this.attacker.verifierForceMin( arme ); + this.attacker.incItemUse(arme._id); // Usage + this.attacker.verifierForceMin(arme); const dialog = await RdDRoll.create(this.attacker, rollData, { @@ -491,7 +759,7 @@ export class RdDCombat { let esquiveUsage = 0; let esquive = this.defender.getCompetence("esquive"); if (esquive) { - esquiveUsage = this.defender.getItemUse( esquive._id); + esquiveUsage = this.defender.getItemUse(esquive._id); } const paramChatDefense = { @@ -551,8 +819,8 @@ export class RdDCombat { _filterArmesParade(defender, competence) { let items = defender.data.items; items = items.filter(item => RdDItemArme.isArmeUtilisable(item) || RdDItemCompetenceCreature.isCompetenceParade(item)); - for( let item of items) { - item.data.nbUsage = defender.getItemUse( item._id); // Ajout du # d'utilisation ce round + for (let item of items) { + item.data.nbUsage = defender.getItemUse(item._id); // Ajout du # d'utilisation ce round } switch (competence.data.categorie) { case 'tir': @@ -572,9 +840,8 @@ export class RdDCombat { RdDCombat._storeAttaque(this.attackerId, attackerRoll); - // Finesse et Rapidité seulement en mêlée et si la difficulté libre est de -1 minimum ChatMessage.create({ - whisper: ChatMessage.getWhisperRecipients(this.attacker.name), + whisper: ChatUtility.getWhisperRecipientsAndGMs(this.attacker.name), content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-demande-attaque-etotal.html', { attackerId: this.attackerId, attacker: this.attacker, @@ -592,7 +859,7 @@ export class RdDCombat { const avecArme = arme?.data.categorie_parade != 'sans-armes'; const action = (rollData.attackerRoll ? (arme ? "la parade" : "l'esquive") : "l'attaque"); ChatUtility.createChatWithRollMode(this.defender.name, { - content: `Echec total à ${action}! ` + await RdDRollTables.getMaladresse({ arme: avecArme }) + content: `Maladresse à ${action}! ` + await RdDRollTables.getMaladresse({ arme: avecArme }) }); } @@ -617,7 +884,7 @@ export class RdDCombat { let arme = this.defender.getArmeParade(armeParadeId); console.log("RdDCombat.parade >>>", attackerRoll, armeParadeId, arme); - this.defender.incItemUse( armeParadeId ); // Usage + this.defender.incItemUse(armeParadeId); // Usage let rollData = this._prepareParade(attackerRoll, arme); @@ -725,7 +992,7 @@ export class RdDCombat { } console.log("RdDCombat.esquive >>>", attackerRoll, esquive); let rollData = this._prepareEsquive(attackerRoll, esquive); - this.defender.incItemUse( esquive._id ); // Usage + this.defender.incItemUse(esquive._id); // Usage const dialog = await RdDRoll.create(this.defender, rollData, { html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-competence.html' }, { @@ -804,10 +1071,10 @@ export class RdDCombat { const dmg = attackerRoll.dmg.dmgArme + attackerRoll.dmg.dmgActor; let arme = defenderRoll.arme; let msg = ""; - if ( arme.data.magique ) { + if (arme.data.magique) { defenderRoll.show.deteriorationArme = 'resiste'; // Par défaut if (arme.data.resistance_magique == undefined) arme.data.resistance_magique = 0; // Quick fix - if ( dmg > arme.data.resistance_magique) { // Jet uniquement si dommages supérieur à résistance magique (cf. 274) + if (dmg > arme.data.resistance_magique) { // Jet uniquement si dommages supérieur à résistance magique (cf. 274) let resistance = Misc.toInt(arme.data.resistance); // Jet de résistance de l'arme de parade (p.132) let resistRoll = await RdDResolutionTable.rollData({ @@ -815,14 +1082,14 @@ export class RdDCombat { finalLevel: - dmg, showDice: false }); - if ( !resistRoll.rolled.isSuccess) { - let perteResistance = ( dmg - arme.data.resistance_magique) + if (!resistRoll.rolled.isSuccess) { + let perteResistance = (dmg - arme.data.resistance_magique) resistance -= perteResistance; - defenderRoll.show.deteriorationArme = resistance <= 0 ? 'brise': 'perte'; + defenderRoll.show.deteriorationArme = resistance <= 0 ? 'brise' : 'perte'; defenderRoll.show.perteResistance = perteResistance; this.defender.updateEmbeddedEntity("OwnedItem", { _id: defenderRoll.arme._id, 'data.resistance': resistance }); } - } + } } else { let resistance = Misc.toInt(arme.data.resistance); // Jet de résistance de l'arme de parade (p.132) @@ -835,7 +1102,7 @@ export class RdDCombat { defenderRoll.show.deteriorationArme = 'resiste'; } else { resistance -= dmg; - defenderRoll.show.deteriorationArme = resistance <= 0 ? 'brise': 'perte'; + defenderRoll.show.deteriorationArme = resistance <= 0 ? 'brise' : 'perte'; defenderRoll.show.perteResistance = dmg; this.defender.updateEmbeddedEntity("OwnedItem", { _id: defenderRoll.arme._id, 'data.resistance': resistance }); } diff --git a/module/rdd-main.js b/module/rdd-main.js index 72dabf6d..2b7d2e43 100644 --- a/module/rdd-main.js +++ b/module/rdd-main.js @@ -34,77 +34,7 @@ import { EffetsDraconiques } from "./tmr/effets-draconiques.js"; /* Foundry VTT Initialization */ /* -------------------------------------------- */ -/************************************************************************************/ -const _patch_initiative = () => { - Combat.prototype.rollInitiative = async function ( - ids, - formula = undefined, - messageOptions = {} - ) { - console.log( - `${game.data.system.data.title} | Combat.rollInitiative()`, - ids, - formula, - messageOptions - ); - // Structure input data - ids = typeof ids === "string" ? [ids] : ids; - const currentId = this.combatant._id; - // calculate initiative - for (let cId = 0; cId < ids.length; cId++) { - const c = this.getCombatant(ids[cId]); - //if (!c) return results; - let rollFormula = formula; // Init per default - if (!rollFormula) { - let armeCombat, competence; - if (c.actor.data.type == 'creature' || c.actor.data.type == 'entite') { - for (const competenceItem of c.actor.data.items) { - if (competenceItem.data.iscombat) { - competence = duplicate(competenceItem); - } - } - rollFormula = "2+( ("+RdDUtility.calculInitiative(competence.data.niveau, competence.data.carac_value)+")/100)"; - } else { - for (const item of c.actor.data.items) { - if (item.type == "arme" && item.data.equipe) { - armeCombat = duplicate(item); - } - } - let compName = (armeCombat == undefined) ? "Corps à corps" : armeCombat.data.competence; - competence = RdDItemCompetence.findCompetence(c.actor.data.items, compName); - let bonusEcaille = (armeCombat && armeCombat.data.magique) ? armeCombat.data.ecaille_efficacite : 0; - rollFormula = "2+( ("+RdDUtility.calculInitiative(competence.data.niveau, c.actor.data.data.carac[competence.data.defaut_carac].value, bonusEcaille) + ")/100)"; - } - } - //console.log("Combatat", c); - const roll = this._getInitiativeRoll(c, rollFormula); - if (roll.total <= 0) roll.total = 0.00; - console.log("Compute init for", rollFormula, roll.total); - await this.updateEmbeddedEntity("Combatant", { _id: c._id, initiative: roll.total }); - - // Send a chat message - let rollMode = messageOptions.rollMode || game.settings.get("core", "rollMode"); - let messageData = mergeObject( - { - speaker: { - scene: canvas.scene._id, - actor: c.actor ? c.actor._id : null, - token: c.token._id, - alias: c.token.name, - sound: CONFIG.sounds.dice, - }, - flavor: `${c.token.name} a fait son jet d'Initiative (${messageOptions.initInfo})`, - }, - messageOptions - ); - roll.toMessage(messageData, { rollMode, create: true }); - - RdDUtility.processPremierRoundInit( ); - } - return this; - }; -} /************************************************************************************/ Hooks.once("init", async function () { @@ -222,15 +152,10 @@ Hooks.once("init", async function () { Items.registerSheet("foundryvtt-reve-de-dragon", RdDItemSheet, { makeDefault: true }); CONFIG.Combat.entityClass = RdDCombatManager; - // Handlebar function pour container - Handlebars.registerHelper('buildConteneur', (objet) => { return RdDUtility.buildConteneur(objet); }); - - // Patch the initiative formula - _patch_initiative(); - // préparation des différents modules RdDCommands.init(); RdDCombat.init(); + RdDCombatManager.init(), RdDTokenHud.init(); RdDActor.init(); RddCompendiumOrganiser.init(); @@ -299,10 +224,6 @@ Hooks.on("chatMessage", (html, content, msg) => { return true; }); -/* -------------------------------------------- */ -Hooks.on("getCombatTrackerEntryContext", (html, options) => { - RdDUtility.pushInitiativeOptions(html, options); -}); /* -------------------------------------------- */ Hooks.on("renderChatMessage", async (app, html, msg) => { diff --git a/module/rdd-resolution-table.js b/module/rdd-resolution-table.js index e020deaf..ca5376a9 100644 --- a/module/rdd-resolution-table.js +++ b/module/rdd-resolution-table.js @@ -125,7 +125,7 @@ export class RdDResolutionTable { /* -------------------------------------------- */ static _updateChancesFactor(chances, diviseur) { - if (diviseur && diviseur > 1) { + if (chances.level > -11 && diviseur && diviseur > 1) { let newScore = Math.floor(chances.score / diviseur); mergeObject(chances, this._computeCell(null, newScore), { overwrite: true }); } diff --git a/module/rdd-token-hud.js b/module/rdd-token-hud.js index 0f9abcb0..27c96c46 100644 --- a/module/rdd-token-hud.js +++ b/module/rdd-token-hud.js @@ -1,5 +1,6 @@ /* -------------------------------------------- */ import { HtmlUtility } from "./html-utility.js"; +import { RdDCombatManager } from "./rdd-combat.js"; import { RdDUtility } from "./rdd-utility.js"; /* -------------------------------------------- */ @@ -26,7 +27,7 @@ export class RdDTokenHud { let combatant = game.combat.data.combatants.find(c => c.tokenId == token.data._id); app.hasExtension = true; - let armesList = RdDUtility.buildListeActionsCombat(combatant) ; + let armesList = RdDCombatManager.buildListeActionsCombat(combatant) ; const hudData = { combatant: combatant, armes: armesList, commandes: [{ name: 'Initiative +1', command: 'inc', value: 0.01}, { name: 'Initiative -1',command: 'dec', value: -0.01}] }; @@ -38,11 +39,11 @@ export class RdDTokenHud { if ( !initCommand ) { let armeIndex = event.currentTarget.attributes['data-arme-id'].value; let arme = armesList[armeIndex]; - RdDUtility.rollInitiativeCompetence(combatantId, arme); + RdDCombatManager.rollInitiativeCompetence(combatantId, arme); } else if (initCommand == 'inc') { - RdDUtility.incDecInit( combatantId, 0.01 ); + RdDCombatManager.incDecInit( combatantId, 0.01 ); } else if ( initCommand == 'dec') { - RdDUtility.incDecInit( combatantId, -0.01 ); + RdDCombatManager.incDecInit( combatantId, -0.01 ); } }); diff --git a/module/rdd-utility.js b/module/rdd-utility.js index 9f8340c6..0c579375 100644 --- a/module/rdd-utility.js +++ b/module/rdd-utility.js @@ -2,7 +2,7 @@ import { RdDRollTables } from "./rdd-rolltables.js"; import { ChatUtility } from "./chat-utility.js"; -import { RdDCombat } from "./rdd-combat.js"; +import { RdDCombat, RdDCombatManager } from "./rdd-combat.js"; import { RdDRollResolutionTable } from "./rdd-roll-resolution-table.js"; import { RdDItemCompetenceCreature } from "./item-competencecreature.js"; import { RdDItemArme } from "./item-arme.js"; @@ -24,18 +24,18 @@ const categorieCompetences = { /* -------------------------------------------- */ const limitesArchetypes = [ - { "niveau": 0, "nombreMax": 100, "nombre":0}, - { "niveau": 1, "nombreMax": 10, "nombre":0}, - { "niveau": 2, "nombreMax": 9, "nombre":0}, - { "niveau": 3, "nombreMax": 8, "nombre":0}, - { "niveau": 4, "nombreMax": 7, "nombre":0}, - { "niveau": 5, "nombreMax": 6, "nombre":0}, - { "niveau": 6, "nombreMax": 5, "nombre":0}, - { "niveau": 7, "nombreMax": 4, "nombre":0}, - { "niveau": 8, "nombreMax": 3, "nombre":0}, - { "niveau": 9, "nombreMax": 2, "nombre":0}, - { "niveau": 10, "nombreMax": 1, "nombre":0}, - { "niveau": 11, "nombreMax": 1, "nombre":0} + { "niveau": 0, "nombreMax": 100, "nombre": 0 }, + { "niveau": 1, "nombreMax": 10, "nombre": 0 }, + { "niveau": 2, "nombreMax": 9, "nombre": 0 }, + { "niveau": 3, "nombreMax": 8, "nombre": 0 }, + { "niveau": 4, "nombreMax": 7, "nombre": 0 }, + { "niveau": 5, "nombreMax": 6, "nombre": 0 }, + { "niveau": 6, "nombreMax": 5, "nombre": 0 }, + { "niveau": 7, "nombreMax": 4, "nombre": 0 }, + { "niveau": 8, "nombreMax": 3, "nombre": 0 }, + { "niveau": 9, "nombreMax": 2, "nombre": 0 }, + { "niveau": 10, "nombreMax": 1, "nombre": 0 }, + { "niveau": 11, "nombreMax": 1, "nombre": 0 } ]; /* -------------------------------------------- */ @@ -274,12 +274,14 @@ export class RdDUtility { Handlebars.registerHelper('upper', str => str?.toUpperCase() ?? 'NULL'); Handlebars.registerHelper('le', str => Grammar.articleDetermine(str)); Handlebars.registerHelper('un', str => Grammar.articleIndetermine(str)); + Handlebars.registerHelper('accord', (genre, ...args) => Grammar.accord(genre, args)); + Handlebars.registerHelper('buildConteneur', (objet) => { return RdDUtility.buildConteneur(objet); }); return loadTemplates(templatePaths); } /* -------------------------------------------- */ - static getLimitesArchetypes( ) { + static getLimitesArchetypes() { return duplicate(limitesArchetypes); } @@ -458,50 +460,6 @@ export class RdDUtility { return tableCaracDerivee[targetValue]?.xp ?? 200; } - /* -------------------------------------------- */ - /** Retourne une liste triée d'armes avec le split arme1 main / arme 2 main */ - static _finalizeArmeList(armes, competences, carac) { - // Gestion des armes 1/2 mains - let armesEquipe = []; - for (const arme of armes) { - if (arme.data.equipe) { - armesEquipe.push(arme); - let comp = competences.find(c => c.name == arme.data.competence); - arme.data.initiative = RdDUtility.calculInitiative(arme.data.niveau, carac[comp.data.defaut_carac].value); - // Dupliquer les armes pouvant être à 1 main et 2 mains en patchant la compétence - if (arme.data.unemain && !arme.data.deuxmains) { - arme.data.mainInfo = "(1m)"; - } else if (!arme.data.unemain && arme.data.deuxmains) { - arme.data.mainInfo = "(2m)"; - } else if (arme.data.unemain && arme.data.deuxmains) { - arme.data.mainInfo = "(1m)"; - let arme2main = duplicate(arme); - arme2main.data.mainInfo = "(2m)"; - arme2main.data.dommages = arme2main.data.dommages.split("/")[1]; // Existence temporaire uniquement dans la liste des armes, donc OK - arme2main.data.competence = arme2main.data.competence.replace(" 1 main", " 2 mains"); // Replace ! - let comp = competences.find(c => c.name == arme2main.data.competence); - arme2main.data.niveau = comp.data.niveau; - arme2main.data.initiative = RdDUtility.calculInitiative(arme2main.data.niveau, carac[comp.data.defaut_carac].value); - armesEquipe.push(arme2main); - } - } - } - return armesEquipe.sort((a, b) => { - const nameA = a.name + (a.data.mainInfo ?? ''); - const nameB = b.name + (b.data.mainInfo ?? ''); - if (nameA > nameB) return 1; - if (nameA < nameB) return -1; - return 0; - }); - } - - /* -------------------------------------------- */ - static calculInitiative(niveau, caracValue, bonusEcaille = 0) { - let base = niveau + Math.floor(caracValue / 2); - base += bonusEcaille; - return "1d6" + (base >= 0 ? "+" : "") + base; - } - /* -------------------------------------------- */ static computeCarac(data) { data.carac.force.value = Math.min(data.carac.force.value, parseInt(data.carac.taille.value) + 4); @@ -709,173 +667,8 @@ export class RdDUtility { } } - /* -------------------------------------------- */ - static processPremierRoundInit() { - // Check if we have the whole init ! - if (game.user.isGM) { - let initDone = true; - for (let combatant of game.combat.data.combatants) { - if (!combatant.initiative) initDone = false; - } - if (initDone && game.combat.current.round == 1) { // Premier round ! - for (let combatant of game.combat.data.combatants) { - let arme = combatant.initiativeData.arme; - //console.log("Parsed !!!", combatant, initDone, game.combat.current, arme); - if (arme && arme.type == "arme") { - for (let initData of premierRoundInit) { - if (arme.data.initpremierround.toLowerCase().includes(initData.pattern)) { - let msg = `

L'initiative de ${combatant.actor.name} a été modifiée !

-
-
- Etant donné son ${arme.name}, son initative pour ce premier round est désormais de ${initData.init}. -
` - ChatMessage.create({ content: msg }); - game.combat.setInitiative(combatant._id, initData.init); - } - } - } - } - } - } - } - /* -------------------------------------------- */ - static rollInitiativeCompetence(combatantId, arme) { - const combatant = game.combat.getCombatant(combatantId); - const actor = combatant.actor; - let initInfo = ""; - let initOffset = 0; - let caracForInit = 0; - let compNiveau = 0; - let competence = { name: "Aucune"}; - if (actor.getSurprise() == "totale") { - initOffset = -1; // To force 0 - initInfo = "Surprise Totale" - } else if (actor.getSurprise() == "demi") { - initOffset = 0; - initInfo = "Demi Surprise" - } else if (arme.name == "Autre action") { - initOffset = 2; - initInfo = "Autre Action" - } else if (arme.name == "Draconic") { - initOffset = 7; - initInfo = "Draconic" - } else { - initOffset = 3; // Melée = 3.XX - competence = RdDItemCompetence.findCompetence(combatant.actor.data.items, arme.data.competence); - compNiveau = competence.data.niveau; - initInfo = arme.name + " / " + arme.data.competence; - - if (actor.data.type == 'creature' || actor.data.type == 'entite') { - caracForInit = competence.data.carac_value; - if (competence.data.categorie == "lancer") { - initOffset = 5; - } - } else { - caracForInit = actor.data.data.carac[competence.data.defaut_carac].value; - if (competence.data.categorie == "lancer") { // Offset de principe pour les armes de jet - initOffset = 4; - } - if (competence.data.categorie == "tir") { // Offset de principe pour les armes de jet - initOffset = 5; - } - if (competence.data.categorie == "melee") { // Offset de principe pour les armes de jet - initOffset = 3; - } - } - } - let malus = actor.getEtatGeneral(); // Prise en compte état général - // Cas des créatures et entités vs personnages - let rollFormula = initOffset + "+ ( (" + RdDUtility.calculInitiative(compNiveau, caracForInit) + " + " + malus + ") /100)"; - // Garder la trace de l'arme/compétence utilisée pour l'iniative - combatant.initiativeData = { arme: arme } // pour reclasser l'init au round 0 - game.combat.rollInitiative(combatantId, rollFormula, { initInfo: initInfo}); - } - - /* -------------------------------------------- */ - static buildListeActionsCombat(combatant) { - const actor = combatant.actor; // Easy access - let items = actor.data.items; - let actions = [] - if (actor.isCreature()) { - actions = actions.concat(items.filter(it => RdDItemCompetenceCreature.isCompetenceAttaque(it)) - .map(competence => RdDItemCompetenceCreature.toArme(competence))); - } else { - // Recupération des items 'arme' - let armes = items.filter(it => RdDItemArme.isArmeUtilisable(it)) - .map(arme => duplicate(arme)) /* pas de changements aux armes d'origine */ - .concat(RdDItemArme.mainsNues()); - - let competences = items.filter(it => it.type == 'competence'); - actions = actions.concat(this._finalizeArmeList(armes, competences, actor.data.data.carac)); - - actions.push({ name: "Draconic", data: { initOnly: true, competence: "Draconic" } }); - } - - actions.push({ name: "Autre action", data: { initOnly: true, competence: "Autre action" } }); - for (let index = 0; index < actions.length; index++) { - actions[index].index = index; - } - return actions; - } - - /* -------------------------------------------- */ - static displayInitiativeMenu(html, combatantId) { - const combatant = game.combat.getCombatant(combatantId); - let armesList = this.buildListeActionsCombat(combatant); - - // Build the relevant submenu - if (armesList) { - let menuItems = []; - for (let arme of armesList) { - menuItems.push({ - name: arme.data.competence, - icon: "", - callback: target => { RdDUtility.rollInitiativeCompetence(combatantId, arme) } - }); - } - new ContextMenu(html, ".directory-list", menuItems).render(); - } - } - - /* -------------------------------------------- */ - static incDecInit(combatantId, incDecValue) { - const combatant = game.combat.getCombatant(combatantId); - let initValue = combatant.initiative + incDecValue; - game.combat.setInitiative(combatantId, initValue); - } - - /* -------------------------------------------- */ - static pushInitiativeOptions(html, options) { - for (let i = 0; i < options.length; i++) { - let option = options[i]; - if (option.name == 'COMBAT.CombatantReroll') { // Replace ! - option.name = "Sélectionner l'initiative..."; - option.condition = true; - option.icon = ''; - option.callback = target => { - RdDUtility.displayInitiativeMenu(html, target.data('combatant-id')); - } - } - } - options.push({ - name: "Incrémenter initiative", - condition: true, - icon: '', - callback: target => { - RdDUtility.incDecInit(target.data('combatant-id'), +0.01); - } - }); - options.push({ - name: "Décrémenter initiative", - condition: true, - icon: '', - callback: target => { - RdDUtility.incDecInit(target.data('combatant-id'), -0.01); - } - }); - } /* -------------------------------------------- */ static async chatListeners(html) { diff --git a/packs/vehicules.db b/packs/vehicules.db index 9ced9deb..f458efb6 100644 --- a/packs/vehicules.db +++ b/packs/vehicules.db @@ -5,4 +5,5 @@ {"_id":"RFOYL8HBUxd32DXS","name":"Galère","permission":{"default":0,"rYShh2P1DNavdoBD":3},"type":"vehicule","data":{"categorie":"Bateau","resistance":36,"structure":14,"vitesse":"2/2/1","bonus":"(12)/+12/+16","manoeuvrabilite":"0/-4/-6","equipage":10,"capacite_encombrement":300,"description":"Description ...","notesmj":"Notes du MJ"},"sort":100001,"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/vehicules/galere.webp","token":{"flags":{},"name":"Galère","displayName":0,"img":"systems/foundryvtt-reve-de-dragon/icons/vehicules/galere_token.webp","tint":"","width":14,"height":14,"scale":1,"mirrorX":false,"mirrorY":false,"lockRotation":false,"rotation":0,"vision":false,"dimSight":0,"brightSight":0,"dimLight":0,"brightLight":0,"sightAngle":360,"lightAngle":360,"lightColor":"","lightAlpha":1,"lightAnimation":{"type":"","speed":5,"intensity":5},"actorId":"RFOYL8HBUxd32DXS","actorLink":false,"disposition":0,"displayBars":0,"bar1":{"attribute":""},"bar2":{"attribute":""},"randomImg":false},"items":[],"effects":[]} {"_id":"TDpSn7GawJ1LCHp7","name":"Charette","permission":{"default":0,"rYShh2P1DNavdoBD":3},"type":"vehicule","data":{"categorie":"Chariot","resistance":16,"structure":8,"vitesse":"","bonus":"","manoeuvrabilite":"","equipage":1,"capacite_encombrement":100,"description":"Description ...","notesmj":"Notes du MJ"},"sort":100001,"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/vehicules/charette.webp","token":{"flags":{},"name":"Charette","displayName":0,"img":"systems/foundryvtt-reve-de-dragon/icons/vehicules/charette_token.webp","tint":"","width":3,"height":3,"scale":1.1,"mirrorX":false,"mirrorY":false,"lockRotation":false,"rotation":0,"vision":false,"dimSight":0,"brightSight":0,"dimLight":0,"brightLight":0,"sightAngle":360,"lightAngle":360,"lightColor":"","lightAlpha":1,"lightAnimation":{"type":"","speed":5,"intensity":5},"actorId":"TDpSn7GawJ1LCHp7","actorLink":false,"disposition":0,"displayBars":0,"bar1":{"attribute":""},"bar2":{"attribute":""},"randomImg":false},"items":[],"effects":[]} {"_id":"ZiyRDzz3gGzlpLIc","name":"Barque","permission":{"default":0,"rYShh2P1DNavdoBD":3},"type":"vehicule","data":{"categorie":"Barque","resistance":20,"structure":10,"vitesse":"3/2/1","bonus":"(4)/+4/+6","manoeuvrabilite":"0/0/-4","equipage":4,"capacite_encombrement":100,"description":"Description ...","notesmj":"Notes du MJ"},"sort":100001,"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/vehicules/barque.webp","token":{"flags":{},"name":"Barque","displayName":0,"img":"systems/foundryvtt-reve-de-dragon/icons/vehicules/barque_token.webp","tint":"","width":6,"height":6,"scale":1,"mirrorX":false,"mirrorY":false,"lockRotation":false,"rotation":0,"vision":false,"dimSight":0,"brightSight":0,"dimLight":0,"brightLight":0,"sightAngle":360,"lightAngle":360,"lightColor":"","lightAlpha":1,"lightAnimation":{"type":"","speed":5,"intensity":5},"actorId":"ZiyRDzz3gGzlpLIc","actorLink":false,"disposition":0,"displayBars":0,"bar1":{"attribute":""},"bar2":{"attribute":""},"randomImg":false},"items":[],"effects":[]} +{"_id":"gM77co80kmpVsYg6","name":"Posé par terre","permission":{"default":0,"Q2G6GTdrotKzYGUC":3},"type":"vehicule","data":{"categorie":"Autre","resistance":0,"structure":0,"vitesse":"","bonus":"","manoeuvrabilite":"","equipage":0,"capacite_encombrement":100,"description":"

Déposer ici les objets que vous voulez échanger avec d'autres joueurs

","notesmj":""},"sort":100001,"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/templates/icone_parchement_vierge.png","token":{"flags":{},"name":"Posé par terre","displayName":0,"img":"systems/foundryvtt-reve-de-dragon/icons/templates/icone_parchement_vierge.png","tint":"","width":1,"height":1,"scale":1,"mirrorX":false,"mirrorY":false,"lockRotation":false,"rotation":0,"vision":false,"dimSight":0,"brightSight":0,"dimLight":0,"brightLight":0,"sightAngle":360,"lightAngle":360,"lightColor":"","lightAlpha":1,"lightAnimation":{"type":"","speed":5,"intensity":5},"actorId":"gM77co80kmpVsYg6","actorLink":false,"disposition":0,"displayBars":0,"bar1":{"attribute":""},"bar2":{"attribute":""},"randomImg":false},"items":[],"effects":[]} {"_id":"idyDmDWYpQ4Eppen","name":"Chariot","permission":{"default":0,"rYShh2P1DNavdoBD":3},"type":"vehicule","data":{"categorie":"Chariot","resistance":20,"structure":10,"vitesse":"","bonus":"","manoeuvrabilite":"","equipage":1,"capacite_encombrement":150,"description":"Description ...","notesmj":"Notes du MJ"},"sort":100001,"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/vehicules/chariot.webp","token":{"flags":{},"name":"Chariot","displayName":0,"img":"systems/foundryvtt-reve-de-dragon/icons/vehicules/chariot_token.webp","tint":"","width":4,"height":4,"scale":1.4,"mirrorX":false,"mirrorY":false,"lockRotation":false,"rotation":0,"vision":false,"dimSight":0,"brightSight":0,"dimLight":0,"brightLight":0,"sightAngle":360,"lightAngle":360,"lightColor":"","lightAlpha":1,"lightAnimation":{"type":"","speed":5,"intensity":5},"actorId":"idyDmDWYpQ4Eppen","actorLink":false,"disposition":0,"displayBars":0,"bar1":{"attribute":""},"bar2":{"attribute":""},"randomImg":false},"items":[],"effects":[]} diff --git a/templates/chat-resultat-recettecuisine.html b/templates/chat-resultat-recettecuisine.html index fa75d72d..086b6e81 100644 --- a/templates/chat-resultat-recettecuisine.html +++ b/templates/chat-resultat-recettecuisine.html @@ -8,7 +8,7 @@ {{#if rolled.isSuccess}} {{alias}} réussit sa recette, avec un plat de {{qualiteFinale}} pour {{oeuvre.data.sust}} Points de Sustentation. {{else}} - {{alias}} fait un pière cuisinier(e), et obtient {{#if (lt qualiteFinale 0)}}un plat à l'exotisme certain{{else}}un plat de qualité {{qualiteFinale}}{{/if}}. + {{alias}} fait un piètre cuisinier(e), et obtient {{#if (lt qualiteFinale 0)}}un plat à l'exotisme certain{{else}}un plat de qualité {{qualiteFinale}}{{/if}}. Selon la décision du MJ, le plat peut fournir {{oeuvre.data.sust}} Points de Sustentation {{/if}}