From 6587334f1b611e8974250868d51cee3b19c49b22 Mon Sep 17 00:00:00 2001 From: Ulty Date: Sat, 18 May 2024 15:21:17 +0200 Subject: [PATCH] COFantasy: update for custom slots on the sheet. --- COFantasy/3.15/COFantasy.js | 1354 ++++++++++++++++++++++++----------- COFantasy/3.15/doc.html | 259 ++++++- COFantasy/COFantasy.js | 1354 ++++++++++++++++++++++++----------- COFantasy/ChangeLog.md | 10 + COFantasy/doc.html | 259 ++++++- 5 files changed, 2380 insertions(+), 856 deletions(-) diff --git a/COFantasy/3.15/COFantasy.js b/COFantasy/3.15/COFantasy.js index baf0c9b32..26b5d93c3 100644 --- a/COFantasy/3.15/COFantasy.js +++ b/COFantasy/3.15/COFantasy.js @@ -1,3 +1,4 @@ +//Derni\xE8re modification : sam. 18 mai 2024, 03:02 // ------------------ generateRowID code from the Aaron --------------------- const generateUUID = (function() { "use strict"; @@ -79,6 +80,7 @@ let COF_loaded = false; // - nextPrescience : pour le changement de tour car prescience ne revient que d'un tour // - afterDisplay : donn\xE9es \xE0 afficher apr\xE8s un display // - version : la version du script en cours, pour d\xE9tecter qu'on change de version +// - personnageCibleCree : savoir si la cible a \xE9t\xE9 cr\xE9\xE9e // statistiques : des statistiques pour les jets de d\xE9s // statistiquesEnPause @@ -86,7 +88,7 @@ var COFantasy = COFantasy || function() { "use strict"; - const versionFiche = 5.03; + const versionFiche = 5.04; const PIX_PER_UNIT = 70; const HISTORY_SIZE = 200; @@ -242,6 +244,11 @@ var COFantasy = COFantasy || function() { val: false, type: 'bool' }, + PR_rend_mana: { + explications: "L'utilisation d'un PR pour se reposer rend aussi de la mana en plus des PV. Le montant rendu est le d\xE9 de mana + mod. de magie.", + val: false, + type: 'bool' + }, contrecoup: { explications: "Avec la Mana Totale, permet au lanceur de sort de payer un d\xE9ficit de PM en PV (COF p. 181)", val: false, @@ -682,38 +689,62 @@ var COFantasy = COFantasy || function() { charId }; let labelArmeGauche = 0; - let armures; - let labelArmure = ficheAttribute(perso, 'torseequipe', '0'); - if (labelArmure && labelArmure != '-1') { - armures = armures || listAllArmors(perso); - let armure = armures[labelArmure]; - if (armure) { - let rawArmure = fieldAsString(armure, 'effetarmure', ''); - if (rawArmure) raw += '\n' + rawArmure; - } - } - let labelCasque = ficheAttribute(perso, 'teteequipe', '0'); - if (labelCasque && labelCasque != '-1') { - armures = armures || listAllArmors(perso); - let casque = armures[labelCasque]; - if (casque) { - let rawCasque = fieldAsString(casque, 'effetarmure', ''); - if (rawCasque) raw += '\n' + rawCasque; - } - } let labelArme = ficheAttributeAsInt(perso, 'maindroite', 0); let labelGauche = ficheAttribute(perso, 'maingauche', '0'); - if (typeof labelGauche == 'string' && labelGauche.startsWith('b')) { - if (labelGauche != 'b-1') { + let versionFiche = findObjs({ + _type: 'attribute', + _characterid: charId, + name: 'version', + }, { + caseInsensitive: true + }); + if (versionFiche.length === 0) versionFiche = 0; + else versionFiche = parseFloat(versionFiche[0].get('current')); + if (versionFiche < 5.04) { + let armures; + let labelArmure = ficheAttribute(perso, 'torseequipe', '0'); + if (labelArmure && labelArmure != '-1') { armures = armures || listAllArmors(perso); - let labelBouclier = labelGauche.substring(1); - let bouclier = armures[labelBouclier]; - if (bouclier) { - let rawBouclier = fieldAsString(bouclier, 'effetarmure', ''); - if (rawBouclier) raw += '\n' + rawBouclier; + let armure = armures[labelArmure]; + if (armure) { + let rawArmure = fieldAsString(armure, 'effetarmure', ''); + if (rawArmure) raw += '\n' + rawArmure; + } + } + let labelCasque = ficheAttribute(perso, 'teteequipe', '0'); + if (labelCasque && labelCasque != '-1') { + armures = armures || listAllArmors(perso); + let casque = armures[labelCasque]; + if (casque) { + let rawCasque = fieldAsString(casque, 'effetarmure', ''); + if (rawCasque) raw += '\n' + rawCasque; + } + } + if (typeof labelGauche == 'string' && labelGauche.startsWith('b')) { + if (labelGauche != 'b-1') { + armures = armures || listAllArmors(perso); + let labelBouclier = labelGauche.substring(1); + let bouclier = armures[labelBouclier]; + if (bouclier) { + let rawBouclier = fieldAsString(bouclier, 'effetarmure', ''); + if (rawBouclier) raw += '\n' + rawBouclier; + } + } + } else if (labelGauche != '2m') labelArmeGauche = toInt(labelGauche); + } else { //versionFiche >= 5.04 + //D'abord les objets \xE9quip\xE9s + let armures = listAllArmors(perso); + for (let label in armures) { + let ar = armures[label]; + if (ar.equipearmure == '1') { + let r = fieldAsString(ar, 'effetarmure', ''); + if (r) raw += '\n' + r; } } - } else if (labelGauche != '2m') labelArmeGauche = toInt(labelGauche); + if (typeof labelGauche != 'string' || + (!labelGauche.startsWith('b') && labelGauche != '2m')) + labelArmeGauche = toInt(labelGauche); + } let att; let attaques; if (labelArme) { @@ -775,68 +806,128 @@ var COFantasy = COFantasy || function() { return perso.predicates; } let raw = ficheAttribute(perso, 'predicats_script', ''); - if (perso.armesEnMain) { - if (perso.arme && perso.arme.predicats) - raw += '\n' + perso.arme.predicats; - if (perso.armeGauche && perso.armeGauche.predicats) - raw += '\n' + perso.armeGauche.predicats; - } else if (perso.arme) { //possible si appel\xE9 depuis armesEnMain - if (perso.arme.predicats) - raw += '\n' + perso.arme.predicats; - } else { //il faut chercher les pr\xE9dicats des armes en main - //On n'appelle pas armesEnMain pour \xE9viter la r\xE9cursion - //et pour \xE9viter trop de calcul - let labelArmeGauche = 0; - let armures; - let labelArmure = ficheAttribute(perso, 'torseequipe', '0'); - if (labelArmure && labelArmure != '-1') { - armures = armures || listAllArmors(perso); - let armure = armures[labelArmure]; - if (armure) { - let rawArmure = fieldAsString(armure, 'effetarmure', ''); - if (rawArmure) raw += '\n' + rawArmure; + let versionFiche = findObjs({ + _type: 'attribute', + _characterid: perso.charId, + name: 'version', + }, { + caseInsensitive: true + }); + if (versionFiche.length === 0) versionFiche = 0; + else versionFiche = parseFloat(versionFiche[0].get('current')); + if (versionFiche < 5.04) { + if (perso.armesEnMain) { + if (perso.arme && perso.arme.predicats) + raw += '\n' + perso.arme.predicats; + if (perso.armeGauche && perso.armeGauche.predicats) + raw += '\n' + perso.armeGauche.predicats; + } else if (perso.arme) { //possible si appel\xE9 depuis armesEnMain + if (perso.arme.predicats) + raw += '\n' + perso.arme.predicats; + } else { //il faut chercher les pr\xE9dicats des armes en main + //On n'appelle pas armesEnMain pour \xE9viter la r\xE9cursion + //et pour \xE9viter trop de calcul + let armures; + let labelArmure = ficheAttribute(perso, 'torseequipe', '0'); + if (labelArmure && labelArmure != '-1') { + armures = armures || listAllArmors(perso); + let armure = armures[labelArmure]; + if (armure) { + let rawArmure = fieldAsString(armure, 'effetarmure', ''); + if (rawArmure) raw += '\n' + rawArmure; + } } - } - let labelCasque = ficheAttribute(perso, 'teteequipe', '0'); - if (labelCasque && labelCasque != '-1') { - armures = armures || listAllArmors(perso); - let casque = armures[labelCasque]; - if (casque) { - let rawCasque = fieldAsString(casque, 'effetarmure', ''); - if (rawCasque) raw += '\n' + rawCasque; + let labelCasque = ficheAttribute(perso, 'teteequipe', '0'); + if (labelCasque && labelCasque != '-1') { + armures = armures || listAllArmors(perso); + let casque = armures[labelCasque]; + if (casque) { + let rawCasque = fieldAsString(casque, 'effetarmure', ''); + if (rawCasque) raw += '\n' + rawCasque; + } + } + let labelArmeGauche = 0; + let labelArme = getLabelArme(perso, 'droite', estMook); + let labelGauche = getLabelArme(perso, 'gauche', estMook); + if (typeof labelGauche == 'string' && labelGauche.startsWith('b')) { + if (labelGauche != 'b-1') { + armures = armures || listAllArmors(perso); + let labelBouclier = labelGauche.substring(1); + let bouclier = armures[labelBouclier]; + if (bouclier) { + let rawBouclier = fieldAsString(bouclier, 'effetarmure', ''); + if (rawBouclier) raw += '\n' + rawBouclier; + } + } + } else if (labelGauche != '2m') labelArmeGauche = toInt(labelGauche); + let attaques; + if (labelArme) { + attaques = listAllAttacks(perso); + let att = attaques[labelArme]; + if (att) { + let rawArme = fieldAsString(att, 'armepredicats', ''); + if (rawArme) raw += '\n' + rawArme; + } + } + //Ensuite l'arme gauche + if (labelArmeGauche) { + if (!attaques) { + attaques = listAllAttacks(perso); + } + let att = attaques[labelArmeGauche]; + if (att) { + let rawArme = fieldAsString(att, 'armepredicats', ''); + if (rawArme) raw += '\n' + rawArme; + } } } - let labelArme = getLabelArme(perso, 'droite', estMook); - let labelGauche = getLabelArme(perso, 'gauche', estMook); - if (typeof labelGauche == 'string' && labelGauche.startsWith('b')) { - if (labelGauche != 'b-1') { - armures = armures || listAllArmors(perso); - let labelBouclier = labelGauche.substring(1); - let bouclier = armures[labelBouclier]; - if (bouclier) { - let rawBouclier = fieldAsString(bouclier, 'effetarmure', ''); - if (rawBouclier) raw += '\n' + rawBouclier; - } - } - } else if (labelGauche != '2m') labelArmeGauche = toInt(labelGauche); - let attaques; - if (labelArme) { - attaques = listAllAttacks(perso); - let att = attaques[labelArme]; - if (att) { - let rawArme = fieldAsString(att, 'armepredicats', ''); - if (rawArme) raw += '\n' + rawArme; - } - } - //Ensuite l'arme gauche - if (labelArmeGauche) { - if (!attaques) { + } else { //versionFiche >= 5.04 + //D'abord les objets \xE9quip\xE9s + let armures = listAllArmors(perso); + for (let label in armures) { + let ar = armures[label]; + if (ar.equipearmure == '1') { + let r = fieldAsString(ar, 'effetarmure', ''); + if (r) raw += '\n' + r; + } + } + //Puis les armes en main + if (perso.armesEnMain) { + if (perso.arme && perso.arme.predicats) + raw += '\n' + perso.arme.predicats; + if (perso.armeGauche && perso.armeGauche.predicats) + raw += '\n' + perso.armeGauche.predicats; + } else if (perso.arme) { //possible si appel\xE9 depuis armesEnMain + if (perso.arme.predicats) + raw += '\n' + perso.arme.predicats; + } else { //il faut chercher les pr\xE9dicats des armes en main + //On n'appelle pas armesEnMain pour \xE9viter la r\xE9cursion + //et pour \xE9viter trop de calcul + let labelArmeGauche = 0; + let labelArme = getLabelArme(perso, 'droite', estMook); + let labelGauche = getLabelArme(perso, 'gauche', estMook); + if (typeof labelGauche != 'string' || + (!labelGauche.startsWith('b') && labelGauche != '2m')) + labelArmeGauche = toInt(labelGauche); + let attaques; + if (labelArme) { attaques = listAllAttacks(perso); + let att = attaques[labelArme]; + if (att) { + let rawArme = fieldAsString(att, 'armepredicats', ''); + if (rawArme) raw += '\n' + rawArme; + } } - let att = attaques[labelArmeGauche]; - if (att) { - let rawArme = fieldAsString(att, 'armepredicats', ''); - if (rawArme) raw += '\n' + rawArme; + //Ensuite l'arme gauche + if (labelArmeGauche) { + if (!attaques) { + attaques = listAllAttacks(perso); + } + let att = attaques[labelArmeGauche]; + if (att) { + let rawArme = fieldAsString(att, 'armepredicats', ''); + if (rawArme) raw += '\n' + rawArme; + } } } } @@ -849,8 +940,12 @@ var COFantasy = COFantasy || function() { function predicateAsBool(perso, name) { let pred = getPredicates(perso); - if (Array.isArray(pred)) pred = pred[0]; - return pred[name]; + let r = pred[name]; + if (r === undefined) return false; + if (Array.isArray(r)) r = r.find(function(p) { + return p; + }); + return r; } function predicateAsInt(perso, name, def, defPresent) { @@ -858,11 +953,15 @@ var COFantasy = COFantasy || function() { let r = pred[name]; if (r === undefined) return def; if (defPresent !== undefined) def = defPresent; - if (Array.isArray(r)) r = r[0]; + if (Array.isArray(r)) { + if (r.length === 0) return def; + r = Math.max(...r); + } if (r === true) return def; return toInt(r, def); } + //Renvoie toujours un tableau, possiblement vide function predicatesNamed(perso, name) { let pred = getPredicates(perso); let r = pred[name]; @@ -1871,7 +1970,8 @@ var COFantasy = COFantasy || function() { //de peut \xEAtre un nombre > 0 ou bien le r\xE9sultat de parseDice function rollDePlus(de, options) { options = options || {}; - options.nbDes = options.nbDes || 1; + let nbDes = options.nbDes || 1; + let bonus = options.bonus || 0; if (de.dice !== undefined) { if (!de.nbDe) { return { @@ -1879,12 +1979,11 @@ var COFantasy = COFantasy || function() { roll: '' + de.bonus }; } - options.nbDes = options.nbDes || de.nbDe; - options.bonus = options.bonus || de.bonus; + nbDes = options.nbDes || de.nbDe; + bonus = options.bonus || de.bonus; de = de.dice; } - let count = options.nbDes; - let bonus = options.bonus || 0; + let count = nbDes; let explose = options.deExplosif || false; let texteJetDeTotal = ''; let jetTotal = 0; @@ -1914,7 +2013,7 @@ var COFantasy = COFantasy || function() { style += ' background-color: ' + couleurs.background + ';'; style += ' color: ' + couleurs.color + ';'; let msg = ' 0) { msg += '+' + bonus; @@ -1969,7 +2068,7 @@ var COFantasy = COFantasy || function() { max: attr.get('max'), }); } - token.set(fieldv, val);//On le fait aussi pour forcer la mise \xE0 jour de la barre + token.set(fieldv, val); //On le fait aussi pour forcer la mise \xE0 jour de la barre let aset = { current: val, withWorker: true @@ -2154,6 +2253,34 @@ var COFantasy = COFantasy || function() { } } + function modOfCarac(carac) { + switch (carac) { + case 'force': + case 'FORCE': + return 'FOR'; + case 'dexterite': + case 'DEXTERITE': + return 'DEX'; + case 'constitution': + case 'CONSTITUTION': + return 'CON'; + case 'intelligence': + case 'INTELLIGENCE': + return 'INT'; + case 'sagesse': + case 'SAGESSE': + return 'SAG'; + case 'charisme': + case 'CHARISME': + return 'CHA'; + case 'perception': + case 'PERCEPTION': + return 'PER'; + default: + return ''; + } + } + //Retourne le mod de la caract\xE9ristque enti\xE8re. //si carac n'est pas une carac, retourne 0 //perso peut ne pas avoir de token ou \xEAtre juste un charId @@ -2161,31 +2288,26 @@ var COFantasy = COFantasy || function() { if (perso.charId === undefined) perso = { charId: perso }; + let modCarac = modOfCarac(carac); let mod = 0; if (persoEstPNJ(perso)) { - switch (carac) { - case 'force': - case 'FORCE': + switch (modCarac) { + case 'FOR': mod = ficheAttributeAsInt(perso, 'pnj_for', 0); break; - case 'dexterite': - case 'DEXTERITE': + case 'DEX': mod = ficheAttributeAsInt(perso, 'pnj_dex', 0); break; - case 'constitution': - case 'CONSTITUTION': + case 'CON': mod = ficheAttributeAsInt(perso, 'pnj_con', 0); break; - case 'intelligence': - case 'INTELLIGENCE': + case 'INT': mod = ficheAttributeAsInt(perso, 'pnj_int', 0); break; - case 'sagesse': - case 'SAGESSE': + case 'SAG': mod = ficheAttributeAsInt(perso, 'pnj_sag', 0); break; - case 'charisme': - case 'CHARISME': + case 'CHAR': mod = ficheAttributeAsInt(perso, 'pnj_cha', 0); break; default: @@ -2198,11 +2320,12 @@ var COFantasy = COFantasy || function() { ficheAttributeAsInt(perso, carac, 10) - attributeAsInt(perso, 'affaiblissementde' + carac, 0); mod = Math.floor((valCarac - 10) / 2); } - if (carac == 'force' || carac == 'FORCE') { + if (modCarac == 'FOR') { if (attributeAsBool(perso, 'mutationMusclesHypertrophies')) mod += 2; if (attributeAsBool(perso, 'grandeTaille')) mod += 2; if (attributeAsBool(perso, 'lycanthropie')) mod += 1; - } else if ((carac == 'DEXTERITE' || carac == 'dexterite') && attributeAsBool(perso, 'mutationSilhouetteFiliforme')) mod += 4; + } else if (modCarac == 'DEX' && attributeAsBool(perso, 'mutationSilhouetteFiliforme')) mod += 4; + mod += predicateAsInt(perso, 'bonus_' + modCarac, 0); return mod; } @@ -5413,9 +5536,7 @@ var COFantasy = COFantasy || function() { case 'etat': return !getState(perso, condition.etat); case 'attribut': - if (condition.valeur === undefined) - return !attributeAsBool(perso, condition.attribute); - return testAttribut(perso, condition.attrbute, condition.valeur, condition); + return !testConditionAttribut(condition, perso); case 'premiereAttaque': return attributeAsBool(perso, 'attributDeCombat_premiereAttaque'); } @@ -5586,6 +5707,13 @@ var COFantasy = COFantasy || function() { return currentCharge === 0; } + function armeChargeeDeGrenaille(perso, arme) { + if (!arme.charge || !arme.poudre) return false; + let currentCharge = attributeAsInt(perso, 'chargeGrenaille_' + arme.label, 0); + return currentCharge > 0; + } + + //options peut avoir les champs: // - ressource, un attribut // - overlay @@ -6250,7 +6378,9 @@ var COFantasy = COFantasy || function() { } function bonusAuxCompetences(personnage, comp, expliquer) { - let bonus = 0; + let bonus = predicateAsInt(personnage, 'bonusTests_' + comp, 0); + if (bonus) + expliquer("Bonus de comp\xE9tence : " + ((bonus < 0) ? "-" : "+") + bonus); switch (comp) { case 'acrobatie': case 'acrobaties': @@ -6461,7 +6591,9 @@ var COFantasy = COFantasy || function() { return options.cacheBonusToutesCaracs.val; } } - let bonus = 0; + let bonus = predicateAsInt(personnage, 'bonusTousTests', 0); + if (bonus) + expliquer("Bonus aux tests : " + ((bonus < 0) ? "-" : "+") + bonus); if (attributeAsBool(personnage, 'chantDesHeros')) { let bonusChantDesHeros = getIntValeurOfEffet(personnage, 'chantDesHeros', 1); let chantDesHerosIntense = attributeAsInt(personnage, 'chantDesHerosTempeteDeManaIntense', 0); @@ -6674,6 +6806,11 @@ var COFantasy = COFantasy || function() { switch (carac) { case 'DEX': { + let bonusDEX = predicateAsInt(personnage, 'bonusTests_DEX', 0) + Math.max(predicateAsInt(personnage, 'bonusTests_dexterite', 0), predicateAsInt(personnage, 'bonusTests_dexterit\xE9', 0)); + if (bonusDEX) { + expliquer("Bonus aux jets de DEX : " + ((bonusDEX < 0) ? "-" : "+") + bonusDEX); + bonus += bonusDEX; + } if (attributeAsBool(personnage, 'agrandissement')) { expliquer("Agrandi : -2 au jet de DEX"); bonus -= 2; @@ -6723,6 +6860,11 @@ var COFantasy = COFantasy || function() { break; case 'FOR': { + let bonusFOR = predicateAsInt(personnage, 'bonusTests_FOR', 0) + predicateAsInt(personnage, 'bonusTests_force', 0); + if (bonusFOR) { + expliquer("Bonus aux jets de FOR : " + ((bonusFOR < 0) ? "-" : "+") + bonusFOR); + bonus += bonusFOR; + } if (attributeAsBool(personnage, 'rayonAffaiblissant')) { let malusRayonAffaiblissant = getIntValeurOfEffet(personnage, 'rayonAffaiblissant', 2); expliquer("Affaibli : -" + malusRayonAffaiblissant + " au jet de FOR"); @@ -6791,6 +6933,11 @@ var COFantasy = COFantasy || function() { break; case 'INT': { + let bonusINT = predicateAsInt(personnage, 'bonusTests_INT', 0) + predicateAsInt(personnage, 'bonusTests_intelligence', 0); + if (bonusINT) { + expliquer("Bonus aux jets d'INT : " + ((bonusINT < 0) ? "-" : "+") + bonusINT); + bonus += bonusINT; + } if (attributeAsBool(personnage, 'secretsDeLAuDela')) { bonus += 5; expliquer("Secrets de l'au-del\xE0 : +5"); @@ -6803,6 +6950,11 @@ var COFantasy = COFantasy || function() { break; case 'CHA': { + let bonusCHA = predicateAsInt(personnage, 'bonusTests_CHA', 0) + predicateAsInt(personnage, 'bonusTests_charisme', 0); + if (bonusCHA) { + expliquer("Bonus aux jets de CHA : " + ((bonusCHA < 0) ? "-" : "+") + bonusCHA); + bonus += bonusCHA; + } if (attributeAsBool(personnage, 'aspectDeLaSuccube')) { let bonusAspectDeLaSuccube = getIntValeurOfEffet(personnage, 'aspectDeLaSuccube', 5); expliquer("Aspect de la succube : +" + bonusAspectDeLaSuccube + " au jet de CHA"); @@ -6827,6 +6979,11 @@ var COFantasy = COFantasy || function() { break; case 'CON': { + let bonusCON = predicateAsInt(personnage, 'bonusTests_CON', 0) + predicateAsInt(personnage, 'bonusTests_constitution', 0); + if (bonusCON) { + expliquer("Bonus aux jets de CON : " + ((bonusCON < 0) ? "-" : "+") + bonusCON); + bonus += bonusCON; + } if (attributeAsBool(personnage, 'mutationSilhouetteMassive')) { expliquer("Silhouette massive : +5 au jet de CON"); bonus += 5; @@ -6855,6 +7012,15 @@ var COFantasy = COFantasy || function() { } } break; + case 'SAG': + { + let bonusSAG = predicateAsInt(personnage, 'bonusTests_SAG', 0) + predicateAsInt(personnage, 'bonusTests_sagesse', 0); + if (bonusSAG) { + expliquer("Bonus aux jets de SAG : " + ((bonusSAG < 0) ? "-" : "+") + bonusSAG); + bonus += bonusSAG; + } + } + break; } let bonusCompetence; if (options && options.competence) { @@ -6877,8 +7043,8 @@ var COFantasy = COFantasy || function() { bestFit = caracsLimitees.includes(carac); } } + let compSansBlanc = options.competence.toLowerCase().replace(/ /g, '_'); if (bonusCompetence === undefined) { - let compSansBlanc = options.competence.toLowerCase().replace(/ /g, '_'); options.bonusAttrs = options.bonusAttrs || []; options.bonusAttrs.push(compSansBlanc); options.bonusPreds = options.bonusPreds || []; @@ -6931,6 +7097,7 @@ var COFantasy = COFantasy || function() { let dice = 20; if ((estAffaibli(personnage) && !predicateAsBool(personnage, 'insensibleAffaibli')) || getState(personnage, 'immobilise') || + (carac == 'DEX' && getState(personnage, 'encombre')) || attributeAsBool(personnage, 'mortMaisNAbandonnePas')) dice = 12; else { @@ -7255,6 +7422,10 @@ var COFantasy = COFantasy || function() { let d20roll = roll.results.total; let bonusText = (bonusCarac > 0) ? "+" + bonusCarac : (bonusCarac === 0) ? "" : bonusCarac; testRes.texte = jetCache ? d20roll + bonusCarac : buildinline(roll) + bonusText; + if (options.chanceRollId && options.chanceRollId[testId]) { + bonusCarac += options.chanceRollId[testId]; + testRes.texte += "+" + options.chanceRollId[testId]; + } effetAuD20(personnage, d20roll); if (d20roll == 20) { testRes.reussite = true; @@ -7741,22 +7912,29 @@ var COFantasy = COFantasy || function() { layer: 'walls' }); murs = murs.map(function(path) { - let chemin = JSON.parse(path.get('_path')); - if (chemin.length < 2) return []; - if (chemin[1][0] != 'L') return []; - let p = { - angle: path.get('rotation') / 180 * Math.PI, - width: path.get('width'), - height: path.get('height'), - top: path.get('top'), - left: path.get('left'), - scaleX: path.get('scaleX'), - scaleY: path.get('scaleY'), - }; - chemin = chemin.map(function(v) { - return translatePathCoordinates(v[1], v[2], p); - }); - return chemin; + let pa = path.get('_path'); + if (!pa) return []; + try { + let chemin = JSON.parse(pa); + if (chemin.length < 2) return []; + if (chemin[1][0] != 'L') return []; + let p = { + angle: path.get('rotation') / 180 * Math.PI, + width: path.get('width'), + height: path.get('height'), + top: path.get('top'), + left: path.get('left'), + scaleX: path.get('scaleX'), + scaleY: path.get('scaleY'), + }; + chemin = chemin.map(function(v) { + return translatePathCoordinates(v[1], v[2], p); + }); + return chemin; + } catch (error) { + error("Erreur, chemin mal form\xE9 dans le calque d'\xE9clairage dynamique", path); + log(error.name + ": " + error.message); + } }); //On rajoute les portes ferm\xE9es. let doors = findObjs({ @@ -8679,7 +8857,7 @@ var COFantasy = COFantasy || function() { return; } let res = { - type: 'attribut', + type: 'attributCible', attribute: args[1], valeur: args[2].toLowerCase(), text: args[1] + ' ' + args[2] @@ -9866,21 +10044,28 @@ var COFantasy = COFantasy || function() { return; } case 'decrAttribute': - if (cmd.length < 2) { - error("Erreur interne d'une commande g\xE9n\xE9r\xE9e par bouton", cmd); - return; - } - let attr = getObj('attribute', cmd[1]); - if (attr === undefined) { - attr = tokenAttribute(attaquant, cmd[1]); - if (attr.length === 0) { - error("Attribut \xE0 changer perdu", cmd); + { + if (cmd.length < 2) { + error("Erreur interne d'une commande g\xE9n\xE9r\xE9e par bouton", cmd); return; } - attr = attr[0]; + let attr = getObj('attribute', cmd[1]); + if (attr === undefined) { + attr = tokenAttribute(attaquant, cmd[1]); + if (attr.length === 0) { + error("Attribut \xE0 changer perdu", cmd); + return; + } + attr = attr[0]; + } + let da = { + id: attr.id, + val: 1 + }; + if (cmd.length > 2) da.val = toInt(cmd[2], 1); + scope.decrAttribute = da; + return; } - scope.decrAttribute = attr.id; //Seulement l'id pour pouvoir cloner - return; case 'decrLimitePredicatParTour': if (cmd.length < 2) { error("Erreur interne d'une commande g\xE9n\xE9r\xE9e par bouton", cmd); @@ -10705,6 +10890,17 @@ var COFantasy = COFantasy || function() { return (attr[0].get('current') + '').toLowerCase() == valeur; } + function testConditionAttribut(cond, attaquant) { + if (cond.valeur === undefined) { + if (attributeAsBool(attaquant, cond.attribute)) return true; + if (cond.attribute == 'armeDArgent') { + return attributeAsBool(attaquant, 'formeDAnge') && predicateAsInt(attaquant, 'voieDeLArchange', 1) > 2; + } + return false; + } + return testAttribut(attaquant, cond.attribute, cond.valeur, cond); + } + function testCondition(cond, attaquant, cibles, deAttaque, options) { if (cond == 'toujoursVrai') return true; switch (cond.type) { @@ -10731,16 +10927,7 @@ var COFantasy = COFantasy || function() { }); return resEtatCible; case 'attribut': - { - if (cond.valeur === undefined) { - if (attributeAsBool(attaquant, cond.attribute)) return true; - if (cond.attribute == 'armeDArgent') { - return attributeAsBool(attaquant, 'formeDAnge') && predicateAsInt(attaquant, 'voieDeLArchange', 1) > 2; - } - return false; - } - return testAttribut(attaquant, cond.attrbute, cond.valeur, cond); - } + return testConditionAttribut(cond, attaquant); case 'attributCible': { if (cond.valeur === undefined) { @@ -10750,7 +10937,7 @@ var COFantasy = COFantasy || function() { return res; } let res = cibles.every(function(target) { - return testAttribut(target, cond.attrbute, cond.valeur, cond); + return testAttribut(target, cond.attribute, cond.valeur, cond); }); return res; } @@ -10848,24 +11035,27 @@ var COFantasy = COFantasy || function() { } break; case 'decrAttribute': - let attr = getObj('attribute', branch.decrAttribute); - if (attr === undefined) { - error("Attribut introuvable", branch.decrAttribute); - break; - } - let oldval = parseInt(attr.get('current')); - if (isNaN(oldval) || oldval < 1) { - sendChar(attr.get('characterid'), "ne peut plus faire cela", true); + { + let da = branch.decrAttribute; + let attr = getObj('attribute', da.id); + if (attr === undefined) { + error("Attribut introuvable", da.id); + break; + } + let oldval = parseInt(attr.get('current')); + if (isNaN(oldval) || oldval < da.val) { + sendChar(attr.get('characterid'), "ne peut plus faire cela", true); + break; + } + evt.attributes = evt.attributes || []; + evt.attributes.push({ + attribute: attr, + current: oldval, + max: attr.get('max') + }); + attr.set('current', oldval - da.val); break; } - evt.attributes = evt.attributes || []; - evt.attributes.push({ - attribute: attr, - current: oldval, - max: attr.get('max') - }); - attr.set('current', oldval - 1); - break; case 'decrLimitePredicatParTour': //Ne fait que diminuer l'attribut, n'emp\xEAche pas l'attaque let pred = branch.decrLimitePredicatParTour; @@ -12698,7 +12888,9 @@ var COFantasy = COFantasy || function() { } defense -= bonus; } - defense += predicateAsInt(target, 'DEF', 0); + defense += predicateAsInt(target, 'DEF', 0); //deprecated + defense += predicateAsInt(target, 'bonus_DEF', 0); + defense += predicateAsInt(target, 'bonus_DEF(anneau)', 0); if (attaquant && predicateAsBool(target, 'armeDeLEte') && predicateAsBool(attaquant, 'creatureDeLHiver')) { explications.push("Prot\xE9g\xE9 par une arme de l'\xE9t\xE9 => +25 en DEF"); defense += 25; @@ -12706,6 +12898,27 @@ var COFantasy = COFantasy || function() { if (predicateAsBool(target, 'petiteTaille') && !attributeAsBool(target, 'agrandissement')) { defense += 1; } + //Bonus au d\xE9fi duelliste + let defiDuellisteAttr = tokenAttribute(target, 'defiDuelliste'); + if (defiDuellisteAttr.length > 0) { + defiDuellisteAttr = defiDuellisteAttr[0]; + let cibleDefi = defiDuellisteAttr.get('max'); + if (cibleDefi.startsWith(attaquant.token.id)) cibleDefi = true; + else { + let cibleDefiSep = cibleDefi.indexOf(' '); + let cibleDefiName = cibleDefi.substring(cibleDefiSep + 1); + if (cibleDefiName == nomPerso(attaquant)) { + let cibleDefiId = cibleDefi.substring(0, cibleDefiSep); + cibleDefi = persoOfId(cibleDefiId, cibleDefiName, pageId); + cibleDefi = cibleDefi === undefined || cibleDefi.id == attaquant.token.id; + } else cibleDefi = false; + } + if (cibleDefi) { + let bonusDefi = parseInt(defiDuellisteAttr.get('current')); + defense += bonusDefi; + explications.push("D\xE9fi => +" + bonusDefi + " DEF"); + } + } return defense; } @@ -13587,7 +13800,6 @@ var COFantasy = COFantasy || function() { attBonus -= 5; target.attaqueDansLeNoir = 5; } - } } if (predicateAsBool(attaquant, 'liberateurDeDorn') && estGeant(target)) { @@ -13646,6 +13858,27 @@ var COFantasy = COFantasy || function() { target.combattreLaCorruption = combattreLaCorruption; explications.push("Combattre la corruption => +" + combattreLaCorruption + " attaque et DM"); } + //Bonus au d\xE9fi duelliste + let defiDuellisteAttr = tokenAttribute(attaquant, 'defiDuelliste'); + if (defiDuellisteAttr.length > 0) { + defiDuellisteAttr = defiDuellisteAttr[0]; + let cibleDefi = defiDuellisteAttr.get('max'); + if (cibleDefi.startsWith(target.token.id)) cibleDefi = true; + else { + let cibleDefiSep = cibleDefi.indexOf(' '); + let cibleDefiName = cibleDefi.substring(cibleDefiSep + 1); + if (cibleDefiName == nomPerso(target)) { + let cibleDefiId = cibleDefi.substring(0, cibleDefiSep); + cibleDefi = persoOfId(cibleDefiId, cibleDefiName, pageId); + cibleDefi = cibleDefi === undefined || cibleDefi.id == target.token.id; + } else cibleDefi = false; + } + if (cibleDefi) { + let bonusDefi = parseInt(defiDuellisteAttr.get('current')); + attBonus += bonusDefi; + explications.push("D\xE9fi => +" + bonusDefi + " attaque"); + } + } return attBonus; } @@ -13800,22 +14033,22 @@ var COFantasy = COFantasy || function() { //Retourne true si il existe une limite qui emp\xEAche de lancer le sort //N'ajoute pas l'\xE9v\xE9nement \xE0 l'historique - //explications est optionnel - function limiteRessources(personnage, options, defResource, msg, evt, explications) { + //perso et explications sont optionnels + function limiteRessources(perso, options, defResource, msg, evt, explications) { let depMana = { cout_null: true }; - if (options.magieEnArmureMana && personnage) { + if (options.magieEnArmureMana && perso) { options.mana = options.mana || 0; - let ma = malusArmure(personnage); + let ma = malusArmure(perso); let m = ma; if (m > 0) { - let magieEnArmure = predicateAsInt(personnage, 'magieEnArmure', 0); - let defa = defenseArmure(personnage); + let magieEnArmure = predicateAsInt(perso, 'magieEnArmure', 0); + let defa = defenseArmure(perso); if (2 * magieEnArmure >= defa + ma) { //pas de malus m = 0; } else { - if (magieEnArmure > 0 && predicateAsBool(personnage, 'magieEnArmureFacilitee')) { + if (magieEnArmure > 0 && predicateAsBool(perso, 'magieEnArmureFacilitee')) { m -= magieEnArmure; if (m < 0) m = 1; } @@ -13829,29 +14062,29 @@ var COFantasy = COFantasy || function() { } } if (options.mana) { - if (personnage) { - depMana = depenseManaPossible(personnage, options.mana, msg); + if (perso) { + depMana = depenseManaPossible(perso, options.mana, msg); if (!depMana) return true; } else { error("Impossible de savoir qui doit d\xE9penser de la mana", options); return true; } } - depMana = testLimitePar(personnage, 'Jour', options, depMana, defResource, msg, evt, explications); + depMana = testLimitePar(perso, 'Jour', options, depMana, defResource, msg, evt, explications); if (!depMana) return true; - depMana = testLimitePar(personnage, 'Combat', options, depMana, defResource, msg, evt, explications); + depMana = testLimitePar(perso, 'Combat', options, depMana, defResource, msg, evt, explications); if (!depMana) return true; if (!depMana) return true; - depMana = testLimitePar(personnage, 'Tour', options, depMana, defResource, msg, evt, explications); + depMana = testLimitePar(perso, 'Tour', options, depMana, defResource, msg, evt, explications); if (!depMana) return true; if (options.tempsRecharge) { - if (personnage) { - if (attributeAsBool(personnage, options.tempsRecharge.effet)) { - sendPerso(personnage, "ne peut pas encore faire cette action", options.secret); + if (perso) { + if (attributeAsBool(perso, options.tempsRecharge.effet)) { + sendPerso(perso, "ne peut pas encore faire cette action", options.secret); return true; } if (options.tempsRecharge.duree > 0) { - setAttrDuree(personnage, options.tempsRecharge.effet, options.tempsRecharge.duree, evt); + setAttrDuree(perso, options.tempsRecharge.effet, options.tempsRecharge.duree, evt); } } else { error("Impossible de savoir \xE0 qui s'applique le temps de recharge", options); @@ -13859,41 +14092,42 @@ var COFantasy = COFantasy || function() { } } if (options.dose) { - if (personnage) { + if (perso) { let nomDose = options.dose.replace(/_/g, ' '); - let doses = attributeAsInt(personnage, 'dose_' + options.dose, 0); + let doses = attributeAsInt(perso, 'dose_' + options.dose, 0); if (doses === 0) { - sendPerso(personnage, "n'a plus de " + nomDose, options.secret); + sendPerso(perso, "n'a plus de " + nomDose, options.secret); return true; } - setTokenAttr(personnage, 'dose_' + options.dose, doses - 1, evt); + setTokenAttr(perso, 'dose_' + options.dose, doses - 1, evt); } else { error("Impossible de savoir qui doit d\xE9penser la dose", options); return true; } } if (options.limiteAttribut) { - if (personnage) { + if (perso) { let nomAttr = options.limiteAttribut.nom; - let currentAttr = attributeAsInt(personnage, nomAttr, 0); + let currentAttr = attributeAsInt(perso, nomAttr, 0); if (currentAttr >= options.limiteAttribut.limite) { - depMana = depasseLimite(personnage, nomAttr, options.limiteAttribut.message, msg, evt, options); + depMana = depasseLimite(perso, nomAttr, options.limiteAttribut.message, msg, evt, options); if (!depMana) return true; } - setTokenAttr(personnage, nomAttr, currentAttr + 1, evt); + setTokenAttr(perso, nomAttr, currentAttr + 1, evt); } else { error("Impossible de savoir \xE0 qui appliquer la limitation", options); return true; } } if (options.decrAttribute) { - let attr = getObj('attribute', options.decrAttribute); + let da = options.decrAttribute; + let attr = getObj('attribute', da.id); if (attr === undefined) { - error("Attribut introuvable", options.decrAttribute); + error("Attribut introuvable", da.id); return true; } let oldval = parseInt(attr.get('current')); - if (isNaN(oldval) || oldval < 1) { + if (isNaN(oldval) || oldval < da.val) { let expliquer = sendChar; if (options.secret) expliquer = whisperChar; expliquer(attr.get('characterid'), "ne peut plus faire cela", true); @@ -13905,22 +14139,62 @@ var COFantasy = COFantasy || function() { current: oldval, max: attr.get('max') }); - attr.set('current', oldval - 1); + attr.set('current', oldval - da.val); } if (options.decrLimitePredicatParTour) { let pred = options.decrLimitePredicatParTour; - if (personnage) { - let test = testLimiteUtilisationsCapa(personnage, pred, 'tour', + if (perso) { + let test = testLimiteUtilisationsCapa(perso, pred, 'tour', "ne peut plus utiliser " + pred + " ce tour", "Action impossible, pas de pr\xE9dicat " + pred); if (test === undefined) return true; - utiliseCapacite(personnage, test, evt); + utiliseCapacite(perso, test, evt); } else { error("Impossible de savoir \xE0 qui appliquer la limitation du pr\xE9dicat " + pred, options); return true; } } - if (personnage) depenseMana(personnage, depMana, msg, evt); + if (options.depensePR) { + if (!perso) { + error("Impossible de savoir \xE0 qui enlever les PR.", options); + return true; + } + let pr = pointsDeRecuperation(perso); + if (!pr || !pr.current || pr.current < options.depensePR.val) { + if (options.depensePR.pv) { + options.rolls = options.rolls || {}; + let pv = options.rolls.depensePR || rollDePlus(options.depensePR.pv); + evt.action = evt.action || { + rolls: {} + }; + evt.action.rolls.depensePR = pv; + let r = { + total: pv.val, + type: 'normal', + display: pv.roll + }; + let expl = explications || []; + perso.ignoreTouteRD = true; + dealDamage(perso, r, [], evt, false, {}, expl, + function(dmgDisplay, dmg) { + let dmgMsg = "perd " + dmgDisplay + " PV"; + if (explications) explications.push(dmgMsg); + else { + expl.forEach(function(m) { + sendPerso(perso, m, options.secret); + }); + sendPerso(perso, dmgMsg, options.secret); + } + }); + } else { + sendPerso(perso, "Plus de PR \xE0 d\xE9penser", options.secret); + return true; + } + } else { //d\xE9pense de PR + enleverPointDeRecuperation(perso, pr, evt, options.depensePR.val); + } + } + if (perso) depenseMana(perso, depMana, msg, evt); return false; } @@ -14545,7 +14819,7 @@ var COFantasy = COFantasy || function() { let distance = Math.random() * reglesOptionelles.divers.val.echec_critique_boule_de_feu.val * PIX_PER_UNIT / computeScale(pageId); pc.x = Math.round(left + Math.cos(angle) * distance); pc.y = Math.round(top + Math.sin(angle) * distance); - page = page || getObj("page", pageId); + page = page || getObj('page', pageId); let width = page.get('width') * PIX_PER_UNIT; let height = page.get('height') * PIX_PER_UNIT; if (pc.x < 0) pc.x = 0; @@ -15236,7 +15510,9 @@ var COFantasy = COFantasy || function() { } else if (cibles.length == 1 && options.contact && options.attaqueAcrobatique) { let rollId = 'attaqueAcrobatique_' + attackingToken.id; let rollOptions = { - competence: 'acrobatie' + competence: 'acrobatie', + chanceRollId: options.chanceRollId, + rolls: options.rolls }; explications.push("Tentative d'acrobatie pour surprendre " + nomPerso(cible)); testCaracteristique(attaquant, 'DEX', 15, rollId, rollOptions, evt, @@ -15247,8 +15523,11 @@ var COFantasy = COFantasy || function() { }); if (tr.reussite) { explications.push("R\xE9ussite : " + nomPerso(attaquant) + " peut faire une attaque sournoise"); - options.sournoise = options.sournoise || 0; - options.sournoise += options.attaqueAcrobatique; + if (!options.attaqueAcrobatiqueReussie) { + options.sournoise = options.sournoise || 0; + options.sournoise += options.attaqueAcrobatique; + options.attaqueAcrobatiqueReussie = true; + } } else { explications.push("Rat\xE9, " + nomPerso(attaquant) + " r\xE9alise une attaque normale" + tr.rerolls); } @@ -15316,6 +15595,7 @@ var COFantasy = COFantasy || function() { for (let pref in rawList) { let ra = rawList[pref]; if (ra.armelabel === undefined) ra.armelabel = 0; + if (ra.armenom === undefined) ra.armenom = ''; if (liste[ra.armelabel]) { error("Plusieurs attaques de label " + ra.armelabel, ra); continue; @@ -15337,7 +15617,7 @@ var COFantasy = COFantasy || function() { let ra = rawList[pref]; if (ra.labelarmure === undefined) ra.labelarmure = 0; if (liste[ra.labelarmure]) { - error("Plusieurs attaques de label " + ra.labelarmure, ra); + error("Plusieurs armures de label " + ra.labelarmure, ra); continue; } ra.prefixe = pref; @@ -19032,7 +19312,7 @@ var COFantasy = COFantasy || function() { if (cibleDefiName == nomPerso(target)) { let cibleDefiId = cibleDefi.substring(0, cibleDefiSep); cibleDefi = persoOfId(cibleDefiId, cibleDefiName, pageId); - cibleDefi = cibleDefi === undefined || cibleDefi.id == target.id; + cibleDefi = cibleDefi === undefined || cibleDefi.id == target.token.id; } else cibleDefi = false; } if (cibleDefi) { @@ -19140,7 +19420,21 @@ var COFantasy = COFantasy || function() { // 1+nb d\xE9g\xE2ts suppl\xE9mentaires + : rolls de d\xE9g\xE2ts critiques let toEvaluateDmg = "[[" + mainDmgRollExpr + "]]" + extraDmgRollExpr; sendChat('', toEvaluateDmg, function(resDmg) { - let rollsDmg = target.rollsDmg || resDmg[0]; + let rollsDmg = resDmg[0]; + if (target.rollsDmg) { + //We may have more rolls or different rolls + let pos = 0; + let original = target.rollsDmg.inlinerolls; + let reroll = rollsDmg.inlinerolls; + original.forEach(function(r, i) { + while (reroll[pos] && r.expression != reroll[pos].expression) + pos++; + if (reroll[pos]) { + reroll[pos] = r; + } + pos++; + }); + } let afterEvaluateDmg = rollsDmg.content.split(' '); let mainDmgRollNumber = rollNumber(afterEvaluateDmg[0]); mainDmgRoll.total = rollsDmg.inlinerolls[mainDmgRollNumber].results.total; @@ -20380,6 +20674,10 @@ var COFantasy = COFantasy || function() { layer: 'objects' }); let allies = alliesParPerso[attaquant.charId] || new Set(); + let page = getObj('page', evt.action.pageId); + let murs = getWalls(page, evt.action.pageId); + let ptt = pointOfToken(cible.token); + let pta = pointOfToken(attaquant.token); tokens = tokens.filter(function(tok) { if (tok.id == attaquant.token.id) return false; if (tok.id == cible.token.id) return false; @@ -20392,6 +20690,12 @@ var COFantasy = COFantasy || function() { if (dejaTouche) return false; let dist = distanceCombat(cible.token, tok, evt.action.pageId); if (dist === 0 || dist > options.portee) return false; + if (murs) { + let x = tok.get('left'); + let y = tok.get('top'); + if (obstaclePresent(x, y, ptt, murs)) return false; + if (obstaclePresent(x, y, pta, murs)) return false; + } return true; }); let distance = distanceCombat(cible.token, attaquant.token, evt.action.pageId); @@ -20874,6 +21178,10 @@ var COFantasy = COFantasy || function() { res.sauf.argent += 5; } let rd = ficheAttribute(perso, 'RDS', ''); + predicatesNamed(perso, 'bonus_RD').forEach(function(r) { + if (rd === '') rd = r; + else rd += ',' + r; + }); rd = (rd + '').trim(); if (rd === '') { perso.rd = res; @@ -21228,6 +21536,7 @@ var COFantasy = COFantasy || function() { }; //si il faut noter les DMs d'un type particulier if (mainDmgType == 'drain') dmSuivis.drain = dmgTotal; predicatesNamed(target, 'vitaliteSurnaturelle').forEach(function(a) { + if (typeof a != "string") return; let indexType = a.indexOf('/'); if (indexType < 0 || indexType == a.length) return; a = a.substring(indexType + 1); @@ -21505,7 +21814,8 @@ var COFantasy = COFantasy || function() { } // pr doit \xEAtre d\xE9fini, et pr.current > 0 - function enleverPointDeRecuperation(perso, pr, evt) { + // n est optionnel + function enleverPointDeRecuperation(perso, pr, evt, n) { evt.attributes = evt.attributes || []; let attrPR; if (pr.attribut) { @@ -21515,10 +21825,15 @@ var COFantasy = COFantasy || function() { caseInsensitive: true }); if (attrPR.length === 0) { + let current = 4; + if (n) { + current = 5 - n; + if (current < 0) current = 0; + } attrPR = createObj("attribute", { characterid: perso.charId, name: 'pr', - current: 4, + current, max: 5 }); evt.attributes.push({ @@ -21532,7 +21847,10 @@ var COFantasy = COFantasy || function() { attribute: attrPR, current: pr.current }); - pr.current--; + if (n) { + pr.current -= n; + if (pr.current < 0) pr.current = 0; + } else pr.current--; attrPR.set('current', pr.current); } @@ -21761,6 +22079,15 @@ var COFantasy = COFantasy || function() { } let rdTarget = getRDS(target); let rd = rdTarget.rdt || 0; + if (!target.perteDeSubstance && options.attaquant && predicateAsBool(target, 'ancreInvincible')) { + if (predicateAsBool(options.attaquant, 'dragonInvincble')) { + rd += 10; + target.messages.push("Ancre contre le dragon => +10 RD"); + } else if (predicateAsBool(options.attaquant, 'emissaireDuDragonInvincible')) { + rd += 5; + target.messages.push("Ancre contre \xE9missaire du dragon => +5 RD"); + } + } if (rd > 0 && !options.aoe && options.attaquant && predicateAsBool(options.attaquant, 'ventreMou')) { let taille = taillePersonnage(target, 4); if (taille > 4) { @@ -23124,7 +23451,7 @@ var COFantasy = COFantasy || function() { if (!isActive(perso)) return; let persoTest = persoParCharId[perso.charId]; let arme = predicateAsBool(persoTest, 'armeParDefaut'); - if (arme === undefined) return; + if (arme === undefined || arme === false) return; if (arme === true) degainerArme(perso, '', evt); else degainerArme(perso, arme, evt); }); @@ -23152,6 +23479,19 @@ var COFantasy = COFantasy || function() { setTokenAttr(perso, 'charge_' + label, charges[persoTest.charId][label], evt); } }); + //Remise \xE0 z\xE9ro des options de combat + let def0 = { + default: 0 + }; + persosDuCombat.forEach(function(perso) { + setFicheAttr(perso, 'attaque_de_groupe', 1, evt, { + default: 1 + }); + setFicheAttr(perso, 'attaque_en_puissance_check', 0, evt, def0); + setFicheAttr(perso, 'attaque_risquee_check', 0, evt, def0); + setFicheAttr(perso, 'attaque_assuree_check', 0, evt, def0); + setFicheAttr(perso, 'attaque_dm_temp_check', 0, evt, def0); + }); //Effet de ignorerLaDouleur let ilds = allAttributesNamed(attrs, 'douleurIgnoree'); ilds = ilds.concat(allAttributesNamed(attrs, 'memePasMalIgnore')); @@ -23723,7 +24063,12 @@ var COFantasy = COFantasy || function() { log(cmd); return; } - options.decrAttribute = attr.id; + let da = { + id: attr.id, + val: 1 + }; + if (cmd.length > 2) da.val = toInt(cmd[2], 1); + options.decrAttribute = da; return; case 'valeur': if (cmd.length < 2) { @@ -23912,6 +24257,17 @@ var COFantasy = COFantasy || function() { options.terrainDifficile = terrainDifficile; return; } + case 'depensePR': + { + options.depensePR = { + val: 1 + }; + if (cmd.length < 2) return; + options.depensePR.val = toInt(cmd[1], 1, 1); + if (cmd.length < 3) return; + options.depensePR.pv = parseDice(cmd[2], "PV d\xE9pens\xE9s si pas de PR"); + return; + } default: return; } @@ -24441,17 +24797,11 @@ var COFantasy = COFantasy || function() { } } let token = perso.token; - let charId = perso.charId; - let character = getObj("character", charId); - if (character === undefined) { - finalize(); - return; - } let pr = pointsDeRecuperation(perso); let bar2 = parseInt(token.get("bar2_value")); let manaAttr = findObjs({ _type: 'attribute', - _characterid: charId, + _characterid: perso.charId, name: 'PM' }, { caseInsensitive: true @@ -24459,14 +24809,29 @@ var COFantasy = COFantasy || function() { let hasMana = false; let dmTemp = bar2; let estMook = token.get('bar1_link') === ''; + let recupereMana; if (manaAttr.length > 0) { // R\xE9cup\xE9ration des points de mana let manaMax = parseInt(manaAttr[0].get('max')); hasMana = !isNaN(manaMax) && manaMax > 0; if (hasMana) { if (estMook) dmTemp = attributeAsInt(perso, 'DMTEMP', 0); else dmTemp = ficheAttributeAsInt(perso, 'DMTEMP', 0); - if (reposLong && (isNaN(bar2) || bar2 < manaMax)) { - updateCurrentBar(perso, 2, manaMax, evt); + if (isNaN(bar2) || bar2 < manaMax) { + if (reposLong) { + updateCurrentBar(perso, 2, manaMax, evt); + } else if (reglesOptionelles.mana.val.PR_rend_mana.val) { + let de = ficheAttributeAsInt(perso, 'de_mana', 0); + if (de) { + let caracMagique = ficheAttribute(perso, 'carac_mana', '@{INT}'); + if (isNaN(bar2)) bar2 = 0; + recupereMana = { + manaMax, + bar2, + de, + caracMagique + }; + } + } } } } @@ -24485,7 +24850,7 @@ var COFantasy = COFantasy || function() { finalize(); return; } - if (bar1 >= pvmax && !reposLong) { + if (bar1 >= pvmax && !reposLong && !recupereMana) { //Plus rien \xE0 faire si pas un repos long sendPerso(perso, "n'a pas besoin de repos"); finalize(); @@ -24524,7 +24889,6 @@ var COFantasy = COFantasy || function() { finalize(); return; } - let message; if (reposLong && pr && pr.current < pr.max) { // on r\xE9cup\xE8re un PR //Sauf si on a une blessure gave if (getState(perso, 'blesse')) { @@ -24533,11 +24897,11 @@ var COFantasy = COFantasy || function() { sendPerso(perso, "fait un jet de CON pour gu\xE9rir de sa blessure"); let m = "/direct " + onGenre(perso, 'Il', 'Elle') + " fait " + tr.texte; if (tr.reussite) { - sendChar(charId, m + "≥ 8, son \xE9tat s'am\xE9liore nettement." + tr.modifiers, true); + sendPerso(perso, m + "≥ 8, son \xE9tat s'am\xE9liore nettement." + tr.modifiers, true); setState(perso, 'blesse', false, evt); } else { let msgRate = m + "< 8, son \xE9tat reste pr\xE9occupant." + tr.rerolls + tr.modifiers; - sendChar(charId, msgRate, true); + sendPerso(perso, msgRate, true); } finalize(); }); @@ -24549,17 +24913,17 @@ var COFantasy = COFantasy || function() { finalize(); return; } - message = + let message = "Au cours de la nuit, les points de r\xE9cup\xE9ration de " + nomPerso(perso) + " passent de " + (pr.current - 1) + " \xE0 " + pr.current; - sendChar(charId, message, true); + sendPerso(perso, message, true); if (bar1 < pvmax) manquePV.push(perso); finalize(); return; } if (!reposLong && pr) { if (pr.current === 0) { //pas possible de r\xE9cup\xE9rer - message = " a besoin d'une nuit compl\xE8te pour r\xE9cup\xE9rer"; + let message = " a besoin d'une nuit compl\xE8te pour r\xE9cup\xE9rer"; sendPerso(perso, message); finalize(); return; @@ -24569,8 +24933,10 @@ var COFantasy = COFantasy || function() { } let conMod = modCarac(perso, 'constitution'); let niveau = ficheAttributeAsInt(perso, 'niveau', 1); - let characterName = character.get("name"); - let rollExpr = addOrigin(characterName, "[[1d" + dVie + "]]"); + let rollExpr = "[[1d" + dVie + "]]"; + if (recupereMana) { + rollExpr += " [[1d" + recupereMana.de + "]]"; + } sendChat("COF", rollExpr, function(res) { let rollRecupID = "rollRecup_" + perso.token.id; options.rolls = options.rolls || {}; @@ -24579,28 +24945,51 @@ var COFantasy = COFantasy || function() { evt.action = evt.action || {}; evt.action.rolls = evt.action.rolls || {}; evt.action.rolls[rollRecupID] = roll; - let dVieRoll = roll.results.total; - let bonus = conMod + niveau; - let total = dVieRoll + bonus; - if (total < 0) total = 0; - if (bar1 === 0) { - if (attributeAsBool(perso, 'etatExsangue')) { - removeTokenAttr(perso, 'etatExsangue', evt, { - msg: "retrouve des couleurs" - }); + let message; + if (bar1 < pvmax) { + let dVieRoll = roll.results.total; + let bonus = conMod + niveau; + let total = dVieRoll + bonus; + if (total < 0) total = 0; + if (bar1 === 0) { + if (attributeAsBool(perso, 'etatExsangue')) { + removeTokenAttr(perso, 'etatExsangue', evt, { + msg: "retrouve des couleurs" + }); + } } + bar1 += total; + if (bar1 < pvmax) manquePV.push(perso); + else bar1 = pvmax; + updateCurrentBar(perso, 1, bar1, evt); + if (reposLong) { + message = "Au cours de la nuit, "; + } else { + message = "Apr\xE8s 5 minutes de repos, "; + } + message += "r\xE9cup\xE8re " + buildinline(roll) + "+" + bonus + " PV"; + } + if (recupereMana) { + let rollRecupManaID = "rollRecupmana_" + perso.token.id; + roll = + options.rolls[rollRecupManaID] ? options.rolls[rollRecupManaID] : res[0].inlinerolls[1]; + evt.action.rolls[rollRecupManaID] = roll; + let dManaRoll = roll.results.total; + let bonus = computeCarValue(perso, recupereMana.caracMagique); + let total = dManaRoll + bonus; + if (total < 0) total = 0; + let bar2 = recupereMana.bar2 + total; + if (bar2 > recupereMana.manaMax) bar2 = recupereMana.manaMax; + updateCurrentBar(perso, 2, bar2, evt); + if (message) message += " et "; + else message = "Apr\xE8s 5 minutes de repos, r\xE9cup\xE8re "; + message += buildinline(roll) + "+" + bonus + " PM."; + } else message += '.'; + if (pr) { + message += " Il lui reste " + pr.current + " point"; + if (pr.current > 1) message += 's'; + message += " de r\xE9cup\xE9ration"; } - bar1 += total; - if (bar1 < pvmax) manquePV.push(perso); - else bar1 = pvmax; - updateCurrentBar(perso, 1, bar1, evt); - if (reposLong) { - message = "Au cours de la nuit, "; - } else { - message = "Apr\xE8s 5 minutes de repos, "; - } - message += "r\xE9cup\xE8re " + buildinline(roll) + "+" + bonus + " PV."; - if (pr) message += " Il lui reste " + pr.current + " points de r\xE9cup\xE9ration"; sendPerso(perso, message); finalize(); }); @@ -24749,6 +25138,153 @@ var COFantasy = COFantasy || function() { addEvent(evt); } + //Renvoie true si redo possible, false sinon + function redoEvent(evt, action, perso) { + let options = action.options || {}; + options.rolls = action.rolls; + options.choices = action.choices; + switch (evt.type) { + case 'Attaque': + options.redo = true; + if (action.cibles) { + action.cibles.forEach(function(target) { + delete target.partialSaveAuto; + delete target.dmRate; + }); + } + attack(action.playerName, action.playerId, action.attaquant, action.cibles, action.weaponStats, options); + return true; + case 'attaqueMagique': + attaqueMagiqueOpposee(action.playerId, action.attaquant, action.cible, options); + return true; + case 'armeSecrete': + doArmeSecrete(action.perso, action.cible, options); + return true; + case 'boireAlcool': + doBoireAlcool(action.playerId, action.persos, options); + return true; + case 'dmgDirects': + dmgDirects(action.playerId, action.playerName, action.cibles, action.dmg, options); + return true; + case 'degainer': + doDegainer(action.persos, action.armeLabel, options); + return true; + case 'destructionMortsVivants': + doDestructionDesMortsVivants(action.lanceur, action.playerName, action.dm, options); + return true; + case 'echapperEtreinte': + case 'echapperEnveloppement': + doEchapperEnveloppement(action.perso, action.etreinte, action.cube, action.difficulte, options); + return true; + case 'effetTemp': + effetTemporaire(action.playerId, action.cibles, action.effet, action.mEffet, action.duree, options); + return true; + case 'Effet': + effetIndetermine(action.playerId, action.cibles, action.effet, action.activer, action.valeur, options); + return true; + case 'auraDrainDeForce': + if (!action.cibles) return; + action.cibles.forEach(function(perso) { + delete perso.messages; + }); + doAuraDrainDeForce(action.playerId, action.origine, action.cibles, action.mEffet, options); + return true; + case 'auraDrainDeForceSup': + if (!action.cibles) return; + action.cibles.forEach(function(perso) { + delete perso.messages; + }); + doAuraDrainDeForceSup(action.playerId, action.origine, action.cibles, action.mEffet, options); + return true; + case 'enduireDePoison': + doEnduireDePoison(action.perso, action.armeEnduite, action.savePoison, action.forcePoison, action.attribut, + action.testINT, action.infosAdditionelles, options); + return true; + case 'enveloppement': + case '\xE9treinte': + doEnveloppement(action.attaquant, action.cible, action.difficulte, action.type, action.exprDM, options); + return true; + case 'injonction': + injonction(action.playerId, action.attaquant, action.cible, options); + return true; + case 'injonctionMortelle': + injonctionMortelle(action.playerId, action.attaquant, action.cible, options); + return true; + case 'jetPerso': + jetPerso(perso, action.caracteristique, action.difficulte, action.titre, action.playerId, options); + return true; + case 'libererAgrippe': + doLibererAgrippe(action.perso, action.agrippant, action.attrName, options); + return true; + case 'libererEcrase': + doLibererEcrase(action.perso, action.agrippant, action.titre, action.carac, action.difficulte, action.explications, options); + return true; + case 'natureNourriciere': + doNatureNourriciere(action.perso, options); + return true; + case 'nextTurn': + let turnOrder = Campaign().get('turnorder'); + if (turnOrder === '') return false; // nothing in the turn order + turnOrder = JSON.parse(turnOrder); + if (turnOrder.length < 1) return false; // Juste le compteur de tour + let lastTurn = turnOrder.shift(); + turnOrder.push(lastTurn); + Campaign().set('turnorder', JSON.stringify(turnOrder)); + nextTurn(Campaign(), options); + return true; + case 'nouveauJour': + doNouveauJour(action.persos, options); + return true; + case 'peur': + doPeur(action.cibles, action.difficulte, options); + return true; + case 'provocation': + doProvocation(action.voleur, action.cible, options); + return true; + case 'rage': + doRageDuBerserk(action.persos, action.typeRage, options); + return true; + case 'recuperation': + doRecuperation(action.persos, action.reposLong, action.playerId, options); + return true; + case 'save_state': + doSaveState(action.playerId, action.perso, action.etat, action.carac, options, action.opposant, action.seuil); + return true; + case 'save_effet': + doSaveEffet(action.playerId, action.perso, action.effetC, action.attr, action.attrEffet, action.attrName, action.met, action.carac, action.seuil, action.options); + return true; + case 'set_state': + doSetState(action.cibles, action.etat, action.valeur, options); + return true; + case 'sommeil': + doSommeil(action.lanceur, action.cibles, options, action.ciblesSansSave, action.ciblesAvecSave); + return true; + case 'surprise': + doSurprise(action.cibles, action.testSurprise, action.selected, options); + return true; + case 'tourDeForce': //Deprecated + doTourDeForce(action.perso, action.seuil, options); + return true; + case 'tueurFantasmagorique': + tueurFantasmagorique(action.playerId, action.attaquant, action.cible, options); + return true; + case 'enkystementLointain': + enkystementLointain(action.playerId, action.attaquant, action.cible, options); + return true; + case 'vapeursEthyliques': + doVapeursEthyliques(action.playerId, action.persos, options); + return true; + case 'ombre_mouvante': + doOmbreMouvante(action.perso, action.playerId, options); + return true; + case "Sentir la corruption": + sentirLaCorruption(action.playerId, action.chasseur, action.cible, options); + return true; + default: + return false; + } + } + //!cof-bouton-chance [evt.id] [rollId] function boutonChance(msg) { let args = msg.content.split(' '); @@ -24796,7 +25332,7 @@ var COFantasy = COFantasy || function() { } let evtChance = { type: 'chance', - rollId: rollId + rollId }; chance--; undoEvent(evt); @@ -25147,152 +25683,6 @@ var COFantasy = COFantasy || function() { error("Type d'\xE9v\xE8nement pas encore g\xE9r\xE9 pour la chance", evt); } - //Renvoie true si redo possible, false sinon - function redoEvent(evt, action, perso) { - let options = action.options || {}; - options.rolls = action.rolls; - options.choices = action.choices; - switch (evt.type) { - case 'Attaque': - options.redo = true; - if (action.cibles) { - action.cibles.forEach(function(target) { - delete target.partialSaveAuto; - delete target.dmRate; - }); - } - attack(action.playerName, action.playerId, action.attaquant, action.cibles, action.weaponStats, options); - return true; - case 'attaqueMagique': - attaqueMagiqueOpposee(action.playerId, action.attaquant, action.cible, options); - return true; - case 'armeSecrete': - doArmeSecrete(action.perso, action.cible, options); - return true; - case 'boireAlcool': - doBoireAlcool(action.playerId, action.persos, options); - return true; - case 'dmgDirects': - dmgDirects(action.playerId, action.playerName, action.cibles, action.dmg, options); - return true; - case 'degainer': - doDegainer(action.persos, action.armeLabel, options); - return true; - case 'destructionMortsVivants': - doDestructionDesMortsVivants(action.lanceur, action.playerName, action.dm, options); - return true; - case 'echapperEtreinte': - case 'echapperEnveloppement': - doEchapperEnveloppement(action.perso, action.etreinte, action.cube, action.difficulte, options); - return true; - case 'effetTemp': - effetTemporaire(action.playerId, action.cibles, action.effet, action.mEffet, action.duree, options); - return true; - case 'Effet': - effetIndetermine(action.playerId, action.cibles, action.effet, action.activer, action.valeur, options); - return true; - case 'auraDrainDeForce': - if (!action.cibles) return; - action.cibles.forEach(function(perso) { - delete perso.messages; - }); - doAuraDrainDeForce(action.playerId, action.origine, action.cibles, action.mEffet, options); - return true; - case 'auraDrainDeForceSup': - if (!action.cibles) return; - action.cibles.forEach(function(perso) { - delete perso.messages; - }); - doAuraDrainDeForceSup(action.playerId, action.origine, action.cibles, action.mEffet, options); - return true; - case 'enduireDePoison': - doEnduireDePoison(action.perso, action.armeEnduite, action.savePoison, action.forcePoison, action.attribut, - action.testINT, action.infosAdditionelles, options); - return true; - case 'enveloppement': - case '\xE9treinte': - doEnveloppement(action.attaquant, action.cible, action.difficulte, action.type, action.exprDM, options); - return true; - case 'injonction': - injonction(action.playerId, action.attaquant, action.cible, options); - return true; - case 'injonctionMortelle': - injonctionMortelle(action.playerId, action.attaquant, action.cible, options); - return true; - case 'jetPerso': - jetPerso(perso, action.caracteristique, action.difficulte, action.titre, action.playerId, options); - return true; - case 'libererAgrippe': - doLibererAgrippe(action.perso, action.agrippant, action.attrName, options); - return true; - case 'libererEcrase': - doLibererEcrase(action.perso, action.agrippant, action.titre, action.carac, action.difficulte, action.explications, options); - return true; - case 'natureNourriciere': - doNatureNourriciere(action.perso, options); - return true; - case 'nextTurn': - let turnOrder = Campaign().get('turnorder'); - if (turnOrder === '') return false; // nothing in the turn order - turnOrder = JSON.parse(turnOrder); - if (turnOrder.length < 1) return false; // Juste le compteur de tour - let lastTurn = turnOrder.shift(); - turnOrder.push(lastTurn); - Campaign().set('turnorder', JSON.stringify(turnOrder)); - nextTurn(Campaign(), options); - return true; - case 'nouveauJour': - doNouveauJour(action.persos, options); - return true; - case 'peur': - doPeur(action.cibles, action.difficulte, options); - return true; - case 'provocation': - doProvocation(action.voleur, action.cible, options); - return true; - case 'rage': - doRageDuBerserk(action.persos, action.typeRage, options); - return true; - case 'recuperation': - doRecuperation(action.persos, action.reposLong, action.playerId, options); - return true; - case 'save_state': - doSaveState(action.playerId, action.perso, action.etat, action.carac, options, action.opposant, action.seuil); - return true; - case 'save_effet': - doSaveEffet(action.playerId, action.perso, action.effetC, action.attr, action.attrEffet, action.attrName, action.met, action.carac, action.seuil, action.options); - return true; - case 'set_state': - doSetState(action.cibles, action.etat, action.valeur, options); - return true; - case 'sommeil': - doSommeil(action.lanceur, action.cibles, options, action.ciblesSansSave, action.ciblesAvecSave); - return true; - case 'surprise': - doSurprise(action.cibles, action.testSurprise, action.selected, options); - return true; - case 'tourDeForce': //Deprecated - doTourDeForce(action.perso, action.seuil, options); - return true; - case 'tueurFantasmagorique': - tueurFantasmagorique(action.playerId, action.attaquant, action.cible, options); - return true; - case 'enkystementLointain': - enkystementLointain(action.playerId, action.attaquant, action.cible, options); - return true; - case 'vapeursEthyliques': - doVapeursEthyliques(action.playerId, action.persos, options); - return true; - case 'ombre_mouvante': - doOmbreMouvante(action.perso, action.playerId, options); - return true; - case "Sentir la corruption": - sentirLaCorruption(action.playerId, action.chasseur, action.cible, options); - return true; - default: - return false; - } - } function echecTotal(msg) { let args = msg.content.split(' '); @@ -26669,6 +27059,8 @@ var COFantasy = COFantasy || function() { } if (a.charge && attributeAsInt(perso, 'charge_' + l, 1) === 0) degainer += ' (vide)'; + else if (a.poudre && attributeAsInt(perso, 'chargeGrenaille_' + l, 0) > 0) + degainer += ' (grenaille)'; degainer += "," + l + cote + "|"; if (armeADegainer) armeADegainer.unique = undefined; else armeADegainer = { @@ -27070,6 +27462,7 @@ var COFantasy = COFantasy || function() { } else { command += labelArmePrincipale; if (armeDechargee(perso, armePrincipale)) nomCommande += ' (vide)'; + else if (armeChargeeDeGrenaille(perso, armePrincipale)) nomCommande += ' (grenaille)'; } ligneArmePrincipale = bouton(command, nomCommande, perso); } else if (!possedeAttaqueNaturelle) { @@ -27083,6 +27476,7 @@ var COFantasy = COFantasy || function() { if (perso.armeGauche) { let nomCommande = perso.armeGauche.name; if (armeDechargee(perso, perso.armeGauche)) nomCommande += ' (vide)'; + else if (armeChargeeDeGrenaille(perso, perso.armeGauche)) nomCommande += ' (grenaille)'; ligneArmeGauche = bouton("!cof-attack @{selected|token_id} @{target|token_id} " + labelArmeGauche, nomCommande, perso); } //Maintenant on propose de d\xE9gainer @@ -29175,6 +29569,9 @@ var COFantasy = COFantasy || function() { sendChat('', e); }); } + if (options.degainer !== undefined && lanceur) { + degainerArme(lanceur, options.degainer, evt); + } } }; let setOneEffect = function(perso, d) { @@ -29251,6 +29648,9 @@ var COFantasy = COFantasy || function() { setTokenAttr(perso, effet + 'Puissant', puissant, evt); } effetsSpeciaux(lanceur, perso, options); + if (options.degainer !== undefined && !lanceur) { + degainerArme(perso, options.degainer, evt); + } finalize(); }; cibles.forEach(function(perso) { @@ -35558,7 +35958,10 @@ var COFantasy = COFantasy || function() { elixirsACreer = elixirsACreer[0]; let extraFortifiants = toInt(elixirsACreer.get('max'), 0); let extra = extraFortifiants > 0 && elixir.rang == 1; - if (!extra) options.decrAttribute = elixirsACreer.id; + if (!extra) options.decrAttribute = { + id: elixirsACreer.id, + val: 1 + }; if (limiteRessources(forgesort, options, 'elixirsACreer', '\xE9lixirs \xE0 cr\xE9er', evt)) return; if (extra) { evt.attributes = evt.attributes || []; @@ -38355,6 +38758,7 @@ var COFantasy = COFantasy || function() { let token = createObj('graphic', tokSpec); if (token) { evt.tokens = [token]; + toFront(token); } if (stateCOF.options.affichage.val.duree_effets.val) { if (options.brumes) @@ -39290,12 +39694,12 @@ var COFantasy = COFantasy || function() { sendFramedDisplay(display); } - function lancerDefiSamourai(msg) { + function lancerDefi(msg, command, nomAttr, descr, predicatBonus) { let options = parseOptions(msg); if (options === undefined) return; let cmd = options.cmd; if (cmd === undefined || cmd.length < 3) { - error("cof-defi-samourai demande au moins 2 options", + error(command + " demande au moins 2 options", msg.content); return; } @@ -39305,7 +39709,7 @@ var COFantasy = COFantasy || function() { error("Le token s\xE9lectionn\xE9 n'est pas valide", msg.content); return; } - if (attributeAsBool(samourai, 'defiSamourai')) { + if (attributeAsBool(samourai, nomAttr)) { sendPlayer(msg, nomPerso(samourai) + " a d\xE9j\xE0 lanc\xE9 un d\xE9fi durant ce combat."); return; } @@ -39315,7 +39719,7 @@ var COFantasy = COFantasy || function() { return; } const evt = { - type: 'D\xE9fi samoura\xEF' + type: descr }; let explications = []; entrerEnCombat(samourai, [cible], explications, evt); @@ -39326,18 +39730,28 @@ var COFantasy = COFantasy || function() { if (cmd.length > 3) { bonus = parseInt(cmd[3]); if (isNaN(bonus) || bonus < 1) { - error("Bonus de d\xE9fi de samoura\xEF incorrect", cmd[3]); + error("Bonus de " + descr + " incorrect", cmd[3]); bonus = undefined; } } if (bonus === undefined) - bonus = predicateAsInt(samourai, 'voieDeLHonneur', 2); - setTokenAttr(samourai, 'defiSamourai', bonus, evt, { + bonus = predicateAsInt(samourai, predicatBonus, 2); + setTokenAttr(samourai, nomAttr, bonus, evt, { msg: nomPerso(samourai) + " lance un d\xE9fi \xE0 " + nomPerso(cible), maxVal: idName(cible) }); } + function lancerDefiSamourai(msg) { + lancerDefi(msg, 'cof-defi-samourai', 'defiSamourai', 'd\xE9fi de samoura\xEF', + 'voieDeLHonneur'); + } + + function lancerDefiDuelliste(msg) { + lancerDefi(msg, 'cof-defi-duelliste', 'defiDuelliste', 'd\xE9fi de duelliste', + 'voieDuDuelliste'); + } + function parseEnveloppement(msg) { const options = parseOptions(msg); if (options === undefined) return; @@ -40109,7 +40523,7 @@ var COFantasy = COFantasy || function() { error('pas de token s\xE9lectionn\xE9 pour !cof-bonus-couvert'); return; } - var evt = { + const evt = { type: 'Bonus couvert' }; addEvent(evt); @@ -44874,6 +45288,8 @@ var COFantasy = COFantasy || function() { stateCOF.afterDisplay = undefined; stateCOF.statistiquesEnPause = undefined; stateCOF.statistiques = undefined; + stateCOF.personnageCibleCree = undefined; + stateCOF.predicats = {}; log("stateCOf purg\xE9"); log(stateCOF); sendPlayer(msg, "\xC9tat global de COFantasy purg\xE9."); @@ -45090,6 +45506,79 @@ var COFantasy = COFantasy || function() { }); } + //!cof-recupere-mana montant + function recupereMana(msg) { + let options = parseOptions(msg); + if (options === undefined) return; + let cmd = options.cmd; + if (cmd === undefined) { + error("Probl\xE8me de parse options", msg.content); + return; + } + if (cmd.length < 2) { + sendPlayer(msg, "Il manque le montant de mana \xE0 r\xE9cup\xE9rer pour !cof-recupere-mana"); + return; + } + let mana = parseDice(cmd[1], "mana"); + if (mana === undefined) return; + getSelected(msg, function(selected, playerId) { + if (selected.length === 0) { + sendPlayer(msg, "Personne ne r\xE9cup\xE8re de mana", playerId); + return; + } + const evt = { + type: 'r\xE9cup\xE9ration de mana' + }; + addEvent(evt); + iterSelected(selected, function(perso) { + let manaAttr = findObjs({ + _type: 'attribute', + _characterid: perso.charId, + name: 'PM' + }, { + caseInsensitive: true + }); + let manaMax; + if (manaAttr.length > 0) { + manaMax = parseInt(manaAttr[0].get('max')); + } + if (!manaMax || isNaN(manaMax) || manaMax < 0) { + sendPerso(perso, "n'a pas de mana."); + return; + } + let token = perso.token; + let bar2 = parseInt(token.get('bar2_value')); + if (isNaN(bar2)) { + if (token.get('bar1_link') === '') bar2 = 0; + else { //devrait \xEAtre li\xE9 \xE0 la mana courante + sendPerso(perso, "*** Attention, la barre de mana du token n'est pas li\xE9e \xE0 la mana de la fiche ***"); + bar2 = parseInt(manaAttr[0].get('current')); + } + } + if (bar2 >= manaMax) { + sendPerso(perso, "est d\xE9j\xE0 au maximum de mana"); + return; + } + if (limiteRessources(perso, options, 'recupereMana', "r\xE9cup\xE9rer de la mana", evt)) return; + let r = rollDePlus(mana); + let recupere = r.val; + bar2 += recupere; + if (bar2 >= manaMax) { + recupere -= bar2 - manaMax; + bar2 = manaMax; + } + let msg = "r\xE9cup\xE8re "; + if (r.val == recupere) msg += r.roll; + else msg += recupere; + msg += " PM"; + if (recupere > 1) msg += 's'; + if (r.val > recupere) msg += " (le r\xE9sultat du jet \xE9tait " + r.roll + ")"; + sendPerso(perso, msg, options.secret); + updateCurrentBar(perso, 2, bar2, evt); + }); + }, options); + } + function apiCommand(msg) { msg.content = msg.content.replace(/\s+/g, ' '); //remove duplicate whites const command = msg.content.split(' ', 1); @@ -45150,6 +45639,9 @@ var COFantasy = COFantasy || function() { case '!cof-dmg': parseDmgDirects(msg); return; + case '!cof-echange-init': + echangeInit(msg); + return; case '!cof-effet-chaque-d20': setEffetChaqueD20(msg); return; @@ -45235,6 +45727,9 @@ var COFantasy = COFantasy || function() { case '!cof-recuperation': parseRecuperer(msg); return; + case '!cof-recupere-mana': + recupereMana(msg); + return; case '!cof-remove-buf-def': removeBufDef(msg); return; @@ -45294,9 +45789,6 @@ var COFantasy = COFantasy || function() { case '!cof-zone-de-vie': lancerZoneDeVie(msg); return; - case "!cof-echange-init": - echangeInit(msg); - return; case "!cof-a-couvert": aCouvert(msg); return; @@ -45575,6 +46067,9 @@ var COFantasy = COFantasy || function() { case '!cof-torche': switchTorche(msg); return; + case '!cof-defi-duelliste': + lancerDefiDuelliste(msg); + return; case '!cof-defi-samourai': lancerDefiSamourai(msg); return; @@ -46895,6 +47390,11 @@ var COFantasy = COFantasy || function() { actif: "est en combat", fin: '' }, + defiDuelliste: { + activation: "lance un d\xE9fi", + actif: "a lanc\xE9 un d\xE9fi", + fin: '' + }, defiSamourai: { activation: "lance un d\xE9fi", actif: "a lanc\xE9 un d\xE9fi", @@ -49176,6 +49676,7 @@ var COFantasy = COFantasy || function() { // assure un nom unique en ajoutant un num\xE9ro // On en profite aussi pour mettre certaines valeurs par d\xE9faut // retourne un perso si c'est un token de personnage + //Si la barre de vie est li\xE9e, on met \xE0 jour les valeurs, ce n'est plus fait automatiquement oar Roll20 function renameToken(token, tokenName) { let charId = token.get('represents'); if (charId === undefined || charId === '') return; @@ -49232,9 +49733,23 @@ var COFantasy = COFantasy || function() { attrMonteSur[0].remove(); } } - synchronisationDesEtats(perso); - synchronisationDesLumieres(perso, pageId); } + synchronisationDesEtats(perso); + for (let barNumber = 1; barNumber <= 3; barNumber++) { + let attrId = token.get('bar' + barNumber + '_link'); + if (attrId) { + let attr = getObj('attribute', attrId); + if (attr) { + let fieldv = 'bar' + barNumber + '_value'; + token.set(fieldv, attr.get('current')); + let fieldm = 'bar' + barNumber + '_max'; + token.set(fieldm, attr.get('max')); + } + } + } + //On synchronise les barres, au cas o\xF9 la fiche n'a pas \xE9t\xE9 ouverte + //d\xE9j\xE0 fait par l'appel de renameToken + //synchronisationDesLumieres(perso, pageId); return perso; } //cas des mooks : num\xE9rotation @@ -51381,6 +51896,7 @@ var COFantasy = COFantasy || function() { function changePredicats(attr, prev) { let curPred = attr.get('current'); let prevPred = prev.current; + if (!prevPred) return; if (curPred.includes('attaqueEnMeute') != prevPred.includes('attaqueEnMeute')) { recomputeAllies(); } @@ -51478,8 +51994,8 @@ on("change:attribute", function(attr, prev) { if (!COF_loaded) return; let predicats = state.COFantasy.predicats; if (!predicats) return; - let n = attr.get("name"); - if (n == 'predicats_script' || n.includes('armepredicats') || n.includes('effetarmure') || n == 'maindroite' || n == 'maingauche' || n == 'torseequipe' || n == 'teteequipe') { + let n = attr.get('name'); + if (n == 'predicats_script' || n.endsWith('_armepredicats') || n.endsWith('_effetarmure') || n.endsWith('_equipearmure') || n == 'maindroite' || n == 'maingauche') { predicats[attr.get('characterid')] = undefined; COFantasy.changePredicats(attr, prev); } diff --git a/COFantasy/3.15/doc.html b/COFantasy/3.15/doc.html index ac5980e33..bdae6b0a4 100644 --- a/COFantasy/3.15/doc.html +++ b/COFantasy/3.15/doc.html @@ -127,6 +127,7 @@

Sommaire

  • Purger les variables d'état du jeu
  • Conversion depuis Pathfinder
  • +
  • Index des prédicats
  • @@ -136,7 +137,7 @@

    1. Comment utiliser Le scr

    1.1 Tokens et personnages

    -

    Ces scripts ont été écrits pour fonctionner avec les fiches de personnages développées par Natha.

    +

    Ces scripts ont été écrits pour fonctionner avec les fiches de personnages développées par Natha (documentation de la fiche).

    A choisir pendant la création de la partie sur Roll20.
    @@ -170,6 +171,7 @@

    1.3 Prédicats

    Il est aussi possible d'associer des prédicats à des attaques. Ces prédicats sont pris en compte quand ses attaques sont des armes, et ce que ces armes sont tenues en main. Cela permet de coder une grande variété d'effets magiques des armes. Attention, cela ne fonctionne pas pour les mooks avec les prédicats animeAPartirDExistant, intercepter, lienEpique, attaqueEnTraitre, sansPeur, immuniteSaignement, et controleSanguin.

    Une épée +1 affûtée avec le prédicat enchainement (actif pour toute attaque, tant que cette épée est en main).
    +

    Les prédicats à valeur numériques ne se cumulent pas : le script prend la plus grande valeur, si le prédicat apparaît plusieurs fois. Pour les autres prédicats, en général, l'ensemble des prédicats est pris comme un seul prédicat, si cela est possible. Sinon, un seul prédicat est utilisé, les autres sont ignorés.

    1.4 Méthodes de sélection de groupes

    @@ -198,6 +200,12 @@

    Spécifier un coût en mana

  • Même un sort qui ne coûte pas de mana devrait utiliser --mana 0. Cela permet au script de dissiper automatiquement un effet généré par un lanceur de sort mis hors de combat.
  • Une option d'affichage disponible via !cof-options permet de demander au script d'afficher explicitement dans le chat toute dépense de PM effectuée par un personnage.
  • +

    Récupérer de la mana

    +
      +
    • Faire passer une nuit de repos remet les points de mana au maximum.
    • +
    • Une option règle de jeu (accessible avec !cof-options) permet de récupérer de la mana quand on dépense 1 PR pour se reposer (il faut spécifier un dé de mana sur la fiche).
    • +
    • La commande !cof-recupere-mana m permet de récupérer m points de mana. m peut être un nombre positif ou une expression de dé simple.
    • +

    Système de base (COF p. 79)

    Le support des effets supplémentaires à disposition du lanceur de sort sont gérées via l'utilisation d'options sur les commandes utilisées.

      @@ -383,7 +391,7 @@

      Options pour l'attaque :

    • --if condition options --endif: permet de ne prendre en compte des options que si certaines conditions sont réalisées. Il est possible d'utiliser un --else pour activer des options quand les conditions ne sont pas réalisées. Attention, les options qui modifient une option précédente (comme --valeur) doivent apparaître au même niveau que l'option qu'elles modifient. Comme les conditions peuvent dépendre du dé d'attaque, un certain nombre d'options ne sont pas affectées par cette conditionnelle : tempeteDeMana, les options d'aoe (ligne, cone, disque, target), avecd12, avantage, auto, demiAuto, test, pointsVitaux, tempsRecharge, sortilege, bonusCritique, disparition, affute, munition, tirDouble, semonce, tirDeBarrage, poudre, traquenard, feinte, magique, tranchant, percant, contondant, pasDeDmg. Ne pas hésiter à me demander si vous aviez besoin que l'une de ces options puissent être conditionnelle.
    • --ifSaveFails carac seuil options --endif : comme --if, mais fait réaliser une jet de sauvegarde à la cible (comme avec --save), et si le test est raté, applique les options qui suivent. Un --else permet de choisir des options à appliquer quand le test est réussi. À utiliser seulement si on ne peut pas se contenter d'un --save, car ça marche moins bien.
    • -
    • --decrAttribute nom : l'attaque n'est possible que si l'attribut existe et si sa valeur est strictement positive. L'attaque diminue cette valeur de 1. Si cette option est utilisée à l'intérieur d'un --if, alors l'option se contente de diminuer l'attribut (on peut savoir si la valeur est positive grâce à --if etat nom).
    • +
    • --decrAttribute nom : l'attaque n'est possible que si l'attribut existe et si sa valeur est strictement positive. L'attaque diminue cette valeur de 1. On peut preciser une valeur plus grande après le nom de l'attribut (--decrAttribute nom d teste si la valeur est supérieure à d et la diminue de d). Si cette option est utilisée à l'intérieur d'un --if, alors l'option se contente de diminuer l'attribut (on peut savoir si la valeur est positive grâce à --if etat nom).
    • --decrLimitePredicatParTour nom : l'attaque n'est possible que si un prédicat nom existe et si elle n'a pas été utilisée plus de fois dans le tour que la valeur de ce prédicat. L'attaque augmente ce nombre de 1.
    • --tempsRecharge effet duree : l'attaque n'est possible que si l'effet est inactif sur l'attaquant, et de plus active l'effet sur l'attaquant pour la durée indiquée si l'attaque est possible. Il existe un effet temporaire générique, rechargeGen(desc) que vous pouvez utiliser si aucun effet existant ne correspond pour votre attaque.
    • --etat e: si l'attaque touche, la cible passe dans l'état e. Il est aussi possible de spécifier une caractéristique et un seuil (comme pour !cof-set-state) pour faire afficher à chaque tour une action permettant de se libérer de l'état.
    • @@ -545,7 +553,7 @@

      Dégainer une arme : !cof-degaine
    • Enfin, en fin de combat, si le personnage possède un prédicat armeParDefaut, le script va lui faire dégainer l'arme dont le label est la valeur du prédicat. Donc si le prédicat n'a pas de label associé, le personnage va rengainer son arme.

    Dans tous les cas, si le personnage dégaine une arme à 2 mains, le script fait enlever le bouclier, et il le fait remettre si on passe d'une arme à 2 mains à une arme à 1 main.

    -

    Il existe un prédicat DEF qui permet d'ajouter sa valeur à la défense d'un personnage. C'est particulièrement utile pour coder une arme de parade. Par exemple avecDEF:2 dans les prédicats de l'arme, elle augmentera la défense de 2 quand elle sera portée.

    +

    Il existe un prédicat bonus_DEF qui permet d'ajouter sa valeur à la défense d'un personnage. C'est particulièrement utile pour coder une arme de parade. Par exemple avecbonus_DEF:2 dans les prédicats de l'arme, elle augmentera la défense de 2 quand elle sera portée.

    Couvert :!cof-bonus-couvert b

    @@ -797,6 +805,7 @@

    Autres bufs et debufs

  • --limiteParCombat : même effet que la limite par jour. Si aucun argument n'est donné, la limite est de une fois par combat. Attention, il faut un lanceur auquel appliquer la limite par combat !
  • --tempsRecharge effet duree : l'action n'est possible que si l'effet est inactif sur le personnage qui génère l'effet, et de plus active l'effet sur celui-ci pour la durée indiquée. Il existe un effet temporaire générique, rechargeGen(desc) que vous pouvez utiliser si aucun effet existant ne correspond.
  • --dose nom: définie une ressource nommée dose_nom, dont la valeur doit être positive. Utile par exemple pour les parchemins, les potions ou les baguettes.
  • +
  • --depensePR: demande de dépenser un point de récupération. Si un premier argument est ajouté, on enlève ce nombre aux PR au lieu de 1. Si un second argument est précisé, on enlève ce second argument aux PV quand le personnage n'a plus de PR.
  • --portee id d : impose aux cibles de l'effet d'être à moins de d mètres du token id. Si --lanceur est précisé, inutile de donner id.
  • --puissant : lance la version puissante de l'effet
  • --puissant oui : idem
  • @@ -1044,7 +1053,7 @@

    Barbare

    Voie de la brute

    1. Argument de taille : ajouter le bonus de PV sur la fiche. Ajouter un prédicat argumentDeTaille. Le script devrait automatiquement tenir compte de cette capacité pour les tests de négociation, persuasion et intimidation. Pour s'assurer que la bonne compétence est utilisée, vous pouvez ajouter ces noms au handout de compétence (voir la partie sur les jet de caractéristiques, le handout Compétences) ou sur les fiches de personnages.
    2. -
    3. Tour de force : ajouter un prédicat tourDeForce. Le script ajoutera systématique un bouton lors de tous les tests de FOR du personnage. Au MJ de décider s'il peut être utilisé ou non.
    4. +
    5. Tour de force : ajouter un prédicat tourDeForce.
    6. Attaque brutale : faire l'attaque avec les options --bonusAttaque -2 --plus 1d6
    7. Briseur d'os : ajouter un prédicat briseurDOs. Cela va automatiquement affecter les chances de coups critique au contact.
    8. Force héroïque : reporter sur la fiche.
    9. @@ -1282,7 +1291,7 @@

      Voie de la divination

    10. 6eme sens : ajouter le bonus en DEF divers et initiative diver, ajouter une compétence vigilance, de valeur 2*rang (le bonus aux jet de surprise).
    11. Détection de l'invisible : !cof-effet-temp detectionDeLInvisible [[5+@{selected|CHA}]] --mana 0.
    12. Clairvoyance : !cof-lancer-sort peut voir et entendre à distance --mana 1
    13. -
    14. Prescience : ajouter un prédicat prescience (on peut y associer un nombre pour avoir plus d'une prescience par tour). Si le personnage était sur la carte au moment de lancer le combat, il se verra proposer un bouton pour utiliser sa prescience à la fin de chaque tour. Attention, en l'état, il est impossible d'annuler l'utilisation de ce bouton. De plus, ce bouton ne fera rien si le script a été interrompu depuis le début du tour (interruption de la partie, redémarrage du script, etc...).
    15. +
    16. Prescience : ajouter un prédicat prescience (on peut y associer un nombre pour avoir plus d'une prescience par combat). Si le personnage était sur la carte au moment de lancer le combat, il se verra proposer un bouton pour utiliser sa prescience à la fin de chaque tour. Attention, en l'état, il est impossible d'annuler l'utilisation de ce bouton. De plus, ce bouton ne fera rien si le script a été interrompu depuis le début du tour (interruption de la partie, redémarrage du script, etc...).
    17. Hyperconscience : reporter sur la fiche.

    Voie de l'envoûteur

    @@ -1888,7 +1897,7 @@

    Voie de l'arc et du cheval

    1. Monture loyale : ajouter un prédicat montureLoyale. Pour la monture, il faut faire un personnage avec un prédicat monture. Pour monter sur la monture, utiliser !cof-en-selle @{selected|token_id} nom_de_la_monture, et pour en descendre, il suffit de faire la même commande (utiliser une ability "token action" rend cela assez facile à l'usage).
    2. Tir en mouvement : pas de support particulier.
    3. -
    4. Tir fatal : ajouter un prédicat tirFatal. Pour tenir compte du bonus supplémentaire avec le rang dans la voie, ajouter un prédicat voieDeLArcEtDuCheval de valeur le rang atteint dans cette voie. Si vous souhaitez que cette capacité s'applique à une autre classe d'arme que les arcs, vous pouvez associer ce nom au prédicat tirfatal (attention, sans accent). Par exemple, pour les arbalètes, tirFatal:arbalete.
    5. +
    6. Tir fatal : ajouter un prédicat tirFatal. Pour tenir compte du bonus supplémentaire avec le rang dans la voie, ajouter un prédicat voieDeLArcEtDuCheval de valeur le rang atteint dans cette voie. Si vous souhaitez que cette capacité s'applique à une autre classe d'arme que les arcs, vous pouvez associer ce nom au prédicat tirFatal (attention, sans accent). Par exemple, pour les arbalètes, tirFatal:arbalete.

    Voie du dirigeant

      @@ -2114,7 +2123,7 @@

      Voie de l'archange

      1. Forme d'ange : !cof-effet-temp formeDAnge [[5+@{selected|SAG}]] --mana 0. Le MJ devra gérer l'aspect en vol.
      2. Soins améliorés : utiliser un prédicat voieDeLArchange avec comme valeur le rang dans la voie.
      3. -
      4. Épée céleste : rien à faire si le prédicat voieDeLArchange a la bonne valeur. Si le personnage n'a pas déjà la capacité Arme d'argent, ajouter une attaque pour cette arme, avec le modificateur armeDArgent et l'option --si etat armeDArgent.
      5. +
      6. Épée céleste : rien à faire si le prédicat voieDeLArchange a la bonne valeur. Si le personnage n'a pas déjà la capacité Arme d'argent, ajouter une attaque pour cette arme, avec le modificateur armeDArgent et l'option --si etat armeDArgent. Si vous souhaitez que cette arme soit dégainée par défaut, vous pouvez ajouter --degainer L à la commande de la forme d'ange (L est le label de l'attaque).

      Voie de l'archer arcanique

        @@ -2209,6 +2218,11 @@

        Voie du danseur de guerre

      1. Attaque en mouvement : pas de support du script.
      2. Danse des lames : !cof-effet-combat danseDesLames. Pour l'attaque gratuite supplémentaire, on peut faire #Attaque -1 --si etat danseDesLames --avecd12crit.
      +

      Voie du duelliste

      +
        +
      1. Vives lames : reporter la caractéristique choisie sur la fiche.
      2. +
      3. Défi : !cof-defi-duelliste @{selected|token_id} @{target|token_id} 2.
      4. +

      Voie du familier fantastique

      1. Familier fantastique : créer un personnage (de type PNJ) pour le familier, controlé par le joueur et avec un token lié ayant la capacité de voir. @@ -2477,7 +2491,7 @@

        4.5 Capacités diverses

      2. Noyade (plante carnivore) : il existe un effet de combat nommé noyade (pas de durée, il dure par défaut tout le combat) qui fait perdre 1d6 PV par tour, sauf à réussir un test de CON difficulté 15, et qui impose un malus de -3 aux attaques et aux DMs.
      3. Ondes corruptrices (Anathazerïn) : Pour simplement le premier effet, faire !cof-effet ondesCorruptrices 2, puis pour augmeter l'effet !cof-effet ondesCorruptrices +2. L'effet peut être annulé avec !cof-effet ondesCorruptrices 0. Pour le porteur du bouclier de Grabuge, on peut mettre un prédicat porteurDuBouclierDeGrabuge.
      4. Paralysie des goules : ajouter à l'attaque de morsure --effet paralyseGoule [[1d6]] --save CON 10.
      5. -
      6. Perte de substance (Invincible) : Ajouter pour tous les personnages concernés un prédicat perteDeSubstance. Le changement de jour va augmenter un compteur, à condition que les tokens des joueurs soient sur la page, et soit on ne sélectionne aucun token quand on lance la commande de changement de jour, soit on sélectionne au moins tous les tokens des joueurs. Le test de CHA n'est pas automatisé, mais le script en rappelle la difficulté. Pour les ancres, ajouter un prédicat ancreInvincible (par exemple dans les prédicats de l'arme). Pour la réduction des dégâts, ajouter au dragon un prédicat dragonInvincible. Pour les DM indirects, il faudra gérer à la main la réduction de DM.
      7. +
      8. Perte de substance (Invincible) : Ajouter pour tous les personnages concernés un prédicat perteDeSubstance. Le changement de jour va augmenter un compteur, à condition que les tokens des joueurs soient sur la page, et soit on ne sélectionne aucun token quand on lance la commande de changement de jour, soit on sélectionne au moins tous les tokens des joueurs. Le test de CHA n'est pas automatisé, mais le script en rappelle la difficulté. Pour les ancres, ajouter un prédicat ancreInvincible (par exemple dans les prédicats de l'arme). Pour la réduction des dégâts, ajouter au dragon un prédicat dragonInvincible. Pour la réduction des dégâts contre les émissaires du dragon, ajouter à ces derniers un prédicat emissaireDuDragonInvincible. Pour les DM indirects, il faudra gérer à la main la réduction de DM.
      9. Phylactère de canalisation : ajouter un prédicat phylacterePositif pour un phylactère de canalisation positive, ou phylactereNegatif pour un phylactère de canalisation négative. Associez à ce prédicat la valeur ajoutée pour chaque PM dépensé (nécessite l'utilisation du prédicat deCanalisation). Une valeur de 1d4 semble adaptée pour le phylactère de canalisation de Pathfinder 1.
      10. Piqûres d'insectes : Rajouter un prédicat piquresDInsectes avec comme valeur la résistance aux dégâts à distance appropriée
      11. @@ -3056,13 +3070,240 @@

        8. Utiliser des personnages Pathfinder<

        Il existe un support basique des fiches Roll20 venant du système Pathfinder 1. La fonction !cof-pathfinder1 se lance en sélectionnant un ou plusieurs tokens liés à des fiches Pathfinder (par exemple obtenues en achetant des aventures Pathfinder sur le marketplace de Roll20). L'effet est d'une part de transformer une partie des attributs de la fiche en attributs compris par la fiche COF et le script COFantasy, de changer un peu le token associé à la fiche, mais surtout d'effacer les attributs inutiles pour COF. En effet, le script est relativement sensible au nombre total d'attributs en jeu, et les fiches Pathfinder ont tendence à en avoir vraiment beaucoup. Tout ce qui n'est pas compris par le script se retrouve dans un attribut Attributs Pathfinder. Mais le mieux est de compléter la fiche COF en se basant sur la fiche Pathfinder ouverte dans une autre partie.

        -

        9. Statistiques

        + +

        9. Index des prédicats

        +
        +
          +
        • actionDegainern : labels d'attaques, séparés par des -, propose de dégainer ce ou ces labels en premier, dans l'ordre du numéro n (partant de 1).
        • +
        • adaptable : entier, bonus aux tests de caractéristique en combat, si le script a reconnu un test raté de même caractéristique au tour précédent.
        • +
        • agripper : booléen, le personnage peut agripper une cible si le jet d'attaque est 15 ou plus.
        • +
        • ambidextrie : booléen, permet d'attaquer avec l'arme en main gauche sans malus.
        • +
        • ameFeline : entier, bonus en acrobatie, course, escalade, saut et initiative.
        • +
        • animal : booléen, le personnage est un animal.
        • +
        • anneauProtection : booléen, annule les effets critiques des attaques.
        • +
        • arcDeMaitre : booléen, augmente la portée des arcs de 20 m.
        • +
        • argumentDeTaille : booléen, augmente les tests de CHA des alliés de la valeur du mod. de FOR du personnage.
        • +
        • armeDeGrand : booléen, seulement pour les armes à deux mains, ndique qu'une arme peut être portée à une main quand agrandi.
        • +
        • armeDePredilection : type d'arme, donne un bonus en attaque de +1 pour ce type d'arme.
        • +
        • armeParDefaut : label, arme tenue en main par défaut, toujours dégaînée à la fin d'un combat.
        • +
        • armureDeVent : entier, augmente la DEF de cette valeur si le personnage ne porte pas d'armure.
        • +
        • armureLourdeGuerrier : booléen, annule les effets critiques des attaques si le personnage porte une armure de DEF au moins 7.
        • +
        • armureProtection : booléen, annule les effets critiques des attaques si le personnage porte une armure.
        • +
        • attaqueAuBouclier : label, propose d'utiliser l'attaque de ce label comme attaque au bouclier.
        • +
        • attaqueEnMeute : booléen ou entier (2 par défaut), bonus à l'attaque quand le personnage attaque la même cible qu'un allié.
        • +
        • attaqueEnTraitre : booléen, propose une attaque en traître quand un allié touche un adversaire au contact.
        • +
        • attaqueSournoise : entier, nombre de d6 à ajouter pour les DM d'attaques sournoises.
        • +
        • attributsDeStatut : nom, spécifie un attribut qui doit être afficher avec le statt d'un personnage. On peut spécifier plusieurs attributs en ayant plusieur prédicats attributsDeStatut.
        • +
        • asDeLaGachette : booléen, augmente les DM des arbalètes et armes à poudre de 1d6 si le jet d'attaque atteint 25.
        • +
        • aucuneActionCombat : booléen, le personnage ne rentre pas en combat.
        • +
        • batarde : expression de dés, seulement pour les prédicats des armes à 1 mains : dés de DM quand l'arme est portée à 2 mains.
        • +
        • blessureSanglante : booléen ou entier (1 par défaut), les blessures infligées par le personnage saignent.La valeur est la durée du saignement
        • +
        • bonusAttaqueBouclier : entier, bonus pour le jet d'absorber un coup.
        • +
        • bonus_CHA : entier, ajoute la valeur au mod de CHA.
        • +
        • bonus_CON : entier, ajoute la valeur au mod de CON.
        • +
        • bonus_DEF : entier, valeur ajoutée à la DEF.
        • +
        • bonus_DEF(anneau) : entier, valeur ajoutée à la DEF.
        • +
        • bonus_DEX : entier, ajoute la valeur au mod de DEX.
        • +
        • bonus_FOR : entier, ajoute la valeur au mod de FOR.
        • +
        • bonus_INT : entier, ajoute la valeur au mod d'INT.
        • +
        • bonus_RD : possibilité d'avoir la même chose que dans la case RD. Cela s'ajoute et se cumule à la RD. Attention d'utiliser la syntaxe avec :: si ce bonus contient un : ou une ,.
        • +
        • bonus_SAG : entier, ajoute la valeur au mod de SAG.
        • +
        • bonusFeinte : entier (5 par défaut), valeur du bonus à l'attaque en cas de feinte réussie.
        • +
        • bonusSaveContre_type : entier, bonus aux saves de contre les effets ou DM de ce type.
        • +
        • bonusTests_nom : entier, ajoute la valeur aux tests de compétence caractéristique nom ou de compétence nom.
        • +
        • bonusTousTests : entier, ajoute la valeur aux tests de caractéristique.
        • +
        • botteMortelle : booléen, ajoute 2d6 de DM si le jet d'attaque dépasse de 10 la DEF de la cible.
        • +
        • bouclierDeLaFoi : entier, bonus à la DEF quand le personnage porte un bouclier.
        • +
        • bouclierProtection : booléen, annule les effets critiques des attaques si le personnage porte un bouclier.
        • +
        • bouclierPsi : booléen, augmente de 5 la DEF contre les attaques mentales et divise par 2 les DM des attaques mentales.
        • +
        • briseurDOs : booléen, augmente les chances de critique des attaques au contact, et applique un effet d'os brisés a la cible en cas de critique.
        • +
        • bucheron : booléen, augmente les DM des armes à 2 mains de 1d6 contre les créatures grandes, et 2d6 contre les créatures énormes.
        • +
        • cavalierEmerite : entier, s'ajoute à l'attaque au contact quand le personnage est sur une monture.
        • +
        • charge : entier (défaut 1), indique que l'arme doit être rechargée (ainsi que le nombre de charge, si on associe un nombre).
        • +
        • chasseurDeSorciere : booléen, ajoute 2 aux DM et à la DEF contre les nécromanciens. +
        • chasseurEmerite : booléen, rajoute +2 à l'attaque et aux DM contre les animaux.
        • +
        • chimiste : booléen, indique que le personnage n'a pas besoin de lancer de dé de poudre pour les attaques avec une arme à poudre.
        • +
        • coefPVMana : entier (défaut 1), multiplie la valeur de conversion entre mana et PV pour la brûlure de mana
        • +
        • combatADeuxArmes : booléen, propose de dégaîner une arme en main gauche.
        • +
        • combatADeuxArmesAmeliore : booléen, permet d'attaquer avec 2 armes sans malus.
        • +
        • combatEnPhalange : booléen, ajoute un bonus en attaque et en DEF en fonction du nombre d'alliés au contact.
        • +
        • combatKinetique : booléen, ajoute 3 à la RD tant que le personnage est conscient de l'attaque.
        • +
        • combattreLaCorruption : booléen ou entier (1 par défaut), bonus à l'attaque et aux DM contre les cibles corrompues.
        • +
        • connaissanceDuPoison : booléen, permet d'enduire une arme de poison sans avoir à faire de jet.
        • +
        • controleLoupGarou : entier, bonus au jet de lycanthrope pour éviter la transformation.
        • +
        • controleSanguin : booléen, immunise au poison et aux effets de saignement, et divise par 2 les DM de vampirisation.
        • +
        • controleDuMetabolisme : booléen, ajoute le mod. de CHA aux tests de CON et à l'initiative.
        • +
        • corrompu : booléen, le personnage est corronpu (utilise pour la voie du chasseur de corruption).
        • +
        • courage : entier, bonus ajouté pour résister aux effets de peur.
        • +
        • creatureArtificielle : booléen, immunise au poison, à l'asphyxie et aux maladies.
        • +
        • crocEnJambe : booléen, fait tomber les adversaires sur un jet de dé d'attaque de 17-20. Passe à 19-20 contre des adversaires quadrupèdes.
        • +
        • DEF_magie : entier, augmente la défense contre les sortilèges.
        • +
        • defDeriveeDe : nom de personnage, la valeur de défense est égale à celle du personnage du prédicat.
        • +
        • defenseIntuitive : booléen, ajoute la SAG à la DEF.
        • +
        • defierLaMort : booléen, fait lancer un dé pour garder 1 PV quand le personnage devrait mourir.
        • +
        • dentellesEtRapiere : entier, s'ajoute à la DEF contre les attaques aux contact, mais seulement quand le personnage ne porte pas d'armure.
        • +
        • devierLesCoups : booléen ou entier (1 par défaut), nombre maximum de fois par tour où on propose de dévier un coup.
        • +
        • difficulteOmbreMouvante : entier (10 par défaut), difficulté du jet de DEX pour passer dans les ombres. Cela peut permettre de tenir compte de l'environnement.
        • +
        • diviseEffet_type : booléen, divise par 2 les effets associés à des DM du type.
        • +
        • drainDeSang : booléen, le personnage est soigné de la moitié des DM des effets de saignement qu'il provoque.
        • +
        • durACuire : booléen, laisee un tour d'actions après être arrivé à 0 PV avant de mourir.
        • +
        • eclaire : entier, le personage fait de la lumière, et la valeur du prédicat est le rayon d'illumination.
        • +
        • eclaireFaible : entier, si le personage fait de la lumière, donne le rayon à partir duquel la lumière devient faible.
        • +
        • ecuyer : booléen, augmente de 1 les chances de critique.
        • +
        • ecuyerDe : nom de personnage, soigne chaque nuit les alliés du chevalier.
        • +
        • enchainement : booléen, propose une attaque gratuite sur une cible au contact quand le personnage tue une autre cible.
        • +
        • energieImpie : booléen, permet de bénéficier d'energie impie (bonus aux DM et aux soins) quand le personnage draine des PVs.
        • +
        • ennemiJure : race ou type de créature, donne un bonus de 1d6 DM et ajute le mod de SAG à l'attaque contre les créatures de ce type. Il est possible d'utiliser un nom inventé, qu'il faut alors rajouter en prédicats sur les créatures concernées.
        • +
        • entrerEnCombatAvec : nom de token, fait rentrer en combat à chaque fois que ce token entre en combat.
        • +
        • espritVide : booléen ou entier (3 par défaut), augmente l'initiative.
        • +
        • esquiveAcrobatique : booléen, propose des esquives acrobatiques.
        • +
        • esquiveDeLaMagie : booléen, propose des jets pour esquiver la magie, et réduit les DM même si le jet est raté.
        • +
        • esquiveFatale : booléen ou entier (1 par défaut), propose des esquives fatales, et limite le nombre d'esquives fatales par combat.
        • +
        • esquiveVoleur : entier, bonus à la DEF.
        • +
        • exemplaire : booléen, propose aux alliés de relancer des attaques.
        • +
        • expertDuCombat : entier, contrôle les capacités d'expert du combat.
        • +
        • expertiseSpecialisee : caractéristique, compétence ou mot clé, ajoute 5 aux jets pour une caractéristique, 10 aux jet pour une compétence, et 2 pour la furie du berserk,
        • +
        • exsangue : booléen, permet de continuer à agir à 0 PV, jusqu'à la prochaine blessure.
        • +
        • familier : nom de personnage, nom du personnage représentant le familier qui donne un bonus en initiative.
        • +
        • formeHybrideSuperieure : booléan, augmente les effets de formeHybride.
        • +
        • frappeChirurgicale : booléen, ajoute le mod. d'INT aux chances de coups critiques avec l'arme en main droite, au contact.
        • +
        • frappeDuVide : booléen, rajoute +2 à l'attaque et 1d6 aux DM de la première attaque si l'arme était au fourreau. De plus, fait automatiquement rengainer l'arme à la fin du combat.
        • +
        • fureurDrakonide : booléen, augmente l'attaque et les DM de 1 quand le personnage est sous la moitié de ses PV max ou qu'il a reçu un coup critique.
        • +
        • graceFeline : booléen, ajoute le mod. de CHA à l'initiative et à la défense.
        • +
        • graceFelineVoleur : booléen, ajoute le charisme aux jets de déplacement (course, escalade, saut).
        • +
        • grosMonstreGrosseArme : booléen, augmente la catégorie de dé des armes à deux main au contact contre les grosses créatures.
        • +
        • grosseTete : booléen, remplace le mod. de FOR par le mod. d'INT pour les tests.
        • +
        • guetteur : nom de personnage, nom du personnage représentant le compagnon qui donne un bonus en perception.
        • +
        • hachesEtMarteaux : booléen, ajoute un bonus de 1 en attaque est DM pour les haches et les marteaux.
        • +
        • humanoide : booléen, le personnage est humanoïde.
        • +
        • ignorerLaDouleur : booléen, permet de reporter les DM d'une attaque à la fin du combat.
        • +
        • immunite_x : booléen, empêche de mettre le personnage dans l'état ou effet temporaire nommé x. On peut aussi immuniser aux effets de pétrification avec x=petrification, ou contre la desctruction des mort-vivants avec x=destruction.
        • +
        • immuniteAbsorptionVampire : booléen, immunise aux attaque de drain venant des vampires (ayant un prédicat vampire.
        • +
        • immuniteAuxSournoises : booléen, annule les DM d'effets de type sournoise.
        • +
        • immuniteSaignement : booléen, immunise aux effets de saignement.
        • +
        • increvable : booléen, permet de se relever une fois par combat après avoir été mis à 0 PV.
        • +
        • increvableHumain : booléen, propose un jet pour éviter une attaque qui mettrait à 0 PV.
        • +
        • initiativeDeriveeDe : nom de personnage, l'initiative est celle de ce personnage.
        • +
        • insecte : booléen, le personnage est un insecte.
        • +
        • insignifiant : booléen, bonus de +2 en DEF contre les créature grandes et +4 en DEF contre celles encore plus grandes.
        • +
        • instinctDeSurvieHumain : booléen, les DM qui amènent à 0 PV sont divisés par 2.
        • +
        • intelligenceDuCombat : booléen, ajoute le mod d'INT à l'initiative et à la DEF.
        • +
        • intercepter : booléen, propose d'intercepter les attaques sur les alliés au contact.
        • +
        • interventionDivine : booléen, autorise l'utilisation de la commande !cof-intervention-divine une fois par combat.
        • +
        • invulnerable : booléen, divise par 2 les dégâts élémentaires.
        • +
        • joliCoup : booléen, ignore les obstacles pour les attaques à distance.
        • +
        • kiai : entier, nombre de kiaï que le personnage peut pousser dans un combat (avec un délai de 1d6 tours entre chaque).
        • +
        • laissezLeMoi : booléen, rajoute 1d6 aux DM contre les chefs.
        • +
        • liberateurDAnathazerin : booléen, augmente l'attaque de 2 et les DM de 2d6 contre les elfes noirs et les insectes, et donne un bonus de +2 pour résister aux poisons.
        • +
        • liberateurDeDorn : booléen, augmente la défense de 2, l'attaque de 2 et les DM de 2d6 contre les géants.
        • +
        • liberateurDeKerserac : booléen, augmente l'attaque de 2 et les DM de 1d6 contre les géants, les insectes et les elfes noirs.
        • +
        • liberteDAction : booléen, immunise à tout ce qui entrave le mouvement.
        • +
        • lienEpique : nom, le personnage est en lien épique avec les personnages ayant le même prédicat (bonus de 1d6 aux DM s'ils attaquent la même cible).
        • +
        • loupParmiLesLoups : entier, bonus aux DM contre les humanoïdes.
        • +
        • lycanthrope : booléen, le personnage est un lycanthrope. Propose un jet pour résister à la transformation.
        • +
        • lycanthropeEventre : booléen, permet léventration des lycanthropes.
        • +
        • magieDeCombat : booléen ou entier (1 par défaut), bonus aux chances de critique des sorts.
        • +
        • magieEnArmure : entier, valeur d'armure (moyenne entre DEF et malus d'armure) en dessous de laquelle le personnage peut faire de la magie sans pénalité.
        • +
        • magieEnArmureFacilitee : booléen, diminue la valeur d'armure de magieEnArmure pour déterminer le malus aux jets pour lancer des sorts en armure.
        • +
        • marcheSylvestre : booléen, immunise aux effets de conditions hostiles et donne +2 en attaque et en défense en terrain difficile.
        • +
        • mauvais : booléen, le personnage est mauvais, ce qui signifie normalement, démon, mort-vivant ou élémentaire (impacte la protection contre le mal).
        • +
        • memePasMal : booléen, reporte les DM des coups critiques et donne un bonus aux DMs.
        • +
        • monture : booléen, indique que le personnage est une monture, il est donc possible de monter dessus (avec !cof-en-selle).
        • +
        • montureLoyale : booléen, augmente la défense et l'attaque de 1 quand le personnage est sur une monture.
        • +
        • montureMagique : booléen, soigne automatiquement la monture pendant les repos longs.
        • +
        • morsureDuSerpent : booléen, augmente les chances de critique au contact.
        • +
        • mortDemandeConfirmation : booléen, indique que la mort du personnage ne peut être effective qu'une fois que le MJ l'aura confirmée (ce qui lui permet par exemple de prendre le temps de décrire cette mort).
        • +
        • mortVivant : booléen, indique que le personnage est un mort-vivant.
        • +
        • natureNourriciereBaies : booléen, permet de trouver des baies magiques si le jet de nature nourricière est assez haut.
        • +
        • nbDesFeinte : entier (2 par défaut), nombre de dés à ajouter aux DM des feintes réussies.
        • +
        • necromancien : booléen, le personnage est un nécromancien ou une sorcière.
        • +
        • nonVivant : booléen, le personnage n'est pas vivant (élémentaire, golem ou autre).
        • +
        • ordreDuChevalierDragon : booléen, augmente de 5 les jets de persuasion et d'intimidation quand le personnage est sur une monture.
        • +
        • pacifisme : booléen, augmente la DEF de 5 tant que le personnage n'a pas attaqué dans un combat.
        • +
        • pacteSanglant : entier, propose un bouton pour augmenter la DEF de cette valeur, en échange d'une dépense de PV (1d4 pour 3 ou moins, 2d4 sinon).
        • +
        • paradeAuBouclier : booléen, propose un bouton pour les parades au bouclier.
        • +
        • paradeDeProjectiles : booléen, propose un jet pour parer les projectiles.
        • +
        • pasDuVent : entier, bonus à l'initiative.
        • +
        • peauDePierre : booléen, augmente la DEF de la valeur de CON.
        • +
        • perception : entier, bonus aux jets de perception.
        • +
        • petiteTaille : booléen, bonus de +2 en discrétion et +1 en DEF, et le personnage est considéré de taille petit.
        • +
        • petitVeinard : booléen ou entier (1 par défaut), propose de relancer des jets ce nombre de fois pas combat maximum.
        • +
        • piquresDInsectes : entier, diminue les DM des attaques à distance de ce nombre, si le personnage porte une armure de DEF au moins 5.
        • +
        • pirouettes : entier, augmente la DEF si le personnage a un malud d'armure inféreur ou égal à 3.
        • +
        • plusViteQueSonOmbre : booléen ou entier (par défaut 10), augmente l'initiative si le personnage à une arme à poudre chargée en main. On peut aussi spécifier un autre type d'armes, comme les arbalète, en associant ce nom à la valeur du prédicat.
        • +
        • poudrePuissante : booléen, augmente de 10 la portée et de 2 les DM des armes à poudre.
        • +
        • prescience : booléen ou entier (défaut 1), propose de refaire le tour. Le nombre associé indique le nombre de fois que le personnage peut faire ça par combat.
        • +
        • proprioception : booléen, immunise aux effets de douleur et de peur,
        • +
        • protectionContreLesProjectiles : entier, valeur de la RD apportée par l'effet temporaire de même nom
        • +
        • prouesse : booléen, propose un bouton pour les prouesses de guerrier.
        • +
        • PVPartagesAvec : nom de personnage, nom du personnage avec lequel on partage les PVs.
        • +
        • quadrupede : booléen, le personnage marche sur 4 pattes ou plus.
        • +
        • radarMental : entier, bonus aux jets de surprise contre les créatures vivantes. Si le prédicat est présent annule les malus liées à la condition aveugle contre les créatures vivantes.
        • +
        • rageDuBerserkAmelioree : booléen, divise par 2 les malus de rage.
        • +
        • rapideCommeSonOmbre : booléen ou entier (3 par défaut), bonus à la discrétion et à l'initiative.
        • +
        • RD_critique : entier, s'ajoute à la RD contre les coups critiques des casques.
        • +
        • reduireLaDistance : booléen, augmente la défense contre les grandes créatures (2 à 4, selon la taille).
        • +
        • reflexesFelins : entier, bonus aux esquives et à l'initiative.
        • +
        • resistanceA_nonMagique : booléen, divise par 2 les DM non magiques.
        • +
        • resistanceA_type : booléen, divise par 2 les DM du type.
        • +
        • resistanceALaMagieBarbare : booléen, propose un jet pour résister à la magie.
        • +
        • riposteGuerrier : booléen, propose un jet de riposte au personnage.
        • +
        • sangFroid : booléen, réduit les DM quand l'ogre prend sur lui pour éviter une réaction violente.
        • +
        • sansPeur : booléen, immunise aux effets de peur et donne un bonus de 2+CHA aux alliés en vue pour résister aux effets de peur.
        • +
        • scienceDuCritique : booléen, augmente de 1 les chances de critique.
        • +
        • secondSouffle : booléen ou entier, propose un jet de récupération de PV une fois par combat, appliqué automatiquement à la fin du combat si pas utilisé. Si on met un entier, on soigne avec cette valeur à la place de CON + niveau.
        • +
        • sensAffutes : booléen, ajoute la sagesse aux dégâts des arcs et à l'initiative.
        • +
        • sournoisesParTour : entier (1 par défaut), nombre maximum d'effets d'attaques sournoises par tour pour le personnage.
        • +
        • specialisationGuerrier : booléen ou entier (2 par défaut), donne un bonus aux DM pour l'arme de prédilection.
        • +
        • siphonDesAmes : booléen ou entier, fait récupérer 1d6 PV quand une créature meurt dans les 20 mètres. Si une valeur est précisée, elle est ajoutée à ce dé. +
        • siphonDesAmesPrioritaire : entier, indique une priorité pour le siphon des âmes, si plusieurs personnages peuvent siphoner une âme. Le personnage ayant un prédicat siphonDesAmesPrioritaire de plus haute valeur sera prioritaire : il absorbera l'âme (et bénéficiera du soin) s'il n'est pas au maximum de PV. Si il est au maximum de PV, on passe au personnage suivant dans l'ordre des priorités. Un personnage n'ayant pas ce prédicat est dernier sur cette liste.
        • +
        • surveillance : nom d'un token, ajoute +5 aux tests contre la surprise si le token est présent. Et augmente l'initiative de 5 dans ces cas.
        • +
        • techniqueDuSabre : entier, bonus aux DM pour le combat au sabre, katana ou vivelame. Ce bonus ne se cumule qu'à moitié avec le mod. de FOR.
        • +
        • tenacite : booléen, donne un bonus de +2 puis +5 contre un adversaire que le personnage n'a pas réussi à toucher.
        • +
        • tirDeSemonce : booléan, propose un tir de semonce au tour suivant un tir raté.
        • +
        • tirFatal : booléen ou nom de type d'arme (par défaut, arc), augmente les chance de coup critique du mod. de SAG pour les armes de ce type. De plus les DM des coups critiques pour ces attaques sont augmentées de 1 ou 2 d6 (selon la valeur du prédicat voieDeLArcEtDuCheval).
        • +
        • tirParabolique : booléen, augmente la portée des armes à distance (comme la capacité tir parabolique).
        • +
        • tirPrecis : entier, ajouté aux DM quand la cible est à moins de 5xDEX mètres.
        • +
        • tokenFormeDArbre : url d'une image, l'image utilisée pour la forme d'arbre.
        • +
        • tourDeForce : booléen, propose de sacrifier des PV pour rajouter un bonus aux tests de force ratés.
        • +
        • toutPetit : booléen, ajoute 5 aux jets de discrétion et 2 en DEF.
        • +
        • traquenard : booléen, permet la bonne utilisation de l'option d'attaque --traquenard.
        • +
        • tropPetit : booléen, change les d6 de DM en d4.
        • +
        • vampire : booléen, le personnage est un vampire.
        • +
        • ventreMou : booléen, réduit la RD fixe des grandes créatures.
        • +
        • vetementsSacres : entier, bonus à la DEF quand le personnage ne porte pas d'armure.
        • +
        • vieArtificielle : booléan, divise les soins par 2.
        • +
        • violenceCiblee : booléen, permet d'emmagasiner et d'utliser des points de violence.
        • +
        • visionDansLeNoir : entier, le personnage voit dans le noir jusqu'à la distance indiquée par le prédicat, en mètres.
        • +
        • vitesseDuFelin : entier, bonus à l'initiative et aux jets de course, d'escalade, et de saut.
        • +
        • voieDeLaConjuration : entier, rang dans la Voie de la conjuration. Contrôle les conjurations de prédateurs et la conjuration d'armées.
        • +
        • voieDeLaMagieElementaire : entier, on utilise 2 fois cette valeur pour la valeur de la protection des éléments.
        • +
        • voieDeLArchange : entier, rang dans la Voie de l'archange. contrôle l'utilisation de la forme d'ange.
        • +
        • voieDeLArcEtDuCheval : entier (3 par défaut), ocntrôle le nombre de d6 rajoutés par le ir fatal en cas de critique.
        • +
        • voieDeLaTelekinesie : entier (2 par défaut), bonus de DEF accordé par l'effet champDeProtection.
        • +
        • voieDeLaSurvie : entier, contrôle le nombre de plantes trouvées avec Nature nourricière.
        • +
        • voieDeLHonneur : entier, bonus aux DM par défaut des défis.
        • +
        • voieDesElixirs : entier, rang dans la voie des élixirs, contrôle les élixirs proposés à la création, leur nombre total maximum et la puissance de certains d'entre eux.
        • +
        • voieDesMutations : entier, bonus de défense de la cuirasse de la voie des mutations.
        • +
        • voieDesRunes : entier, rang dans la voie des runes, définit le bonus à la DEF et contrôle les runes proposées à la création et la puissance de certaines d'entre elles.
        • +
        • voieDesSoins : entier, rang dans la voie des soins, limite le nombre total de soins légers et de soins modérés par jour.
        • +
        • voieDesVegetaux : entier (1 par défaut), bonus de DEF de la peau d'écorce.
        • +
        • voieDuGuerisseur : entier, bonus aux soins legers, soins modérés et soins de groupe.
        • +
        • voieDuMeneurDHomme : entier (2 par défaut), RD appliquée en cas d'interception (minimum 2).
        • +
        • voieDuMetal : entier, bonus aux DM de l'arme enflammée du forgesort.
        • +
        • voieDuSoldat : entier, limite le bonus de la posture de combat.
        • +
        • voieOutreTombe : entier (1 par défaut), maximum de combies ocntrôlés à la fois.
        • +
        • volant : booléen, le personnage peut voler. Cela influe sur la possibilité de croc-en-jambe, la possibilité d'échapper à une armée de morts, ...
        • +
        +
        diff --git a/COFantasy/COFantasy.js b/COFantasy/COFantasy.js index baf0c9b32..26b5d93c3 100644 --- a/COFantasy/COFantasy.js +++ b/COFantasy/COFantasy.js @@ -1,3 +1,4 @@ +//Derni\xE8re modification : sam. 18 mai 2024, 03:02 // ------------------ generateRowID code from the Aaron --------------------- const generateUUID = (function() { "use strict"; @@ -79,6 +80,7 @@ let COF_loaded = false; // - nextPrescience : pour le changement de tour car prescience ne revient que d'un tour // - afterDisplay : donn\xE9es \xE0 afficher apr\xE8s un display // - version : la version du script en cours, pour d\xE9tecter qu'on change de version +// - personnageCibleCree : savoir si la cible a \xE9t\xE9 cr\xE9\xE9e // statistiques : des statistiques pour les jets de d\xE9s // statistiquesEnPause @@ -86,7 +88,7 @@ var COFantasy = COFantasy || function() { "use strict"; - const versionFiche = 5.03; + const versionFiche = 5.04; const PIX_PER_UNIT = 70; const HISTORY_SIZE = 200; @@ -242,6 +244,11 @@ var COFantasy = COFantasy || function() { val: false, type: 'bool' }, + PR_rend_mana: { + explications: "L'utilisation d'un PR pour se reposer rend aussi de la mana en plus des PV. Le montant rendu est le d\xE9 de mana + mod. de magie.", + val: false, + type: 'bool' + }, contrecoup: { explications: "Avec la Mana Totale, permet au lanceur de sort de payer un d\xE9ficit de PM en PV (COF p. 181)", val: false, @@ -682,38 +689,62 @@ var COFantasy = COFantasy || function() { charId }; let labelArmeGauche = 0; - let armures; - let labelArmure = ficheAttribute(perso, 'torseequipe', '0'); - if (labelArmure && labelArmure != '-1') { - armures = armures || listAllArmors(perso); - let armure = armures[labelArmure]; - if (armure) { - let rawArmure = fieldAsString(armure, 'effetarmure', ''); - if (rawArmure) raw += '\n' + rawArmure; - } - } - let labelCasque = ficheAttribute(perso, 'teteequipe', '0'); - if (labelCasque && labelCasque != '-1') { - armures = armures || listAllArmors(perso); - let casque = armures[labelCasque]; - if (casque) { - let rawCasque = fieldAsString(casque, 'effetarmure', ''); - if (rawCasque) raw += '\n' + rawCasque; - } - } let labelArme = ficheAttributeAsInt(perso, 'maindroite', 0); let labelGauche = ficheAttribute(perso, 'maingauche', '0'); - if (typeof labelGauche == 'string' && labelGauche.startsWith('b')) { - if (labelGauche != 'b-1') { + let versionFiche = findObjs({ + _type: 'attribute', + _characterid: charId, + name: 'version', + }, { + caseInsensitive: true + }); + if (versionFiche.length === 0) versionFiche = 0; + else versionFiche = parseFloat(versionFiche[0].get('current')); + if (versionFiche < 5.04) { + let armures; + let labelArmure = ficheAttribute(perso, 'torseequipe', '0'); + if (labelArmure && labelArmure != '-1') { armures = armures || listAllArmors(perso); - let labelBouclier = labelGauche.substring(1); - let bouclier = armures[labelBouclier]; - if (bouclier) { - let rawBouclier = fieldAsString(bouclier, 'effetarmure', ''); - if (rawBouclier) raw += '\n' + rawBouclier; + let armure = armures[labelArmure]; + if (armure) { + let rawArmure = fieldAsString(armure, 'effetarmure', ''); + if (rawArmure) raw += '\n' + rawArmure; + } + } + let labelCasque = ficheAttribute(perso, 'teteequipe', '0'); + if (labelCasque && labelCasque != '-1') { + armures = armures || listAllArmors(perso); + let casque = armures[labelCasque]; + if (casque) { + let rawCasque = fieldAsString(casque, 'effetarmure', ''); + if (rawCasque) raw += '\n' + rawCasque; + } + } + if (typeof labelGauche == 'string' && labelGauche.startsWith('b')) { + if (labelGauche != 'b-1') { + armures = armures || listAllArmors(perso); + let labelBouclier = labelGauche.substring(1); + let bouclier = armures[labelBouclier]; + if (bouclier) { + let rawBouclier = fieldAsString(bouclier, 'effetarmure', ''); + if (rawBouclier) raw += '\n' + rawBouclier; + } + } + } else if (labelGauche != '2m') labelArmeGauche = toInt(labelGauche); + } else { //versionFiche >= 5.04 + //D'abord les objets \xE9quip\xE9s + let armures = listAllArmors(perso); + for (let label in armures) { + let ar = armures[label]; + if (ar.equipearmure == '1') { + let r = fieldAsString(ar, 'effetarmure', ''); + if (r) raw += '\n' + r; } } - } else if (labelGauche != '2m') labelArmeGauche = toInt(labelGauche); + if (typeof labelGauche != 'string' || + (!labelGauche.startsWith('b') && labelGauche != '2m')) + labelArmeGauche = toInt(labelGauche); + } let att; let attaques; if (labelArme) { @@ -775,68 +806,128 @@ var COFantasy = COFantasy || function() { return perso.predicates; } let raw = ficheAttribute(perso, 'predicats_script', ''); - if (perso.armesEnMain) { - if (perso.arme && perso.arme.predicats) - raw += '\n' + perso.arme.predicats; - if (perso.armeGauche && perso.armeGauche.predicats) - raw += '\n' + perso.armeGauche.predicats; - } else if (perso.arme) { //possible si appel\xE9 depuis armesEnMain - if (perso.arme.predicats) - raw += '\n' + perso.arme.predicats; - } else { //il faut chercher les pr\xE9dicats des armes en main - //On n'appelle pas armesEnMain pour \xE9viter la r\xE9cursion - //et pour \xE9viter trop de calcul - let labelArmeGauche = 0; - let armures; - let labelArmure = ficheAttribute(perso, 'torseequipe', '0'); - if (labelArmure && labelArmure != '-1') { - armures = armures || listAllArmors(perso); - let armure = armures[labelArmure]; - if (armure) { - let rawArmure = fieldAsString(armure, 'effetarmure', ''); - if (rawArmure) raw += '\n' + rawArmure; + let versionFiche = findObjs({ + _type: 'attribute', + _characterid: perso.charId, + name: 'version', + }, { + caseInsensitive: true + }); + if (versionFiche.length === 0) versionFiche = 0; + else versionFiche = parseFloat(versionFiche[0].get('current')); + if (versionFiche < 5.04) { + if (perso.armesEnMain) { + if (perso.arme && perso.arme.predicats) + raw += '\n' + perso.arme.predicats; + if (perso.armeGauche && perso.armeGauche.predicats) + raw += '\n' + perso.armeGauche.predicats; + } else if (perso.arme) { //possible si appel\xE9 depuis armesEnMain + if (perso.arme.predicats) + raw += '\n' + perso.arme.predicats; + } else { //il faut chercher les pr\xE9dicats des armes en main + //On n'appelle pas armesEnMain pour \xE9viter la r\xE9cursion + //et pour \xE9viter trop de calcul + let armures; + let labelArmure = ficheAttribute(perso, 'torseequipe', '0'); + if (labelArmure && labelArmure != '-1') { + armures = armures || listAllArmors(perso); + let armure = armures[labelArmure]; + if (armure) { + let rawArmure = fieldAsString(armure, 'effetarmure', ''); + if (rawArmure) raw += '\n' + rawArmure; + } } - } - let labelCasque = ficheAttribute(perso, 'teteequipe', '0'); - if (labelCasque && labelCasque != '-1') { - armures = armures || listAllArmors(perso); - let casque = armures[labelCasque]; - if (casque) { - let rawCasque = fieldAsString(casque, 'effetarmure', ''); - if (rawCasque) raw += '\n' + rawCasque; + let labelCasque = ficheAttribute(perso, 'teteequipe', '0'); + if (labelCasque && labelCasque != '-1') { + armures = armures || listAllArmors(perso); + let casque = armures[labelCasque]; + if (casque) { + let rawCasque = fieldAsString(casque, 'effetarmure', ''); + if (rawCasque) raw += '\n' + rawCasque; + } + } + let labelArmeGauche = 0; + let labelArme = getLabelArme(perso, 'droite', estMook); + let labelGauche = getLabelArme(perso, 'gauche', estMook); + if (typeof labelGauche == 'string' && labelGauche.startsWith('b')) { + if (labelGauche != 'b-1') { + armures = armures || listAllArmors(perso); + let labelBouclier = labelGauche.substring(1); + let bouclier = armures[labelBouclier]; + if (bouclier) { + let rawBouclier = fieldAsString(bouclier, 'effetarmure', ''); + if (rawBouclier) raw += '\n' + rawBouclier; + } + } + } else if (labelGauche != '2m') labelArmeGauche = toInt(labelGauche); + let attaques; + if (labelArme) { + attaques = listAllAttacks(perso); + let att = attaques[labelArme]; + if (att) { + let rawArme = fieldAsString(att, 'armepredicats', ''); + if (rawArme) raw += '\n' + rawArme; + } + } + //Ensuite l'arme gauche + if (labelArmeGauche) { + if (!attaques) { + attaques = listAllAttacks(perso); + } + let att = attaques[labelArmeGauche]; + if (att) { + let rawArme = fieldAsString(att, 'armepredicats', ''); + if (rawArme) raw += '\n' + rawArme; + } } } - let labelArme = getLabelArme(perso, 'droite', estMook); - let labelGauche = getLabelArme(perso, 'gauche', estMook); - if (typeof labelGauche == 'string' && labelGauche.startsWith('b')) { - if (labelGauche != 'b-1') { - armures = armures || listAllArmors(perso); - let labelBouclier = labelGauche.substring(1); - let bouclier = armures[labelBouclier]; - if (bouclier) { - let rawBouclier = fieldAsString(bouclier, 'effetarmure', ''); - if (rawBouclier) raw += '\n' + rawBouclier; - } - } - } else if (labelGauche != '2m') labelArmeGauche = toInt(labelGauche); - let attaques; - if (labelArme) { - attaques = listAllAttacks(perso); - let att = attaques[labelArme]; - if (att) { - let rawArme = fieldAsString(att, 'armepredicats', ''); - if (rawArme) raw += '\n' + rawArme; - } - } - //Ensuite l'arme gauche - if (labelArmeGauche) { - if (!attaques) { + } else { //versionFiche >= 5.04 + //D'abord les objets \xE9quip\xE9s + let armures = listAllArmors(perso); + for (let label in armures) { + let ar = armures[label]; + if (ar.equipearmure == '1') { + let r = fieldAsString(ar, 'effetarmure', ''); + if (r) raw += '\n' + r; + } + } + //Puis les armes en main + if (perso.armesEnMain) { + if (perso.arme && perso.arme.predicats) + raw += '\n' + perso.arme.predicats; + if (perso.armeGauche && perso.armeGauche.predicats) + raw += '\n' + perso.armeGauche.predicats; + } else if (perso.arme) { //possible si appel\xE9 depuis armesEnMain + if (perso.arme.predicats) + raw += '\n' + perso.arme.predicats; + } else { //il faut chercher les pr\xE9dicats des armes en main + //On n'appelle pas armesEnMain pour \xE9viter la r\xE9cursion + //et pour \xE9viter trop de calcul + let labelArmeGauche = 0; + let labelArme = getLabelArme(perso, 'droite', estMook); + let labelGauche = getLabelArme(perso, 'gauche', estMook); + if (typeof labelGauche != 'string' || + (!labelGauche.startsWith('b') && labelGauche != '2m')) + labelArmeGauche = toInt(labelGauche); + let attaques; + if (labelArme) { attaques = listAllAttacks(perso); + let att = attaques[labelArme]; + if (att) { + let rawArme = fieldAsString(att, 'armepredicats', ''); + if (rawArme) raw += '\n' + rawArme; + } } - let att = attaques[labelArmeGauche]; - if (att) { - let rawArme = fieldAsString(att, 'armepredicats', ''); - if (rawArme) raw += '\n' + rawArme; + //Ensuite l'arme gauche + if (labelArmeGauche) { + if (!attaques) { + attaques = listAllAttacks(perso); + } + let att = attaques[labelArmeGauche]; + if (att) { + let rawArme = fieldAsString(att, 'armepredicats', ''); + if (rawArme) raw += '\n' + rawArme; + } } } } @@ -849,8 +940,12 @@ var COFantasy = COFantasy || function() { function predicateAsBool(perso, name) { let pred = getPredicates(perso); - if (Array.isArray(pred)) pred = pred[0]; - return pred[name]; + let r = pred[name]; + if (r === undefined) return false; + if (Array.isArray(r)) r = r.find(function(p) { + return p; + }); + return r; } function predicateAsInt(perso, name, def, defPresent) { @@ -858,11 +953,15 @@ var COFantasy = COFantasy || function() { let r = pred[name]; if (r === undefined) return def; if (defPresent !== undefined) def = defPresent; - if (Array.isArray(r)) r = r[0]; + if (Array.isArray(r)) { + if (r.length === 0) return def; + r = Math.max(...r); + } if (r === true) return def; return toInt(r, def); } + //Renvoie toujours un tableau, possiblement vide function predicatesNamed(perso, name) { let pred = getPredicates(perso); let r = pred[name]; @@ -1871,7 +1970,8 @@ var COFantasy = COFantasy || function() { //de peut \xEAtre un nombre > 0 ou bien le r\xE9sultat de parseDice function rollDePlus(de, options) { options = options || {}; - options.nbDes = options.nbDes || 1; + let nbDes = options.nbDes || 1; + let bonus = options.bonus || 0; if (de.dice !== undefined) { if (!de.nbDe) { return { @@ -1879,12 +1979,11 @@ var COFantasy = COFantasy || function() { roll: '' + de.bonus }; } - options.nbDes = options.nbDes || de.nbDe; - options.bonus = options.bonus || de.bonus; + nbDes = options.nbDes || de.nbDe; + bonus = options.bonus || de.bonus; de = de.dice; } - let count = options.nbDes; - let bonus = options.bonus || 0; + let count = nbDes; let explose = options.deExplosif || false; let texteJetDeTotal = ''; let jetTotal = 0; @@ -1914,7 +2013,7 @@ var COFantasy = COFantasy || function() { style += ' background-color: ' + couleurs.background + ';'; style += ' color: ' + couleurs.color + ';'; let msg = ' 0) { msg += '+' + bonus; @@ -1969,7 +2068,7 @@ var COFantasy = COFantasy || function() { max: attr.get('max'), }); } - token.set(fieldv, val);//On le fait aussi pour forcer la mise \xE0 jour de la barre + token.set(fieldv, val); //On le fait aussi pour forcer la mise \xE0 jour de la barre let aset = { current: val, withWorker: true @@ -2154,6 +2253,34 @@ var COFantasy = COFantasy || function() { } } + function modOfCarac(carac) { + switch (carac) { + case 'force': + case 'FORCE': + return 'FOR'; + case 'dexterite': + case 'DEXTERITE': + return 'DEX'; + case 'constitution': + case 'CONSTITUTION': + return 'CON'; + case 'intelligence': + case 'INTELLIGENCE': + return 'INT'; + case 'sagesse': + case 'SAGESSE': + return 'SAG'; + case 'charisme': + case 'CHARISME': + return 'CHA'; + case 'perception': + case 'PERCEPTION': + return 'PER'; + default: + return ''; + } + } + //Retourne le mod de la caract\xE9ristque enti\xE8re. //si carac n'est pas une carac, retourne 0 //perso peut ne pas avoir de token ou \xEAtre juste un charId @@ -2161,31 +2288,26 @@ var COFantasy = COFantasy || function() { if (perso.charId === undefined) perso = { charId: perso }; + let modCarac = modOfCarac(carac); let mod = 0; if (persoEstPNJ(perso)) { - switch (carac) { - case 'force': - case 'FORCE': + switch (modCarac) { + case 'FOR': mod = ficheAttributeAsInt(perso, 'pnj_for', 0); break; - case 'dexterite': - case 'DEXTERITE': + case 'DEX': mod = ficheAttributeAsInt(perso, 'pnj_dex', 0); break; - case 'constitution': - case 'CONSTITUTION': + case 'CON': mod = ficheAttributeAsInt(perso, 'pnj_con', 0); break; - case 'intelligence': - case 'INTELLIGENCE': + case 'INT': mod = ficheAttributeAsInt(perso, 'pnj_int', 0); break; - case 'sagesse': - case 'SAGESSE': + case 'SAG': mod = ficheAttributeAsInt(perso, 'pnj_sag', 0); break; - case 'charisme': - case 'CHARISME': + case 'CHAR': mod = ficheAttributeAsInt(perso, 'pnj_cha', 0); break; default: @@ -2198,11 +2320,12 @@ var COFantasy = COFantasy || function() { ficheAttributeAsInt(perso, carac, 10) - attributeAsInt(perso, 'affaiblissementde' + carac, 0); mod = Math.floor((valCarac - 10) / 2); } - if (carac == 'force' || carac == 'FORCE') { + if (modCarac == 'FOR') { if (attributeAsBool(perso, 'mutationMusclesHypertrophies')) mod += 2; if (attributeAsBool(perso, 'grandeTaille')) mod += 2; if (attributeAsBool(perso, 'lycanthropie')) mod += 1; - } else if ((carac == 'DEXTERITE' || carac == 'dexterite') && attributeAsBool(perso, 'mutationSilhouetteFiliforme')) mod += 4; + } else if (modCarac == 'DEX' && attributeAsBool(perso, 'mutationSilhouetteFiliforme')) mod += 4; + mod += predicateAsInt(perso, 'bonus_' + modCarac, 0); return mod; } @@ -5413,9 +5536,7 @@ var COFantasy = COFantasy || function() { case 'etat': return !getState(perso, condition.etat); case 'attribut': - if (condition.valeur === undefined) - return !attributeAsBool(perso, condition.attribute); - return testAttribut(perso, condition.attrbute, condition.valeur, condition); + return !testConditionAttribut(condition, perso); case 'premiereAttaque': return attributeAsBool(perso, 'attributDeCombat_premiereAttaque'); } @@ -5586,6 +5707,13 @@ var COFantasy = COFantasy || function() { return currentCharge === 0; } + function armeChargeeDeGrenaille(perso, arme) { + if (!arme.charge || !arme.poudre) return false; + let currentCharge = attributeAsInt(perso, 'chargeGrenaille_' + arme.label, 0); + return currentCharge > 0; + } + + //options peut avoir les champs: // - ressource, un attribut // - overlay @@ -6250,7 +6378,9 @@ var COFantasy = COFantasy || function() { } function bonusAuxCompetences(personnage, comp, expliquer) { - let bonus = 0; + let bonus = predicateAsInt(personnage, 'bonusTests_' + comp, 0); + if (bonus) + expliquer("Bonus de comp\xE9tence : " + ((bonus < 0) ? "-" : "+") + bonus); switch (comp) { case 'acrobatie': case 'acrobaties': @@ -6461,7 +6591,9 @@ var COFantasy = COFantasy || function() { return options.cacheBonusToutesCaracs.val; } } - let bonus = 0; + let bonus = predicateAsInt(personnage, 'bonusTousTests', 0); + if (bonus) + expliquer("Bonus aux tests : " + ((bonus < 0) ? "-" : "+") + bonus); if (attributeAsBool(personnage, 'chantDesHeros')) { let bonusChantDesHeros = getIntValeurOfEffet(personnage, 'chantDesHeros', 1); let chantDesHerosIntense = attributeAsInt(personnage, 'chantDesHerosTempeteDeManaIntense', 0); @@ -6674,6 +6806,11 @@ var COFantasy = COFantasy || function() { switch (carac) { case 'DEX': { + let bonusDEX = predicateAsInt(personnage, 'bonusTests_DEX', 0) + Math.max(predicateAsInt(personnage, 'bonusTests_dexterite', 0), predicateAsInt(personnage, 'bonusTests_dexterit\xE9', 0)); + if (bonusDEX) { + expliquer("Bonus aux jets de DEX : " + ((bonusDEX < 0) ? "-" : "+") + bonusDEX); + bonus += bonusDEX; + } if (attributeAsBool(personnage, 'agrandissement')) { expliquer("Agrandi : -2 au jet de DEX"); bonus -= 2; @@ -6723,6 +6860,11 @@ var COFantasy = COFantasy || function() { break; case 'FOR': { + let bonusFOR = predicateAsInt(personnage, 'bonusTests_FOR', 0) + predicateAsInt(personnage, 'bonusTests_force', 0); + if (bonusFOR) { + expliquer("Bonus aux jets de FOR : " + ((bonusFOR < 0) ? "-" : "+") + bonusFOR); + bonus += bonusFOR; + } if (attributeAsBool(personnage, 'rayonAffaiblissant')) { let malusRayonAffaiblissant = getIntValeurOfEffet(personnage, 'rayonAffaiblissant', 2); expliquer("Affaibli : -" + malusRayonAffaiblissant + " au jet de FOR"); @@ -6791,6 +6933,11 @@ var COFantasy = COFantasy || function() { break; case 'INT': { + let bonusINT = predicateAsInt(personnage, 'bonusTests_INT', 0) + predicateAsInt(personnage, 'bonusTests_intelligence', 0); + if (bonusINT) { + expliquer("Bonus aux jets d'INT : " + ((bonusINT < 0) ? "-" : "+") + bonusINT); + bonus += bonusINT; + } if (attributeAsBool(personnage, 'secretsDeLAuDela')) { bonus += 5; expliquer("Secrets de l'au-del\xE0 : +5"); @@ -6803,6 +6950,11 @@ var COFantasy = COFantasy || function() { break; case 'CHA': { + let bonusCHA = predicateAsInt(personnage, 'bonusTests_CHA', 0) + predicateAsInt(personnage, 'bonusTests_charisme', 0); + if (bonusCHA) { + expliquer("Bonus aux jets de CHA : " + ((bonusCHA < 0) ? "-" : "+") + bonusCHA); + bonus += bonusCHA; + } if (attributeAsBool(personnage, 'aspectDeLaSuccube')) { let bonusAspectDeLaSuccube = getIntValeurOfEffet(personnage, 'aspectDeLaSuccube', 5); expliquer("Aspect de la succube : +" + bonusAspectDeLaSuccube + " au jet de CHA"); @@ -6827,6 +6979,11 @@ var COFantasy = COFantasy || function() { break; case 'CON': { + let bonusCON = predicateAsInt(personnage, 'bonusTests_CON', 0) + predicateAsInt(personnage, 'bonusTests_constitution', 0); + if (bonusCON) { + expliquer("Bonus aux jets de CON : " + ((bonusCON < 0) ? "-" : "+") + bonusCON); + bonus += bonusCON; + } if (attributeAsBool(personnage, 'mutationSilhouetteMassive')) { expliquer("Silhouette massive : +5 au jet de CON"); bonus += 5; @@ -6855,6 +7012,15 @@ var COFantasy = COFantasy || function() { } } break; + case 'SAG': + { + let bonusSAG = predicateAsInt(personnage, 'bonusTests_SAG', 0) + predicateAsInt(personnage, 'bonusTests_sagesse', 0); + if (bonusSAG) { + expliquer("Bonus aux jets de SAG : " + ((bonusSAG < 0) ? "-" : "+") + bonusSAG); + bonus += bonusSAG; + } + } + break; } let bonusCompetence; if (options && options.competence) { @@ -6877,8 +7043,8 @@ var COFantasy = COFantasy || function() { bestFit = caracsLimitees.includes(carac); } } + let compSansBlanc = options.competence.toLowerCase().replace(/ /g, '_'); if (bonusCompetence === undefined) { - let compSansBlanc = options.competence.toLowerCase().replace(/ /g, '_'); options.bonusAttrs = options.bonusAttrs || []; options.bonusAttrs.push(compSansBlanc); options.bonusPreds = options.bonusPreds || []; @@ -6931,6 +7097,7 @@ var COFantasy = COFantasy || function() { let dice = 20; if ((estAffaibli(personnage) && !predicateAsBool(personnage, 'insensibleAffaibli')) || getState(personnage, 'immobilise') || + (carac == 'DEX' && getState(personnage, 'encombre')) || attributeAsBool(personnage, 'mortMaisNAbandonnePas')) dice = 12; else { @@ -7255,6 +7422,10 @@ var COFantasy = COFantasy || function() { let d20roll = roll.results.total; let bonusText = (bonusCarac > 0) ? "+" + bonusCarac : (bonusCarac === 0) ? "" : bonusCarac; testRes.texte = jetCache ? d20roll + bonusCarac : buildinline(roll) + bonusText; + if (options.chanceRollId && options.chanceRollId[testId]) { + bonusCarac += options.chanceRollId[testId]; + testRes.texte += "+" + options.chanceRollId[testId]; + } effetAuD20(personnage, d20roll); if (d20roll == 20) { testRes.reussite = true; @@ -7741,22 +7912,29 @@ var COFantasy = COFantasy || function() { layer: 'walls' }); murs = murs.map(function(path) { - let chemin = JSON.parse(path.get('_path')); - if (chemin.length < 2) return []; - if (chemin[1][0] != 'L') return []; - let p = { - angle: path.get('rotation') / 180 * Math.PI, - width: path.get('width'), - height: path.get('height'), - top: path.get('top'), - left: path.get('left'), - scaleX: path.get('scaleX'), - scaleY: path.get('scaleY'), - }; - chemin = chemin.map(function(v) { - return translatePathCoordinates(v[1], v[2], p); - }); - return chemin; + let pa = path.get('_path'); + if (!pa) return []; + try { + let chemin = JSON.parse(pa); + if (chemin.length < 2) return []; + if (chemin[1][0] != 'L') return []; + let p = { + angle: path.get('rotation') / 180 * Math.PI, + width: path.get('width'), + height: path.get('height'), + top: path.get('top'), + left: path.get('left'), + scaleX: path.get('scaleX'), + scaleY: path.get('scaleY'), + }; + chemin = chemin.map(function(v) { + return translatePathCoordinates(v[1], v[2], p); + }); + return chemin; + } catch (error) { + error("Erreur, chemin mal form\xE9 dans le calque d'\xE9clairage dynamique", path); + log(error.name + ": " + error.message); + } }); //On rajoute les portes ferm\xE9es. let doors = findObjs({ @@ -8679,7 +8857,7 @@ var COFantasy = COFantasy || function() { return; } let res = { - type: 'attribut', + type: 'attributCible', attribute: args[1], valeur: args[2].toLowerCase(), text: args[1] + ' ' + args[2] @@ -9866,21 +10044,28 @@ var COFantasy = COFantasy || function() { return; } case 'decrAttribute': - if (cmd.length < 2) { - error("Erreur interne d'une commande g\xE9n\xE9r\xE9e par bouton", cmd); - return; - } - let attr = getObj('attribute', cmd[1]); - if (attr === undefined) { - attr = tokenAttribute(attaquant, cmd[1]); - if (attr.length === 0) { - error("Attribut \xE0 changer perdu", cmd); + { + if (cmd.length < 2) { + error("Erreur interne d'une commande g\xE9n\xE9r\xE9e par bouton", cmd); return; } - attr = attr[0]; + let attr = getObj('attribute', cmd[1]); + if (attr === undefined) { + attr = tokenAttribute(attaquant, cmd[1]); + if (attr.length === 0) { + error("Attribut \xE0 changer perdu", cmd); + return; + } + attr = attr[0]; + } + let da = { + id: attr.id, + val: 1 + }; + if (cmd.length > 2) da.val = toInt(cmd[2], 1); + scope.decrAttribute = da; + return; } - scope.decrAttribute = attr.id; //Seulement l'id pour pouvoir cloner - return; case 'decrLimitePredicatParTour': if (cmd.length < 2) { error("Erreur interne d'une commande g\xE9n\xE9r\xE9e par bouton", cmd); @@ -10705,6 +10890,17 @@ var COFantasy = COFantasy || function() { return (attr[0].get('current') + '').toLowerCase() == valeur; } + function testConditionAttribut(cond, attaquant) { + if (cond.valeur === undefined) { + if (attributeAsBool(attaquant, cond.attribute)) return true; + if (cond.attribute == 'armeDArgent') { + return attributeAsBool(attaquant, 'formeDAnge') && predicateAsInt(attaquant, 'voieDeLArchange', 1) > 2; + } + return false; + } + return testAttribut(attaquant, cond.attribute, cond.valeur, cond); + } + function testCondition(cond, attaquant, cibles, deAttaque, options) { if (cond == 'toujoursVrai') return true; switch (cond.type) { @@ -10731,16 +10927,7 @@ var COFantasy = COFantasy || function() { }); return resEtatCible; case 'attribut': - { - if (cond.valeur === undefined) { - if (attributeAsBool(attaquant, cond.attribute)) return true; - if (cond.attribute == 'armeDArgent') { - return attributeAsBool(attaquant, 'formeDAnge') && predicateAsInt(attaquant, 'voieDeLArchange', 1) > 2; - } - return false; - } - return testAttribut(attaquant, cond.attrbute, cond.valeur, cond); - } + return testConditionAttribut(cond, attaquant); case 'attributCible': { if (cond.valeur === undefined) { @@ -10750,7 +10937,7 @@ var COFantasy = COFantasy || function() { return res; } let res = cibles.every(function(target) { - return testAttribut(target, cond.attrbute, cond.valeur, cond); + return testAttribut(target, cond.attribute, cond.valeur, cond); }); return res; } @@ -10848,24 +11035,27 @@ var COFantasy = COFantasy || function() { } break; case 'decrAttribute': - let attr = getObj('attribute', branch.decrAttribute); - if (attr === undefined) { - error("Attribut introuvable", branch.decrAttribute); - break; - } - let oldval = parseInt(attr.get('current')); - if (isNaN(oldval) || oldval < 1) { - sendChar(attr.get('characterid'), "ne peut plus faire cela", true); + { + let da = branch.decrAttribute; + let attr = getObj('attribute', da.id); + if (attr === undefined) { + error("Attribut introuvable", da.id); + break; + } + let oldval = parseInt(attr.get('current')); + if (isNaN(oldval) || oldval < da.val) { + sendChar(attr.get('characterid'), "ne peut plus faire cela", true); + break; + } + evt.attributes = evt.attributes || []; + evt.attributes.push({ + attribute: attr, + current: oldval, + max: attr.get('max') + }); + attr.set('current', oldval - da.val); break; } - evt.attributes = evt.attributes || []; - evt.attributes.push({ - attribute: attr, - current: oldval, - max: attr.get('max') - }); - attr.set('current', oldval - 1); - break; case 'decrLimitePredicatParTour': //Ne fait que diminuer l'attribut, n'emp\xEAche pas l'attaque let pred = branch.decrLimitePredicatParTour; @@ -12698,7 +12888,9 @@ var COFantasy = COFantasy || function() { } defense -= bonus; } - defense += predicateAsInt(target, 'DEF', 0); + defense += predicateAsInt(target, 'DEF', 0); //deprecated + defense += predicateAsInt(target, 'bonus_DEF', 0); + defense += predicateAsInt(target, 'bonus_DEF(anneau)', 0); if (attaquant && predicateAsBool(target, 'armeDeLEte') && predicateAsBool(attaquant, 'creatureDeLHiver')) { explications.push("Prot\xE9g\xE9 par une arme de l'\xE9t\xE9 => +25 en DEF"); defense += 25; @@ -12706,6 +12898,27 @@ var COFantasy = COFantasy || function() { if (predicateAsBool(target, 'petiteTaille') && !attributeAsBool(target, 'agrandissement')) { defense += 1; } + //Bonus au d\xE9fi duelliste + let defiDuellisteAttr = tokenAttribute(target, 'defiDuelliste'); + if (defiDuellisteAttr.length > 0) { + defiDuellisteAttr = defiDuellisteAttr[0]; + let cibleDefi = defiDuellisteAttr.get('max'); + if (cibleDefi.startsWith(attaquant.token.id)) cibleDefi = true; + else { + let cibleDefiSep = cibleDefi.indexOf(' '); + let cibleDefiName = cibleDefi.substring(cibleDefiSep + 1); + if (cibleDefiName == nomPerso(attaquant)) { + let cibleDefiId = cibleDefi.substring(0, cibleDefiSep); + cibleDefi = persoOfId(cibleDefiId, cibleDefiName, pageId); + cibleDefi = cibleDefi === undefined || cibleDefi.id == attaquant.token.id; + } else cibleDefi = false; + } + if (cibleDefi) { + let bonusDefi = parseInt(defiDuellisteAttr.get('current')); + defense += bonusDefi; + explications.push("D\xE9fi => +" + bonusDefi + " DEF"); + } + } return defense; } @@ -13587,7 +13800,6 @@ var COFantasy = COFantasy || function() { attBonus -= 5; target.attaqueDansLeNoir = 5; } - } } if (predicateAsBool(attaquant, 'liberateurDeDorn') && estGeant(target)) { @@ -13646,6 +13858,27 @@ var COFantasy = COFantasy || function() { target.combattreLaCorruption = combattreLaCorruption; explications.push("Combattre la corruption => +" + combattreLaCorruption + " attaque et DM"); } + //Bonus au d\xE9fi duelliste + let defiDuellisteAttr = tokenAttribute(attaquant, 'defiDuelliste'); + if (defiDuellisteAttr.length > 0) { + defiDuellisteAttr = defiDuellisteAttr[0]; + let cibleDefi = defiDuellisteAttr.get('max'); + if (cibleDefi.startsWith(target.token.id)) cibleDefi = true; + else { + let cibleDefiSep = cibleDefi.indexOf(' '); + let cibleDefiName = cibleDefi.substring(cibleDefiSep + 1); + if (cibleDefiName == nomPerso(target)) { + let cibleDefiId = cibleDefi.substring(0, cibleDefiSep); + cibleDefi = persoOfId(cibleDefiId, cibleDefiName, pageId); + cibleDefi = cibleDefi === undefined || cibleDefi.id == target.token.id; + } else cibleDefi = false; + } + if (cibleDefi) { + let bonusDefi = parseInt(defiDuellisteAttr.get('current')); + attBonus += bonusDefi; + explications.push("D\xE9fi => +" + bonusDefi + " attaque"); + } + } return attBonus; } @@ -13800,22 +14033,22 @@ var COFantasy = COFantasy || function() { //Retourne true si il existe une limite qui emp\xEAche de lancer le sort //N'ajoute pas l'\xE9v\xE9nement \xE0 l'historique - //explications est optionnel - function limiteRessources(personnage, options, defResource, msg, evt, explications) { + //perso et explications sont optionnels + function limiteRessources(perso, options, defResource, msg, evt, explications) { let depMana = { cout_null: true }; - if (options.magieEnArmureMana && personnage) { + if (options.magieEnArmureMana && perso) { options.mana = options.mana || 0; - let ma = malusArmure(personnage); + let ma = malusArmure(perso); let m = ma; if (m > 0) { - let magieEnArmure = predicateAsInt(personnage, 'magieEnArmure', 0); - let defa = defenseArmure(personnage); + let magieEnArmure = predicateAsInt(perso, 'magieEnArmure', 0); + let defa = defenseArmure(perso); if (2 * magieEnArmure >= defa + ma) { //pas de malus m = 0; } else { - if (magieEnArmure > 0 && predicateAsBool(personnage, 'magieEnArmureFacilitee')) { + if (magieEnArmure > 0 && predicateAsBool(perso, 'magieEnArmureFacilitee')) { m -= magieEnArmure; if (m < 0) m = 1; } @@ -13829,29 +14062,29 @@ var COFantasy = COFantasy || function() { } } if (options.mana) { - if (personnage) { - depMana = depenseManaPossible(personnage, options.mana, msg); + if (perso) { + depMana = depenseManaPossible(perso, options.mana, msg); if (!depMana) return true; } else { error("Impossible de savoir qui doit d\xE9penser de la mana", options); return true; } } - depMana = testLimitePar(personnage, 'Jour', options, depMana, defResource, msg, evt, explications); + depMana = testLimitePar(perso, 'Jour', options, depMana, defResource, msg, evt, explications); if (!depMana) return true; - depMana = testLimitePar(personnage, 'Combat', options, depMana, defResource, msg, evt, explications); + depMana = testLimitePar(perso, 'Combat', options, depMana, defResource, msg, evt, explications); if (!depMana) return true; if (!depMana) return true; - depMana = testLimitePar(personnage, 'Tour', options, depMana, defResource, msg, evt, explications); + depMana = testLimitePar(perso, 'Tour', options, depMana, defResource, msg, evt, explications); if (!depMana) return true; if (options.tempsRecharge) { - if (personnage) { - if (attributeAsBool(personnage, options.tempsRecharge.effet)) { - sendPerso(personnage, "ne peut pas encore faire cette action", options.secret); + if (perso) { + if (attributeAsBool(perso, options.tempsRecharge.effet)) { + sendPerso(perso, "ne peut pas encore faire cette action", options.secret); return true; } if (options.tempsRecharge.duree > 0) { - setAttrDuree(personnage, options.tempsRecharge.effet, options.tempsRecharge.duree, evt); + setAttrDuree(perso, options.tempsRecharge.effet, options.tempsRecharge.duree, evt); } } else { error("Impossible de savoir \xE0 qui s'applique le temps de recharge", options); @@ -13859,41 +14092,42 @@ var COFantasy = COFantasy || function() { } } if (options.dose) { - if (personnage) { + if (perso) { let nomDose = options.dose.replace(/_/g, ' '); - let doses = attributeAsInt(personnage, 'dose_' + options.dose, 0); + let doses = attributeAsInt(perso, 'dose_' + options.dose, 0); if (doses === 0) { - sendPerso(personnage, "n'a plus de " + nomDose, options.secret); + sendPerso(perso, "n'a plus de " + nomDose, options.secret); return true; } - setTokenAttr(personnage, 'dose_' + options.dose, doses - 1, evt); + setTokenAttr(perso, 'dose_' + options.dose, doses - 1, evt); } else { error("Impossible de savoir qui doit d\xE9penser la dose", options); return true; } } if (options.limiteAttribut) { - if (personnage) { + if (perso) { let nomAttr = options.limiteAttribut.nom; - let currentAttr = attributeAsInt(personnage, nomAttr, 0); + let currentAttr = attributeAsInt(perso, nomAttr, 0); if (currentAttr >= options.limiteAttribut.limite) { - depMana = depasseLimite(personnage, nomAttr, options.limiteAttribut.message, msg, evt, options); + depMana = depasseLimite(perso, nomAttr, options.limiteAttribut.message, msg, evt, options); if (!depMana) return true; } - setTokenAttr(personnage, nomAttr, currentAttr + 1, evt); + setTokenAttr(perso, nomAttr, currentAttr + 1, evt); } else { error("Impossible de savoir \xE0 qui appliquer la limitation", options); return true; } } if (options.decrAttribute) { - let attr = getObj('attribute', options.decrAttribute); + let da = options.decrAttribute; + let attr = getObj('attribute', da.id); if (attr === undefined) { - error("Attribut introuvable", options.decrAttribute); + error("Attribut introuvable", da.id); return true; } let oldval = parseInt(attr.get('current')); - if (isNaN(oldval) || oldval < 1) { + if (isNaN(oldval) || oldval < da.val) { let expliquer = sendChar; if (options.secret) expliquer = whisperChar; expliquer(attr.get('characterid'), "ne peut plus faire cela", true); @@ -13905,22 +14139,62 @@ var COFantasy = COFantasy || function() { current: oldval, max: attr.get('max') }); - attr.set('current', oldval - 1); + attr.set('current', oldval - da.val); } if (options.decrLimitePredicatParTour) { let pred = options.decrLimitePredicatParTour; - if (personnage) { - let test = testLimiteUtilisationsCapa(personnage, pred, 'tour', + if (perso) { + let test = testLimiteUtilisationsCapa(perso, pred, 'tour', "ne peut plus utiliser " + pred + " ce tour", "Action impossible, pas de pr\xE9dicat " + pred); if (test === undefined) return true; - utiliseCapacite(personnage, test, evt); + utiliseCapacite(perso, test, evt); } else { error("Impossible de savoir \xE0 qui appliquer la limitation du pr\xE9dicat " + pred, options); return true; } } - if (personnage) depenseMana(personnage, depMana, msg, evt); + if (options.depensePR) { + if (!perso) { + error("Impossible de savoir \xE0 qui enlever les PR.", options); + return true; + } + let pr = pointsDeRecuperation(perso); + if (!pr || !pr.current || pr.current < options.depensePR.val) { + if (options.depensePR.pv) { + options.rolls = options.rolls || {}; + let pv = options.rolls.depensePR || rollDePlus(options.depensePR.pv); + evt.action = evt.action || { + rolls: {} + }; + evt.action.rolls.depensePR = pv; + let r = { + total: pv.val, + type: 'normal', + display: pv.roll + }; + let expl = explications || []; + perso.ignoreTouteRD = true; + dealDamage(perso, r, [], evt, false, {}, expl, + function(dmgDisplay, dmg) { + let dmgMsg = "perd " + dmgDisplay + " PV"; + if (explications) explications.push(dmgMsg); + else { + expl.forEach(function(m) { + sendPerso(perso, m, options.secret); + }); + sendPerso(perso, dmgMsg, options.secret); + } + }); + } else { + sendPerso(perso, "Plus de PR \xE0 d\xE9penser", options.secret); + return true; + } + } else { //d\xE9pense de PR + enleverPointDeRecuperation(perso, pr, evt, options.depensePR.val); + } + } + if (perso) depenseMana(perso, depMana, msg, evt); return false; } @@ -14545,7 +14819,7 @@ var COFantasy = COFantasy || function() { let distance = Math.random() * reglesOptionelles.divers.val.echec_critique_boule_de_feu.val * PIX_PER_UNIT / computeScale(pageId); pc.x = Math.round(left + Math.cos(angle) * distance); pc.y = Math.round(top + Math.sin(angle) * distance); - page = page || getObj("page", pageId); + page = page || getObj('page', pageId); let width = page.get('width') * PIX_PER_UNIT; let height = page.get('height') * PIX_PER_UNIT; if (pc.x < 0) pc.x = 0; @@ -15236,7 +15510,9 @@ var COFantasy = COFantasy || function() { } else if (cibles.length == 1 && options.contact && options.attaqueAcrobatique) { let rollId = 'attaqueAcrobatique_' + attackingToken.id; let rollOptions = { - competence: 'acrobatie' + competence: 'acrobatie', + chanceRollId: options.chanceRollId, + rolls: options.rolls }; explications.push("Tentative d'acrobatie pour surprendre " + nomPerso(cible)); testCaracteristique(attaquant, 'DEX', 15, rollId, rollOptions, evt, @@ -15247,8 +15523,11 @@ var COFantasy = COFantasy || function() { }); if (tr.reussite) { explications.push("R\xE9ussite : " + nomPerso(attaquant) + " peut faire une attaque sournoise"); - options.sournoise = options.sournoise || 0; - options.sournoise += options.attaqueAcrobatique; + if (!options.attaqueAcrobatiqueReussie) { + options.sournoise = options.sournoise || 0; + options.sournoise += options.attaqueAcrobatique; + options.attaqueAcrobatiqueReussie = true; + } } else { explications.push("Rat\xE9, " + nomPerso(attaquant) + " r\xE9alise une attaque normale" + tr.rerolls); } @@ -15316,6 +15595,7 @@ var COFantasy = COFantasy || function() { for (let pref in rawList) { let ra = rawList[pref]; if (ra.armelabel === undefined) ra.armelabel = 0; + if (ra.armenom === undefined) ra.armenom = ''; if (liste[ra.armelabel]) { error("Plusieurs attaques de label " + ra.armelabel, ra); continue; @@ -15337,7 +15617,7 @@ var COFantasy = COFantasy || function() { let ra = rawList[pref]; if (ra.labelarmure === undefined) ra.labelarmure = 0; if (liste[ra.labelarmure]) { - error("Plusieurs attaques de label " + ra.labelarmure, ra); + error("Plusieurs armures de label " + ra.labelarmure, ra); continue; } ra.prefixe = pref; @@ -19032,7 +19312,7 @@ var COFantasy = COFantasy || function() { if (cibleDefiName == nomPerso(target)) { let cibleDefiId = cibleDefi.substring(0, cibleDefiSep); cibleDefi = persoOfId(cibleDefiId, cibleDefiName, pageId); - cibleDefi = cibleDefi === undefined || cibleDefi.id == target.id; + cibleDefi = cibleDefi === undefined || cibleDefi.id == target.token.id; } else cibleDefi = false; } if (cibleDefi) { @@ -19140,7 +19420,21 @@ var COFantasy = COFantasy || function() { // 1+nb d\xE9g\xE2ts suppl\xE9mentaires + : rolls de d\xE9g\xE2ts critiques let toEvaluateDmg = "[[" + mainDmgRollExpr + "]]" + extraDmgRollExpr; sendChat('', toEvaluateDmg, function(resDmg) { - let rollsDmg = target.rollsDmg || resDmg[0]; + let rollsDmg = resDmg[0]; + if (target.rollsDmg) { + //We may have more rolls or different rolls + let pos = 0; + let original = target.rollsDmg.inlinerolls; + let reroll = rollsDmg.inlinerolls; + original.forEach(function(r, i) { + while (reroll[pos] && r.expression != reroll[pos].expression) + pos++; + if (reroll[pos]) { + reroll[pos] = r; + } + pos++; + }); + } let afterEvaluateDmg = rollsDmg.content.split(' '); let mainDmgRollNumber = rollNumber(afterEvaluateDmg[0]); mainDmgRoll.total = rollsDmg.inlinerolls[mainDmgRollNumber].results.total; @@ -20380,6 +20674,10 @@ var COFantasy = COFantasy || function() { layer: 'objects' }); let allies = alliesParPerso[attaquant.charId] || new Set(); + let page = getObj('page', evt.action.pageId); + let murs = getWalls(page, evt.action.pageId); + let ptt = pointOfToken(cible.token); + let pta = pointOfToken(attaquant.token); tokens = tokens.filter(function(tok) { if (tok.id == attaquant.token.id) return false; if (tok.id == cible.token.id) return false; @@ -20392,6 +20690,12 @@ var COFantasy = COFantasy || function() { if (dejaTouche) return false; let dist = distanceCombat(cible.token, tok, evt.action.pageId); if (dist === 0 || dist > options.portee) return false; + if (murs) { + let x = tok.get('left'); + let y = tok.get('top'); + if (obstaclePresent(x, y, ptt, murs)) return false; + if (obstaclePresent(x, y, pta, murs)) return false; + } return true; }); let distance = distanceCombat(cible.token, attaquant.token, evt.action.pageId); @@ -20874,6 +21178,10 @@ var COFantasy = COFantasy || function() { res.sauf.argent += 5; } let rd = ficheAttribute(perso, 'RDS', ''); + predicatesNamed(perso, 'bonus_RD').forEach(function(r) { + if (rd === '') rd = r; + else rd += ',' + r; + }); rd = (rd + '').trim(); if (rd === '') { perso.rd = res; @@ -21228,6 +21536,7 @@ var COFantasy = COFantasy || function() { }; //si il faut noter les DMs d'un type particulier if (mainDmgType == 'drain') dmSuivis.drain = dmgTotal; predicatesNamed(target, 'vitaliteSurnaturelle').forEach(function(a) { + if (typeof a != "string") return; let indexType = a.indexOf('/'); if (indexType < 0 || indexType == a.length) return; a = a.substring(indexType + 1); @@ -21505,7 +21814,8 @@ var COFantasy = COFantasy || function() { } // pr doit \xEAtre d\xE9fini, et pr.current > 0 - function enleverPointDeRecuperation(perso, pr, evt) { + // n est optionnel + function enleverPointDeRecuperation(perso, pr, evt, n) { evt.attributes = evt.attributes || []; let attrPR; if (pr.attribut) { @@ -21515,10 +21825,15 @@ var COFantasy = COFantasy || function() { caseInsensitive: true }); if (attrPR.length === 0) { + let current = 4; + if (n) { + current = 5 - n; + if (current < 0) current = 0; + } attrPR = createObj("attribute", { characterid: perso.charId, name: 'pr', - current: 4, + current, max: 5 }); evt.attributes.push({ @@ -21532,7 +21847,10 @@ var COFantasy = COFantasy || function() { attribute: attrPR, current: pr.current }); - pr.current--; + if (n) { + pr.current -= n; + if (pr.current < 0) pr.current = 0; + } else pr.current--; attrPR.set('current', pr.current); } @@ -21761,6 +22079,15 @@ var COFantasy = COFantasy || function() { } let rdTarget = getRDS(target); let rd = rdTarget.rdt || 0; + if (!target.perteDeSubstance && options.attaquant && predicateAsBool(target, 'ancreInvincible')) { + if (predicateAsBool(options.attaquant, 'dragonInvincble')) { + rd += 10; + target.messages.push("Ancre contre le dragon => +10 RD"); + } else if (predicateAsBool(options.attaquant, 'emissaireDuDragonInvincible')) { + rd += 5; + target.messages.push("Ancre contre \xE9missaire du dragon => +5 RD"); + } + } if (rd > 0 && !options.aoe && options.attaquant && predicateAsBool(options.attaquant, 'ventreMou')) { let taille = taillePersonnage(target, 4); if (taille > 4) { @@ -23124,7 +23451,7 @@ var COFantasy = COFantasy || function() { if (!isActive(perso)) return; let persoTest = persoParCharId[perso.charId]; let arme = predicateAsBool(persoTest, 'armeParDefaut'); - if (arme === undefined) return; + if (arme === undefined || arme === false) return; if (arme === true) degainerArme(perso, '', evt); else degainerArme(perso, arme, evt); }); @@ -23152,6 +23479,19 @@ var COFantasy = COFantasy || function() { setTokenAttr(perso, 'charge_' + label, charges[persoTest.charId][label], evt); } }); + //Remise \xE0 z\xE9ro des options de combat + let def0 = { + default: 0 + }; + persosDuCombat.forEach(function(perso) { + setFicheAttr(perso, 'attaque_de_groupe', 1, evt, { + default: 1 + }); + setFicheAttr(perso, 'attaque_en_puissance_check', 0, evt, def0); + setFicheAttr(perso, 'attaque_risquee_check', 0, evt, def0); + setFicheAttr(perso, 'attaque_assuree_check', 0, evt, def0); + setFicheAttr(perso, 'attaque_dm_temp_check', 0, evt, def0); + }); //Effet de ignorerLaDouleur let ilds = allAttributesNamed(attrs, 'douleurIgnoree'); ilds = ilds.concat(allAttributesNamed(attrs, 'memePasMalIgnore')); @@ -23723,7 +24063,12 @@ var COFantasy = COFantasy || function() { log(cmd); return; } - options.decrAttribute = attr.id; + let da = { + id: attr.id, + val: 1 + }; + if (cmd.length > 2) da.val = toInt(cmd[2], 1); + options.decrAttribute = da; return; case 'valeur': if (cmd.length < 2) { @@ -23912,6 +24257,17 @@ var COFantasy = COFantasy || function() { options.terrainDifficile = terrainDifficile; return; } + case 'depensePR': + { + options.depensePR = { + val: 1 + }; + if (cmd.length < 2) return; + options.depensePR.val = toInt(cmd[1], 1, 1); + if (cmd.length < 3) return; + options.depensePR.pv = parseDice(cmd[2], "PV d\xE9pens\xE9s si pas de PR"); + return; + } default: return; } @@ -24441,17 +24797,11 @@ var COFantasy = COFantasy || function() { } } let token = perso.token; - let charId = perso.charId; - let character = getObj("character", charId); - if (character === undefined) { - finalize(); - return; - } let pr = pointsDeRecuperation(perso); let bar2 = parseInt(token.get("bar2_value")); let manaAttr = findObjs({ _type: 'attribute', - _characterid: charId, + _characterid: perso.charId, name: 'PM' }, { caseInsensitive: true @@ -24459,14 +24809,29 @@ var COFantasy = COFantasy || function() { let hasMana = false; let dmTemp = bar2; let estMook = token.get('bar1_link') === ''; + let recupereMana; if (manaAttr.length > 0) { // R\xE9cup\xE9ration des points de mana let manaMax = parseInt(manaAttr[0].get('max')); hasMana = !isNaN(manaMax) && manaMax > 0; if (hasMana) { if (estMook) dmTemp = attributeAsInt(perso, 'DMTEMP', 0); else dmTemp = ficheAttributeAsInt(perso, 'DMTEMP', 0); - if (reposLong && (isNaN(bar2) || bar2 < manaMax)) { - updateCurrentBar(perso, 2, manaMax, evt); + if (isNaN(bar2) || bar2 < manaMax) { + if (reposLong) { + updateCurrentBar(perso, 2, manaMax, evt); + } else if (reglesOptionelles.mana.val.PR_rend_mana.val) { + let de = ficheAttributeAsInt(perso, 'de_mana', 0); + if (de) { + let caracMagique = ficheAttribute(perso, 'carac_mana', '@{INT}'); + if (isNaN(bar2)) bar2 = 0; + recupereMana = { + manaMax, + bar2, + de, + caracMagique + }; + } + } } } } @@ -24485,7 +24850,7 @@ var COFantasy = COFantasy || function() { finalize(); return; } - if (bar1 >= pvmax && !reposLong) { + if (bar1 >= pvmax && !reposLong && !recupereMana) { //Plus rien \xE0 faire si pas un repos long sendPerso(perso, "n'a pas besoin de repos"); finalize(); @@ -24524,7 +24889,6 @@ var COFantasy = COFantasy || function() { finalize(); return; } - let message; if (reposLong && pr && pr.current < pr.max) { // on r\xE9cup\xE8re un PR //Sauf si on a une blessure gave if (getState(perso, 'blesse')) { @@ -24533,11 +24897,11 @@ var COFantasy = COFantasy || function() { sendPerso(perso, "fait un jet de CON pour gu\xE9rir de sa blessure"); let m = "/direct " + onGenre(perso, 'Il', 'Elle') + " fait " + tr.texte; if (tr.reussite) { - sendChar(charId, m + "≥ 8, son \xE9tat s'am\xE9liore nettement." + tr.modifiers, true); + sendPerso(perso, m + "≥ 8, son \xE9tat s'am\xE9liore nettement." + tr.modifiers, true); setState(perso, 'blesse', false, evt); } else { let msgRate = m + "< 8, son \xE9tat reste pr\xE9occupant." + tr.rerolls + tr.modifiers; - sendChar(charId, msgRate, true); + sendPerso(perso, msgRate, true); } finalize(); }); @@ -24549,17 +24913,17 @@ var COFantasy = COFantasy || function() { finalize(); return; } - message = + let message = "Au cours de la nuit, les points de r\xE9cup\xE9ration de " + nomPerso(perso) + " passent de " + (pr.current - 1) + " \xE0 " + pr.current; - sendChar(charId, message, true); + sendPerso(perso, message, true); if (bar1 < pvmax) manquePV.push(perso); finalize(); return; } if (!reposLong && pr) { if (pr.current === 0) { //pas possible de r\xE9cup\xE9rer - message = " a besoin d'une nuit compl\xE8te pour r\xE9cup\xE9rer"; + let message = " a besoin d'une nuit compl\xE8te pour r\xE9cup\xE9rer"; sendPerso(perso, message); finalize(); return; @@ -24569,8 +24933,10 @@ var COFantasy = COFantasy || function() { } let conMod = modCarac(perso, 'constitution'); let niveau = ficheAttributeAsInt(perso, 'niveau', 1); - let characterName = character.get("name"); - let rollExpr = addOrigin(characterName, "[[1d" + dVie + "]]"); + let rollExpr = "[[1d" + dVie + "]]"; + if (recupereMana) { + rollExpr += " [[1d" + recupereMana.de + "]]"; + } sendChat("COF", rollExpr, function(res) { let rollRecupID = "rollRecup_" + perso.token.id; options.rolls = options.rolls || {}; @@ -24579,28 +24945,51 @@ var COFantasy = COFantasy || function() { evt.action = evt.action || {}; evt.action.rolls = evt.action.rolls || {}; evt.action.rolls[rollRecupID] = roll; - let dVieRoll = roll.results.total; - let bonus = conMod + niveau; - let total = dVieRoll + bonus; - if (total < 0) total = 0; - if (bar1 === 0) { - if (attributeAsBool(perso, 'etatExsangue')) { - removeTokenAttr(perso, 'etatExsangue', evt, { - msg: "retrouve des couleurs" - }); + let message; + if (bar1 < pvmax) { + let dVieRoll = roll.results.total; + let bonus = conMod + niveau; + let total = dVieRoll + bonus; + if (total < 0) total = 0; + if (bar1 === 0) { + if (attributeAsBool(perso, 'etatExsangue')) { + removeTokenAttr(perso, 'etatExsangue', evt, { + msg: "retrouve des couleurs" + }); + } } + bar1 += total; + if (bar1 < pvmax) manquePV.push(perso); + else bar1 = pvmax; + updateCurrentBar(perso, 1, bar1, evt); + if (reposLong) { + message = "Au cours de la nuit, "; + } else { + message = "Apr\xE8s 5 minutes de repos, "; + } + message += "r\xE9cup\xE8re " + buildinline(roll) + "+" + bonus + " PV"; + } + if (recupereMana) { + let rollRecupManaID = "rollRecupmana_" + perso.token.id; + roll = + options.rolls[rollRecupManaID] ? options.rolls[rollRecupManaID] : res[0].inlinerolls[1]; + evt.action.rolls[rollRecupManaID] = roll; + let dManaRoll = roll.results.total; + let bonus = computeCarValue(perso, recupereMana.caracMagique); + let total = dManaRoll + bonus; + if (total < 0) total = 0; + let bar2 = recupereMana.bar2 + total; + if (bar2 > recupereMana.manaMax) bar2 = recupereMana.manaMax; + updateCurrentBar(perso, 2, bar2, evt); + if (message) message += " et "; + else message = "Apr\xE8s 5 minutes de repos, r\xE9cup\xE8re "; + message += buildinline(roll) + "+" + bonus + " PM."; + } else message += '.'; + if (pr) { + message += " Il lui reste " + pr.current + " point"; + if (pr.current > 1) message += 's'; + message += " de r\xE9cup\xE9ration"; } - bar1 += total; - if (bar1 < pvmax) manquePV.push(perso); - else bar1 = pvmax; - updateCurrentBar(perso, 1, bar1, evt); - if (reposLong) { - message = "Au cours de la nuit, "; - } else { - message = "Apr\xE8s 5 minutes de repos, "; - } - message += "r\xE9cup\xE8re " + buildinline(roll) + "+" + bonus + " PV."; - if (pr) message += " Il lui reste " + pr.current + " points de r\xE9cup\xE9ration"; sendPerso(perso, message); finalize(); }); @@ -24749,6 +25138,153 @@ var COFantasy = COFantasy || function() { addEvent(evt); } + //Renvoie true si redo possible, false sinon + function redoEvent(evt, action, perso) { + let options = action.options || {}; + options.rolls = action.rolls; + options.choices = action.choices; + switch (evt.type) { + case 'Attaque': + options.redo = true; + if (action.cibles) { + action.cibles.forEach(function(target) { + delete target.partialSaveAuto; + delete target.dmRate; + }); + } + attack(action.playerName, action.playerId, action.attaquant, action.cibles, action.weaponStats, options); + return true; + case 'attaqueMagique': + attaqueMagiqueOpposee(action.playerId, action.attaquant, action.cible, options); + return true; + case 'armeSecrete': + doArmeSecrete(action.perso, action.cible, options); + return true; + case 'boireAlcool': + doBoireAlcool(action.playerId, action.persos, options); + return true; + case 'dmgDirects': + dmgDirects(action.playerId, action.playerName, action.cibles, action.dmg, options); + return true; + case 'degainer': + doDegainer(action.persos, action.armeLabel, options); + return true; + case 'destructionMortsVivants': + doDestructionDesMortsVivants(action.lanceur, action.playerName, action.dm, options); + return true; + case 'echapperEtreinte': + case 'echapperEnveloppement': + doEchapperEnveloppement(action.perso, action.etreinte, action.cube, action.difficulte, options); + return true; + case 'effetTemp': + effetTemporaire(action.playerId, action.cibles, action.effet, action.mEffet, action.duree, options); + return true; + case 'Effet': + effetIndetermine(action.playerId, action.cibles, action.effet, action.activer, action.valeur, options); + return true; + case 'auraDrainDeForce': + if (!action.cibles) return; + action.cibles.forEach(function(perso) { + delete perso.messages; + }); + doAuraDrainDeForce(action.playerId, action.origine, action.cibles, action.mEffet, options); + return true; + case 'auraDrainDeForceSup': + if (!action.cibles) return; + action.cibles.forEach(function(perso) { + delete perso.messages; + }); + doAuraDrainDeForceSup(action.playerId, action.origine, action.cibles, action.mEffet, options); + return true; + case 'enduireDePoison': + doEnduireDePoison(action.perso, action.armeEnduite, action.savePoison, action.forcePoison, action.attribut, + action.testINT, action.infosAdditionelles, options); + return true; + case 'enveloppement': + case '\xE9treinte': + doEnveloppement(action.attaquant, action.cible, action.difficulte, action.type, action.exprDM, options); + return true; + case 'injonction': + injonction(action.playerId, action.attaquant, action.cible, options); + return true; + case 'injonctionMortelle': + injonctionMortelle(action.playerId, action.attaquant, action.cible, options); + return true; + case 'jetPerso': + jetPerso(perso, action.caracteristique, action.difficulte, action.titre, action.playerId, options); + return true; + case 'libererAgrippe': + doLibererAgrippe(action.perso, action.agrippant, action.attrName, options); + return true; + case 'libererEcrase': + doLibererEcrase(action.perso, action.agrippant, action.titre, action.carac, action.difficulte, action.explications, options); + return true; + case 'natureNourriciere': + doNatureNourriciere(action.perso, options); + return true; + case 'nextTurn': + let turnOrder = Campaign().get('turnorder'); + if (turnOrder === '') return false; // nothing in the turn order + turnOrder = JSON.parse(turnOrder); + if (turnOrder.length < 1) return false; // Juste le compteur de tour + let lastTurn = turnOrder.shift(); + turnOrder.push(lastTurn); + Campaign().set('turnorder', JSON.stringify(turnOrder)); + nextTurn(Campaign(), options); + return true; + case 'nouveauJour': + doNouveauJour(action.persos, options); + return true; + case 'peur': + doPeur(action.cibles, action.difficulte, options); + return true; + case 'provocation': + doProvocation(action.voleur, action.cible, options); + return true; + case 'rage': + doRageDuBerserk(action.persos, action.typeRage, options); + return true; + case 'recuperation': + doRecuperation(action.persos, action.reposLong, action.playerId, options); + return true; + case 'save_state': + doSaveState(action.playerId, action.perso, action.etat, action.carac, options, action.opposant, action.seuil); + return true; + case 'save_effet': + doSaveEffet(action.playerId, action.perso, action.effetC, action.attr, action.attrEffet, action.attrName, action.met, action.carac, action.seuil, action.options); + return true; + case 'set_state': + doSetState(action.cibles, action.etat, action.valeur, options); + return true; + case 'sommeil': + doSommeil(action.lanceur, action.cibles, options, action.ciblesSansSave, action.ciblesAvecSave); + return true; + case 'surprise': + doSurprise(action.cibles, action.testSurprise, action.selected, options); + return true; + case 'tourDeForce': //Deprecated + doTourDeForce(action.perso, action.seuil, options); + return true; + case 'tueurFantasmagorique': + tueurFantasmagorique(action.playerId, action.attaquant, action.cible, options); + return true; + case 'enkystementLointain': + enkystementLointain(action.playerId, action.attaquant, action.cible, options); + return true; + case 'vapeursEthyliques': + doVapeursEthyliques(action.playerId, action.persos, options); + return true; + case 'ombre_mouvante': + doOmbreMouvante(action.perso, action.playerId, options); + return true; + case "Sentir la corruption": + sentirLaCorruption(action.playerId, action.chasseur, action.cible, options); + return true; + default: + return false; + } + } + //!cof-bouton-chance [evt.id] [rollId] function boutonChance(msg) { let args = msg.content.split(' '); @@ -24796,7 +25332,7 @@ var COFantasy = COFantasy || function() { } let evtChance = { type: 'chance', - rollId: rollId + rollId }; chance--; undoEvent(evt); @@ -25147,152 +25683,6 @@ var COFantasy = COFantasy || function() { error("Type d'\xE9v\xE8nement pas encore g\xE9r\xE9 pour la chance", evt); } - //Renvoie true si redo possible, false sinon - function redoEvent(evt, action, perso) { - let options = action.options || {}; - options.rolls = action.rolls; - options.choices = action.choices; - switch (evt.type) { - case 'Attaque': - options.redo = true; - if (action.cibles) { - action.cibles.forEach(function(target) { - delete target.partialSaveAuto; - delete target.dmRate; - }); - } - attack(action.playerName, action.playerId, action.attaquant, action.cibles, action.weaponStats, options); - return true; - case 'attaqueMagique': - attaqueMagiqueOpposee(action.playerId, action.attaquant, action.cible, options); - return true; - case 'armeSecrete': - doArmeSecrete(action.perso, action.cible, options); - return true; - case 'boireAlcool': - doBoireAlcool(action.playerId, action.persos, options); - return true; - case 'dmgDirects': - dmgDirects(action.playerId, action.playerName, action.cibles, action.dmg, options); - return true; - case 'degainer': - doDegainer(action.persos, action.armeLabel, options); - return true; - case 'destructionMortsVivants': - doDestructionDesMortsVivants(action.lanceur, action.playerName, action.dm, options); - return true; - case 'echapperEtreinte': - case 'echapperEnveloppement': - doEchapperEnveloppement(action.perso, action.etreinte, action.cube, action.difficulte, options); - return true; - case 'effetTemp': - effetTemporaire(action.playerId, action.cibles, action.effet, action.mEffet, action.duree, options); - return true; - case 'Effet': - effetIndetermine(action.playerId, action.cibles, action.effet, action.activer, action.valeur, options); - return true; - case 'auraDrainDeForce': - if (!action.cibles) return; - action.cibles.forEach(function(perso) { - delete perso.messages; - }); - doAuraDrainDeForce(action.playerId, action.origine, action.cibles, action.mEffet, options); - return true; - case 'auraDrainDeForceSup': - if (!action.cibles) return; - action.cibles.forEach(function(perso) { - delete perso.messages; - }); - doAuraDrainDeForceSup(action.playerId, action.origine, action.cibles, action.mEffet, options); - return true; - case 'enduireDePoison': - doEnduireDePoison(action.perso, action.armeEnduite, action.savePoison, action.forcePoison, action.attribut, - action.testINT, action.infosAdditionelles, options); - return true; - case 'enveloppement': - case '\xE9treinte': - doEnveloppement(action.attaquant, action.cible, action.difficulte, action.type, action.exprDM, options); - return true; - case 'injonction': - injonction(action.playerId, action.attaquant, action.cible, options); - return true; - case 'injonctionMortelle': - injonctionMortelle(action.playerId, action.attaquant, action.cible, options); - return true; - case 'jetPerso': - jetPerso(perso, action.caracteristique, action.difficulte, action.titre, action.playerId, options); - return true; - case 'libererAgrippe': - doLibererAgrippe(action.perso, action.agrippant, action.attrName, options); - return true; - case 'libererEcrase': - doLibererEcrase(action.perso, action.agrippant, action.titre, action.carac, action.difficulte, action.explications, options); - return true; - case 'natureNourriciere': - doNatureNourriciere(action.perso, options); - return true; - case 'nextTurn': - let turnOrder = Campaign().get('turnorder'); - if (turnOrder === '') return false; // nothing in the turn order - turnOrder = JSON.parse(turnOrder); - if (turnOrder.length < 1) return false; // Juste le compteur de tour - let lastTurn = turnOrder.shift(); - turnOrder.push(lastTurn); - Campaign().set('turnorder', JSON.stringify(turnOrder)); - nextTurn(Campaign(), options); - return true; - case 'nouveauJour': - doNouveauJour(action.persos, options); - return true; - case 'peur': - doPeur(action.cibles, action.difficulte, options); - return true; - case 'provocation': - doProvocation(action.voleur, action.cible, options); - return true; - case 'rage': - doRageDuBerserk(action.persos, action.typeRage, options); - return true; - case 'recuperation': - doRecuperation(action.persos, action.reposLong, action.playerId, options); - return true; - case 'save_state': - doSaveState(action.playerId, action.perso, action.etat, action.carac, options, action.opposant, action.seuil); - return true; - case 'save_effet': - doSaveEffet(action.playerId, action.perso, action.effetC, action.attr, action.attrEffet, action.attrName, action.met, action.carac, action.seuil, action.options); - return true; - case 'set_state': - doSetState(action.cibles, action.etat, action.valeur, options); - return true; - case 'sommeil': - doSommeil(action.lanceur, action.cibles, options, action.ciblesSansSave, action.ciblesAvecSave); - return true; - case 'surprise': - doSurprise(action.cibles, action.testSurprise, action.selected, options); - return true; - case 'tourDeForce': //Deprecated - doTourDeForce(action.perso, action.seuil, options); - return true; - case 'tueurFantasmagorique': - tueurFantasmagorique(action.playerId, action.attaquant, action.cible, options); - return true; - case 'enkystementLointain': - enkystementLointain(action.playerId, action.attaquant, action.cible, options); - return true; - case 'vapeursEthyliques': - doVapeursEthyliques(action.playerId, action.persos, options); - return true; - case 'ombre_mouvante': - doOmbreMouvante(action.perso, action.playerId, options); - return true; - case "Sentir la corruption": - sentirLaCorruption(action.playerId, action.chasseur, action.cible, options); - return true; - default: - return false; - } - } function echecTotal(msg) { let args = msg.content.split(' '); @@ -26669,6 +27059,8 @@ var COFantasy = COFantasy || function() { } if (a.charge && attributeAsInt(perso, 'charge_' + l, 1) === 0) degainer += ' (vide)'; + else if (a.poudre && attributeAsInt(perso, 'chargeGrenaille_' + l, 0) > 0) + degainer += ' (grenaille)'; degainer += "," + l + cote + "|"; if (armeADegainer) armeADegainer.unique = undefined; else armeADegainer = { @@ -27070,6 +27462,7 @@ var COFantasy = COFantasy || function() { } else { command += labelArmePrincipale; if (armeDechargee(perso, armePrincipale)) nomCommande += ' (vide)'; + else if (armeChargeeDeGrenaille(perso, armePrincipale)) nomCommande += ' (grenaille)'; } ligneArmePrincipale = bouton(command, nomCommande, perso); } else if (!possedeAttaqueNaturelle) { @@ -27083,6 +27476,7 @@ var COFantasy = COFantasy || function() { if (perso.armeGauche) { let nomCommande = perso.armeGauche.name; if (armeDechargee(perso, perso.armeGauche)) nomCommande += ' (vide)'; + else if (armeChargeeDeGrenaille(perso, perso.armeGauche)) nomCommande += ' (grenaille)'; ligneArmeGauche = bouton("!cof-attack @{selected|token_id} @{target|token_id} " + labelArmeGauche, nomCommande, perso); } //Maintenant on propose de d\xE9gainer @@ -29175,6 +29569,9 @@ var COFantasy = COFantasy || function() { sendChat('', e); }); } + if (options.degainer !== undefined && lanceur) { + degainerArme(lanceur, options.degainer, evt); + } } }; let setOneEffect = function(perso, d) { @@ -29251,6 +29648,9 @@ var COFantasy = COFantasy || function() { setTokenAttr(perso, effet + 'Puissant', puissant, evt); } effetsSpeciaux(lanceur, perso, options); + if (options.degainer !== undefined && !lanceur) { + degainerArme(perso, options.degainer, evt); + } finalize(); }; cibles.forEach(function(perso) { @@ -35558,7 +35958,10 @@ var COFantasy = COFantasy || function() { elixirsACreer = elixirsACreer[0]; let extraFortifiants = toInt(elixirsACreer.get('max'), 0); let extra = extraFortifiants > 0 && elixir.rang == 1; - if (!extra) options.decrAttribute = elixirsACreer.id; + if (!extra) options.decrAttribute = { + id: elixirsACreer.id, + val: 1 + }; if (limiteRessources(forgesort, options, 'elixirsACreer', '\xE9lixirs \xE0 cr\xE9er', evt)) return; if (extra) { evt.attributes = evt.attributes || []; @@ -38355,6 +38758,7 @@ var COFantasy = COFantasy || function() { let token = createObj('graphic', tokSpec); if (token) { evt.tokens = [token]; + toFront(token); } if (stateCOF.options.affichage.val.duree_effets.val) { if (options.brumes) @@ -39290,12 +39694,12 @@ var COFantasy = COFantasy || function() { sendFramedDisplay(display); } - function lancerDefiSamourai(msg) { + function lancerDefi(msg, command, nomAttr, descr, predicatBonus) { let options = parseOptions(msg); if (options === undefined) return; let cmd = options.cmd; if (cmd === undefined || cmd.length < 3) { - error("cof-defi-samourai demande au moins 2 options", + error(command + " demande au moins 2 options", msg.content); return; } @@ -39305,7 +39709,7 @@ var COFantasy = COFantasy || function() { error("Le token s\xE9lectionn\xE9 n'est pas valide", msg.content); return; } - if (attributeAsBool(samourai, 'defiSamourai')) { + if (attributeAsBool(samourai, nomAttr)) { sendPlayer(msg, nomPerso(samourai) + " a d\xE9j\xE0 lanc\xE9 un d\xE9fi durant ce combat."); return; } @@ -39315,7 +39719,7 @@ var COFantasy = COFantasy || function() { return; } const evt = { - type: 'D\xE9fi samoura\xEF' + type: descr }; let explications = []; entrerEnCombat(samourai, [cible], explications, evt); @@ -39326,18 +39730,28 @@ var COFantasy = COFantasy || function() { if (cmd.length > 3) { bonus = parseInt(cmd[3]); if (isNaN(bonus) || bonus < 1) { - error("Bonus de d\xE9fi de samoura\xEF incorrect", cmd[3]); + error("Bonus de " + descr + " incorrect", cmd[3]); bonus = undefined; } } if (bonus === undefined) - bonus = predicateAsInt(samourai, 'voieDeLHonneur', 2); - setTokenAttr(samourai, 'defiSamourai', bonus, evt, { + bonus = predicateAsInt(samourai, predicatBonus, 2); + setTokenAttr(samourai, nomAttr, bonus, evt, { msg: nomPerso(samourai) + " lance un d\xE9fi \xE0 " + nomPerso(cible), maxVal: idName(cible) }); } + function lancerDefiSamourai(msg) { + lancerDefi(msg, 'cof-defi-samourai', 'defiSamourai', 'd\xE9fi de samoura\xEF', + 'voieDeLHonneur'); + } + + function lancerDefiDuelliste(msg) { + lancerDefi(msg, 'cof-defi-duelliste', 'defiDuelliste', 'd\xE9fi de duelliste', + 'voieDuDuelliste'); + } + function parseEnveloppement(msg) { const options = parseOptions(msg); if (options === undefined) return; @@ -40109,7 +40523,7 @@ var COFantasy = COFantasy || function() { error('pas de token s\xE9lectionn\xE9 pour !cof-bonus-couvert'); return; } - var evt = { + const evt = { type: 'Bonus couvert' }; addEvent(evt); @@ -44874,6 +45288,8 @@ var COFantasy = COFantasy || function() { stateCOF.afterDisplay = undefined; stateCOF.statistiquesEnPause = undefined; stateCOF.statistiques = undefined; + stateCOF.personnageCibleCree = undefined; + stateCOF.predicats = {}; log("stateCOf purg\xE9"); log(stateCOF); sendPlayer(msg, "\xC9tat global de COFantasy purg\xE9."); @@ -45090,6 +45506,79 @@ var COFantasy = COFantasy || function() { }); } + //!cof-recupere-mana montant + function recupereMana(msg) { + let options = parseOptions(msg); + if (options === undefined) return; + let cmd = options.cmd; + if (cmd === undefined) { + error("Probl\xE8me de parse options", msg.content); + return; + } + if (cmd.length < 2) { + sendPlayer(msg, "Il manque le montant de mana \xE0 r\xE9cup\xE9rer pour !cof-recupere-mana"); + return; + } + let mana = parseDice(cmd[1], "mana"); + if (mana === undefined) return; + getSelected(msg, function(selected, playerId) { + if (selected.length === 0) { + sendPlayer(msg, "Personne ne r\xE9cup\xE8re de mana", playerId); + return; + } + const evt = { + type: 'r\xE9cup\xE9ration de mana' + }; + addEvent(evt); + iterSelected(selected, function(perso) { + let manaAttr = findObjs({ + _type: 'attribute', + _characterid: perso.charId, + name: 'PM' + }, { + caseInsensitive: true + }); + let manaMax; + if (manaAttr.length > 0) { + manaMax = parseInt(manaAttr[0].get('max')); + } + if (!manaMax || isNaN(manaMax) || manaMax < 0) { + sendPerso(perso, "n'a pas de mana."); + return; + } + let token = perso.token; + let bar2 = parseInt(token.get('bar2_value')); + if (isNaN(bar2)) { + if (token.get('bar1_link') === '') bar2 = 0; + else { //devrait \xEAtre li\xE9 \xE0 la mana courante + sendPerso(perso, "*** Attention, la barre de mana du token n'est pas li\xE9e \xE0 la mana de la fiche ***"); + bar2 = parseInt(manaAttr[0].get('current')); + } + } + if (bar2 >= manaMax) { + sendPerso(perso, "est d\xE9j\xE0 au maximum de mana"); + return; + } + if (limiteRessources(perso, options, 'recupereMana', "r\xE9cup\xE9rer de la mana", evt)) return; + let r = rollDePlus(mana); + let recupere = r.val; + bar2 += recupere; + if (bar2 >= manaMax) { + recupere -= bar2 - manaMax; + bar2 = manaMax; + } + let msg = "r\xE9cup\xE8re "; + if (r.val == recupere) msg += r.roll; + else msg += recupere; + msg += " PM"; + if (recupere > 1) msg += 's'; + if (r.val > recupere) msg += " (le r\xE9sultat du jet \xE9tait " + r.roll + ")"; + sendPerso(perso, msg, options.secret); + updateCurrentBar(perso, 2, bar2, evt); + }); + }, options); + } + function apiCommand(msg) { msg.content = msg.content.replace(/\s+/g, ' '); //remove duplicate whites const command = msg.content.split(' ', 1); @@ -45150,6 +45639,9 @@ var COFantasy = COFantasy || function() { case '!cof-dmg': parseDmgDirects(msg); return; + case '!cof-echange-init': + echangeInit(msg); + return; case '!cof-effet-chaque-d20': setEffetChaqueD20(msg); return; @@ -45235,6 +45727,9 @@ var COFantasy = COFantasy || function() { case '!cof-recuperation': parseRecuperer(msg); return; + case '!cof-recupere-mana': + recupereMana(msg); + return; case '!cof-remove-buf-def': removeBufDef(msg); return; @@ -45294,9 +45789,6 @@ var COFantasy = COFantasy || function() { case '!cof-zone-de-vie': lancerZoneDeVie(msg); return; - case "!cof-echange-init": - echangeInit(msg); - return; case "!cof-a-couvert": aCouvert(msg); return; @@ -45575,6 +46067,9 @@ var COFantasy = COFantasy || function() { case '!cof-torche': switchTorche(msg); return; + case '!cof-defi-duelliste': + lancerDefiDuelliste(msg); + return; case '!cof-defi-samourai': lancerDefiSamourai(msg); return; @@ -46895,6 +47390,11 @@ var COFantasy = COFantasy || function() { actif: "est en combat", fin: '' }, + defiDuelliste: { + activation: "lance un d\xE9fi", + actif: "a lanc\xE9 un d\xE9fi", + fin: '' + }, defiSamourai: { activation: "lance un d\xE9fi", actif: "a lanc\xE9 un d\xE9fi", @@ -49176,6 +49676,7 @@ var COFantasy = COFantasy || function() { // assure un nom unique en ajoutant un num\xE9ro // On en profite aussi pour mettre certaines valeurs par d\xE9faut // retourne un perso si c'est un token de personnage + //Si la barre de vie est li\xE9e, on met \xE0 jour les valeurs, ce n'est plus fait automatiquement oar Roll20 function renameToken(token, tokenName) { let charId = token.get('represents'); if (charId === undefined || charId === '') return; @@ -49232,9 +49733,23 @@ var COFantasy = COFantasy || function() { attrMonteSur[0].remove(); } } - synchronisationDesEtats(perso); - synchronisationDesLumieres(perso, pageId); } + synchronisationDesEtats(perso); + for (let barNumber = 1; barNumber <= 3; barNumber++) { + let attrId = token.get('bar' + barNumber + '_link'); + if (attrId) { + let attr = getObj('attribute', attrId); + if (attr) { + let fieldv = 'bar' + barNumber + '_value'; + token.set(fieldv, attr.get('current')); + let fieldm = 'bar' + barNumber + '_max'; + token.set(fieldm, attr.get('max')); + } + } + } + //On synchronise les barres, au cas o\xF9 la fiche n'a pas \xE9t\xE9 ouverte + //d\xE9j\xE0 fait par l'appel de renameToken + //synchronisationDesLumieres(perso, pageId); return perso; } //cas des mooks : num\xE9rotation @@ -51381,6 +51896,7 @@ var COFantasy = COFantasy || function() { function changePredicats(attr, prev) { let curPred = attr.get('current'); let prevPred = prev.current; + if (!prevPred) return; if (curPred.includes('attaqueEnMeute') != prevPred.includes('attaqueEnMeute')) { recomputeAllies(); } @@ -51478,8 +51994,8 @@ on("change:attribute", function(attr, prev) { if (!COF_loaded) return; let predicats = state.COFantasy.predicats; if (!predicats) return; - let n = attr.get("name"); - if (n == 'predicats_script' || n.includes('armepredicats') || n.includes('effetarmure') || n == 'maindroite' || n == 'maingauche' || n == 'torseequipe' || n == 'teteequipe') { + let n = attr.get('name'); + if (n == 'predicats_script' || n.endsWith('_armepredicats') || n.endsWith('_effetarmure') || n.endsWith('_equipearmure') || n == 'maindroite' || n == 'maingauche') { predicats[attr.get('characterid')] = undefined; COFantasy.changePredicats(attr, prev); } diff --git a/COFantasy/ChangeLog.md b/COFantasy/ChangeLog.md index bd717217d..b0f0e56f2 100644 --- a/COFantasy/ChangeLog.md +++ b/COFantasy/ChangeLog.md @@ -1,15 +1,25 @@ # COFantasy: Historique des changements (depuis la version 1.0) ## 3.15 ### Capacités +* Défi de la Voie du Duelliste * Meilleure prise en charge de la spécialisation du guerrier. * Implémentation de la version avancée du drain de force de Dominia. ### Autres améliorations +* Ajout d'une option --depensePR pour les actions autres que les attaques +* Ajout d'une commande !cof-recupere-mana. +* Règle optionnelle pour faire récupérer des points de mana quand on dépense un point de récupération. +* Implémentation de prédicats pour les bonus en RD et aux caractéristiques. +* Prise en compte des émissaires du dragon pour les ancres. +* Amélioration de la gestion des utilisations de points de chance pour des attaques complexes. +* Réinitialisation des options d'attaque à la fin d'un combat +* Synchronisation des barres de tokens quand on le pose : Roll20 ne le fait plus correctement. * Extension des mécanismes d'escaliers pour Invincible. * Ajout d'un bonus magique pour absorber un coup ou un sort quand le bouclier est magique. * Prise en charge de plusieurs casque à mettre depuis le statut ### Correction de bugs +* Correction d'un bug pour les conditions d'attaque sur les attributs. * Correction pour les armes en main des mooks * Ne pas demander qu'un PNJ ait un prédicat pour se battre à 2 armes sans malus. diff --git a/COFantasy/doc.html b/COFantasy/doc.html index ac5980e33..bdae6b0a4 100644 --- a/COFantasy/doc.html +++ b/COFantasy/doc.html @@ -127,6 +127,7 @@

        Sommaire

      12. Purger les variables d'état du jeu
      13. Conversion depuis Pathfinder
      14. +
      15. Index des prédicats
      @@ -136,7 +137,7 @@

      1. Comment utiliser Le scr

      1.1 Tokens et personnages

      -

      Ces scripts ont été écrits pour fonctionner avec les fiches de personnages développées par Natha.

      +

      Ces scripts ont été écrits pour fonctionner avec les fiches de personnages développées par Natha (documentation de la fiche).

      A choisir pendant la création de la partie sur Roll20.
      @@ -170,6 +171,7 @@

      1.3 Prédicats

      Il est aussi possible d'associer des prédicats à des attaques. Ces prédicats sont pris en compte quand ses attaques sont des armes, et ce que ces armes sont tenues en main. Cela permet de coder une grande variété d'effets magiques des armes. Attention, cela ne fonctionne pas pour les mooks avec les prédicats animeAPartirDExistant, intercepter, lienEpique, attaqueEnTraitre, sansPeur, immuniteSaignement, et controleSanguin.

      Une épée +1 affûtée avec le prédicat enchainement (actif pour toute attaque, tant que cette épée est en main).
      +

      Les prédicats à valeur numériques ne se cumulent pas : le script prend la plus grande valeur, si le prédicat apparaît plusieurs fois. Pour les autres prédicats, en général, l'ensemble des prédicats est pris comme un seul prédicat, si cela est possible. Sinon, un seul prédicat est utilisé, les autres sont ignorés.

      1.4 Méthodes de sélection de groupes

      @@ -198,6 +200,12 @@

      Spécifier un coût en mana

    1. Même un sort qui ne coûte pas de mana devrait utiliser --mana 0. Cela permet au script de dissiper automatiquement un effet généré par un lanceur de sort mis hors de combat.
    2. Une option d'affichage disponible via !cof-options permet de demander au script d'afficher explicitement dans le chat toute dépense de PM effectuée par un personnage.
    3. +

      Récupérer de la mana

      +
        +
      • Faire passer une nuit de repos remet les points de mana au maximum.
      • +
      • Une option règle de jeu (accessible avec !cof-options) permet de récupérer de la mana quand on dépense 1 PR pour se reposer (il faut spécifier un dé de mana sur la fiche).
      • +
      • La commande !cof-recupere-mana m permet de récupérer m points de mana. m peut être un nombre positif ou une expression de dé simple.
      • +

      Système de base (COF p. 79)

      Le support des effets supplémentaires à disposition du lanceur de sort sont gérées via l'utilisation d'options sur les commandes utilisées.

        @@ -383,7 +391,7 @@

        Options pour l'attaque :

      • --if condition options --endif: permet de ne prendre en compte des options que si certaines conditions sont réalisées. Il est possible d'utiliser un --else pour activer des options quand les conditions ne sont pas réalisées. Attention, les options qui modifient une option précédente (comme --valeur) doivent apparaître au même niveau que l'option qu'elles modifient. Comme les conditions peuvent dépendre du dé d'attaque, un certain nombre d'options ne sont pas affectées par cette conditionnelle : tempeteDeMana, les options d'aoe (ligne, cone, disque, target), avecd12, avantage, auto, demiAuto, test, pointsVitaux, tempsRecharge, sortilege, bonusCritique, disparition, affute, munition, tirDouble, semonce, tirDeBarrage, poudre, traquenard, feinte, magique, tranchant, percant, contondant, pasDeDmg. Ne pas hésiter à me demander si vous aviez besoin que l'une de ces options puissent être conditionnelle.
      • --ifSaveFails carac seuil options --endif : comme --if, mais fait réaliser une jet de sauvegarde à la cible (comme avec --save), et si le test est raté, applique les options qui suivent. Un --else permet de choisir des options à appliquer quand le test est réussi. À utiliser seulement si on ne peut pas se contenter d'un --save, car ça marche moins bien.
      • -
      • --decrAttribute nom : l'attaque n'est possible que si l'attribut existe et si sa valeur est strictement positive. L'attaque diminue cette valeur de 1. Si cette option est utilisée à l'intérieur d'un --if, alors l'option se contente de diminuer l'attribut (on peut savoir si la valeur est positive grâce à --if etat nom).
      • +
      • --decrAttribute nom : l'attaque n'est possible que si l'attribut existe et si sa valeur est strictement positive. L'attaque diminue cette valeur de 1. On peut preciser une valeur plus grande après le nom de l'attribut (--decrAttribute nom d teste si la valeur est supérieure à d et la diminue de d). Si cette option est utilisée à l'intérieur d'un --if, alors l'option se contente de diminuer l'attribut (on peut savoir si la valeur est positive grâce à --if etat nom).
      • --decrLimitePredicatParTour nom : l'attaque n'est possible que si un prédicat nom existe et si elle n'a pas été utilisée plus de fois dans le tour que la valeur de ce prédicat. L'attaque augmente ce nombre de 1.
      • --tempsRecharge effet duree : l'attaque n'est possible que si l'effet est inactif sur l'attaquant, et de plus active l'effet sur l'attaquant pour la durée indiquée si l'attaque est possible. Il existe un effet temporaire générique, rechargeGen(desc) que vous pouvez utiliser si aucun effet existant ne correspond pour votre attaque.
      • --etat e: si l'attaque touche, la cible passe dans l'état e. Il est aussi possible de spécifier une caractéristique et un seuil (comme pour !cof-set-state) pour faire afficher à chaque tour une action permettant de se libérer de l'état.
      • @@ -545,7 +553,7 @@

        Dégainer une arme : !cof-degaine
      • Enfin, en fin de combat, si le personnage possède un prédicat armeParDefaut, le script va lui faire dégainer l'arme dont le label est la valeur du prédicat. Donc si le prédicat n'a pas de label associé, le personnage va rengainer son arme.

      Dans tous les cas, si le personnage dégaine une arme à 2 mains, le script fait enlever le bouclier, et il le fait remettre si on passe d'une arme à 2 mains à une arme à 1 main.

      -

      Il existe un prédicat DEF qui permet d'ajouter sa valeur à la défense d'un personnage. C'est particulièrement utile pour coder une arme de parade. Par exemple avecDEF:2 dans les prédicats de l'arme, elle augmentera la défense de 2 quand elle sera portée.

      +

      Il existe un prédicat bonus_DEF qui permet d'ajouter sa valeur à la défense d'un personnage. C'est particulièrement utile pour coder une arme de parade. Par exemple avecbonus_DEF:2 dans les prédicats de l'arme, elle augmentera la défense de 2 quand elle sera portée.

      Couvert :!cof-bonus-couvert b

      @@ -797,6 +805,7 @@

      Autres bufs et debufs

    4. --limiteParCombat : même effet que la limite par jour. Si aucun argument n'est donné, la limite est de une fois par combat. Attention, il faut un lanceur auquel appliquer la limite par combat !
    5. --tempsRecharge effet duree : l'action n'est possible que si l'effet est inactif sur le personnage qui génère l'effet, et de plus active l'effet sur celui-ci pour la durée indiquée. Il existe un effet temporaire générique, rechargeGen(desc) que vous pouvez utiliser si aucun effet existant ne correspond.
    6. --dose nom: définie une ressource nommée dose_nom, dont la valeur doit être positive. Utile par exemple pour les parchemins, les potions ou les baguettes.
    7. +
    8. --depensePR: demande de dépenser un point de récupération. Si un premier argument est ajouté, on enlève ce nombre aux PR au lieu de 1. Si un second argument est précisé, on enlève ce second argument aux PV quand le personnage n'a plus de PR.
    9. --portee id d : impose aux cibles de l'effet d'être à moins de d mètres du token id. Si --lanceur est précisé, inutile de donner id.
    10. --puissant : lance la version puissante de l'effet
    11. --puissant oui : idem
    12. @@ -1044,7 +1053,7 @@

      Barbare

      Voie de la brute

      1. Argument de taille : ajouter le bonus de PV sur la fiche. Ajouter un prédicat argumentDeTaille. Le script devrait automatiquement tenir compte de cette capacité pour les tests de négociation, persuasion et intimidation. Pour s'assurer que la bonne compétence est utilisée, vous pouvez ajouter ces noms au handout de compétence (voir la partie sur les jet de caractéristiques, le handout Compétences) ou sur les fiches de personnages.
      2. -
      3. Tour de force : ajouter un prédicat tourDeForce. Le script ajoutera systématique un bouton lors de tous les tests de FOR du personnage. Au MJ de décider s'il peut être utilisé ou non.
      4. +
      5. Tour de force : ajouter un prédicat tourDeForce.
      6. Attaque brutale : faire l'attaque avec les options --bonusAttaque -2 --plus 1d6
      7. Briseur d'os : ajouter un prédicat briseurDOs. Cela va automatiquement affecter les chances de coups critique au contact.
      8. Force héroïque : reporter sur la fiche.
      9. @@ -1282,7 +1291,7 @@

        Voie de la divination

      10. 6eme sens : ajouter le bonus en DEF divers et initiative diver, ajouter une compétence vigilance, de valeur 2*rang (le bonus aux jet de surprise).
      11. Détection de l'invisible : !cof-effet-temp detectionDeLInvisible [[5+@{selected|CHA}]] --mana 0.
      12. Clairvoyance : !cof-lancer-sort peut voir et entendre à distance --mana 1
      13. -
      14. Prescience : ajouter un prédicat prescience (on peut y associer un nombre pour avoir plus d'une prescience par tour). Si le personnage était sur la carte au moment de lancer le combat, il se verra proposer un bouton pour utiliser sa prescience à la fin de chaque tour. Attention, en l'état, il est impossible d'annuler l'utilisation de ce bouton. De plus, ce bouton ne fera rien si le script a été interrompu depuis le début du tour (interruption de la partie, redémarrage du script, etc...).
      15. +
      16. Prescience : ajouter un prédicat prescience (on peut y associer un nombre pour avoir plus d'une prescience par combat). Si le personnage était sur la carte au moment de lancer le combat, il se verra proposer un bouton pour utiliser sa prescience à la fin de chaque tour. Attention, en l'état, il est impossible d'annuler l'utilisation de ce bouton. De plus, ce bouton ne fera rien si le script a été interrompu depuis le début du tour (interruption de la partie, redémarrage du script, etc...).
      17. Hyperconscience : reporter sur la fiche.

      Voie de l'envoûteur

      @@ -1888,7 +1897,7 @@

      Voie de l'arc et du cheval

      1. Monture loyale : ajouter un prédicat montureLoyale. Pour la monture, il faut faire un personnage avec un prédicat monture. Pour monter sur la monture, utiliser !cof-en-selle @{selected|token_id} nom_de_la_monture, et pour en descendre, il suffit de faire la même commande (utiliser une ability "token action" rend cela assez facile à l'usage).
      2. Tir en mouvement : pas de support particulier.
      3. -
      4. Tir fatal : ajouter un prédicat tirFatal. Pour tenir compte du bonus supplémentaire avec le rang dans la voie, ajouter un prédicat voieDeLArcEtDuCheval de valeur le rang atteint dans cette voie. Si vous souhaitez que cette capacité s'applique à une autre classe d'arme que les arcs, vous pouvez associer ce nom au prédicat tirfatal (attention, sans accent). Par exemple, pour les arbalètes, tirFatal:arbalete.
      5. +
      6. Tir fatal : ajouter un prédicat tirFatal. Pour tenir compte du bonus supplémentaire avec le rang dans la voie, ajouter un prédicat voieDeLArcEtDuCheval de valeur le rang atteint dans cette voie. Si vous souhaitez que cette capacité s'applique à une autre classe d'arme que les arcs, vous pouvez associer ce nom au prédicat tirFatal (attention, sans accent). Par exemple, pour les arbalètes, tirFatal:arbalete.

      Voie du dirigeant

        @@ -2114,7 +2123,7 @@

        Voie de l'archange

        1. Forme d'ange : !cof-effet-temp formeDAnge [[5+@{selected|SAG}]] --mana 0. Le MJ devra gérer l'aspect en vol.
        2. Soins améliorés : utiliser un prédicat voieDeLArchange avec comme valeur le rang dans la voie.
        3. -
        4. Épée céleste : rien à faire si le prédicat voieDeLArchange a la bonne valeur. Si le personnage n'a pas déjà la capacité Arme d'argent, ajouter une attaque pour cette arme, avec le modificateur armeDArgent et l'option --si etat armeDArgent.
        5. +
        6. Épée céleste : rien à faire si le prédicat voieDeLArchange a la bonne valeur. Si le personnage n'a pas déjà la capacité Arme d'argent, ajouter une attaque pour cette arme, avec le modificateur armeDArgent et l'option --si etat armeDArgent. Si vous souhaitez que cette arme soit dégainée par défaut, vous pouvez ajouter --degainer L à la commande de la forme d'ange (L est le label de l'attaque).

        Voie de l'archer arcanique

          @@ -2209,6 +2218,11 @@

          Voie du danseur de guerre

        1. Attaque en mouvement : pas de support du script.
        2. Danse des lames : !cof-effet-combat danseDesLames. Pour l'attaque gratuite supplémentaire, on peut faire #Attaque -1 --si etat danseDesLames --avecd12crit.
        +

        Voie du duelliste

        +
          +
        1. Vives lames : reporter la caractéristique choisie sur la fiche.
        2. +
        3. Défi : !cof-defi-duelliste @{selected|token_id} @{target|token_id} 2.
        4. +

        Voie du familier fantastique

        1. Familier fantastique : créer un personnage (de type PNJ) pour le familier, controlé par le joueur et avec un token lié ayant la capacité de voir. @@ -2477,7 +2491,7 @@

          4.5 Capacités diverses

        2. Noyade (plante carnivore) : il existe un effet de combat nommé noyade (pas de durée, il dure par défaut tout le combat) qui fait perdre 1d6 PV par tour, sauf à réussir un test de CON difficulté 15, et qui impose un malus de -3 aux attaques et aux DMs.
        3. Ondes corruptrices (Anathazerïn) : Pour simplement le premier effet, faire !cof-effet ondesCorruptrices 2, puis pour augmeter l'effet !cof-effet ondesCorruptrices +2. L'effet peut être annulé avec !cof-effet ondesCorruptrices 0. Pour le porteur du bouclier de Grabuge, on peut mettre un prédicat porteurDuBouclierDeGrabuge.
        4. Paralysie des goules : ajouter à l'attaque de morsure --effet paralyseGoule [[1d6]] --save CON 10.
        5. -
        6. Perte de substance (Invincible) : Ajouter pour tous les personnages concernés un prédicat perteDeSubstance. Le changement de jour va augmenter un compteur, à condition que les tokens des joueurs soient sur la page, et soit on ne sélectionne aucun token quand on lance la commande de changement de jour, soit on sélectionne au moins tous les tokens des joueurs. Le test de CHA n'est pas automatisé, mais le script en rappelle la difficulté. Pour les ancres, ajouter un prédicat ancreInvincible (par exemple dans les prédicats de l'arme). Pour la réduction des dégâts, ajouter au dragon un prédicat dragonInvincible. Pour les DM indirects, il faudra gérer à la main la réduction de DM.
        7. +
        8. Perte de substance (Invincible) : Ajouter pour tous les personnages concernés un prédicat perteDeSubstance. Le changement de jour va augmenter un compteur, à condition que les tokens des joueurs soient sur la page, et soit on ne sélectionne aucun token quand on lance la commande de changement de jour, soit on sélectionne au moins tous les tokens des joueurs. Le test de CHA n'est pas automatisé, mais le script en rappelle la difficulté. Pour les ancres, ajouter un prédicat ancreInvincible (par exemple dans les prédicats de l'arme). Pour la réduction des dégâts, ajouter au dragon un prédicat dragonInvincible. Pour la réduction des dégâts contre les émissaires du dragon, ajouter à ces derniers un prédicat emissaireDuDragonInvincible. Pour les DM indirects, il faudra gérer à la main la réduction de DM.
        9. Phylactère de canalisation : ajouter un prédicat phylacterePositif pour un phylactère de canalisation positive, ou phylactereNegatif pour un phylactère de canalisation négative. Associez à ce prédicat la valeur ajoutée pour chaque PM dépensé (nécessite l'utilisation du prédicat deCanalisation). Une valeur de 1d4 semble adaptée pour le phylactère de canalisation de Pathfinder 1.
        10. Piqûres d'insectes : Rajouter un prédicat piquresDInsectes avec comme valeur la résistance aux dégâts à distance appropriée
        11. @@ -3056,13 +3070,240 @@

          8. Utiliser des personnages Pathfinder<

          Il existe un support basique des fiches Roll20 venant du système Pathfinder 1. La fonction !cof-pathfinder1 se lance en sélectionnant un ou plusieurs tokens liés à des fiches Pathfinder (par exemple obtenues en achetant des aventures Pathfinder sur le marketplace de Roll20). L'effet est d'une part de transformer une partie des attributs de la fiche en attributs compris par la fiche COF et le script COFantasy, de changer un peu le token associé à la fiche, mais surtout d'effacer les attributs inutiles pour COF. En effet, le script est relativement sensible au nombre total d'attributs en jeu, et les fiches Pathfinder ont tendence à en avoir vraiment beaucoup. Tout ce qui n'est pas compris par le script se retrouve dans un attribut Attributs Pathfinder. Mais le mieux est de compléter la fiche COF en se basant sur la fiche Pathfinder ouverte dans une autre partie.

          -

          9. Statistiques

          + +

          9. Index des prédicats

          +
          +
            +
          • actionDegainern : labels d'attaques, séparés par des -, propose de dégainer ce ou ces labels en premier, dans l'ordre du numéro n (partant de 1).
          • +
          • adaptable : entier, bonus aux tests de caractéristique en combat, si le script a reconnu un test raté de même caractéristique au tour précédent.
          • +
          • agripper : booléen, le personnage peut agripper une cible si le jet d'attaque est 15 ou plus.
          • +
          • ambidextrie : booléen, permet d'attaquer avec l'arme en main gauche sans malus.
          • +
          • ameFeline : entier, bonus en acrobatie, course, escalade, saut et initiative.
          • +
          • animal : booléen, le personnage est un animal.
          • +
          • anneauProtection : booléen, annule les effets critiques des attaques.
          • +
          • arcDeMaitre : booléen, augmente la portée des arcs de 20 m.
          • +
          • argumentDeTaille : booléen, augmente les tests de CHA des alliés de la valeur du mod. de FOR du personnage.
          • +
          • armeDeGrand : booléen, seulement pour les armes à deux mains, ndique qu'une arme peut être portée à une main quand agrandi.
          • +
          • armeDePredilection : type d'arme, donne un bonus en attaque de +1 pour ce type d'arme.
          • +
          • armeParDefaut : label, arme tenue en main par défaut, toujours dégaînée à la fin d'un combat.
          • +
          • armureDeVent : entier, augmente la DEF de cette valeur si le personnage ne porte pas d'armure.
          • +
          • armureLourdeGuerrier : booléen, annule les effets critiques des attaques si le personnage porte une armure de DEF au moins 7.
          • +
          • armureProtection : booléen, annule les effets critiques des attaques si le personnage porte une armure.
          • +
          • attaqueAuBouclier : label, propose d'utiliser l'attaque de ce label comme attaque au bouclier.
          • +
          • attaqueEnMeute : booléen ou entier (2 par défaut), bonus à l'attaque quand le personnage attaque la même cible qu'un allié.
          • +
          • attaqueEnTraitre : booléen, propose une attaque en traître quand un allié touche un adversaire au contact.
          • +
          • attaqueSournoise : entier, nombre de d6 à ajouter pour les DM d'attaques sournoises.
          • +
          • attributsDeStatut : nom, spécifie un attribut qui doit être afficher avec le statt d'un personnage. On peut spécifier plusieurs attributs en ayant plusieur prédicats attributsDeStatut.
          • +
          • asDeLaGachette : booléen, augmente les DM des arbalètes et armes à poudre de 1d6 si le jet d'attaque atteint 25.
          • +
          • aucuneActionCombat : booléen, le personnage ne rentre pas en combat.
          • +
          • batarde : expression de dés, seulement pour les prédicats des armes à 1 mains : dés de DM quand l'arme est portée à 2 mains.
          • +
          • blessureSanglante : booléen ou entier (1 par défaut), les blessures infligées par le personnage saignent.La valeur est la durée du saignement
          • +
          • bonusAttaqueBouclier : entier, bonus pour le jet d'absorber un coup.
          • +
          • bonus_CHA : entier, ajoute la valeur au mod de CHA.
          • +
          • bonus_CON : entier, ajoute la valeur au mod de CON.
          • +
          • bonus_DEF : entier, valeur ajoutée à la DEF.
          • +
          • bonus_DEF(anneau) : entier, valeur ajoutée à la DEF.
          • +
          • bonus_DEX : entier, ajoute la valeur au mod de DEX.
          • +
          • bonus_FOR : entier, ajoute la valeur au mod de FOR.
          • +
          • bonus_INT : entier, ajoute la valeur au mod d'INT.
          • +
          • bonus_RD : possibilité d'avoir la même chose que dans la case RD. Cela s'ajoute et se cumule à la RD. Attention d'utiliser la syntaxe avec :: si ce bonus contient un : ou une ,.
          • +
          • bonus_SAG : entier, ajoute la valeur au mod de SAG.
          • +
          • bonusFeinte : entier (5 par défaut), valeur du bonus à l'attaque en cas de feinte réussie.
          • +
          • bonusSaveContre_type : entier, bonus aux saves de contre les effets ou DM de ce type.
          • +
          • bonusTests_nom : entier, ajoute la valeur aux tests de compétence caractéristique nom ou de compétence nom.
          • +
          • bonusTousTests : entier, ajoute la valeur aux tests de caractéristique.
          • +
          • botteMortelle : booléen, ajoute 2d6 de DM si le jet d'attaque dépasse de 10 la DEF de la cible.
          • +
          • bouclierDeLaFoi : entier, bonus à la DEF quand le personnage porte un bouclier.
          • +
          • bouclierProtection : booléen, annule les effets critiques des attaques si le personnage porte un bouclier.
          • +
          • bouclierPsi : booléen, augmente de 5 la DEF contre les attaques mentales et divise par 2 les DM des attaques mentales.
          • +
          • briseurDOs : booléen, augmente les chances de critique des attaques au contact, et applique un effet d'os brisés a la cible en cas de critique.
          • +
          • bucheron : booléen, augmente les DM des armes à 2 mains de 1d6 contre les créatures grandes, et 2d6 contre les créatures énormes.
          • +
          • cavalierEmerite : entier, s'ajoute à l'attaque au contact quand le personnage est sur une monture.
          • +
          • charge : entier (défaut 1), indique que l'arme doit être rechargée (ainsi que le nombre de charge, si on associe un nombre).
          • +
          • chasseurDeSorciere : booléen, ajoute 2 aux DM et à la DEF contre les nécromanciens. +
          • chasseurEmerite : booléen, rajoute +2 à l'attaque et aux DM contre les animaux.
          • +
          • chimiste : booléen, indique que le personnage n'a pas besoin de lancer de dé de poudre pour les attaques avec une arme à poudre.
          • +
          • coefPVMana : entier (défaut 1), multiplie la valeur de conversion entre mana et PV pour la brûlure de mana
          • +
          • combatADeuxArmes : booléen, propose de dégaîner une arme en main gauche.
          • +
          • combatADeuxArmesAmeliore : booléen, permet d'attaquer avec 2 armes sans malus.
          • +
          • combatEnPhalange : booléen, ajoute un bonus en attaque et en DEF en fonction du nombre d'alliés au contact.
          • +
          • combatKinetique : booléen, ajoute 3 à la RD tant que le personnage est conscient de l'attaque.
          • +
          • combattreLaCorruption : booléen ou entier (1 par défaut), bonus à l'attaque et aux DM contre les cibles corrompues.
          • +
          • connaissanceDuPoison : booléen, permet d'enduire une arme de poison sans avoir à faire de jet.
          • +
          • controleLoupGarou : entier, bonus au jet de lycanthrope pour éviter la transformation.
          • +
          • controleSanguin : booléen, immunise au poison et aux effets de saignement, et divise par 2 les DM de vampirisation.
          • +
          • controleDuMetabolisme : booléen, ajoute le mod. de CHA aux tests de CON et à l'initiative.
          • +
          • corrompu : booléen, le personnage est corronpu (utilise pour la voie du chasseur de corruption).
          • +
          • courage : entier, bonus ajouté pour résister aux effets de peur.
          • +
          • creatureArtificielle : booléen, immunise au poison, à l'asphyxie et aux maladies.
          • +
          • crocEnJambe : booléen, fait tomber les adversaires sur un jet de dé d'attaque de 17-20. Passe à 19-20 contre des adversaires quadrupèdes.
          • +
          • DEF_magie : entier, augmente la défense contre les sortilèges.
          • +
          • defDeriveeDe : nom de personnage, la valeur de défense est égale à celle du personnage du prédicat.
          • +
          • defenseIntuitive : booléen, ajoute la SAG à la DEF.
          • +
          • defierLaMort : booléen, fait lancer un dé pour garder 1 PV quand le personnage devrait mourir.
          • +
          • dentellesEtRapiere : entier, s'ajoute à la DEF contre les attaques aux contact, mais seulement quand le personnage ne porte pas d'armure.
          • +
          • devierLesCoups : booléen ou entier (1 par défaut), nombre maximum de fois par tour où on propose de dévier un coup.
          • +
          • difficulteOmbreMouvante : entier (10 par défaut), difficulté du jet de DEX pour passer dans les ombres. Cela peut permettre de tenir compte de l'environnement.
          • +
          • diviseEffet_type : booléen, divise par 2 les effets associés à des DM du type.
          • +
          • drainDeSang : booléen, le personnage est soigné de la moitié des DM des effets de saignement qu'il provoque.
          • +
          • durACuire : booléen, laisee un tour d'actions après être arrivé à 0 PV avant de mourir.
          • +
          • eclaire : entier, le personage fait de la lumière, et la valeur du prédicat est le rayon d'illumination.
          • +
          • eclaireFaible : entier, si le personage fait de la lumière, donne le rayon à partir duquel la lumière devient faible.
          • +
          • ecuyer : booléen, augmente de 1 les chances de critique.
          • +
          • ecuyerDe : nom de personnage, soigne chaque nuit les alliés du chevalier.
          • +
          • enchainement : booléen, propose une attaque gratuite sur une cible au contact quand le personnage tue une autre cible.
          • +
          • energieImpie : booléen, permet de bénéficier d'energie impie (bonus aux DM et aux soins) quand le personnage draine des PVs.
          • +
          • ennemiJure : race ou type de créature, donne un bonus de 1d6 DM et ajute le mod de SAG à l'attaque contre les créatures de ce type. Il est possible d'utiliser un nom inventé, qu'il faut alors rajouter en prédicats sur les créatures concernées.
          • +
          • entrerEnCombatAvec : nom de token, fait rentrer en combat à chaque fois que ce token entre en combat.
          • +
          • espritVide : booléen ou entier (3 par défaut), augmente l'initiative.
          • +
          • esquiveAcrobatique : booléen, propose des esquives acrobatiques.
          • +
          • esquiveDeLaMagie : booléen, propose des jets pour esquiver la magie, et réduit les DM même si le jet est raté.
          • +
          • esquiveFatale : booléen ou entier (1 par défaut), propose des esquives fatales, et limite le nombre d'esquives fatales par combat.
          • +
          • esquiveVoleur : entier, bonus à la DEF.
          • +
          • exemplaire : booléen, propose aux alliés de relancer des attaques.
          • +
          • expertDuCombat : entier, contrôle les capacités d'expert du combat.
          • +
          • expertiseSpecialisee : caractéristique, compétence ou mot clé, ajoute 5 aux jets pour une caractéristique, 10 aux jet pour une compétence, et 2 pour la furie du berserk,
          • +
          • exsangue : booléen, permet de continuer à agir à 0 PV, jusqu'à la prochaine blessure.
          • +
          • familier : nom de personnage, nom du personnage représentant le familier qui donne un bonus en initiative.
          • +
          • formeHybrideSuperieure : booléan, augmente les effets de formeHybride.
          • +
          • frappeChirurgicale : booléen, ajoute le mod. d'INT aux chances de coups critiques avec l'arme en main droite, au contact.
          • +
          • frappeDuVide : booléen, rajoute +2 à l'attaque et 1d6 aux DM de la première attaque si l'arme était au fourreau. De plus, fait automatiquement rengainer l'arme à la fin du combat.
          • +
          • fureurDrakonide : booléen, augmente l'attaque et les DM de 1 quand le personnage est sous la moitié de ses PV max ou qu'il a reçu un coup critique.
          • +
          • graceFeline : booléen, ajoute le mod. de CHA à l'initiative et à la défense.
          • +
          • graceFelineVoleur : booléen, ajoute le charisme aux jets de déplacement (course, escalade, saut).
          • +
          • grosMonstreGrosseArme : booléen, augmente la catégorie de dé des armes à deux main au contact contre les grosses créatures.
          • +
          • grosseTete : booléen, remplace le mod. de FOR par le mod. d'INT pour les tests.
          • +
          • guetteur : nom de personnage, nom du personnage représentant le compagnon qui donne un bonus en perception.
          • +
          • hachesEtMarteaux : booléen, ajoute un bonus de 1 en attaque est DM pour les haches et les marteaux.
          • +
          • humanoide : booléen, le personnage est humanoïde.
          • +
          • ignorerLaDouleur : booléen, permet de reporter les DM d'une attaque à la fin du combat.
          • +
          • immunite_x : booléen, empêche de mettre le personnage dans l'état ou effet temporaire nommé x. On peut aussi immuniser aux effets de pétrification avec x=petrification, ou contre la desctruction des mort-vivants avec x=destruction.
          • +
          • immuniteAbsorptionVampire : booléen, immunise aux attaque de drain venant des vampires (ayant un prédicat vampire.
          • +
          • immuniteAuxSournoises : booléen, annule les DM d'effets de type sournoise.
          • +
          • immuniteSaignement : booléen, immunise aux effets de saignement.
          • +
          • increvable : booléen, permet de se relever une fois par combat après avoir été mis à 0 PV.
          • +
          • increvableHumain : booléen, propose un jet pour éviter une attaque qui mettrait à 0 PV.
          • +
          • initiativeDeriveeDe : nom de personnage, l'initiative est celle de ce personnage.
          • +
          • insecte : booléen, le personnage est un insecte.
          • +
          • insignifiant : booléen, bonus de +2 en DEF contre les créature grandes et +4 en DEF contre celles encore plus grandes.
          • +
          • instinctDeSurvieHumain : booléen, les DM qui amènent à 0 PV sont divisés par 2.
          • +
          • intelligenceDuCombat : booléen, ajoute le mod d'INT à l'initiative et à la DEF.
          • +
          • intercepter : booléen, propose d'intercepter les attaques sur les alliés au contact.
          • +
          • interventionDivine : booléen, autorise l'utilisation de la commande !cof-intervention-divine une fois par combat.
          • +
          • invulnerable : booléen, divise par 2 les dégâts élémentaires.
          • +
          • joliCoup : booléen, ignore les obstacles pour les attaques à distance.
          • +
          • kiai : entier, nombre de kiaï que le personnage peut pousser dans un combat (avec un délai de 1d6 tours entre chaque).
          • +
          • laissezLeMoi : booléen, rajoute 1d6 aux DM contre les chefs.
          • +
          • liberateurDAnathazerin : booléen, augmente l'attaque de 2 et les DM de 2d6 contre les elfes noirs et les insectes, et donne un bonus de +2 pour résister aux poisons.
          • +
          • liberateurDeDorn : booléen, augmente la défense de 2, l'attaque de 2 et les DM de 2d6 contre les géants.
          • +
          • liberateurDeKerserac : booléen, augmente l'attaque de 2 et les DM de 1d6 contre les géants, les insectes et les elfes noirs.
          • +
          • liberteDAction : booléen, immunise à tout ce qui entrave le mouvement.
          • +
          • lienEpique : nom, le personnage est en lien épique avec les personnages ayant le même prédicat (bonus de 1d6 aux DM s'ils attaquent la même cible).
          • +
          • loupParmiLesLoups : entier, bonus aux DM contre les humanoïdes.
          • +
          • lycanthrope : booléen, le personnage est un lycanthrope. Propose un jet pour résister à la transformation.
          • +
          • lycanthropeEventre : booléen, permet léventration des lycanthropes.
          • +
          • magieDeCombat : booléen ou entier (1 par défaut), bonus aux chances de critique des sorts.
          • +
          • magieEnArmure : entier, valeur d'armure (moyenne entre DEF et malus d'armure) en dessous de laquelle le personnage peut faire de la magie sans pénalité.
          • +
          • magieEnArmureFacilitee : booléen, diminue la valeur d'armure de magieEnArmure pour déterminer le malus aux jets pour lancer des sorts en armure.
          • +
          • marcheSylvestre : booléen, immunise aux effets de conditions hostiles et donne +2 en attaque et en défense en terrain difficile.
          • +
          • mauvais : booléen, le personnage est mauvais, ce qui signifie normalement, démon, mort-vivant ou élémentaire (impacte la protection contre le mal).
          • +
          • memePasMal : booléen, reporte les DM des coups critiques et donne un bonus aux DMs.
          • +
          • monture : booléen, indique que le personnage est une monture, il est donc possible de monter dessus (avec !cof-en-selle).
          • +
          • montureLoyale : booléen, augmente la défense et l'attaque de 1 quand le personnage est sur une monture.
          • +
          • montureMagique : booléen, soigne automatiquement la monture pendant les repos longs.
          • +
          • morsureDuSerpent : booléen, augmente les chances de critique au contact.
          • +
          • mortDemandeConfirmation : booléen, indique que la mort du personnage ne peut être effective qu'une fois que le MJ l'aura confirmée (ce qui lui permet par exemple de prendre le temps de décrire cette mort).
          • +
          • mortVivant : booléen, indique que le personnage est un mort-vivant.
          • +
          • natureNourriciereBaies : booléen, permet de trouver des baies magiques si le jet de nature nourricière est assez haut.
          • +
          • nbDesFeinte : entier (2 par défaut), nombre de dés à ajouter aux DM des feintes réussies.
          • +
          • necromancien : booléen, le personnage est un nécromancien ou une sorcière.
          • +
          • nonVivant : booléen, le personnage n'est pas vivant (élémentaire, golem ou autre).
          • +
          • ordreDuChevalierDragon : booléen, augmente de 5 les jets de persuasion et d'intimidation quand le personnage est sur une monture.
          • +
          • pacifisme : booléen, augmente la DEF de 5 tant que le personnage n'a pas attaqué dans un combat.
          • +
          • pacteSanglant : entier, propose un bouton pour augmenter la DEF de cette valeur, en échange d'une dépense de PV (1d4 pour 3 ou moins, 2d4 sinon).
          • +
          • paradeAuBouclier : booléen, propose un bouton pour les parades au bouclier.
          • +
          • paradeDeProjectiles : booléen, propose un jet pour parer les projectiles.
          • +
          • pasDuVent : entier, bonus à l'initiative.
          • +
          • peauDePierre : booléen, augmente la DEF de la valeur de CON.
          • +
          • perception : entier, bonus aux jets de perception.
          • +
          • petiteTaille : booléen, bonus de +2 en discrétion et +1 en DEF, et le personnage est considéré de taille petit.
          • +
          • petitVeinard : booléen ou entier (1 par défaut), propose de relancer des jets ce nombre de fois pas combat maximum.
          • +
          • piquresDInsectes : entier, diminue les DM des attaques à distance de ce nombre, si le personnage porte une armure de DEF au moins 5.
          • +
          • pirouettes : entier, augmente la DEF si le personnage a un malud d'armure inféreur ou égal à 3.
          • +
          • plusViteQueSonOmbre : booléen ou entier (par défaut 10), augmente l'initiative si le personnage à une arme à poudre chargée en main. On peut aussi spécifier un autre type d'armes, comme les arbalète, en associant ce nom à la valeur du prédicat.
          • +
          • poudrePuissante : booléen, augmente de 10 la portée et de 2 les DM des armes à poudre.
          • +
          • prescience : booléen ou entier (défaut 1), propose de refaire le tour. Le nombre associé indique le nombre de fois que le personnage peut faire ça par combat.
          • +
          • proprioception : booléen, immunise aux effets de douleur et de peur,
          • +
          • protectionContreLesProjectiles : entier, valeur de la RD apportée par l'effet temporaire de même nom
          • +
          • prouesse : booléen, propose un bouton pour les prouesses de guerrier.
          • +
          • PVPartagesAvec : nom de personnage, nom du personnage avec lequel on partage les PVs.
          • +
          • quadrupede : booléen, le personnage marche sur 4 pattes ou plus.
          • +
          • radarMental : entier, bonus aux jets de surprise contre les créatures vivantes. Si le prédicat est présent annule les malus liées à la condition aveugle contre les créatures vivantes.
          • +
          • rageDuBerserkAmelioree : booléen, divise par 2 les malus de rage.
          • +
          • rapideCommeSonOmbre : booléen ou entier (3 par défaut), bonus à la discrétion et à l'initiative.
          • +
          • RD_critique : entier, s'ajoute à la RD contre les coups critiques des casques.
          • +
          • reduireLaDistance : booléen, augmente la défense contre les grandes créatures (2 à 4, selon la taille).
          • +
          • reflexesFelins : entier, bonus aux esquives et à l'initiative.
          • +
          • resistanceA_nonMagique : booléen, divise par 2 les DM non magiques.
          • +
          • resistanceA_type : booléen, divise par 2 les DM du type.
          • +
          • resistanceALaMagieBarbare : booléen, propose un jet pour résister à la magie.
          • +
          • riposteGuerrier : booléen, propose un jet de riposte au personnage.
          • +
          • sangFroid : booléen, réduit les DM quand l'ogre prend sur lui pour éviter une réaction violente.
          • +
          • sansPeur : booléen, immunise aux effets de peur et donne un bonus de 2+CHA aux alliés en vue pour résister aux effets de peur.
          • +
          • scienceDuCritique : booléen, augmente de 1 les chances de critique.
          • +
          • secondSouffle : booléen ou entier, propose un jet de récupération de PV une fois par combat, appliqué automatiquement à la fin du combat si pas utilisé. Si on met un entier, on soigne avec cette valeur à la place de CON + niveau.
          • +
          • sensAffutes : booléen, ajoute la sagesse aux dégâts des arcs et à l'initiative.
          • +
          • sournoisesParTour : entier (1 par défaut), nombre maximum d'effets d'attaques sournoises par tour pour le personnage.
          • +
          • specialisationGuerrier : booléen ou entier (2 par défaut), donne un bonus aux DM pour l'arme de prédilection.
          • +
          • siphonDesAmes : booléen ou entier, fait récupérer 1d6 PV quand une créature meurt dans les 20 mètres. Si une valeur est précisée, elle est ajoutée à ce dé. +
          • siphonDesAmesPrioritaire : entier, indique une priorité pour le siphon des âmes, si plusieurs personnages peuvent siphoner une âme. Le personnage ayant un prédicat siphonDesAmesPrioritaire de plus haute valeur sera prioritaire : il absorbera l'âme (et bénéficiera du soin) s'il n'est pas au maximum de PV. Si il est au maximum de PV, on passe au personnage suivant dans l'ordre des priorités. Un personnage n'ayant pas ce prédicat est dernier sur cette liste.
          • +
          • surveillance : nom d'un token, ajoute +5 aux tests contre la surprise si le token est présent. Et augmente l'initiative de 5 dans ces cas.
          • +
          • techniqueDuSabre : entier, bonus aux DM pour le combat au sabre, katana ou vivelame. Ce bonus ne se cumule qu'à moitié avec le mod. de FOR.
          • +
          • tenacite : booléen, donne un bonus de +2 puis +5 contre un adversaire que le personnage n'a pas réussi à toucher.
          • +
          • tirDeSemonce : booléan, propose un tir de semonce au tour suivant un tir raté.
          • +
          • tirFatal : booléen ou nom de type d'arme (par défaut, arc), augmente les chance de coup critique du mod. de SAG pour les armes de ce type. De plus les DM des coups critiques pour ces attaques sont augmentées de 1 ou 2 d6 (selon la valeur du prédicat voieDeLArcEtDuCheval).
          • +
          • tirParabolique : booléen, augmente la portée des armes à distance (comme la capacité tir parabolique).
          • +
          • tirPrecis : entier, ajouté aux DM quand la cible est à moins de 5xDEX mètres.
          • +
          • tokenFormeDArbre : url d'une image, l'image utilisée pour la forme d'arbre.
          • +
          • tourDeForce : booléen, propose de sacrifier des PV pour rajouter un bonus aux tests de force ratés.
          • +
          • toutPetit : booléen, ajoute 5 aux jets de discrétion et 2 en DEF.
          • +
          • traquenard : booléen, permet la bonne utilisation de l'option d'attaque --traquenard.
          • +
          • tropPetit : booléen, change les d6 de DM en d4.
          • +
          • vampire : booléen, le personnage est un vampire.
          • +
          • ventreMou : booléen, réduit la RD fixe des grandes créatures.
          • +
          • vetementsSacres : entier, bonus à la DEF quand le personnage ne porte pas d'armure.
          • +
          • vieArtificielle : booléan, divise les soins par 2.
          • +
          • violenceCiblee : booléen, permet d'emmagasiner et d'utliser des points de violence.
          • +
          • visionDansLeNoir : entier, le personnage voit dans le noir jusqu'à la distance indiquée par le prédicat, en mètres.
          • +
          • vitesseDuFelin : entier, bonus à l'initiative et aux jets de course, d'escalade, et de saut.
          • +
          • voieDeLaConjuration : entier, rang dans la Voie de la conjuration. Contrôle les conjurations de prédateurs et la conjuration d'armées.
          • +
          • voieDeLaMagieElementaire : entier, on utilise 2 fois cette valeur pour la valeur de la protection des éléments.
          • +
          • voieDeLArchange : entier, rang dans la Voie de l'archange. contrôle l'utilisation de la forme d'ange.
          • +
          • voieDeLArcEtDuCheval : entier (3 par défaut), ocntrôle le nombre de d6 rajoutés par le ir fatal en cas de critique.
          • +
          • voieDeLaTelekinesie : entier (2 par défaut), bonus de DEF accordé par l'effet champDeProtection.
          • +
          • voieDeLaSurvie : entier, contrôle le nombre de plantes trouvées avec Nature nourricière.
          • +
          • voieDeLHonneur : entier, bonus aux DM par défaut des défis.
          • +
          • voieDesElixirs : entier, rang dans la voie des élixirs, contrôle les élixirs proposés à la création, leur nombre total maximum et la puissance de certains d'entre eux.
          • +
          • voieDesMutations : entier, bonus de défense de la cuirasse de la voie des mutations.
          • +
          • voieDesRunes : entier, rang dans la voie des runes, définit le bonus à la DEF et contrôle les runes proposées à la création et la puissance de certaines d'entre elles.
          • +
          • voieDesSoins : entier, rang dans la voie des soins, limite le nombre total de soins légers et de soins modérés par jour.
          • +
          • voieDesVegetaux : entier (1 par défaut), bonus de DEF de la peau d'écorce.
          • +
          • voieDuGuerisseur : entier, bonus aux soins legers, soins modérés et soins de groupe.
          • +
          • voieDuMeneurDHomme : entier (2 par défaut), RD appliquée en cas d'interception (minimum 2).
          • +
          • voieDuMetal : entier, bonus aux DM de l'arme enflammée du forgesort.
          • +
          • voieDuSoldat : entier, limite le bonus de la posture de combat.
          • +
          • voieOutreTombe : entier (1 par défaut), maximum de combies ocntrôlés à la fois.
          • +
          • volant : booléen, le personnage peut voler. Cela influe sur la possibilité de croc-en-jambe, la possibilité d'échapper à une armée de morts, ...
          • +
          +