Vous n'avez aucun consommable");
else addLineToFramedDisplay(display, 'Cliquez sur le consommable pour l\'utiliser ou sur r pour l\'échanger avec un autre personnage.');
sendChat('', endFramedDisplay(display));
});
}); //fin du getSelected
}
// Le premier argument est le message reçu : msg => String
// Le deuxième nous indique si on effectue un échange ou pas : echange => true/false
function utiliseConsommable(msg, echange) {
var cmd = msg.content.split(' ');
if ((!echange && cmd.length < 3) || (echange && cmd.length < 5)) {
error("Erreur interne de consommables", cmd);
return;
}
//perso1 = token avec qui utilise (ou qui va échanger) le consommable
var perso1 = tokenOfId(cmd[1]);
if (perso1 === undefined) {
log("Propriétaire perdu");
sendChat('COF', "Plus possible d'utiliser cette action. Réafficher les consommables.");
return;
}
var perso2;
if (echange) {
//perso2 = token avec lequel on va faire l'échange
perso2 = tokenOfId(cmd[2]);
if (perso2 === undefined) {
log("Destinataire perdu");
sendChat('COF', "Erreur concernant le destinataire. Veuillez réessayer.");
return;
}
}
// Vérifie les droits d'utiliser le consommable
if (msg.selected && msg.selected.length == 1) {
var utilisateur = tokenOfId(msg.selected[0]._id);
if (utilisateur === undefined) {
sendChat('COF', "Le token sélectionné n'est pas valide");
return;
}
var d = distanceCombat(perso1.token, utilisateur.token);
if (d > 0) {
sendChar(utilisateur.charId, "est trop loin de " + perso1.token.get('name') + " pour utiliser ses objets");
return;
}
perso1 = utilisateur;
} else {
//On regarde si le joueur contrôle le token
if (!peutController(msg, perso1)) {
sendPlayer(msg, "Pas les droits pour ça");
return;
}
}
//on récupère l'attribut à utiliser/échanger de perso1
var attr1;
if (echange) attr1 = getObj('attribute', cmd[4]);
else attr1 = getObj('attribute', cmd[2]);
var attrName = attr1.get('name').trim();
var effet = attr1.get('max').trim();
if (attr1 === undefined) {
log("Attribut a changé/perdu");
log(cmd);
sendChat('COF', "Plus possible d'utiliser cette action. Veuillez réafficher les consommables.");
return;
}
//Nom du consommable (pour affichage)
var consName = attrName.substring(attrName.indexOf('_') + 1);
consName = "" + consName.replace(/_/g, ' ').trim() + "";
// quantité actuelle pour perso1
var quantite1 = parseInt(attr1.get('current'));
if (isNaN(quantite1) || quantite1 < 1) {
attr1.set('current', 0);
sendChat('COF', "/w " + perso1.token.get('name') + " Vous ne disposez plus de " + consName);
return;
}
var evt = {
type: "Utilisation de consommable",
attributes: []
};
if (echange) {
//c'est un échange
evt.type = "Échange de consommable";
if (perso1.charId != perso2.charId) {
// on baisse la valeur de 1 du consommable qu'on s'apprête à échanger
attr1.set('current', quantite1 - 1);
evt.attributes.push({
attribute: attr1,
current: quantite1,
max: effet
});
// ajout du consommable dans perso2 :
var attributes = findObjs({
_type: 'attribute',
_characterid: perso2.charId
});
var found = false;
var quantite2;
// on recherche si le consommable existe chez perso2
attributes.forEach(function(attr2) {
var attrName2 = attr2.get('name').trim();
if (!found && attrName == attrName2) {
if (attr2.get('max').trim() != effet) {
error("Échange dangereux : pas le même effet pour le consommable selon le personnage \n" + effet + "\n" + attr2.get('max'), attr2);
return;
}
found = true;
// si oui, on augmente sa quantité de 1
quantite2 = parseInt(attr2.get('current'));
if (isNaN(quantite2) || quantite2 < 1) quantite2 = 0;
attr2.set('current', quantite2 + 1);
evt.attributes.push({
attribute: attr2,
current: quantite2,
max: effet
});
return;
}
});
// si le consommable n'a pas été trouvé, on le créé avec une valeur de 1.
if (!found) {
var attr2 = createObj("attribute", {
name: attrName,
current: 1,
max: effet,
characterid: perso2.charId
});
evt.attributes.push({
attribute: attr2,
current: null,
});
}
// on envoie un petit message précisant la résultante de l'action.
sendChat('COF', "Echange entre " + perso1.token.get('name') + " et " + perso2.token.get('name') + " terminée.");
sendChat('COF', "/w " + perso1.token.get('name') + " Il vous reste " + parseInt(attr1.get('current')) + " " + consName + ".");
sendChat('COF', "/w " + perso2.token.get('name') + " Vous possédez désormais " + quantite2 + " " + consName + ".");
// le MJ est notifié :
sendChat('COF', "/w GM " + perso1.token.get('name') + " vient de donner 1 " + consName + " à " + perso2.token.get('name') + ".");
} else {
sendChat('COF', "/w " + perso1.token.get('name') + " Vous ne pouvez pas échanger un consommable avec vous-même ...");
}
} else {
// on utilise le consommable
attr1.set('current', quantite1 - 1);
evt.attributes.push({
attribute: attr1,
current: quantite1,
max: effet
});
var start = msg.content.indexOf(' --message ') + 10;
sendChar(perso1.charId, msg.content.substring(start));
}
addEvent(evt);
}
//asynchrone
//callback(resultat, crit):
// resultat peut être 0, 1 ou 2 : 0 = match null, 1 le perso 1 gagne, 2 le perso 2 gagne.
// crit peut être 1 si un des deux perso a fait une réussite critique et pas l'autre, -1 si un des personnage a fait un échec critique et pas l'autre, et 0 sinon
function testOppose(perso1, carac1, perso2, carac2, explications, evt, callback) {
if (carac2 === undefined) carac2 = carac1;
var nom1 = perso1.token.get('name');
var nom2 = perso2.token.get('name');
jetCaracteristique(perso1, carac1, {}, function(rt1) {
jetCaracteristique(perso2, carac2, {}, function(rt2) {
explications.push("Jet de " + carac1 + " de " + nom1 + " :" + rt1.texte);
explications.push("Jet de " + carac2 + " de " + nom2 + " :" + rt2.texte);
var reussite;
var crit = 0;
if (rt1.total > rt2.total) reussite = 1;
else if (rt2.total > rt1.total) reussite = 2;
else reussite = 0;
if (rt1.echecCritique) {
if (!rt2.echecCritique) {
reussite = 2;
crit = -1;
}
} else if (rt2.echecCritique) {
reussite = 1;
crit = -1;
} else if (rt1.critique) {
if (!rt2.critique) {
reussite = 1;
crit = 1;
}
} else if (rt2.critique) {
reussite = 2;
crit = 1;
}
switch (reussite) {
case 1:
diminueMalediction(perso2, evt);
break;
case 2:
diminueMalediction(perso1, evt);
break;
}
callback(reussite, crit);
}); //Fin du jet du deuxième perso
}); //Fin du jet du premier perso
}
function provocation(msg) {
var args = msg.content.split(' --');
var cmd = args[0].split(' ');
if (cmd.length < 3) {
error("La commande !cof-provocation requiert 2 arguments", cmd);
return;
}
var voleur = tokenOfId(cmd[1]);
if (voleur === undefined) {
error("Le premier argument de !cof-provocation n'est pas un token valide");
return;
}
var cible = tokenOfId(cmd[2]);
if (cible === undefined) {
error("Le deuxième argument de !cof-provocation n'est pas un token valide");
return;
}
var nomVoleur = voleur.token.get('name');
var nomCible = cible.token.get('name');
var display =
startFramedDisplay(msg.playerid, 'Provocation', voleur, cible);
var evt = {
type: 'Provocation'
};
var jets = [];
testOppose(voleur, 'CHA', cible, 'INT', jets, evt, function(res, crit) {
jets.forEach(function(l) {
addLineToFramedDisplay(display, l);
});
var reussite;
switch (res) {
case 0: //en cas d'égalité, on considère que la provocation est réussie
diminueMalediction(cible, evt);
switch (crit) {
case -1:
reussite = "Sur un malentendu, la provocation réussit...";
break;
case 0:
case 1:
reussite = "La provocation réussit tout juste.";
}
break;
case 1:
switch (crit) {
case -1:
reussite = nomCible + " marche complètement, il attaque " + nomVoleur;
break;
case 0:
reussite = "La provocation réussit.";
break;
case 1:
reussite = "La provocation est une réussite critique !";
}
break;
case 2:
switch (crit) {
case -1:
reussite = "Échec critique de la provocation !";
break;
case 0:
reussite = "La provocation échoue";
break;
case 1:
reussite = nomCible + " voit clair dans le jeu de " + nomCible + ". La provocation échoue.";
}
}
addLineToFramedDisplay(display, reussite);
addEvent(evt);
sendChat('', endFramedDisplay(display));
}); //Fin du test opposé
}
function enSelle(msg) {
var cmd = msg.content.split(' ');
if (cmd.length < 3) {
error("Il faut 2 arguments pour !cof-en-selle", cmd);
return;
}
var cavalier = tokenOfId(cmd[1]);
if (cavalier === undefined) {
error("Premier argument de !cof-en-selle incorrect", cmd);
return;
}
if (attributeAsBool(cavalier, 'monteSur')) {
sendChar(cavalier.charId, " est déjà en selle");
return;
}
var tokenC = cavalier.token;
var pageId = tokenC.get('pageid');
var monture = tokenOfId(cmd[2], cmd[2], pageId);
if (monture === undefined || !charAttributeAsBool(monture, 'monture')) {
sendChar(cavalier.charId, " ne peut pas monter là-dessus");
log(cmd);
return;
}
var tokenM = monture.token;
var nomMonture = tokenM.get('name');
if (attributeAsBool(monture, 'estMontePar')) {
sendChar(cavalier.charId, " ne peut monter sur " + nomMonture + " car elle a déjà un cavalier");
return;
}
if (distanceCombat(tokenC, tokenM, pageId) > 0) {
sendChar(cavalier.charId, " est trop loin de " + nomMonture);
return;
}
var evt = {
type: 'En selle'
};
setTokenAttr(cavalier, 'monteSur', tokenM.id, evt, " monte sur " + nomMonture, nomMonture);
setTokenAttr(monture, 'estMontePar', tokenC.id, evt, undefined, tokenC.get('name'));
setTokenAttr(monture, 'positionSurMonture', tokenC.get('left') - tokenM.get('left'), evt, undefined, tokenC.get('top') - tokenM.get('top'));
setTokenAttr(monture, 'directionSurMonture', tokenC.get('rotation') - tokenM.get('rotation'), evt);
addEvent(evt);
}
function listeElixirs(rang) {
var liste = [{
nom: 'fortifiant',
action: "!cof-fortifiant $rang"
}];
if (rang < 2) return liste;
liste.push({
nom: 'feu_grégeois',
action: "!cof-dmg $rangd6 --feu --psave DEX [[10+@{selected|INT}]] --disque @{target|token_id} 3 10 --lanceur @{selected|token_id}"
});
if (rang < 3) return liste;
liste.push({
nom: 'élixir_de_guérison',
action: "!cof-soin 3d6+$INT"
});
if (rang < 4) return liste;
liste.push({
nom: "potion_d_agrandissement",
action: "!cof-effet-temp agrandissement [[5+$INT]]"
});
liste.push({
nom: "potion_de_forme_gazeuse",
action: "!cof-effet-temp formeGazeuse [[1d4+$INT]]"
});
liste.push({
nom: "potion_de_protection_contre_les_éléments",
action: "!cof-effet-temp protectionContreLesElements [[5+$INT]] --valeur $rang"
});
liste.push({
nom: "potion_d_armure_de_mage",
action: "!cof-effet-combat armureDuMage"
});
liste.push({
nom: "potion_de_chute_ralentie",
action: "est léger comme une plume."
});
if (rang < 5) return liste;
liste.push({
nom: "potion_d_invisibilité",
action: "!cof-set-state invisible true --message se rend invisible ([[1d6+$INT]] minutes)"
});
liste.push({
nom: "potion_de_vol",
action: "se met à voler"
});
liste.push({
nom: "potion_de_respiration_aquatique",
action: "peut respirer sous l'eau"
});
liste.push({
nom: "potion_de_flou",
action: "!cof-effet-temp flou [[1d4+$INT]]"
});
liste.push({
nom: "potion_de_hâte",
action: "!cof-effet-temp hate [[1d6+$INT]]"
});
return liste;
}
//!cof-creer-elixir token_id nom_token elixir
function creerElixir(msg) {
var options = parseOptions(msg);
var cmd = options.cmd;
if (cmd === undefined || cmd.length < 4) {
error("Pas assez d'arguments pour !cof-creer-elixir", msg.content);
return;
}
var forgesort = tokenOfId(cmd[1], cmd[2], options.pageId);
if (forgesort === undefined) {
if (msg.selected && msg.selected.length == 1) {
forgesort = tokenOfId(msg.selected[0]._id);
}
if (forgesort === undefined) {
error("Impossible de savoir qui crée l'élixir", cmd);
return;
}
}
var voieDesElixirs = charAttributeAsInt(forgesort, 'voieDesElixirs', 0);
if (voieDesElixirs < 1) {
sendChar(forgesort.charId, " ne connaît pas la Voie des Élixirs");
return;
}
var elixir = listeElixirs(voieDesElixirs).find(function(i) {
if (i.nom == cmd[3]) return true;
return false;
});
if (elixir === undefined) {
error(forgesort.token.get('name') + " est incapable de créer " + cmd[3], cmd);
return;
}
var evt = {
type: "Création d'élixir"
};
if (limiteRessources(forgesort, options, 'elixirsACreer', 'élixirs à créer', evt)) return;
var attrName = 'elixir_' + elixir.nom;
var message = "crée un " + elixir.nom.replace(/_/g, ' ');
var attr = tokenAttribute(forgesort, attrName);
if (attr.length === 0) {
var action = elixir.action.replace(/\$rang/g, voieDesElixirs);
action = action.replace(/\$INT/g, modCarac(forgesort, 'INTELLIGENCE'));
setTokenAttr(forgesort, attrName, 1, evt, message, action);
} else {
var nb = parseInt(attr[0].get('current'));
if (isNaN(nb) || nb < 1) nb = 0;
setTokenAttr(forgesort, attrName, nb + 1, evt, message);
}
addEvent(evt);
}
function gestionElixir(msg) {
var playerId = msg.playerid;
var player = getObj('player', playerId);
getSelected(msg, function(selected) {
iterSelected(selected, function(forgesort) {
var voieDesElixirs = charAttributeAsInt(forgesort, 'voieDesElixirs', 0);
if (voieDesElixirs < 1) {
sendChar(forgesort.charId, " ne connaît pas la Voie des Élixirs");
return;
}
var elixirsACreer = voieDesElixirs * 2;
var attrElixirs = tokenAttribute(forgesort, 'elixirsACreer');
if (attrElixirs.length === 0) {
attrElixirs = setTokenAttr(forgesort, 'elixirsACreer', elixirsACreer, {});
} else {
attrElixirs = attrElixirs[0];
elixirsACreer = parseInt(attrElixirs.get('current'));
if (isNaN(elixirsACreer)) elixirsACreer = 0;
}
var titre;
if (elixirsACreer < 1)
titre = "Impossible de créer un autre élixir aujourd'hui";
else titre = "Encore " + elixirsACreer + " élixirs à créer";
var display = startFramedDisplay(playerId, titre, forgesort, undefined, true);
listeElixirs(voieDesElixirs).forEach(function(elixir) {
var nbElixirs = 0;
var attr = tokenAttribute(forgesort, 'elixir_' + elixir.nom);
if (attr.length > 0) {
attr = attr[0];
nbElixirs = parseInt(attr.get('current'));
if (isNaN(nbElixirs) || nbElixirs < 0) nbElixirs = 0;
}
var nomElixir = elixir.nom.replace(/_/g, ' ');
var options = '';
var action;
if (elixirsACreer > 0) {
action = "!cof-creer-elixir " + forgesort.token.id + ' ' + forgesort.token.get('name') + ' ' + elixir.nom;
options += bouton(action, nbElixirs, forgesort, attrElixirs);
} else {
options = nbElixirs + ' ';
}
if (nbElixirs > 0) {
action = elixir.action;
action = action.replace(/\$rang/g, voieDesElixirs);
action = action.replace(/\$INT/g, modCarac(forgesort, 'INTELLIGENCE'));
options += bouton(action, nomElixir, forgesort, attr);
//TODO: Rajouter la possibilité de donner un élixir à un autre token
} else {
options += nomElixir;
}
addLineToFramedDisplay(display, options);
});
sendChat('', endFramedDisplay(display));
});
}); //Fin du getSelected
}
function rageDuBerserk(msg) {
getSelected(msg, function(selection) {
if (selection.length === 0) {
sendPlayer(msg, "Pas de token sélectionné pour la rage");
return;
}
iterSelected(selection, function(perso) {
var evt = {
type: "Rage"
};
if (attributeAsBool(perso, 'rageDuBerserk')) {
//Jet de sagesse difficulté 13 pour sortir de cet état
var options = {};
var display = startFramedDisplay(msg.playerid, "Essaie de calmer sa rage", perso);
testCaracteristique(perso, 'SAG', 13, options, evt,
function(tr) {
addLineToFramedDisplay(display, "Résultat du jet de SAG : " + tr.texte);
addEvent(evt);
if (tr.reussite) {
addLineToFramedDisplay(display, "C'est réussi, " + perso.token.get('name') + " se calme.");
removeTokenAttr(perso, 'rageDuBerserk', evt);
} else {
var msgRate = "C'est raté, " + perso.token.get('name') + " reste enragé";
//TODO : ajouter un bouton de chance
addLineToFramedDisplay(display, msgRate);
}
sendChat('', endFramedDisplay(display));
});
} else {
//Le barbare passe en rage
if (!state.COFantasy.combat) {
initiative(selection, evt);
}
setTokenAttr(perso, 'rageDuBerserk', true, evt, "entre dans une rage berserk !");
}
}); //fin iterSelected
}); //fin getSelected
}
//!cof-arme-secrete @{selected|token_id} @{target|token_id}
// TODO: ajouter la possibilité d'utiliser la chance
function armeSecrete(msg) {
var cmd = msg.content.split(' ');
if (cmd.length < 3) {
error("Il faut deux arguments à !cof-arme-secrete", cmd);
return;
}
var barde = tokenOfId(cmd[1]);
var cible = tokenOfId(cmd[2]);
if (barde === undefined || cible === undefined) {
error("Token non valide pour l'arme secrète", cmd);
return;
}
if (attributeAsInt(barde, 'armeSecreteBardeUtilisee')) {
sendChar(barde.charId, "a déjà utilisé son arme secrète durant ce combat");
return;
}
var evt = {
type: 'Arme secrète'
};
if (!state.COFantasy.combat) {
initiative([{
_id: barde.token.id
}, {
_id: cible.token.id
}], evt);
}
setTokenAttr(barde, 'armeSecreteBardeUtilisee', true, evt);
var intCible = charAttributeAsInt(cible, 'INTELLIGENCE', 10);
testCaracteristique(barde, 'CHA', intCible, {}, evt, function(testRes) {
var display = startFramedDisplay(msg.playerid, "Arme secrète", barde, cible);
var line = "Jet de CHA : " + testRes.texte;
if (testRes) {
line += " ≥ " + intCible;
addLineToFramedDisplay(display, line);
addLineToFramedDisplay(display, cible.token.get('name') + " est complètement déstabilisé");
setTokenAttr(cible, 'armeSecreteBarde', 1, evt, '', getInit());
} else {
line += "< " + intCible;
addLineToFramedDisplay(display, line);
addLineToFramedDisplay(display, cible.token.get('name') + " reste insensible au charme de " + barde.token.get('name'));
}
sendChat("", endFramedDisplay(display));
addEvent(evt);
}); //fin testCarac
// testRes.texte est l'affichage du jet de dé
// testRes.reussite indique si le jet est réussi
// testRes.echecCritique, testRes.critique pour le type
}
function nouveauNomDePerso(nom) {
var characters = findObjs({
_type: 'character'
});
characters = characters.map(function(c) {
return c.get('name');
});
var trouve = characters.indexOf(nom);
if (trouve < 0) return nom;
var n = 2;
while (1) {
var nomP = nom + ' ' + n;
trouve = characters.indexOf(nomP);
if (trouve < 0) return nomP;
n++;
}
}
//!cof-animer-arbre lanceur-id target-id [rang]
function animerUnArbre(msg) {
var options = parseOptions(msg);
var cmd = options.cmd;
if (cmd === undefined || cmd.length < 3) {
error("cof-animer-arbre attend 2 arguments", msg.content);
return;
}
var druide = tokenOfId(cmd[1], cmd[1], options.pageId);
if (druide === undefined) {
error("Le premier argument de !cof-animer-arbre n'est pas un token valie", cmd);
return;
}
var tokenArbre = getObj('graphic', cmd[2]);
if (tokenArbre === undefined) {
error("Le deuxième argument de !cof-animer-arbre n'est pas un token", cmd);
return;
}
if (tokenArbre.get('represents') !== '') {
sendChar(druide.charId, "ne peut pas animer " + tokenArbre.get('name'));
return;
}
if (options.portee !== undefined) {
var dist = distanceCombat(druide.token, tokenArbre, options.pageId);
if (dist > options.portee) {
sendChar(druide.charId, " est trop loin de l'arbre");
return;
}
}
var rang = charAttributeAsInt(druide, 'voieDesVegetaux', 3);
if (cmd.length > 3) { //Le rang est spécifié en argument optionnel
var cmd3 = parseInt(cmd[3]);
if (isNaN(cmd3) || cmd3 < 1) {
error("Le rang n'est pas un nombre valie. On utilise " + rang + " à la place", cmd);
} else rang = cmd3;
}
var evt = {
type: "Animation d'un arbre"
};
if (limiteRessources(druide, options, 'animerUnArbre', 'animer un arbre', evt)) return;
if (!state.COFantasy.combat) {
initiative([{
_id: druide.token.id
}], evt);
}
var nomArbre = nouveauNomDePerso('Arbre animé');
var charArbre = createObj('character', {
name: nomArbre,
avatar: "https://s3.amazonaws.com/files.d20.io/images/42323556/6qxlm965aFhBXGoYFy5fqg/thumb.png?1510582137"
});
evt.characters = [charArbre];
var caid = charArbre.id;
var pvArbre = rang * 10;
setToken(tokenArbre, 'represents', caid, evt);
//Les attributs n'ont pas besoin d'être ajoutés à evt, on les enlève en supprimant le personnage
createObj('attribute', {
characterid: caid,
name: 'DEXTERITE',
current: 7
});
createObj('attribute', {
characterid: caid,
name: 'DEFDIV',
current: 5
});
var pvAttr = createObj('attribute', {
characterid: caid,
name: 'PV',
current: pvArbre,
max: pvArbre
});
createObj('attribute', {
characterid: caid,
name: 'RD_sauf_feu_tranchant',
current: 10
});
var niveau = charAttributeAsInt(druide, 'NIVEAU', 1);
createObj('ability', {
characterid: caid,
name: 'Attaque',
istokenaction: true,
action: '!cof-attack @{selected|token_id} @{target|token_id} ["Branches",[' + niveau + ',0],20,[1,6,3,0],0]'
});
setToken(tokenArbre, 'bar1_link', pvAttr.id, evt);
setToken(tokenArbre, 'bar1_value', pvArbre, evt);
setToken(tokenArbre, 'bar1_max', pvArbre, evt);
setToken(tokenArbre, 'sjowplayers_bar1', true, evt);
setToken(tokenArbre, 'name', nomArbre, evt);
setToken(tokenArbre, 'showname', true, evt);
setToken(tokenArbre, 'showplayers_name', true, evt);
createObj('attribute', {
characterid: caid,
name: 'arbreAnime',
current: niveau,
max: getInit()
});
sendChar(caid, "commence à s'animer");
initiative([{
_id: tokenArbre.id
}], evt);
addEvent(evt);
}
function runeProtection(msg) {
if (!state.COFantasy.combat) {
sendPlayer(msg, "On ne peut utiliser les runes de protection qu'en combat");
return;
}
getSelected(msg, function(selected) {
iterSelected(selected, function(perso) {
var evt = {
type: "Rune de protection",
attributes: []
};
var attr = tokenAttribute(perso, 'runeDeProtection');
if (attr.length === 0) {
sendChar(perso.charId, "n'a pas de rune de protection");
return;
}
attr = attr[0];
var dispo = attr.get('current');
if (dispo) {
var lastAct = lastEvent();
if (lastAct === undefined) {
sendChar(perso.charId, "pas de dernière action sur laquelle utiliser la rune de protection");
return;
}
if (lastAct.affectes === undefined || lastAct.type != 'Attaque') {
sendChar(perso.charId, "la dernière action n'est pas une attaque, on ne peut utiliser la rune de protection");
return;
}
var aff = lastAct.affectes[perso.token.id];
var currentPV = perso.token.get('bar1_value');
if (aff === undefined || aff.prev === undefined ||
aff.prev.bar1_value === undefined ||
aff.prev.bar1_value <= currentPV) {
sendChar(perso.charId, "la dernière action n'a pas diminué les PV de " + perso.token.get('name'));
return;
}
sendChar(perso.charId, "utilise sa rune de protection pour ignorer les derniers dommages");
evt.attributes.push({
attribute: attr,
current: dispo
});
attr.set('current', 0);
setToken(perso.token, 'bar1_value', aff.prev.bar1_value, evt);
if (getState(perso, 'mort')) setState(perso, 'mort', false, evt);
return;
}
sendChar(perso.charId, "a déjà utilisé sa rune de protection durant ce combat");
return;
});
});
}
//!cof-delivrance @{selected|token_id} @{target|token_id}
function delivrance(msg) {
var options = parseOptions(msg);
var cmd = options.cmd;
if (cmd === undefined || cmd.length < 3) {
error("cof-delivrance attend 2 arguments", msg.content);
return;
}
var pretre = tokenOfId(cmd[1], cmd[1], options.pageId);
if (pretre === undefined) {
error("Le premier argument de !cof-delivrance n'est pas un token valide", msg.content);
return;
}
var cible = tokenOfId(cmd[2], cmd[2], options.pageId);
if (cible === undefined) {
error("Le deuxième argument de !cof-delivrance n'est pas un token valide", msg.content);
return;
}
cible.tokName = cible.token.get('name');
if (options.portee !== undefined) {
var dist = distanceCombat(pretre.token, cible.token, options.pageId);
if (dist > options.portee) {
sendChar(pretre.charId, " est trop loin de " + cible.tokName);
return;
}
}
var evt = {
type: "Délivrance",
deletedAttributes: []
};
if (limiteRessources(pretre, options, 'délivrance', 'délivrance', evt)) return;
var attr;
var display = startFramedDisplay(msg.playerid, 'Délivrance', pretre, cible);
var printEffet = function(message) {
addLineToFramedDisplay(display, "La cible " + message);
};
var optFin = {
print: printEffet
};
_.each(messageEffetTemp, function(effet, nomEffet) {
if (effet.prejudiciable) {
attr = tokenAttribute(cible, nomEffet);
if (attr.length > 0)
finDEffet(attr[0], nomEffet, attr[0].get('name'), cible.charId, evt, optFin);
}
});
_.each(messageEffetCombat, function(effet, nomEffet) {
if (effet.prejudiciable) {
attr = tokenAttribute(cible, nomEffet);
if (attr.length > 0) {
printEffet(effet.fin);
evt.deletedAttributes.push(attr[0]);
attr[0].remove();
}
}
});
if (attributeAsBool(cible, 'malediction')) {
printEffet("n'est plus maudite");
removeTokenAttr(cible, 'malediction', evt);
}
//On enlève enfin les états préjudiciables
if (getState(cible, 'aveugle')) {
printEffet("retrouve la vue");
setState(cible, 'aveugle', false, evt);
}
if (getState(cible, 'affaibli')) {
printEffet("retrouve des forces");
setState(cible, 'affaibli', false, evt);
}
if (getState(cible, 'etourdi')) {
printEffet("retrouve ses esprits");
setState(cible, 'etourdi', false, evt);
}
if (getState(cible, 'paralyse')) {
printEffet("peut à nouveau bouger");
setState(cible, 'paralyse', false, evt);
}
if (getState(cible, 'ralenti')) {
printEffet("retrouve une vitesse normale");
setState(cible, 'ralenti', false, evt);
}
if (getState(cible, 'endormi')) {
printEffet("se réveille");
setState(cible, 'endormi', false, evt);
}
if (getState(cible, 'apeure')) {
printEffet("reprend courage");
setState(cible, 'apeure', false, evt);
}
sendChat('', endFramedDisplay(display));
addEvent(evt);
}
function apiCommand(msg) {
msg.content = msg.content.replace(/\s+/g, ' '); //remove duplicate whites
var command = msg.content.split(" ", 1);
// First replace inline rolls by their values
replaceInline(msg);
var evt;
switch (command[0]) {
case "!cof-jet":
jet(msg);
return;
case "!cof-resultat-jet":
resultatJet(msg);
return;
case "!cof-attack":
parseAttack(msg);
return;
case "!cof-undo":
undoEvent();
return;
case "!cof-hors-combat":
sortirDuCombat();
return;
case "!cof-nuit":
nuit(msg);
return;
case "!cof-jour":
evt = {
type: "Nouveau jour"
};
jour(evt);
addEvent(evt);
return;
case "!cof-recuperation":
recuperer(msg);
return;
case "!cof-recharger":
recharger(msg);
return;
case "!cof-chance": //deprecated
chance(msg);
return;
case "!cof-bouton-chance":
boutonChance(msg);
return;
case "!cof-bouton-rune-energie":
runeEnergie(msg);
return;
case "!cof-rune-protection":
runeProtection(msg);
return;
case "!cof-surprise":
surprise(msg);
return;
case "!cof-init":
if (msg.selected === undefined) {
error("Dans !cof-init : rien à faire, pas de token selectionné", msg);
return;
}
if (msg.content.indexOf('--aura') !== -1) aura_token_on_turn = true;
else aura_token_on_turn = false;
evt = {
type: "initiative"
};
initiative(msg.selected, evt);
addEvent(evt);
return;
case "!cof-attendre":
attendreInit(msg);
return;
case "!cof-statut":
statut(msg);
return;
case "!cof-armure-magique":
armureMagique(msg);
return;
case "!cof-buf-def":
bufDef(msg);
return;
case "!cof-remove-buf-def":
removeBufDef(msg);
return;
case "!cof-aoe": //deprecated
case "!cof-dmg":
dmgDirects(msg);
return;
case "!cof-set-state":
interfaceSetState(msg);
return;
case "!cof-degainer":
degainer(msg);
return;
case "!cof-echange-init":
echangeInit(msg);
return;
case "!cof-a-couvert":
aCouvert(msg);
return;
case "!cof-effet-temp":
effetTemporaire(msg);
return;
case "!cof-effet-combat":
effetCombat(msg);
return;
case "!cof-effet":
effetIndetermine(msg);
return;
case "!cof-attaque-magique":
attaqueMagique(msg);
return;
case "!cof-sommeil":
sommeil(msg);
return;
case "!cof-transe-guerison":
transeGuerison(msg);
return;
case "!cof-soin":
soigner(msg);
return;
case "!cof-aoe-soin": //Deprecated
aoeSoin(msg);
return;
case "!cof-nature-nourriciere":
natureNourriciere(msg);
return;
case "!cof-ignorer-la-douleur":
ignorerLaDouleur(msg);
return;
case "!cof-fortifiant":
fortifiant(msg);
return;
case "!cof-intercepter":
intercepter(msg);
return;
case "!cof-interposer":
interposer(msg);
return;
case "!cof-esquive-fatale":
esquiveFatale(msg);
return;
case "!cof-exemplaire":
exemplaire(msg);
return;
case "!cof-lancer-sort":
lancerSort(msg);
return;
case "!cof-peur":
peur(msg);
return;
case "!cof-distribuer-baies":
distribuerBaies(msg);
return;
case "!cof-consommer-baie":
consommerBaie(msg);
return;
case "!cof-proteger-un-allie":
protegerUnAllie(msg);
return;
case "!cof-action-defensive":
actionDefensive(msg);
return;
case "!cof-strangulation":
strangulation(msg);
return;
case "!cof-ombre-mortelle":
ombreMortelle(msg);
return;
case "!cof-escalier":
escalier(msg);
return;
case "!cof-defaut-dans-la-cuirasse":
defautDansLaCuirasse(msg);
return;
case "!cof-posture-de-combat":
postureDeCombat(msg);
return;
case "!cof-mur-de-force":
murDeForce(msg);
return;
case "!cof-capitaine":
devientCapitaine(msg);
return;
case "!cof-tueur-fantasmagorique":
tueurFantasmagorique(msg);
return;
case "!cof-tour-de-force":
tourDeForce(msg);
return;
case "!cof-encaisser-un-coup":
encaisserUnCoup(msg);
return;
case "!cof-absorber-au-bouclier":
absorberAuBouclier(msg);
return;
case "!cof-demarrer-statistiques":
if (state.COFantasy.statistiquesEnPause) {
state.COFantasy.statistiques = state.COFantasy.statistiquesEnPause;
state.COFantasy.statistiquesEnPause = undefined;
} else {
state.COFantasy.statistiques = {}; //remet aussi les statistiques à 0
}
return;
case "!cof-arreter-statistiques":
state.COFantasy.statistiques = undefined;
return;
case "!cof-pause-statistiques":
if (state.COFantasy.statistiques) {
state.COFantasy.statistiquesEnPause = state.COFantasy.statistiques;
state.COFantasy.statistiques = undefined;
} // sinon, ne pas écraser les statistiques déjà en pause
return;
case "!cof-statistiques":
displayStatistics(msg);
return;
case "!cof-destruction-des-morts-vivants":
destructionDesMortsVivants(msg);
return;
case "!cof-enduire-poison":
enduireDePoison(msg);
return;
case "!cof-consommables":
listeConsommables(msg);
return;
case "!cof-utilise-consommable": //Usage interne seulement
utiliseConsommable(msg, false);
return;
case "!cof-echange-consommables": //Usage interne seulement
utiliseConsommable(msg, true);
return;
case "!cof-provocation":
provocation(msg);
return;
case "!cof-en-selle":
enSelle(msg);
return;
case "!cof-creer-elixir": //usage interne seulement
creerElixir(msg);
return;
case "!cof-elixirs":
gestionElixir(msg);
return;
case "!cof-rage-du-berserk":
rageDuBerserk(msg);
return;
case "!cof-arme-secrete":
armeSecrete(msg);
return;
case "!cof-animer-arbre":
animerUnArbre(msg);
return;
case "!cof-delivrance":
delivrance(msg);
return;
default:
return;
}
}
var messageEffetTemp = {
sous_tension: {
activation: "se charge d'énergie électrique",
actif: "est chargé d'énergie électrique",
fin: "n'est plus chargé d'énergie électrique"
},
a_couvert: {
activation: "reste à couvert",
actif: "est à couvert",
fin: "n'est plas à couvert"
},
image_decalee: {
activation: "décale légèrement son image",
actif: "a décalé son image",
fin: "apparaît à nouveau là où il se trouve"
},
chant_des_heros: {
activation: "écoute le chant du barde",
actif: "est inspiré par le Chant des Héros",
fin: "n'est plus inspiré par le Chant des Héros"
},
benediction: {
activation: "est touché par la bénédiction",
actif: "est béni",
fin: "l'effet de la bénédiction s'estompe"
},
peau_d_ecorce: {
activation: "donne à sa peau la consistance de l'écorce",
actif: "a la peau dure comme l'écorce",
fin: "retrouve une peau normale"
},
rayon_affaiblissant: {
activation: "est touché par un rayon affaiblissant",
actif: "est sous l'effet d'un rayon affaiblissant",
fin: "n'est plus affaibli",
prejudiciable: true
},
peur: {
activation: "prend peur",
actif: "est dominé par sa peur",
fin: "retrouve du courage",
prejudiciable: true
},
peurEtourdi: {
activation: "prend peur: il peut fuir ou rester recroquevillé",
actif: "est paralysé par la peur",
fin: "retrouve du courage et peut à nouveau agir",
prejudiciable: true
},
aveugleTemp: {
activation: "n'y voit plus rien !",
actif: "", //Déjà affiché avec l'état aveugle
fin: "retrouve la vue",
prejudiciable: true
},
ralentiTemp: {
activation: "est ralenti : une seule action, pas d'action limitée",
actif: "", //Déjà affiché avec l'état ralenti
fin: "n'est plus ralenti",
prejudiciable: true
},
epeeDansante: {
activation: "fait apparaître une lame d'énergie lumineuse",
actif: "contrôle une lame d'énergie lumineuse",
fin: "La lame d'énergie lumineuse disparaît"
},
putrefaction: {
activation: "vient de contracter une sorte de lèpre fulgurante",
actif: "est en pleine putréfaction",
fin: "La putréfaction s'arrête.",
prejudiciable: true
},
forgeron: {
activation: "enflamme son arme",
actif: "a une arme en feu",
fin: "L'arme n'est plus enflammée."
},
armeEnflammee: {
activation: "voit son arme prendre feu",
actif: "a une arme enflammée",
fin: "L'arme n'est plus enflammée.",
},
dmgArme1d6: {
activation: "enduit son arme d'une huile magique",
actif: "a une arme plus puissante",
fin: "L'arme retrouve sa puissance normale"
},
flou: {
activation: "devient flou",
actif: "apparaît flou",
fin: "redevient net"
},
agrandissement: {
activation: "se met à grandir",
actif: "est vraiment très grand",
fin: "retrouve sa taille normale"
},
formeGazeuse: {
activation: "semble perdre de la consistance",
actif: "est en forme gazeuse",
fin: "retrouve sa consistance normale"
},
intangible: {
activation: "devient translucide",
actif: "est intangible",
fin: "redevient solide"
},
strangulation: {
activation: "commence à étouffer",
actif: "est étranglé",
fin: "respire enfin",
prejudiciable: true,
seulementVivant: true
},
ombreMortelle: {
activation: "voit son ombre s'animer et l'attaquer !",
actif: "est une ombre animée",
fin: "retrouve une ombre normale"
},
dedoublement: {
activation: "voit un double translucide sortir de lui",
actif: "est un double translucide",
fin: "le double disparaît"
},
zoneDeSilence: {
activation: "n'entend plus rien",
actif: "est totalement sourd",
fin: "peut à nouveau entendre"
},
danseIrresistible: {
activation: "se met à danser",
actif: "danse malgré lui",
fin: "s'arrête de danser",
prejudiciable: true
},
confusion: {
activation: "ne sait plus très bien ce qu'il fait là",
actif: "est en pleine confusion",
fin: "retrouve ses esprits",
prejudiciable: true
},
murDeForce: {
activation: "fait apparaître un mur de force",
actif: "en entouré d'un mur de force",
fin: "voit son mur de force disparaître"
},
asphyxie: {
activation: "commence à manquer d'air",
actif: "étouffe",
fin: "peut à nouveau respirer",
prejudiciable: true,
seulementVivant: true
},
forceDeGeant: {
activation: "devient plus fort",
actif: "a une force de géant",
fin: "retrouve sa force normale"
},
saignementsSang: {
activation: "commence à saigner du nez, des oreilles et des yeux",
actif: "saigne de tous les orifices du visage",
fin: "ne saigne plus",
prejudiciable: true
},
encaisserUnCoup: {
activation: "se place de façon à dévier un coup sur son armure",
actif: "est placé de façon à dévier un coup",
fin: "n'est plus en position pour encaisser un coup"
},
absorberUnCoup: {
activation: "se prépare à absorber un coup avec son bouclier",
actif: "est prêt à absorber un coup avec son bouclier",
fin: "n'est plus en position de prendre le prochain coup sur son bouclier"
},
absorberUnSort: {
activation: "se prépare à absorber un sort avec son bouclier",
actif: "est prêt à absorber un sort avec son bouclier",
fin: "n'est plus en position de se protéger d'un sort avec son bouclier"
},
nueeDInsectes: {
activation: "est attaqué par une nuée d'insectes",
actif: "est entouré d'une nuée d'insectes",
fin: "est enfin débarassé des insectes",
prejudiciable: true
},
prisonVegetale: {
activation: "voit des plantes pousser et s'enrouler autour de ses jambes",
actif: "est bloqué par des plantes",
fin: "se libère des plantes",
prejudiciable: true
},
protectionContreLesElements: {
activation: "lance un sort de protection contre les éléments",
actif: "est protégé contre les éléments",
fin: "n'est plus protégé contre les éléments"
},
masqueMortuaire: {
activation: "prend l'apparence de la mort",
actif: "semble mort et animé",
fin: "retrouve une apparence de vivant"
},
armeBrulante: {
activation: "sent son arme lui chauffer la main",
actif: "se brûle la main sur son arme",
fin: "sent son arme refroidir"
},
armureBrulante: {
activation: "sent son armure chauffer",
actif: "brûle dans son armure",
fin: "sent son armure refroidir"
},
masqueDuPredateur: {
activation: "prend les traits d'un prédateur",
actif: "a les traits d'un prédateur",
fin: "redevient normal"
},
aspectDeLaSuccube: {
activation: "acquiert une beauté fascinante",
actif: "est d'une beauté fascinante",
fin: "retrouve sa beauté habituelle"
},
sangMordant: {
activation: "transforme son sang",
actif: "a du sang acide",
fin: "retrouve un sang normal"
},
armeSecreteBarde: {
activation: "est déstabilisé",
actif: "est déstabilisé par une action de charme",
fin: "retrouve ses esprits",
prejudiciable: true
},
regeneration: {
activation: "commence à se régénérer",
actif: "se régénère",
fin: "a fini de se régénérer"
},
arbreAnime: {
activation: "commence à bouger",
actif: "est un arbre animé",
fin: "redevient un arbre ordinaire"
},
magnetisme: {
activation: "contrôle le magnétisme",
actif: "contrôle le magnétisme",
fin: "relache son contrôle du magnétisme"
},
hate: {
activation: "voit son métabolisme s'accélérer",
actif: "peut faire une action de plus par tour",
fin: "retrouve un métabolisme normal (plus d'action supplémentaire)"
},
ailesCelestes: {
activation: "sent des ailes célestes lui pousser dans le dos",
actif: "possède des ailes célestes",
fin: "n'a plus d'aile céleste. Espérons qu'il était au sol..."
},
sanctuaire: {
activation: "lance un sort de sanctuaire",
actif: "est protégé par un sanctuaire",
fin: "n'est plus protégé par le sanctuaire"
}
};
function buildPatternEffets(listeEffets, postfix) {
if (postfix && postfix.length === 0) postfix = undefined;
var expression = "(";
expression = _.reduce(listeEffets, function(reg, msg, effet) {
var res = reg;
if (res !== "(") res += "|";
res += "^" + effet + "(";
if (postfix) {
postfix.forEach(function(p, i) {
if (i) res += "|";
res += p + "$|" + p + "_";
});
} else res += "$|_";
res += ")";
return res;
}, expression);
expression += ")";
return new RegExp(expression);
}
var patternEffetsTemp = buildPatternEffets(messageEffetTemp);
function estEffetTemp(name) {
return (patternEffetsTemp.test(name));
}
var patternAttributEffetsTemp = buildPatternEffets(messageEffetTemp, ["Puissant", "Valeur", "SaveParTour"]);
function estAttributEffetTemp(name) {
return (patternAttributEffetsTemp.test(name));
}
function effetTempOfAttribute(attr) {
var ef = attr.get('name');
if (ef === undefined || messageEffetTemp[ef]) return ef;
for (var effet in messageEffetTemp) {
if (ef.startsWith(effet + "_")) return effet;
}
error("Impossible de déterminer l'effet correspondant à " + ef, attr);
return undefined;
}
var messageEffetCombat = {
armureMagique: {
activation: "est entouré d'un halo magique",
actif: "est protégé par une armure magique",
fin: "n'est plus entouré d'un halo magique"
},
criDeGuerre: {
activation: "pousse son cri de guerre",
actif: "a effrayé ses adversaires",
fin: "n'effraie plus ses adversaires"
},
armureDuMage: {
activation: "fait apparaître un nuage magique argenté qui le protège",
actif: "est entouré d'une armure du mage",
fin: "n'a plus son armure du mage"
},
armeDArgent: {
activation: "crée une arme d'argent et de lumière",
actif: "possède une arme d'argent et de lumière",
fin: "ne possède plus d'arme d'argent et de lumière"
},
protectionContreLeMal: {
activation: "reçoit une bénédiction de protection contre le mal",
actif: "est protégé contre le mal",
fin: "n'est plus protégé contre le mal"
},
rageDuBerserk: {
activation: "entre dans une rage berserk",
actif: "est dans une rage berserk",
fin: "retrouve son calme"
},
bonusInitEmbuscade: { //Effet interne pour la capacité Surveillance
activation: "a un temps d'avance en cas d'embuscade",
actif: "a un temps d'avance",
fin: ""
},
putrefactionOutreTombe: {
activation: "sent ses chairs pourrir",
actif: "subit le contrecoup d'une putréfaction",
fin: "se remet de la putréfaction",
prejudiciable: true
}
};
var patternEffetsCombat = buildPatternEffets(messageEffetCombat);
function estEffetCombat(name) {
return (patternEffetsCombat.test(name));
}
var patternAttributEffetsCombat = buildPatternEffets(messageEffetCombat, ["Puissant", "Valeur", "SaveParTour"]);
function estAttributEffetCombat(name) {
return (patternAttributEffetsCombat.test(name));
}
function effetCombatOfAttribute(attr) {
var ef = attr.get('name');
if (ef === undefined || messageEffetCombat[ef]) return ef;
for (var effet in messageEffetCombat) {
if (ef.startsWith(effet + "_")) return effet;
}
error("Impossible de déterminer l'effet correspondant à " + ef, attr);
return undefined;
}
var messageEffetIndetermine = {
aCheval: { //deprecated, mieux vaut utiliser la commande !cof-en-selle
activation: "monte sur sa monture",
actif: "est sur sa monture",
fin: "descend de sa monture"
},
marcheSylvestre: {
activation: "se deplace maintenant en terrain difficile",
actif: "profite du terrain difficile",
fin: "est maintenant en terrain normal"
}
};
var patternEffetsIndetermine = buildPatternEffets(messageEffetIndetermine);
function estEffetIndetermine(name) {
return (patternEffetsIndetermine.test(name));
}
function effetIndetermineOfAttribute(attr) {
var ef = attr.get('name');
if (ef === undefined || messageEffetIndetermine[ef]) return ef;
for (var effet in messageEffetIndetermine) {
if (ef.startsWith(effet + "_")) return effet;
}
error("Impossible de déterminer l'effet correspondant à " + ef, attr);
return undefined;
}
// Fait foo sur tous les tokens représentant charId, ayant l'effet donné, et correspondant au nom d'attribut. Pour le cas où le token doit être lié au personnage, on ne prend qu'un seul token, sauf si filterUnique est défini, auquel cas on fait l'appel sur tous les tokens qui passent filterUnique
function iterTokensOfEffet(charId, effet, attrName, foo, filterUnique) {
var total = 1; //Nombre de tokens affectés, pour gérer l'asynchronie si besoin
if (attrName == effet) { //token lié au character
var tokens =
findObjs({
_type: 'graphic',
_subtype: 'token',
represents: charId
});
tokens = tokens.filter(function(tok) {
return (tok.get('bar1_link') !== '');
});
if (tokens.length === 0) {
log("Pas de token pour un personnage");
log(charId);
log(attrName);
return;
}
if (filterUnique) {
total = tokens.length;
tokens.forEach(function(tok) {
if (filterUnique(tok)) foo(tok, total);
});
} else foo(tokens[0], 1);
} else { //token non lié au character
var tokenName = attrName.substring(attrName.indexOf('_') + 1);
var tNames =
findObjs({
_type: 'graphic',
_subtype: 'token',
represents: charId,
name: tokenName,
bar1_link: ''
});
total = tNames.length;
if (total > 1) {
var character = getObj('character', charId);
var charName = "d'id " + charId;
if (character) charName = character.get('name');
error("Attention, il y a plusieurs tokens nommés " + tokenName + ", instances du personnage " + charName, total);
}
tNames.forEach(function(tok) {
foo(tok, total);
});
}
}
function getValeurOfEffet(perso, effet, def, attrDef) {
var attrsVal = tokenAttribute(perso, effet + "Valeur");
if (attrsVal.length === 0) {
if (attrDef) return charAttributeAsInt(perso, attrDef, def);
return def;
}
var res = parseInt(attrsVal[0].get('current'));
if (isNaN(res)) return def;
return res;
}
function enleverEffetAttribut(charId, effet, attrName, attribut, evt) {
var nameWithSave = effet + attribut + attrName.substr(effet.length);
findObjs({
_type: 'attribute',
_characterid: charId,
name: nameWithSave
}).
forEach(function(attrS) {
evt.deletedAttributes.push(attrS);
attrS.remove();
});
}
function finDEffet(attr, effet, attrName, charId, evt, options) { //L'effet arrive en fin de vie, doit être supprimé
options = options || {};
//Si on a un attrSave, alors on a déjà imprimé le message de fin d'effet
if (options.attrSave) { //on a un attribut associé à supprimer)
evt.deletedAttributes.push(options.attrSave);
options.attrSave.remove();
} else if (options.gardeAutresAttributs === undefined) { //On cherche si il y en a un
enleverEffetAttribut(charId, effet, attrName, 'SaveParTour', evt);
}
switch (effet) {
case 'agrandissement': //redonner sa taille normale
getObj('character', charId).get('defaulttoken', function(normalToken) {
normalToken = JSON.parse(normalToken);
var largeWidth = normalToken.width + normalToken.width / 2;
var largeHeight = normalToken.height + normalToken.height / 2;
iterTokensOfEffet(charId, effet, attrName, function(token) {
var width = token.get('width');
var height = token.get('height');
affectToken(token, 'width', width, evt);
token.set('width', normalToken.width);
affectToken(token, 'height', height, evt);
token.set('height', normalToken.height);
},
function(token) {
if (token.get('width') == largeWidth) return true;
if (token.get('height') == largeHeight) return true;
return false;
}
);
});
break;
case 'aveugleTemp':
iterTokensOfEffet(charId, effet, attrName, function(token) {
setState({
token: token,
charId: charId
}, 'aveugle', false, evt);
}, function(token) {
return true;
});
break;
case 'ralentiTemp':
iterTokensOfEffet(charId, effet, attrName, function(token) {
setState({
token: token,
charId: charId
}, 'ralenti', false, evt);
}, function(token) {
return true;
});
break;
case 'peur':
case 'peurEtourdi':
iterTokensOfEffet(charId, effet, attrName, function(token) {
setState({
token: token,
charId: charId
}, 'peur', false, evt);
}, function(token) {
return true;
});
break;
case 'ombreMortelle':
case 'dedoublement':
iterTokensOfEffet(charId, effet, attrName, function(token) {
token.remove();
});
break;
case 'murDeForce':
iterTokensOfEffet(charId, effet, attrName, function(token) {
var attr = tokenAttribute({
charId: charId,
token: token
}, 'murDeForceId');
if (attr.length === 0) return;
var imageMur = getObj('graphic', attr[0].get('current'));
if (imageMur) {
imageMur.remove();
}
attr[0].remove();
});
break;
case 'regeneration': //faire les soins restants
iterTokensOfEffet(charId, effet, attrName,
function(token) {
var toursRestant = attr.get('current');
if (toursRestant == 'tourFinal' || isNaN(toursRestant)) return;
var perso = {
token: token,
charId: charId
};
var regen = getValeurOfEffet(perso, 'regeneration', 3);
var soins = regen * toursRestant;
soigneToken(perso, soins, evt,
function(s) {
options.print = function(m) {}; //Pour ne pas afficher le message final.
var tempsEffectif = Math.ceil(s / regen);
sendChar(charId, "récupère encore " + s + " PV en " + tempsEffectif + " tours.");
},
function() {});
});
break;
case 'arbreAnime': //effacer le personnage
//On efface d'abord les attributs et les abilities
var charAttributes = findObjs({
_type: 'attribute',
_characterid: charId
});
charAttributes.forEach(
function(otherAttr) {
if (otherAttr.id != attr.id) otherAttr.remove();
}
);
var charAbilities = findObjs({
_type: 'ability',
_characterid: charId
});
charAbilities.forEach(
function(ab) {
ab.remove();
}
);
iterTokensOfEffet(charId, effet, attrName, function(token) {
removeFromTurnTracker(token.id, evt);
setToken(token, 'bar1_link', '', evt);
setToken(token, 'bar1_value', '', evt);
setToken(token, 'bar1_max', '', evt);
setToken(token, 'showplayers_bar1', false, evt);
setToken(token, 'represents', '', evt);
setToken(token, 'showname', false, evt);
setToken(token, 'showplayers_name', false, evt);
setToken(token, 'name', '', evt);
});
attr.remove();
sendChar(charId, messageEffetTemp[effet].fin);
var arbreChar = getObj('character', charId);
if (arbreChar) {
evt.deletedCharacters = evt.deletedCharacters || [];
evt.deletedCharacters.push({
id: charId,
name: arbreChar.get('name'),
avatar: arbreChar.get('avatar'),
attributes: charAttributes,
abilities: charAbilities
});
arbreChar.remove();
}
return; //Pas besoin de faire le reste, car plus de perso
default:
}
if (options.attrSave === undefined && !getState({
charId: charId
}, 'mort')) {
if (options.print) options.print(messageEffetTemp[effet].fin);
else sendChar(charId, messageEffetTemp[effet].fin);
}
if (options.gardeAutresAttributs === undefined) {
enleverEffetAttribut(charId, effet, attrName, 'Puissant', evt);
enleverEffetAttribut(charId, effet, attrName, 'Valeur', evt);
}
evt.deletedAttributes.push(attr);
attr.remove();
}
//asynchrone
function degatsParTour(charId, effet, attrName, dmg, type, msg, evt, options, callback) {
options = options || {};
msg = msg || '';
var count = -1;
iterTokensOfEffet(charId, effet, attrName, function(token, total) {
if (count < 0) count = total;
sendChat('', "[[" + dmg + "]]", function(res) {
var rolls = res[0];
var dmgRoll = rolls.inlinerolls[0];
var r = {
total: dmgRoll.results.total,
type: type,
display: buildinline(dmgRoll, type)
};
var perso = {
token: token,
charId: charId
};
dealDamage(perso, r, [], evt, false, options, undefined,
function(dmgDisplay, dmg) {
sendChar(charId, msg + ". " + onGenre(charId, 'Il', 'Elle') +
" subit " + dmgDisplay + " DM");
count--;
if (count === 0) callback();
});
}); //fin sendChat du jet de dé
}); //fin iterTokensOfEffet
}
//asynchrone
function soigneParTour(charId, effet, attrName, soinsExpr, msg, evt, options, callback) {
options = options || {};
msg = msg || '';
var count = -1;
iterTokensOfEffet(charId, effet, attrName, function(token, total) {
if (count < 0) count = total;
var perso = {
token: token,
charId: charId
};
var localSoinsExpr = soinsExpr;
if (options.valeur) {
var attrsVal = tokenAttribute(perso, options.valeur);
if (attrsVal.length > 0) localSoinsExpr = attrsVal[0].get('current');
}
sendChat('', "[[" + localSoinsExpr + "]]", function(res) {
var rolls = res[0];
var soinRoll = rolls.inlinerolls[0];
var soins = soinRoll.results.total;
var displaySoins = buildinline(soinRoll, 'normal', true);
soigneToken(perso, soins, evt,
function(s) {
if (s < soins) sendChar(charId, "récupère tous ses PV.");
else sendChar(charId, "récupère " + displaySoins + " PV.");
count--;
if (count === 0) callback();
},
function() {
count--;
if (count === 0) callback();
});
}); //fin sendChat du jet de dé
}); //fin iterTokensOfEffet
}
function nextTurn(cmp) {
if (!cmp.get('initiativepage')) return;
var turnOrder = cmp.get('turnorder');
var pageId = state.COFantasy.combat_pageid;
if (pageId === undefined) pageId = cmp.get('playerpageid');
if (turnOrder === '') return; // nothing in the turn order
turnOrder = JSON.parse(turnOrder);
if (turnOrder.length < 1) return; // Juste le compteur de tour
var evt = {
type: 'Personnage suivant',
attributes: [],
deletedAttributes: []
};
var active = turnOrder[0];
var lastHead = turnOrder.pop();
turnOrder.unshift(lastHead);
evt.turnorder = JSON.stringify(turnOrder);
var attrs = findObjs({
_type: 'attribute'
});
// Si on a changé d'initiative, alors diminue les effets temporaires
var init = parseInt(active.pr);
if (active.id == "-1" && active.custom == "Tour") init = 0;
var count = 0; // pour l'aspect asynchrone des effets temporaires
if (state.COFantasy.init > init) {
var attrsTemp = attrs.filter(function(obj) {
if (!estEffetTemp(obj.get('name'))) return false;
var obji = obj.get('max');
return (init < obji && obji <= state.COFantasy.init);
});
evt.init = state.COFantasy.init;
state.COFantasy.init = init;
// Boucle sur les effets temps peut être asynchrone à cause des DM
count = attrsTemp.length;
attrsTemp.forEach(function(attr) {
var charId = attr.get('characterid');
var effet = effetTempOfAttribute(attr);
if (effet === undefined) {
//erreur, on stoppe tout
log(attr);
count--;
return;
}
var attrName = attr.get('name');
var v = attr.get('current');
if (v == 'tourFinal') { //L'effet arrive en fin de vie, doit être supprimé
finDEffet(attr, effet, attrName, charId, evt);
count--;
} else { //Effet encore actif
evt.attributes.push({
attribute: attr,
current: v
});
if (v > 1) attr.set('current', v - 1);
else attr.set('current', 'tourFinal');
switch (effet) { //rien après le switch, donc on sort par un return
case 'putrefaction': //prend 1d6 DM
degatsParTour(charId, effet, attrName, "1d6", 'maladie',
"pourrit", evt, {
magique: true
},
function() {
count--;
if (count === 0) nextTurnOfActive(active, attrs, evt, pageId);
});
return;
case 'asphyxie': //prend 1d6 DM
degatsParTour(charId, effet, attrName, "1d6", 'normal',
"ne peut plus respirer", evt, {
asphyxie: true
},
function() {
count--;
if (count === 0) nextTurnOfActive(active, attrs, evt, pageId);
});
return;
case 'saignementsSang': //prend 1d6 DM
if (charAttributeAsBool(charId, 'immuniteSaignement')) {
count--;
if (count === 0) nextTurnOfActive(active, attrs, evt, pageId);
return;
}
degatsParTour(charId, effet, attrName, "1d6", 'normal',
"saigne par tous les orifices du visage", evt, {
magique: true
},
function() {
count--;
if (count === 0) nextTurnOfActive(active, attrs, evt, pageId);
});
return;
case 'armureBrulante': //prend 1d4 DM
degatsParTour(charId, effet, attrName, "1d4", 'feu',
"brûle dans son armure", evt, {},
function() {
count--;
if (count === 0) nextTurnOfActive(active, attrs, evt, pageId);
});
return;
case 'nueeDInsectes': //prend 1 DM
degatsParTour(charId, effet, attrName, "1", 'normal',
"est piqué par les insectes", evt, {},
function() {
count--;
if (count === 0) nextTurnOfActive(active, attrs, evt, pageId);
});
return;
case 'armeBrulante': //prend 1 DM
degatsParTour(charId, effet, attrName, "1", 'feu',
"se brûle avec son arme", evt, {},
function() {
count--;
if (count === 0) nextTurnOfActive(active, attrs, evt, pageId);
});
return;
case 'regeneration': //soigne
soigneParTour(charId, effet, attrName, 3, "régénère", evt, {
valeur: 'regenerationValeur'
},
function() {
count--;
if (count === 0) nextTurnOfActive(active, attrs, evt, pageId);
});
return;
case 'strangulation':
var nameDureeStrang = 'dureeStrangulation';
if (effet != attrName) { //concerne un token non lié
nameDureeStrang += attrName.substring(attrName.indexOf('_'));
}
var dureeStrang = findObjs({
_type: 'attribute',
_characterid: charId,
name: nameDureeStrang
});
if (dureeStrang.length === 0) {
var attrDuree = createObj('attribute', {
characterid: charId,
name: nameDureeStrang,
current: 0,
max: false
});
evt.attributes.push({
attribute: attrDuree,
current: null
});
} else {
var strangUpdate = dureeStrang[0].get('max');
if (strangUpdate) { //a été mis à jour il y a au plus 1 tour
evt.attributes.push({
attribute: dureeStrang[0],
current: dureeStrang[0].get('current'),
max: strangUpdate
});
dureeStrang[0].set('max', false);
} else { //Ça fait trop longtemps, on arrête tout
sendChar(charId, messageEffetTemp[effet].fin);
attr.set('current', v);
evt.attributes.pop(); //On enlève des attributs modifiés pour mettre dans les attribute supprimés.
evt.deletedAttributes.push(attr);
attr.remove();
evt.deletedAttributes.push(dureeStrang[0]);
dureeStrang[0].remove();
}
}
count--;
return;
default:
count--;
return;
}
}
}); //fin de la boucle sur tous les attributs d'effets
}
if (count === 0) nextTurnOfActive(active, attrs, evt, pageId);
}
//evt a un champ attributes et un champ deletedAttributes
function nextTurnOfActive(active, attrs, evt, pageId) {
if (active.id == "-1" && active.custom == "Tour") {
var tour = parseInt(active.pr);
if (isNaN(tour)) {
error("Tour invalide", active);
return;
}
evt.tour = tour - 1;
evt.updateNextInitSet = updateNextInitSet;
active.pr = tour - 1; // préparation au calcul de l'undo
sendChat("GM", "Début du tour " + tour);
state.COFantasy.tour = tour;
state.COFantasy.init = 1000;
// Enlever les bonus d'un tour
attrs = removeAllAttributes('actionConcertee', evt, attrs);
attrs = removeAllAttributes('intercepter', evt, attrs);
attrs = removeAllAttributes('interposer', evt, attrs);
attrs = removeAllAttributes('exemplaire', evt, attrs);
// Pour défaut dans la cuirasse, on diminue si la valeur est 2, et on supprime si c'est 1
var defautsDansLaCuirasse = allAttributesNamed(attrs, 'defautDansLaCuirasse');
defautsDansLaCuirasse.forEach(function(attr) {
if (attr.get('current') < 2) {
if (evt.deletedAttributes) evt.deletedAttributes.push(attr);
else evt.deletedAttributes = [attr];
attr.remove();
} else {
var prevAttr = {
attribute: attr,
current: 2
};
evt.attributes.push(prevAttr);
attr.set('current', 1);
}
});
// Pour la feinte, on augmente la valeur, et on supprime si la valeur est 2
var feinte = allAttributesNamed(attrs, 'feinte');
feinte.forEach(function(attr) {
var valFeinte = parseInt(attr.get('current'));
if (isNaN(valFeinte) || valFeinte > 0) {
evt.deletedAttributes.push(attr);
attr.remove();
} else {
var prevAttr = {
attribute: attr,
current: 0
};
evt.attributes.push(prevAttr);
attr.set('current', 1);
}
});
// nouveau tour : enlever le statut surpris
// et faire les actions de début de tour
var selected = [];
updateNextInitSet.forEach(function(id) {
selected.push({
_id: id
});
});
findObjs({
_type: 'graphic',
_subtype: 'token',
_pageid: pageId
}).forEach(function(tok) {
var charId = tok.get('represents');
if (charId === '') return;
var perso = {
token: tok,
charId: charId
};
if (getState(perso, 'surpris')) { //surprise
setState(perso, 'surpris', false, {});
selected.push({
_id: tok.id
});
}
var enflammeAttr = tokenAttribute(perso, 'enflamme');
if (enflammeAttr.length > 0) {
var enflamme = parseInt(enflammeAttr[0].get('current'));
// Pour ne pas faire les dégâts plusieurs fois (plusieurs tokens pour un même personnage), on utilise la valeur max de l'attribut
var dernierTourEnflamme = parseInt(enflammeAttr[0].get('max'));
if ((isNaN(dernierTourEnflamme) || dernierTourEnflamme < tour) &&
!isNaN(enflamme) && enflamme > 0) {
var d6Enflamme = randomInteger(6);
var feu = d6Enflamme + enflamme - 1;
var dmg = {
type: 'feu',
total: feu,
display: feu
};
feu = dealDamage(perso, dmg, [], evt);
sendChar(charId, " est en flamme ! " +
onGenre(charId, 'Il', 'Elle') + " subit " + feu + " DM");
if (d6Enflamme < 3) {
sendChar(charId, " les flammes s'éteignent");
removeTokenAttr(perso, 'enflamme', evt);
} else {
enflammeAttr[0].set('max', tour);
}
}
}
});
initiative(selected, evt); // met Tour à la fin et retrie
updateNextInitSet = new Set();
// Saves à faire à la fin de chaque tour
var attrsSave = attrs.filter(function(attr) {
var attrName = attr.get('name');
var indexSave = attrName.indexOf('SaveParTour');
if (indexSave < 0) return false;
return estEffetTemp(attrName.substring(0, indexSave));
});
//Les saves sont asynchrones
var count = attrsSave.length;
attrsSave.forEach(function(attr) {
var attrName = attr.get('name');
var carac = attr.get('current');
if (isNotCarac(carac)) {
error("Save par tour " + attrName + " mal formé", carac);
count--;
if (count === 0) addEvent(evt);
return;
}
var seuil = parseInt(attr.get('max'));
if (isNaN(seuil)) {
error("Save par tour " + attrName + " mal formé", seuil);
count--;
if (count === 0) addEvent(evt);
return;
}
var charId = attr.get('characterid');
var indexSave = attrName.indexOf('SaveParTour');
var effet = attrName.substring(0, indexSave);
attrName = effet + attrName.substr(indexSave + 11);
var token;
iterTokensOfEffet(charId, effet, attrName, function(tok) {
if (token === undefined) token = tok;
});
if (token === undefined) {
log("Pas de token pour le save " + attrName);
count--;
if (count === 0) addEvent(evt);
return;
}
var perso = {
token: token,
charId: charId
};
if (getState(perso, 'mort')) {
count--;
if (count === 0) addEvent(evt);
return;
}
var attrEffet = findObjs({
_type: 'attribute',
_characterid: charId,
name: attrName
});
if (attrEffet === undefined || attrEffet.length === 0) {
error("Save sans effet temporaire " + attrName, attr);
attr.remove();
count--;
if (count === 0) addEvent(evt);
return;
}
attrEffet = attrEffet[0];
var expliquer = function(msg) {
sendChar(charId, msg);
};
var msgPour = " pour ne plus être sous l'effet de " + effet;
var sujet = onGenre(charId, 'il', 'elle');
var msgReussite = ", " + sujet + " " + messageEffetTemp[effet].fin;
var msgRate = ", " + sujet + " " + messageEffetTemp[effet].actif;
var saveOpts = {
msgPour: msgPour,
msgReussite: msgReussite,
msgRate: msgRate
};
save({
carac: carac,
seuil: seuil
}, perso, expliquer, saveOpts, evt,
function(reussite) { //asynchrone
if (reussite) {
finDEffet(attrEffet, effet, attrName, charId, evt, {
attrSave: attr
});
}
count--;
if (count === 0) addEvent(evt);
});
}); //fin boucle attrSave
} else { // change the active token
setActiveToken(active.id, evt);
}
addEvent(evt);
}
function destroyToken(token) { //to remove unused local attributes
var charId = token.get('represeernts');
if (charId === "") return;
if (token.get('bar1_link') !== "") return;
var endName = "_" + token.get('name');
var tokAttr = findObjs({
_type: 'attribute',
_characterid: charId
});
tokAttr = tokAttr.filter(function(obj) {
return obj.get('name').endsWith(endName);
});
if (tokAttr.length > 0) {
log("Removing token local attributes");
log(tokAttr);
tokAttr.forEach(function(attr) {
attr.remove();
});
}
}
function estSurMonture(perso, pageId) {
var attr = tokenAttribute(perso, 'monteSur');
if (attr.length === 0) return false;
if (pageId === undefined) pageId = perso.token.get('pageid');
var monture = tokenOfId(attr[0].get('current'), attr[0].get('max'), pageId);
attr[0].remove();
if (monture === undefined) {
sendChar(perso.charId, "descend de sa monture");
return true;
}
sendChar(perso.charId, "descend de " + monture.token.get('name'));
removeTokenAttr(monture, 'estMontePar');
removeTokenAttr(monture, 'positionSurMonture');
return true;
}
function moveToken(token) {
var charId = token.get('represents');
if (charId === '') return;
var monture = {
token: token,
charId: charId
};
var pageId = token.get('pageid');
if (estSurMonture(monture, pageId)) return;
var attr = tokenAttribute(monture, 'estMontePar');
if (attr.length === 0) return;
var cavalier = tokenOfId(attr[0].get('current'), attr[0].get('max'), pageId);
if (cavalier === undefined) {
attr[0].remove();
return;
}
var x = token.get('left');
var y = token.get('top');
var position = tokenAttribute(monture, 'positionSurMonture');
if (position.length > 0) {
var dx = parseInt(position[0].get('current'));
var dy = parseInt(position[0].get('max'));
if (!(isNaN(dx) || isNaN(dy))) {
x += dx;
y += dy;
}
}
cavalier.token.set('left', x);
cavalier.token.set('top', y);
cavalier.token.set('rotation', monture.token.get('rotation') + attributeAsInt(monture, 'directionSurMonture', 0));
}
return {
apiCommand: apiCommand,
nextTurn: nextTurn,
destroyToken: destroyToken,
moveToken: moveToken,
changeHandout: changeHandout,
};
}();
on("change:handout", function(obj, prev) {
COFantasy.changeHandout(obj, prev);
});
on("destroy:handout", function(prev) {
COFantasy.changeHandout(undefined, prev);
});
on("ready", function() {
var script_version = 0.8;
COF_loaded = true;
state.COFantasy = state.COFantasy || {
combat: false,
tour: 0,
init: 1000,
eventId: 0,
version: script_version,
};
if (state.COFantasy.version === undefined) {
state.COFantasy.eventId = 0;
state.COFantasy.version = script_version;
}
var handout = findObjs({
_type: 'handout'
});
handout.forEach(function(hand) {
COFantasy.changeHandout(hand);
});
log("COFantasy " + script_version + " loaded");
});
on("chat:message", function(msg) {
"use strict";
if (!COF_loaded || msg.type != "api") return;
COFantasy.apiCommand(msg);
});
on("change:campaign:turnorder", COFantasy.nextTurn);
on("destroy:token", COFantasy.destroyToken);
on("change:token:left", COFantasy.moveToken);
on("change:token:top", COFantasy.moveToken);
on("change:token:rotation", COFantasy.moveToken);