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 f78bfc02..9cff8723 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,
{
@@ -490,7 +758,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 = {
@@ -550,8 +818,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':
@@ -571,9 +839,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,
@@ -591,7 +858,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 })
});
}
@@ -616,7 +883,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);
@@ -724,7 +991,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' }, {
@@ -803,10 +1070,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({
@@ -814,14 +1081,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)
@@ -834,7 +1101,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/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}}