Merge branch 'tmrs' into 'v1.3'

Améliorations des TMRs

See merge request LeRatierBretonnien/foundryvtt-reve-de-dragon!147
This commit is contained in:
Leratier Bretonnien 2021-02-11 06:16:34 +00:00
commit 16a01ea48a
36 changed files with 1631 additions and 774 deletions

118
icons/svg/gift.svg Normal file
View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Capa_1"
x="0px"
y="0px"
width="512"
height="512"
viewBox="0 0 512 512"
xml:space="preserve"
sodipodi:docname="gift.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"><metadata
id="metadata51"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs49" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="3840"
inkscape:window-height="2066"
id="namedview47"
showgrid="false"
inkscape:zoom="1"
inkscape:cx="64"
inkscape:cy="64"
inkscape:window-x="-11"
inkscape:window-y="-11"
inkscape:window-maximized="1"
inkscape:current-layer="g14"
inkscape:document-rotation="0" />
<g
id="g14"
transform="matrix(5.7,0,0,5.7,1.6591,1.6521435)">
<g
id="g861"
style="fill:#0044aa"
transform="matrix(3.8,0,0,3.8,3.0606105,3.0571933)">
<path
d="m 15.516,6.331 c 1.966,-0.309 3.471,-1.127 3.471,-3.023 0,-0.685 -0.989,-1.596 -2.675,-1.215 -1.674,0.378 -3.837,2.241 -4.435,3.206 -0.153,-0.367 -0.515,-0.625 -0.938,-0.625 -0.421,0 -0.784,0.257 -0.938,0.623 C 9.403,4.334 7.241,2.469 5.565,2.092 3.88,1.712 2.89,2.622 2.89,3.307 2.89,5.204 4.395,6.021 6.361,6.33 H 0 v 4.167 h 1.323 v 1.778 l 1.778,-1.777 h 0.736 l -2.515,2.516 v 2.302 l 4.817,-4.817 h 0.736 l -5.553,5.554 v 2.303 L 9.178,10.5 H 9.374 V 6.356 C 9.647,6.286 9.86,6.189 9.99,6.062 c 0.15,0.378 0.516,0.648 0.948,0.648 0.432,0 0.8,-0.27 0.946,-0.65 0.13,0.127 0.343,0.225 0.616,0.294 v 4.143 h 0.456 L 3.58,19.874 h 2.302 l 9.375,-9.376 h 0.736 L 6.618,19.874 H 8.92 l 9.375,-9.376 h 0.737 l -9.376,9.376 h 2.303 l 8.592,-8.594 v -0.782 h 1.323 V 6.331 Z M 7.136,6.072 C 5.506,5.889 4.205,5.299 4.205,3.779 c 0,-0.507 0.734,-1.182 1.981,-0.9 1.451,0.327 3.405,2.167 3.405,2.674 0,0.508 -0.978,0.685 -2.455,0.519 z m 5.149,-0.518 c 0,-0.507 1.954,-2.346 3.405,-2.674 1.247,-0.282 1.98,0.393 1.98,0.9 0,1.52 -1.299,2.109 -2.931,2.293 -1.476,0.165 -2.454,-0.012 -2.454,-0.519 z"
id="path851"
style="fill:#0044aa" />
<polygon
points="2.843,19.874 12.218,10.498 12.5,10.498 10.938,8.415 9.375,10.498 9.916,10.498 1.323,19.091 1.323,19.874 "
id="polygon853"
style="fill:#0044aa" />
<polygon
points="20.553,19.874 20.553,18.095 18.774,19.874 "
id="polygon855"
style="fill:#0044aa" />
<polygon
points="18.038,19.874 20.553,17.359 20.553,15.056 15.734,19.874 "
id="polygon857"
style="fill:#0044aa" />
<polygon
points="14.998,19.874 20.553,14.32 20.553,12.017 12.695,19.874 "
id="polygon859"
style="fill:#0044aa" />
</g></g>
<g
id="g16">
</g>
<g
id="g18">
</g>
<g
id="g20">
</g>
<g
id="g22">
</g>
<g
id="g24">
</g>
<g
id="g26">
</g>
<g
id="g28">
</g>
<g
id="g30">
</g>
<g
id="g32">
</g>
<g
id="g34">
</g>
<g
id="g36">
</g>
<g
id="g38">
</g>
<g
id="g40">
</g>
<g
id="g42">
</g>
<g
id="g44">
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

94
icons/svg/wave.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -5,7 +5,6 @@
import { RdDUtility } from "./rdd-utility.js"; import { RdDUtility } from "./rdd-utility.js";
import { HtmlUtility } from "./html-utility.js"; import { HtmlUtility } from "./html-utility.js";
import { RdDItem } from "./item.js";
import { RdDItemArme } from "./item-arme.js"; import { RdDItemArme } from "./item-arme.js";
import { RdDItemCompetence } from "./item-competence.js"; import { RdDItemCompetence } from "./item-competence.js";
import { RdDBonus } from "./rdd-bonus.js"; import { RdDBonus } from "./rdd-bonus.js";

View File

@ -5,7 +5,6 @@
import { RdDUtility } from "./rdd-utility.js"; import { RdDUtility } from "./rdd-utility.js";
import { HtmlUtility } from "./html-utility.js"; import { HtmlUtility } from "./html-utility.js";
import { RdDItem } from "./item.js";
import { Misc } from "./misc.js"; import { Misc } from "./misc.js";
/* -------------------------------------------- */ /* -------------------------------------------- */

View File

@ -1,5 +1,5 @@
import { RdDUtility } from "./rdd-utility.js"; import { RdDUtility } from "./rdd-utility.js";
import { TMRType, TMRUtility } from "./tmr-utility.js"; import { TMRUtility } from "./tmr-utility.js";
import { RdDRollDialogEthylisme } from "./rdd-roll-ethylisme.js"; import { RdDRollDialogEthylisme } from "./rdd-roll-ethylisme.js";
import { RdDRoll } from "./rdd-roll.js"; import { RdDRoll } from "./rdd-roll.js";
import { RdDTMRDialog } from "./rdd-tmr-dialog.js"; import { RdDTMRDialog } from "./rdd-tmr-dialog.js";
@ -21,9 +21,10 @@ import { RdDAlchimie } from "./rdd-alchimie.js";
import { StatusEffects } from "./status-effects.js"; import { StatusEffects } from "./status-effects.js";
import { RdDItemCompetenceCreature } from "./item-competencecreature.js"; import { RdDItemCompetenceCreature } from "./item-competencecreature.js";
import { ReglesOptionelles } from "./regles-optionelles.js"; import { ReglesOptionelles } from "./regles-optionelles.js";
import { RdDItem } from "./item.js";
import { TMRRencontres } from "./tmr-rencontres.js"; import { TMRRencontres } from "./tmr-rencontres.js";
import { Poetique } from "./poetique.js"; import { Poetique } from "./poetique.js";
import { EffetsDraconiques } from "./tmr/effets-draconiques.js";
import { Draconique } from "./tmr/draconique.js";
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -36,6 +37,8 @@ export class RdDActor extends Actor {
static init() { static init() {
Hooks.on("deleteActiveEffect", (actor, effect, options) => actor.onDeleteActiveEffect(effect, options)); Hooks.on("deleteActiveEffect", (actor, effect, options) => actor.onDeleteActiveEffect(effect, options));
Hooks.on("createActiveEffect", (actor, effect, options) => actor.onCreateActiveEffect(effect, options)); Hooks.on("createActiveEffect", (actor, effect, options) => actor.onCreateActiveEffect(effect, options));
Hooks.on("createOwnedItem", (actor, item, options, id) => actor.onCreateOwnedItem(item, options, id));
Hooks.on("deleteOwnedItem", (actor, item, options, id) => actor.onDeleteOwnedItem(item, options, id));
Hooks.on("updateActor", (actor, update, options, actorId) => actor.onUpdateActor(update, options, actorId)); Hooks.on("updateActor", (actor, update, options, actorId) => actor.onUpdateActor(update, options, actorId));
} }
@ -74,7 +77,7 @@ export class RdDActor extends Actor {
} else if (data.type == "entite") { } else if (data.type == "entite") {
compendiumName = "foundryvtt-reve-de-dragon.competences-entites"; compendiumName = "foundryvtt-reve-de-dragon.competences-entites";
} }
if ( compendiumName ) { if (compendiumName) {
data.items = await RdDUtility.loadCompendium(compendiumName); data.items = await RdDUtility.loadCompendium(compendiumName);
} }
// Ajout monnaie // Ajout monnaie
@ -116,7 +119,7 @@ export class RdDActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
_prepareVehiculeData( actorData ) { _prepareVehiculeData(actorData) {
this.computeEncombrementTotalEtMalusArmure(); this.computeEncombrementTotalEtMalusArmure();
} }
@ -217,7 +220,7 @@ export class RdDActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
getAgilite() { getAgilite() {
switch(this.data.type) { switch (this.data.type) {
case 'personnage': return Misc.toInt(this.data.data.carac.agilite?.value); case 'personnage': return Misc.toInt(this.data.data.carac.agilite?.value);
case 'creature': return Misc.toInt(this.data.data.carac.force?.value); case 'creature': return Misc.toInt(this.data.data.carac.force?.value);
case 'entite': return Misc.toInt(this.data.data.carac.reve?.value); case 'entite': return Misc.toInt(this.data.data.carac.reve?.value);
@ -297,7 +300,6 @@ export class RdDActor extends Actor {
} }
return duplicate(list[0]); return duplicate(list[0]);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async deleteSortReserve(sortReserve) { async deleteSortReserve(sortReserve) {
let reserve = duplicate(this.data.data.reve.reserve); let reserve = duplicate(this.data.data.reve.reserve);
@ -318,11 +320,11 @@ export class RdDActor extends Actor {
getSurprise(isCombat = undefined) { getSurprise(isCombat = undefined) {
let niveauSurprise = Array.from(this.effects?.values() ?? []) let niveauSurprise = Array.from(this.effects?.values() ?? [])
.map(effect => StatusEffects.valeurSurprise(effect.data, isCombat)) .map(effect => StatusEffects.valeurSurprise(effect.data, isCombat))
.reduce((a,b)=> a+b, 0); .reduce((a, b) => a + b, 0);
if (niveauSurprise>1) { if (niveauSurprise > 1) {
return 'totale'; return 'totale';
} }
if (niveauSurprise==1 || this.getSonne()) { if (niveauSurprise == 1 || this.getSonne()) {
return 'demi'; return 'demi';
} }
return ''; return '';
@ -472,22 +474,19 @@ export class RdDActor extends Actor {
async dormir(heures = 1) { async dormir(heures = 1) {
let message = { let message = {
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name), whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
content: this.name +": Vous dormez " + heures + (heures > 1 ? " heures. " : "heure. ") content: this.name + ": Vous dormez " + heures + (heures > 1 ? " heures. " : "heure. ")
}; };
await this.recupereEndurance(message); await this.recupereEndurance(message);
for (let i = 0; i < heures; i++) { for (let i = 0; i < heures; i++) {
await this._recupererEthylisme(message); await this._recupererEthylisme(message);
await this.recupererFatigue(message); await this.recupererFatigue(message);
await this.recuperationReve(message); await this.recuperationReve(message);
if (this.isDonDoubleReve()) { if (EffetsDraconiques.isDonDoubleReve(this)) {
await this.recuperationReve(message); await this.recuperationReve(message);
} }
} }
ChatMessage.create(message); ChatMessage.create(message);
} }
isDonDoubleReve() {
return this.data.items.find(item => item.type == 'tete' && item.name == 'Don de double-rêve');
}
/* -------------------------------------------- */ /* -------------------------------------------- */
async _recupererEthylisme(message) { async _recupererEthylisme(message) {
@ -588,7 +587,7 @@ export class RdDActor extends Actor {
canClose: false, canClose: false,
rencontre: duplicate(TMRRencontres.getRencontre('rdd')), rencontre: duplicate(TMRRencontres.getRencontre('rdd')),
tmr: true, tmr: true,
use: {libre: false, conditions: false}, use: { libre: false, conditions: false },
forceCarac: { 'reve-actuel': { label: "Rêve Actuel", value: this.getReveActuel() } } forceCarac: { 'reve-actuel': { label: "Rêve Actuel", value: this.getReveActuel() } }
} }
rollData.rencontre.force = force; rollData.rencontre.force = force;
@ -663,16 +662,16 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
async sortMisEnReserve(rollData, sort) { async sortMisEnReserve(rollData, sort) {
let reserve = duplicate(this.data.data.reve.reserve); let reserve = duplicate(this.data.data.reve.reserve);
reserve.list.push({ coord: rollData.coord, sort: sort, draconic: duplicate(rollData.competence) }); reserve.list.push({ coord: rollData.tmr.coord, sort: sort, draconic: duplicate(rollData.competence) });
await this.update({ "data.reve.reserve": reserve }); await this.update({ "data.reve.reserve": reserve });
this.currentTMR.updateSortReserve(); this.currentTMR.updateTokens();
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async updateCarac(caracName, caracValue) { async updateCarac(caracName, caracValue) {
let caracpath = "data.carac." + caracName + ".value" let caracpath = "data.carac." + caracName + ".value"
if (caracName == "force") { if (caracName == "force") {
if ( Number(caracValue) > this.getTaille() + 4) { if (Number(caracValue) > this.getTaille() + 4) {
ui.notifications.warn("Votre FORCE doit être au maximum de TAILLE+4"); ui.notifications.warn("Votre FORCE doit être au maximum de TAILLE+4");
return; return;
} }
@ -750,7 +749,7 @@ export class RdDActor extends Actor {
} else { } else {
console.log("Competence not found", compName); console.log("Competence not found", compName);
} }
RdDUtility.checkThanatosXP( compName ); RdDUtility.checkThanatosXP(compName);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -810,14 +809,14 @@ export class RdDActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
getRecursiveEnc( objet ) { getRecursiveEnc(objet) {
let sumEnc = 0; let sumEnc = 0;
if ( objet.type == 'conteneur') { if (objet.type == 'conteneur') {
for (let id of objet.data.data.contenu) { for (let id of objet.data.data.contenu) {
let subobjet = this.items.find(objet => (id == objet._id)); let subobjet = this.items.find(objet => (id == objet._id));
if ( subobjet ) { if (subobjet) {
if ( subobjet && subobjet.type == 'conteneur') { if (subobjet && subobjet.type == 'conteneur') {
sumEnc += this.getRecursiveEnc( subobjet ); sumEnc += this.getRecursiveEnc(subobjet);
} else { } else {
sumEnc += Number(subobjet.data.data.encombrement) * Number(subobjet.data.data.quantite); sumEnc += Number(subobjet.data.data.encombrement) * Number(subobjet.data.data.quantite);
} }
@ -840,11 +839,11 @@ export class RdDActor extends Actor {
//console.log("Conteneur trouvé : ", conteneur); //console.log("Conteneur trouvé : ", conteneur);
if (conteneur && conteneur.type == "conteneur") { if (conteneur && conteneur.type == "conteneur") {
// Calculer le total actuel des contenus // Calculer le total actuel des contenus
let encContenu = this.getRecursiveEnc( conteneur ) - Number(conteneur.data.data.encombrement); let encContenu = this.getRecursiveEnc(conteneur) - Number(conteneur.data.data.encombrement);
let nouvelObjet = this.items.find(objet => (itemId == objet._id)); // On chope l'objet let nouvelObjet = this.items.find(objet => (itemId == objet._id)); // On chope l'objet
let newEnc = (nouvelObjet) ? this.getRecursiveEnc( nouvelObjet ) : 0; // Calculer le total actuel du nouvel objet let newEnc = (nouvelObjet) ? this.getRecursiveEnc(nouvelObjet) : 0; // Calculer le total actuel du nouvel objet
//console.log( currentEnc, newEnc, conteneur.data.data.capacite, conteneur.name); //console.log( currentEnc, newEnc, conteneur.data.data.capacite, conteneur.name);
if (nouvelObjet && ( (encContenu + newEnc) > Number(conteneur.data.data.capacite)) ) { if (nouvelObjet && ((encContenu + newEnc) > Number(conteneur.data.data.capacite))) {
ui.notifications.warn("Capacité d'encombrement insuffisante dans le conteneur !"); ui.notifications.warn("Capacité d'encombrement insuffisante dans le conteneur !");
return false; return false;
} }
@ -861,8 +860,8 @@ export class RdDActor extends Actor {
if (subObj && subObj.type == 'conteneur') { if (subObj && subObj.type == 'conteneur') {
this.buildSubConteneurObjetList(subId, deleteList); this.buildSubConteneurObjetList(subId, deleteList);
} }
if ( subObj) // Robust... if (subObj) // Robust...
deleteList.push( {id: subId, conteneurId: conteneurId } ); deleteList.push({ id: subId, conteneurId: conteneurId });
} }
} }
} }
@ -870,7 +869,7 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
async deleteAllConteneur(itemId) { async deleteAllConteneur(itemId) {
let list = []; let list = [];
list.push( {id: itemId, conteneurId: undefined }); // Init list list.push({ id: itemId, conteneurId: undefined }); // Init list
this.buildSubConteneurObjetList(itemId, list); this.buildSubConteneurObjetList(itemId, list);
//console.log("List to delete", list); //console.log("List to delete", list);
for (let item of list) { for (let item of list) {
@ -927,40 +926,40 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
async moveItemsBetweenActors(itemId, sourceActorId) { async moveItemsBetweenActors(itemId, sourceActorId) {
let itemsList = [] let itemsList = []
let sourceActor = game.actors.get( sourceActorId ); let sourceActor = game.actors.get(sourceActorId);
itemsList.push( {id: itemId, conteneurId: undefined }); // Init list itemsList.push({ id: itemId, conteneurId: undefined }); // Init list
sourceActor.buildSubConteneurObjetList( itemId, itemsList ); // Get itemId list sourceActor.buildSubConteneurObjetList(itemId, itemsList); // Get itemId list
let itemMap = {}; let itemMap = {};
for (let item of itemsList) { for (let item of itemsList) {
let srcItem = sourceActor.data.items.find( subItem => subItem._id == item.id ); let srcItem = sourceActor.data.items.find(subItem => subItem._id == item.id);
let newItem = await this.createOwnedItem( duplicate(srcItem) ); let newItem = await this.createOwnedItem(duplicate(srcItem));
console.log('New object', newItem, srcItem); console.log('New object', newItem, srcItem);
itemMap[srcItem._id] = newItem._id; // Pour garder le lien ancien / nouveau itemMap[srcItem._id] = newItem._id; // Pour garder le lien ancien / nouveau
} }
for (let item of itemsList) { // Second boucle pour traiter la remise en conteneurs for (let item of itemsList) { // Second boucle pour traiter la remise en conteneurs
// gestion conteneur/contenu // gestion conteneur/contenu
if ( item.conteneurId) { // l'Objet était dans un conteneur if (item.conteneurId) { // l'Objet était dans un conteneur
let newConteneurId = itemMap[item.conteneurId]; // Get conteneur let newConteneurId = itemMap[item.conteneurId]; // Get conteneur
let newConteneur = this.data.items.find( subItem => subItem._id == newConteneurId ); let newConteneur = this.data.items.find(subItem => subItem._id == newConteneurId);
let newItemId = itemMap[item.id]; // Get newItem let newItemId = itemMap[item.id]; // Get newItem
console.log('New conteneur filling!', newConteneur, newItemId, item ); console.log('New conteneur filling!', newConteneur, newItemId, item);
let contenu = duplicate(newConteneur.data.contenu); let contenu = duplicate(newConteneur.data.contenu);
contenu.push( newItemId ); contenu.push(newItemId);
await this.updateOwnedItem( {_id: newConteneurId, 'data.contenu': contenu }); await this.updateOwnedItem({ _id: newConteneurId, 'data.contenu': contenu });
} }
} }
for( let item of itemsList) { for (let item of itemsList) {
await sourceActor.deleteOwnedItem( item.id ); await sourceActor.deleteOwnedItem(item.id);
} }
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
detectSurEncombrement() { detectSurEncombrement() {
let maxEnc = 0; let maxEnc = 0;
if ( this.data.type == 'vehicule') if (this.data.type == 'vehicule')
maxEnc = this.data.data.capacite_encombrement; maxEnc = this.data.data.capacite_encombrement;
else else
maxEnc = this.data.data.attributs.encombrement.value; maxEnc = this.data.data.attributs.encombrement.value;
@ -1096,6 +1095,7 @@ export class RdDActor extends Actor {
content: this.name + " subit un Souffle de Dragon : " + souffle.name content: this.name + " subit un Souffle de Dragon : " + souffle.name
}); });
} }
// TODO: fermeture cité
return souffle; return souffle;
} }
@ -1123,41 +1123,41 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
displayTMRQueueSouffleInformation() { displayTMRQueueSouffleInformation() {
let messages = [];
for (let item of this.data.items) { for (let item of this.data.items) {
let content if (EffetsDraconiques.isConquete(item)) {
if (item.type == 'queue') { messages.push("Vous souffrez d'une <strong>Conquête</strong> : " + item.data.description);
if (item.name.toLowerCase() == 'conquête') {
content = "RAPPEL ! Vous souffrez d'une <strong>Conquête</strong> : " + item.data.description;
} }
else if (item.name.toLowerCase() == 'pélerinage') { if (EffetsDraconiques.isPelerinage(item)) {
content = "RAPPEL ! Vous souffrez d'un <strong>Pélerinage</strong> : " + item.data.description; messages.push("Vous souffrez d'un <strong>Pélerinage</strong> : " + item.data.description);
} }
else if (item.name.toLowerCase() == 'urgence draconique') { if (EffetsDraconiques.isUrgenceDraconique(item)) {
content = "RAPPEL ! Vous souffrez d'une <strong>Urgence Draconique</strong> : " + item.data.description; messages.push("Vous souffrez d'une <strong>Urgence Draconique</strong> : " + item.data.description);
} }
} else if (item.type == 'souffle') { if (EffetsDraconiques.isPeriple(item)) {
if (item.name.toLowerCase() == 'périple') { messages.push("Vous souffrez du Souffle <strong>Périple</strong>. Vous devez gérer manuellement le détail du Périple.<br>" + item.data.description);
content = "RAPPEL ! Vous souffrez du Souffle <strong>Périple</strong>. Vous devez gérer manuellement le détail du Périple.<br>" + item.data.description; }
} else if (item.name.toLowerCase() == 'fermeture des cités') { if (EffetsDraconiques.isDesorientation(item)) {
content = "RAPPEL ! Vous souffrez du Souffle <strong>Fermeture des Cités</strong>. Vous devez gérer manuellement le détail des Citées ré-ouvertes.<br>" + item.data.description; messages.push("Vous souffrez du Souffle <strong>Désorientation</strong>. Vous devez gérer avec votre MJ les effets de ce souffle.<br>" + item.data.description);
} else if (item.name.toLowerCase() == 'désorientation') {
content = "RAPPEL ! Vous souffrez du Souffle <strong>Désorientation</strong>. Vous devez gérer avec votre MJ les effets de ce souffle.<br>" + item.data.description;
} else if (item.name.toLowerCase() == 'double résistance du fleuve') {
content = "RAPPEL ! Vous souffrez du Souffle <strong>Double Résistance du Fleuve</strong>. Vous devez gérer avec votre MJ les effets de ce souffle.<br>" + item.data.description;
} }
} }
if (content) {
if (messages.length > 0) {
ChatMessage.create({ ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name), whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
content: content content: "RAPPEL !<br>" + messages.join('<hr>')
}); });
} }
} }
}
/* -------------------------------------------- */
getTMRRencontres() {
return this.data.data.reve.rencontre;
}
/* -------------------------------------------- */ /* -------------------------------------------- */
async deleteTMRRencontreAtPosition() { async deleteTMRRencontreAtPosition() {
let rencontres = duplicate(this.data.data.reve.rencontre); let rencontres = duplicate(this.getTMRRencontres());
let len = rencontres.list.length; let len = rencontres.list.length;
let i = 0; let i = 0;
//console.log("List", rencontres, len); //console.log("List", rencontres, len);
@ -1175,7 +1175,7 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
async addTMRRencontre(currentRencontre) { async addTMRRencontre(currentRencontre) {
let rencontres = duplicate(this.data.data.reve.rencontre); let rencontres = duplicate(this.getTMRRencontres());
let len = rencontres.list.length; let len = rencontres.list.length;
let i = 0; let i = 0;
let already = false; let already = false;
@ -1184,7 +1184,7 @@ export class RdDActor extends Actor {
already = true; already = true;
} }
if (!already) { if (!already) {
rencontres.list.push({ coord: this.data.data.reve.tmrpos.coord, rencontre: currentRencontre }); rencontres.list.push(currentRencontre);
await this.update({ "data.reve.rencontre": rencontres }); await this.update({ "data.reve.rencontre": rencontres });
} }
} }
@ -1301,7 +1301,7 @@ export class RdDActor extends Actor {
await RdDDice.show(myRoll); await RdDDice.show(myRoll);
let msgText = "Jet d'Endurance : " + myRoll.total + " / " + this.data.data.sante.endurance.value + "<br>"; let msgText = "Jet d'Endurance : " + myRoll.total + " / " + this.data.data.sante.endurance.value + "<br>";
if (myRoll.total == 1 || (myRoll.total != 20 && myRoll.total <= this.data.data.sante.endurance.value) ) { if (myRoll.total == 1 || (myRoll.total != 20 && myRoll.total <= this.data.data.sante.endurance.value)) {
msgText += `${this.name} a réussi son Jet d'Endurance !`; msgText += `${this.name} a réussi son Jet d'Endurance !`;
if (myRoll.total == 1) { if (myRoll.total == 1) {
msgText += `et gagne 1 Point d'Experience en Constitution`; msgText += `et gagne 1 Point d'Experience en Constitution`;
@ -1352,20 +1352,20 @@ export class RdDActor extends Actor {
const sante = duplicate(this.data.data.sante); const sante = duplicate(this.data.data.sante);
let compteur = sante[name]; let compteur = sante[name];
if (!compteur) { if (!compteur) {
return ; return;
} }
let result = { let result = {
sonne: false, sonne: false,
}; };
let minValue = name == "vie" ? -this.getSConst()-1 : 0; let minValue = name == "vie" ? -this.getSConst() - 1 : 0;
result.newValue = Math.max(minValue, Math.min(compteur.value + inc, compteur.max)); result.newValue = Math.max(minValue, Math.min(compteur.value + inc, compteur.max));
//console.log("New value ", inc, minValue, result.newValue); //console.log("New value ", inc, minValue, result.newValue);
let fatigue = 0; let fatigue = 0;
if (name == "endurance" && !this.isEntiteCauchemar()) { if (name == "endurance" && !this.isEntiteCauchemar()) {
if (result.newValue == 0 && inc < 0 && !isCritique) { // perte endurance et endurance devient 0 (sauf critique) -> -1 vie if (result.newValue == 0 && inc < 0 && !isCritique) { // perte endurance et endurance devient 0 (sauf critique) -> -1 vie
sante.vie.value --; sante.vie.value--;
} }
result.newValue = Math.max(0, result.newValue); result.newValue = Math.max(0, result.newValue);
if (inc > 0) { // le max d'endurance s'applique seulement à la récupération if (inc > 0) { // le max d'endurance s'applique seulement à la récupération
@ -1399,7 +1399,7 @@ export class RdDActor extends Actor {
} }
isDead() { isDead() {
return !this.isEntiteCauchemar() && this.data.data.sante.vie.value<-this.getSConst() return !this.isEntiteCauchemar() && this.data.data.sante.vie.value < -this.getSConst()
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -1658,7 +1658,7 @@ export class RdDActor extends Actor {
async checkCaracXP(caracName) { async checkCaracXP(caracName) {
let carac = this.data.data.carac[caracName]; let carac = this.data.data.carac[caracName];
if (carac && carac.xp > 0) { if (carac && carac.xp > 0) {
let xpNeeded = RdDUtility.getCaracNextXp(carac.value+1); let xpNeeded = RdDUtility.getCaracNextXp(carac.value + 1);
if (carac.xp >= xpNeeded) { if (carac.xp >= xpNeeded) {
carac = duplicate(carac); carac = duplicate(carac);
carac.value = Number(carac.value) + 1; carac.value = Number(carac.value) + 1;
@ -1679,12 +1679,12 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
async checkCompetenceXP(compName, newXP = undefined) { async checkCompetenceXP(compName, newXP = undefined) {
let competence = RdDItemCompetence.findCompetence(this.data.items, compName); let competence = RdDItemCompetence.findCompetence(this.data.items, compName);
if ( competence && newXP && newXP == competence.data.xp ) { // Si édition, mais sans changement XP if (competence && newXP && newXP == competence.data.xp) { // Si édition, mais sans changement XP
return; return;
} }
newXP = (newXP) ? newXP : competence.data.xp; newXP = (newXP) ? newXP : competence.data.xp;
if (competence && newXP > 0) { if (competence && newXP > 0) {
let xpNeeded = RdDItemCompetence.getCompetenceNextXp(competence.data.niveau+1); let xpNeeded = RdDItemCompetence.getCompetenceNextXp(competence.data.niveau + 1);
if (newXP >= xpNeeded) { if (newXP >= xpNeeded) {
let newCompetence = duplicate(competence); let newCompetence = duplicate(competence);
newCompetence.data.niveau += 1; newCompetence.data.niveau += 1;
@ -1712,7 +1712,7 @@ export class RdDActor extends Actor {
if (display && xpResult.result) { if (display && xpResult.result) {
let xpmsg = "<br>Points d'expérience gagnés ! Carac: " + xpResult.xpCarac + ", Comp: " + xpResult.xpCompetence; let xpmsg = "<br>Points d'expérience gagnés ! Carac: " + xpResult.xpCarac + ", Comp: " + xpResult.xpCompetence;
let message = { let message = {
whisher: ChatMessage.getWhisperRecipients( ["GM", this.name ] ), whisher: ChatMessage.getWhisperRecipients(["GM", this.name]),
content: "<strong>" + rollData.selectedCarac.label + "</strong>" + xpmsg, content: "<strong>" + rollData.selectedCarac.label + "</strong>" + xpmsg,
} }
ChatMessage.create(message); ChatMessage.create(message);
@ -1727,18 +1727,18 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
filterSortList(sortList, coord) { filterSortList(sortList, coord) {
let tmr = TMRUtility.getTMR( coord); let tmr = TMRUtility.getTMR(coord);
let letfilteredList = [] let letfilteredList = []
for ( let sort of sortList) { for (let sort of sortList) {
//console.log(sort.name, sort.data.caseTMR.toLowerCase(), sort.data.caseTMRspeciale.toLowerCase(), coord.toLowerCase() ); //console.log(sort.name, sort.data.caseTMR.toLowerCase(), sort.data.caseTMRspeciale.toLowerCase(), coord.toLowerCase() );
if (sort.data.caseTMR.toLowerCase().includes('variable') ) { if (sort.data.caseTMR.toLowerCase().includes('variable')) {
letfilteredList.push( sort); letfilteredList.push(sort);
} else if (sort.data.caseTMRspeciale.toLowerCase().includes('variable') ) { } else if (sort.data.caseTMRspeciale.toLowerCase().includes('variable')) {
letfilteredList.push( sort); letfilteredList.push(sort);
} else if (sort.data.caseTMR.toLowerCase() == tmr.type ) { } else if (sort.data.caseTMR.toLowerCase() == tmr.type) {
letfilteredList.push( sort); letfilteredList.push(sort);
} else if ( sort.data.caseTMR.toLowerCase().includes('special') && sort.data.caseTMRspeciale.toLowerCase().includes( coord.toLowerCase() ) ) { } else if (sort.data.caseTMR.toLowerCase().includes('special') && sort.data.caseTMRspeciale.toLowerCase().includes(coord.toLowerCase())) {
letfilteredList.push( sort); letfilteredList.push(sort);
} }
} }
@ -1746,22 +1746,24 @@ export class RdDActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
filterDraconicList(sortList ) { filterDraconicList(sortList) {
let draconicDone = {}; let draconicDone = {};
let newDraconicList = []; let newDraconicList = [];
let draconicList = this.getDraconicList(); let draconicList = this.getDraconicList();
let bestDraconic = this.getBestDraconic(); let bestDraconic = this.getBestDraconic();
for ( let sort of sortList) { for (let sort of sortList) {
let voie = sort.data.draconic.toLowerCase(); let voie = sort.data.draconic.toLowerCase();
let competenceVoie = draconicList.find(item => item.data.categorie == 'draconic' && item.name.toLowerCase().includes( voie ) ); let draconic = draconicList.find(item => item.data.categorie == 'draconic' && item.name.toLowerCase().includes(voie));
if ( sort.name.toLowerCase().includes('aura') ) { if (sort.name.toLowerCase().includes('aura')) {
competenceVoie = bestDraconic; draconic = bestDraconic;
} }
if (!draconicDone[competenceVoie.name]) { draconic = duplicate(draconic);
newDraconicList.push( competenceVoie ); if (draconicDone[draconic.name] == undefined) {
draconicDone[competenceVoie.name] = newDraconicList.length-1; // Patch local pour relier facilement voie/compétence draconic.data.defaut_carac = 'reve';
newDraconicList.push(draconic);
draconicDone[draconic.name] = newDraconicList.length - 1; // Patch local pour relier facilement voie/compétence
} }
sort.data.listIndex = draconicDone[competenceVoie.name] || 0; sort.data.listIndex = draconicDone[draconic.name] || 0;
} }
return newDraconicList; return newDraconicList;
} }
@ -1782,19 +1784,20 @@ export class RdDActor extends Actor {
let draconicList = this.filterDraconicList(sortList); let draconicList = this.filterDraconicList(sortList);
let rollData = { let rollData = {
selectedCarac: this.data.data.carac.reve, forceCarac: { 'reve': duplicate(this.data.data.carac.reve) },
draconicList: draconicList, draconicList: draconicList,
sortList: sortList, sortList: sortList,
competence: this.getBestDraconic(), competence: draconicList[0],
selectedSort: sortList[0], selectedSort: sortList[0],
tmr: TMRUtility.getTMR(coord), tmr: TMRUtility.getTMR(coord),
diffLibre: sortList[0].data.difficulte, // Per default at startup diffLibre: sortList[0].data.difficulte, // Per default at startup
coutreve: Array(20).fill().map((item, index) => 1 + index) coutreve: Array(20).fill().map((item, index) => 1 + index)
} }
if ( this.currentTMR ) this.currentTMR.minimize(); // Hide if (this.currentTMR) this.currentTMR.minimize(); // Hide
const dialog = await RdDRoll.create(this, rollData, const dialog = await RdDRoll.create(this, rollData,
{ html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-sort.html', {
html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-sort.html',
close: html => { this.currentTMR.maximize() } // Re-display TMR close: html => { this.currentTMR.maximize() } // Re-display TMR
}, },
{ {
@ -1820,7 +1823,7 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
isRencontreSpeciale() { // Gestion queue/souffle 'Mauvaise Rencontre en Perpective' isRencontreSpeciale() { // Gestion queue/souffle 'Mauvaise Rencontre en Perpective'
let addMsg = ""; let addMsg = "";
let rencSpecial = this.data.items.find(item => RdDItem.isHRMauvaiseRencontreEnPerspective(item)); let rencSpecial = this.data.items.find(item => EffetsDraconiques.isMauvaiseRencontre(item));
if (rencSpecial) { if (rencSpecial) {
rencSpecial = duplicate(rencSpecial); // To keep it rencSpecial = duplicate(rencSpecial); // To keep it
if (rencSpecial.type != 'souffle') { if (rencSpecial.type != 'souffle') {
@ -1839,84 +1842,28 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
getTMRFatigue() { // Pour l'instant uniquement Inertie Draconique getTMRFatigue() { // Pour l'instant uniquement Inertie Draconique
let countInertieDraconique = this.data.items.filter(item => RdDItem.isHRInertieDraconique(item)).length; let countInertieDraconique = EffetsDraconiques.countInertieDraconique(this);
if (countInertieDraconique>0) { if (countInertieDraconique > 0) {
ChatMessage.create({ ChatMessage.create({
content: `Vous êtes sous le coup d'une Inertie Draconique : vous perdez ${countInertieDraconique+1} cases de Fatigue par déplacement au lieu d'une.`, content: `Vous êtes sous le coup d'une Inertie Draconique : vous perdez ${countInertieDraconique + 1} cases de Fatigue par déplacement au lieu d'une.`,
whisper: ChatMessage.getWhisperRecipients(game.user.name) whisper: ChatMessage.getWhisperRecipients(game.user.name)
}); });
} }
return countInertieDraconique + 1; return countInertieDraconique + 1;
} }
/* -------------------------------------------- */
isConnaissanceFleuve() {
return this.data.items.find(item => item.type == 'tete' && item.name.toLowerCase().includes('connaissance du fleuve'));
}
/* -------------------------------------------- */
isReserveEnSecurite() {
let reserveSecurite = this.data.items.find(item => item.type == 'tete' && item.name.toLowerCase().includes(' en sécurité'));
return reserveSecurite;
}
/* -------------------------------------------- */
isDoubleResistanceFleuve() {
let resistFleuve = this.data.items.find(item => RdDItem.isHRDoubleResistanceFleuve(item));
if (resistFleuve) {
ChatMessage.create({
content: "Vous êtes sous le coup d'une Double Résistance du Fleuve : vous devez maîtriser 2 fois chaque case humide, un second jet est donc effectué.",
whisper: ChatMessage.getWhisperRecipients(game.user.name)
});
return true;
}
return false;
}
/* -------------------------------------------- */ /* -------------------------------------------- */
async checkSoufflePeage(cellDescr) { async checkSoufflePeage(cellDescr) {
let peage = this.data.items.find(item => RdDItem.isHRPeage(item)); let peage = this.data.items.find(item => EffetsDraconiques.isPeage(item));
if (peage && (cellDescr.type == 'pont' || cellDescr.type == 'cite')) { if (peage && (cellDescr.type == 'pont' || cellDescr.type == 'cite')) {
await this.reveActuelIncDec(-1); await this.reveActuelIncDec(-1);
ChatMessage.create({ ChatMessage.create({
content: "Vous êtes sous le coup d'un Péage : l'entrée sur cette case vous coûte 1 Point de Rêve (déduit automatiquement).", content: "Vous êtes sous le coup d'un Péage : l'entrée sur cette case vous a coûté 1 Point de Rêve (déduit automatiquement).",
whisper: ChatMessage.getWhisperRecipients(game.user.name) whisper: ChatMessage.getWhisperRecipients(game.user.name)
}); });
} }
} }
/* -------------------------------------------- */
checkTeteDeplacementAccelere() {
let deplAccelere = this.data.items.find(item => item.type == 'tete' && item.name.toLowerCase().includes(' déplacement accéléré'));
if (deplAccelere) {
return true;
}
return false;
}
/* -------------------------------------------- */
isCaseHumideAdditionelle(tmr) {
if (tmr.type == 'pont' && this.data.items.find(it => RdDItem.isHRPontImpraticable(it))) {
ChatMessage.create({
content: tmr.label +": Vous êtes sous le coup d'une Impraticabilité des Ponts : ce pont doit être maîtrisé comme une case humide.",
whisper: ChatMessage.getWhisperRecipients(game.user.name)
});
return true;
}
// Débordement ?
let isTmrInondee = this.data.items.filter(it => RdDItem.isCaseTMR(it))
.filter(it => it.data.coord == tmr.coord)
.find(it => RdDItem.isHRCaseInnondee(it));
if (isTmrInondee) {
ChatMessage.create({
content: tmr.label +": cette case est inondée, elle doit être maîtrisée comme une case humide.",
whisper: ChatMessage.getWhisperRecipients(game.user.name)
});
return true;
}
return false;
}
/* -------------------------------------------- */ /* -------------------------------------------- */
async _rollUnSortResult(rollData, isSortReserve = false) { async _rollUnSortResult(rollData, isSortReserve = false) {
let rolled = rollData.rolled; let rolled = rollData.rolled;
@ -1945,7 +1892,7 @@ export class RdDActor extends Actor {
} }
if (myReve.value > rollData.depenseReve) { if (myReve.value > rollData.depenseReve) {
// Incrémenter/gére le bonus de case // Incrémenter/gére le bonus de case
RdDItemSort.incrementBonusCase(this, selectedSort, rollData.coord); RdDItemSort.incrementBonusCase(this, selectedSort, rollData.tmr.coord);
if (rollData.isSortReserve) { if (rollData.isSortReserve) {
await this.sortMisEnReserve(rollData, selectedSort); await this.sortMisEnReserve(rollData, selectedSort);
@ -2064,7 +2011,7 @@ export class RdDActor extends Actor {
competence: competence, competence: competence,
tache: tache, tache: tache,
diffConditions: tache.data.difficulte, diffConditions: tache.data.difficulte,
use: { libre: false, conditions: false}, use: { libre: false, conditions: false },
carac: {} carac: {}
}; };
rollData.carac[tache.data.carac] = duplicate(this.data.data.carac[tache.data.carac]); // Single carac rollData.carac[tache.data.carac] = duplicate(this.data.data.carac[tache.data.carac]); // Single carac
@ -2100,19 +2047,19 @@ export class RdDActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async _rollArt(artData, selectedCarac, oeuvre, callBackResult = r =>this._resultArt(r)) { async _rollArt(artData, selected, oeuvre, callBackResult = r => this._resultArt(r)) {
mergeObject(artData, { mergeObject(artData, {
oeuvre: oeuvre, oeuvre: oeuvre,
art: oeuvre.type, art: oeuvre.type,
competence: duplicate(this.getCompetence(oeuvre.data.competence ?? artData.art)), competence: duplicate(this.getCompetence(oeuvre.data.competence ?? artData.art)),
diffLibre: - (oeuvre.data.niveau ??0), diffLibre: - (oeuvre.data.niveau ?? 0),
diffConditions: 0, diffConditions: 0,
use: { libre: false, conditions: true }, use: { libre: false, conditions: true },
selectedCarac: duplicate(this.data.data.carac[selectedCarac]), selectedCarac: duplicate(this.data.data.carac[selected]),
forceCarac: {} forceCarac: {}
}); });
artData.competence.data.defaut_carac = selectedCarac; artData.competence.data.defaut_carac = selected;
artData.forceCarac[selectedCarac] = duplicate(this.data.data.carac[selectedCarac]); artData.forceCarac[selected] = duplicate(this.data.data.carac[selected]);
console.log("rollArt !!!", artData); console.log("rollArt !!!", artData);
@ -2169,14 +2116,14 @@ export class RdDActor extends Actor {
async rollRecetteCuisine(id) { async rollRecetteCuisine(id) {
const artData = { art: 'cuisine', verbe: 'Cuisiner' }; const artData = { art: 'cuisine', verbe: 'Cuisiner' };
const oeuvre = duplicate(this.getRecetteCuisine(id)); const oeuvre = duplicate(this.getRecetteCuisine(id));
await this._rollArt(artData, 'odoratgout', oeuvre, r => this._resultRecetteCuisine(r) ); await this._rollArt(artData, 'odoratgout', oeuvre, r => this._resultRecetteCuisine(r));
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async _resultRecetteCuisine(artData) { async _resultRecetteCuisine(artData) {
const baseQualite = (artData.rolled.isSuccess ? artData.oeuvre.data.niveau : artData.competence.data.niveau); const baseQualite = (artData.rolled.isSuccess ? artData.oeuvre.data.niveau : artData.competence.data.niveau);
artData.qualiteFinale = Math.min(baseQualite, artData.oeuvre.data.niveau) + artData.rolled.ptQualite; artData.qualiteFinale = Math.min(baseQualite, artData.oeuvre.data.niveau) + artData.rolled.ptQualite;
artData.exotismeFinal = Math.min(Math.min(artData.qualiteFinale, -Math.abs(artData.oeuvre.data.exotisme??0)), 0); artData.exotismeFinal = Math.min(Math.min(artData.qualiteFinale, -Math.abs(artData.oeuvre.data.exotisme ?? 0)), 0);
console.log("OEUVRE", artData.art, artData) console.log("OEUVRE", artData.art, artData)
RdDResolutionTable.displayRollData(artData, this.name, `chat-resultat-${artData.art}.html`); RdDResolutionTable.displayRollData(artData, this.name, `chat-resultat-${artData.art}.html`);
} }
@ -2286,7 +2233,7 @@ export class RdDActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async chanceActuelleIncDec(value, limit=true) { async chanceActuelleIncDec(value, limit = true) {
let chance = duplicate(this.data.data.compteurs.chance); let chance = duplicate(this.data.data.compteurs.chance);
chance.value = Math.max(chance.value + value, 0); chance.value = Math.max(chance.value + value, 0);
if (limit) { if (limit) {
@ -2443,22 +2390,21 @@ export class RdDActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
checkMonteeLaborieuse() { // Return +1 par queue/ombre/souffle Montée Laborieuse présente countMonteeLaborieuse() { // Return +1 par queue/ombre/souffle Montée Laborieuse présente
let countMonteLaborieuse = this.data.items.filter(item => (item.type == 'queue' || item.type == 'ombre' || item.type == 'souffle') && item.name.toLowerCase().includes('montée laborieuse')).length; let countMonteeLaborieuse = this.data.items.filter(item => EffetsDraconiques.isMonteeLaborieuse(item)).length;
if (countMonteLaborieuse) { if (countMonteeLaborieuse > 0) {
ChatMessage.create({ ChatMessage.create({
content: `Vous êtes sous le coup d'une Montée Laborieuse : vos montées en TMR coûtent ${countMonteLaborieuse} Point de Rêve de plus.`, content: `Vous êtes sous le coup d'une Montée Laborieuse : vos montées en TMR coûtent ${countMonteeLaborieuse} Point de Rêve de plus.`,
whisper: ChatMessage.getWhisperRecipients(game.user.name) whisper: ChatMessage.getWhisperRecipients(game.user.name)
}); });
return countMonteLaborieuse;
} }
return 0; return countMonteeLaborieuse;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
refreshTMRView( tmrData ) { refreshTMRView(tmrData) {
console.log("REFRESH !!!!"); console.log("REFRESH !!!!");
if ( this.currentTMR ) { if (this.currentTMR) {
this.currentTMR.forceDemiRevePositionView(); this.currentTMR.forceDemiRevePositionView();
} }
} }
@ -2467,22 +2413,18 @@ export class RdDActor extends Actor {
async displayTMR(mode = "normal") { async displayTMR(mode = "normal") {
let isRapide = mode == "rapide"; let isRapide = mode == "rapide";
if (mode != "visu") { if (mode != "visu") {
let minReveValue = (isRapide) ? 3 : 2; let minReveValue = (isRapide && !EffetsDraconiques.isDeplacementAccelere(this) ? 3 : 2) + this.countMonteeLaborieuse();
if (this.getReveActuel() < minReveValue) { if (this.getReveActuel() < minReveValue) {
ChatMessage.create({ ChatMessage.create({
content: "Vous n'avez plus assez de Points de Reve pour monter dans les Terres Médianes", content: `Vous n'avez les ${minReveValue} Points de Reve nécessaires pour monter dans les Terres Médianes`,
whisper: ChatMessage.getWhisperRecipients(game.user.name) whisper: ChatMessage.getWhisperRecipients(game.user.name)
}); });
return; return;
} }
} }
if (mode != 'visu') {
// Notification au MJ
ChatMessage.create({ content: this.name + " est monté dans les TMR en mode : " + mode, whisper: ChatMessage.getWhisperRecipients("GM") });
}
let data = { let data = {
mode: mode,
fatigue: { fatigue: {
malus: RdDUtility.calculMalusFatigue(this.data.data.sante.fatigue.value, this.data.data.sante.endurance.max), malus: RdDUtility.calculMalusFatigue(this.data.data.sante.fatigue.value, this.data.data.sante.endurance.max),
html: "<table class='table-fatigue'>" + RdDUtility.makeHTMLfatigueMatrix(this.data.data.sante.fatigue.value, this.data.data.sante.endurance.max).html() + "</table>" html: "<table class='table-fatigue'>" + RdDUtility.makeHTMLfatigueMatrix(this.data.data.sante.fatigue.value, this.data.data.sante.endurance.max).html() + "</table>"
@ -2494,7 +2436,7 @@ export class RdDActor extends Actor {
isRapide: isRapide isRapide: isRapide
} }
let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-tmr.html', data); let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-tmr.html', data);
this.currentTMR = new RdDTMRDialog(html, this, data, mode); this.currentTMR = await RdDTMRDialog.create(html, this, data);
this.currentTMR.render(true); this.currentTMR.render(true);
} }
@ -2549,7 +2491,7 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
computeArmure(attackerRoll) { computeArmure(attackerRoll) {
let dmg = (attackerRoll.dmg.dmgArme ??0) + (attackerRoll.dmg.dmgActor ?? 0); let dmg = (attackerRoll.dmg.dmgArme ?? 0) + (attackerRoll.dmg.dmgActor ?? 0);
let arme = attackerRoll.arme; let arme = attackerRoll.arme;
let protection = 0; let protection = 0;
const armures = this.data.items.filter(it => it.type == "armure" && it.data.equipe); const armures = this.data.items.filter(it => it.type == "armure" && it.data.equipe);
@ -2612,7 +2554,7 @@ export class RdDActor extends Actor {
this.ajouterBlessure(encaissement); // Will upate the result table this.ajouterBlessure(encaissement); // Will upate the result table
const perteVie = this.isEntiteCauchemar() const perteVie = this.isEntiteCauchemar()
? { newValue: 0} ? { newValue: 0 }
: await this.santeIncDec("vie", - encaissement.vie); : await this.santeIncDec("vie", - encaissement.vie);
const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, (encaissement.critiques > 0)); const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, (encaissement.critiques > 0));
@ -2718,8 +2660,10 @@ export class RdDActor extends Actor {
} else { } else {
// TODO: status effect dead // TODO: status effect dead
this.addStatusEffectById('dead'); this.addStatusEffectById('dead');
ChatMessage.create({ content: `<img class="chat-icon" src="icons/svg/skull.svg" alt="charge" /> ChatMessage.create({
<strong>${this.name} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !</strong>` }); content: `<img class="chat-icon" src="icons/svg/skull.svg" alt="charge" />
<strong>${this.name} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !</strong>`
});
encaissement.critiques -= count; encaissement.critiques -= count;
encaissement.mort = true; encaissement.mort = true;
break; break;
@ -2957,9 +2901,13 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
buildVehiculesList() { buildVehiculesList() {
return this._buildActorLinksList( return this._buildActorLinksList(
this.data.data.subacteurs?.vehicules??[], this.data.data.subacteurs?.vehicules ?? [],
vehicle => {return { id: vehicle.id, name: vehicle.data.name, categorie: vehicle.data.data.categorie, vehicle => {
structure: vehicle.data.data.structure, img: vehicle.data.img } ;}); return {
id: vehicle.id, name: vehicle.data.name, categorie: vehicle.data.data.categorie,
structure: vehicle.data.data.structure, img: vehicle.data.img
};
});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -2972,35 +2920,35 @@ export class RdDActor extends Actor {
return this._buildActorLinksList(this.data.data.subacteurs?.montures ?? []); return this._buildActorLinksList(this.data.data.subacteurs?.montures ?? []);
} }
_buildActorLinksList(links, actorTransformation=it => { return { id: it.id, name: it.data.name, img: it.data.img }; }) { _buildActorLinksList(links, actorTransformation = it => { return { id: it.id, name: it.data.name, img: it.data.img }; }) {
return links.map(link => game.actors.get(link.id)) return links.map(link => game.actors.get(link.id))
.filter(it => it != null) .filter(it => it != null)
.map(actorTransformation); .map(actorTransformation);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async pushSubacteur( actor, dataArray, dataPath, dataName ) { async pushSubacteur(actor, dataArray, dataPath, dataName) {
let alreadyPresent = dataArray.find( attached => attached.id == actor.data._id); let alreadyPresent = dataArray.find(attached => attached.id == actor.data._id);
if ( !alreadyPresent ) { if (!alreadyPresent) {
let newArray = duplicate(dataArray); let newArray = duplicate(dataArray);
newArray.push( { id: actor.data._id }); newArray.push({ id: actor.data._id });
await this.update( { [dataPath]: newArray }); await this.update({ [dataPath]: newArray });
} else { } else {
ui.notifications.warn(dataName+" est déja attaché à ce Personnage."); ui.notifications.warn(dataName + " est déja attaché à ce Personnage.");
} }
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
addSubacteur( actorId ) { addSubacteur(actorId) {
let actor = game.actors.get( actorId ); let actor = game.actors.get(actorId);
//console.log("Ajout acteur : ", actor, this); //console.log("Ajout acteur : ", actor, this);
if (actor && actor.owner ) { if (actor && actor.owner) {
if (actor.data.type == 'vehicule') { if (actor.data.type == 'vehicule') {
this.pushSubacteur( actor, this.data.data.subacteurs.vehicules, 'data.subacteurs.vehicules', 'Ce Véhicule' ); this.pushSubacteur(actor, this.data.data.subacteurs.vehicules, 'data.subacteurs.vehicules', 'Ce Véhicule');
} else if (actor.data.type == 'creature') { } else if (actor.data.type == 'creature') {
this.pushSubacteur( actor, this.data.data.subacteurs.montures, 'data.subacteurs.montures', 'Cette Monture' ); this.pushSubacteur(actor, this.data.data.subacteurs.montures, 'data.subacteurs.montures', 'Cette Monture');
} else if (actor.data.type == 'personnage') { } else if (actor.data.type == 'personnage') {
this.pushSubacteur( actor, this.data.data.subacteurs.suivants, 'data.subacteurs.suivants', 'Ce Suivant' ); this.pushSubacteur(actor, this.data.data.subacteurs.suivants, 'data.subacteurs.suivants', 'Ce Suivant');
} }
} else { } else {
ui.notifications.warn("Vous n'avez pas les droits sur l'acteur que vous attachez.") ui.notifications.warn("Vous n'avez pas les droits sur l'acteur que vous attachez.")
@ -3008,13 +2956,13 @@ export class RdDActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async removeSubacteur( actorId ) { async removeSubacteur(actorId) {
let newVehicules = this.data.data.subacteurs.vehicules.filter(function(obj, index, arr){ return obj.id != actorId } ); let newVehicules = this.data.data.subacteurs.vehicules.filter(function (obj, index, arr) { return obj.id != actorId });
let newSuivants = this.data.data.subacteurs.suivants.filter(function(obj, index, arr){ return obj.id != actorId } ); let newSuivants = this.data.data.subacteurs.suivants.filter(function (obj, index, arr) { return obj.id != actorId });
let newMontures = this.data.data.subacteurs.montures.filter(function(obj, index, arr){ return obj.id != actorId } ); let newMontures = this.data.data.subacteurs.montures.filter(function (obj, index, arr) { return obj.id != actorId });
await this.update( { 'data.subacteurs.vehicules': newVehicules }); await this.update({ 'data.subacteurs.vehicules': newVehicules });
await this.update( { 'data.subacteurs.suivants': newSuivants }); await this.update({ 'data.subacteurs.suivants': newSuivants });
await this.update( { 'data.subacteurs.montures': newMontures }); await this.update({ 'data.subacteurs.montures': newMontures });
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async onUpdateActor(update, options, actorId) { async onUpdateActor(update, options, actorId) {
@ -3082,14 +3030,14 @@ export class RdDActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
deleteStatusEffectById(id, options = { renderSheet: true}) { deleteStatusEffectById(id, options = { renderSheet: true }) {
const effects = Array.from(this.effects?.values()) const effects = Array.from(this.effects?.values())
.filter(it => it.data.flags.core?.statusId == id); .filter(it => it.data.flags.core?.statusId == id);
this._deleteStatusEffects(effects, options); this._deleteStatusEffects(effects, options);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
deleteStatusEffect(effect, options = { renderSheet: true}) { deleteStatusEffect(effect, options = { renderSheet: true }) {
const toDelete = Array.from(this.effects?.values()) const toDelete = Array.from(this.effects?.values())
.filter(it => StatusEffects.statusId(it.data) == StatusEffects.statusId(effect)); .filter(it => StatusEffects.statusId(it.data) == StatusEffects.statusId(effect));
this._deleteStatusEffects(toDelete, options); this._deleteStatusEffects(toDelete, options);
@ -3107,13 +3055,13 @@ export class RdDActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async addStatusEffectById(id, options = { renderSheet: true}) { async addStatusEffectById(id, options = { renderSheet: true }) {
const statusEffect = CONFIG.statusEffects.find(it => it.id == id); const statusEffect = CONFIG.statusEffects.find(it => it.id == id);
await this.addStatusEffect(statusEffect, options); await this.addStatusEffect(statusEffect, options);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async addStatusEffect(statusEffect, options = { renderSheet: true}) { async addStatusEffect(statusEffect, options = { renderSheet: true }) {
this.deleteStatusEffectById(statusEffect.id, options); this.deleteStatusEffectById(statusEffect.id, options);
const effet = duplicate(statusEffect); const effet = duplicate(statusEffect);
effet["flags.core.statusId"] = effet.id; effet["flags.core.statusId"] = effet.id;
@ -3123,9 +3071,57 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
async updateEmbeddedEntity(embeddedName, data, options) { async updateEmbeddedEntity(embeddedName, data, options) {
if ( data && data['data.defaut_carac'] && data['data.xp'] ) { // C'est une compétence if (data && data['data.defaut_carac'] && data['data.xp']) { // C'est une compétence
this.checkCompetenceXP(data['name'], data['data.xp'] ); this.checkCompetenceXP(data['name'], data['data.xp']);
} }
return super.updateEmbeddedEntity(embeddedName, data, options); return super.updateEmbeddedEntity(embeddedName, data, options);
} }
}
/* -------------------------------------------- */
async onCreateOwnedItem(item, options, id) {
switch (item.type) {
case 'tete':
case 'queue':
case 'ombre':
case 'souffle':
await this.onCreateOwnedDraconique(item, options, id);
break;
}
}
async onDeleteOwnedItem(item, options, id) {
switch (item.type) {
case 'tete':
case 'queue':
case 'ombre':
case 'souffle':
await this.onDeleteOwnedDraconique(item, options, id);
break;
}
}
async onCreateOwnedDraconique(item, options, id) {
let draconique = Draconique.all().find(it => it.match(item));
if (draconique) {
draconique.onActorCreateOwned(this, item)
this.notifyGestionTeteSouffleQueue(item, draconique.manualMessage());
}
}
async onDeleteOwnedDraconique(item, options, id) {
let draconique = Draconique.all().find(it => it.match(item));
if (draconique) {
draconique.onActorDeleteOwned(this, item)
}
}
notifyGestionTeteSouffleQueue(item, manualMessage=true){
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
content: `${this.name} a reçu un/une ${item.type}: ${item.name}, qui ${manualMessage ? "n'est pas" : "est" } géré automatiquement. ${manualMessage ?? ''}`
});
}}

View File

@ -1,41 +0,0 @@
import { Misc } from "./misc.js";
/* -------------------------------------------- */
export class RdDItem {
static isQueueDragon(item) {
return item.type == 'queue' || item.type == 'ombre';
}
static isSouffleDragon(item) {
return item.type == 'souffle';
}
static isHRPontImpraticable(item) {
return RdDItem.isSouffleDragon(item) && item.name.toLowerCase().includes(' des ponts');
}
static isHRDoubleResistanceFleuve(item) {
return RdDItem.isSouffleDragon(item) && item.name.toLowerCase().includes('résistance du fleuve')
}
static isHRPeage(item) {
return RdDItem.isSouffleDragon(item) && item.name.toLowerCase().includes('péage')
}
static isHRMauvaiseRencontreEnPerspective(item) {
return (RdDItem.isQueueDragon(item) || RdDItem.isSouffleDragon(item)) && item.name.toLowerCase().includes('mauvaise rencontre')
}
static isHRInertieDraconique(item) {
return RdDItem.isQueueDragon(item) && item.name.toLowerCase().includes('inertie draconique')
}
static isCaseTMR(item) {
return item.type == 'casetmr';
}
static isHRCaseInnondee(item) {
return RdDItem.isCaseTMR(item) && item.data.specific == 'debordement';
}
}

View File

@ -49,7 +49,8 @@ const poesieHautReve = [
}, },
{ {
reference: 'Denis Gerfaud', reference: 'Denis Gerfaud',
extrait: `Ainsi se cuccèdent les Jours et les Ages. Les jours des Dragons sont les Ages des Hommes` extrait: `Ainsi se cuccèdent les Jours et les Ages.
<br>Les jours des Dragons sont les Ages des Hommes.`
}, },
{ {
reference: 'Denis Gerfaud', reference: 'Denis Gerfaud',

View File

@ -22,12 +22,13 @@ export class RdDCommands {
const rddCommands = new RdDCommands(); const rddCommands = new RdDCommands();
rddCommands.registerCommand({ path: ["/aide"], func: (content, msg, params) => rddCommands.help(msg), descr: "Affiche l'aide pour toutes les commandes" }); rddCommands.registerCommand({ path: ["/aide"], func: (content, msg, params) => rddCommands.help(msg), descr: "Affiche l'aide pour toutes les commandes" });
rddCommands.registerCommand({ path: ["/help"], func: (content, msg, params) => rddCommands.help(msg), descr: "Affiche l'aide pour toutes les commandes" }); rddCommands.registerCommand({ path: ["/help"], func: (content, msg, params) => rddCommands.help(msg), descr: "Affiche l'aide pour toutes les commandes" });
rddCommands.registerCommand({ path: ["/table", "queues"], func: (content, msg, params) => RdDRollTables.getQueue(), descr: "Tire une Queue de Dragon" }); rddCommands.registerCommand({ path: ["/table", "queues"], func: (content, msg, params) => RdDRollTables.getQueue(true), descr: "Tire une Queue de Dragon" });
rddCommands.registerCommand({ path: ["/table", "ombre"], func: (content, msg, params) => RdDRollTables.getOmbre(), descr: "Tire une Ombre de Dragon" }); rddCommands.registerCommand({ path: ["/table", "ombre"], func: (content, msg, params) => RdDRollTables.getOmbre(true), descr: "Tire une Ombre de Dragon" });
rddCommands.registerCommand({ path: ["/table", "tetehr"], func: (content, msg, params) => RdDRollTables.getTeteHR(), descr: "Tire une Tête de Dragon pour Hauts Revants" }); rddCommands.registerCommand({ path: ["/table", "tetehr"], func: (content, msg, params) => RdDRollTables.getTeteHR(true), descr: "Tire une Tête de Dragon pour Hauts Revants" });
rddCommands.registerCommand({ path: ["/table", "tete"], func: (content, msg, params) => RdDRollTables.getTete(), descr: "Tire une Tête de Dragon" }); rddCommands.registerCommand({ path: ["/table", "tete"], func: (content, msg, params) => RdDRollTables.getTete(true), descr: "Tire une Tête de Dragon" });
rddCommands.registerCommand({ path: ["/table", "souffle"], func: (content, msg, params) => RdDRollTables.getSouffle(), descr: " Tire un Souffle de Dragon" }); rddCommands.registerCommand({ path: ["/table", "souffle"], func: (content, msg, params) => RdDRollTables.getSouffle(true), descr: " Tire un Souffle de Dragon" });
rddCommands.registerCommand({ path: ["/table", "tarot"], func: (content, msg, params) => RdDRollTables.getTarot(), descr: "Tire une carte du Tarot Draconique" }); rddCommands.registerCommand({ path: ["/table", "comp"], func: (content, msg, params) => RdDRollTables.getCompetence(true), descr: "Tire une compétence au hasard" });
rddCommands.registerCommand({ path: ["/table", "tarot"], func: (content, msg, params) => RdDRollTables.getTarot(true), descr: "Tire une carte du Tarot Draconique" });
rddCommands.registerCommand({ path: ["/nom"], func: (content, msg, params) => RdDNameGen.getName(msg, params), descr: "Génère un nom aléatoire" }); rddCommands.registerCommand({ path: ["/nom"], func: (content, msg, params) => RdDNameGen.getName(msg, params), descr: "Génère un nom aléatoire" });
rddCommands.registerCommand({ rddCommands.registerCommand({
@ -237,7 +238,7 @@ export class RdDCommands {
getTMRAleatoire(msg, params) { getTMRAleatoire(msg, params) {
if (params.length < 2) { if (params.length < 2) {
let type = params[0]; let type = params[0];
const tmr = TMRUtility.getTMRAleatoire(type); const tmr = TMRUtility.getTMRAleatoire(it => it.type == type);
RdDCommands._chatAnswer(msg, `Case aléatoire: ${tmr.coord} - ${tmr.label}`); RdDCommands._chatAnswer(msg, `Case aléatoire: ${tmr.coord} - ${tmr.label}`);
} }
else { else {

View File

@ -1,17 +1,15 @@
import { Misc } from "./misc.js"; import { Misc } from "./misc.js";
/* -------------------------------------------- */
/**
* Mapping des types d'Item/Actor vers le nom d'affichage.
* Par défaut, on prend le type avec la première lettre
* majuscule, pas besoin d'ajouter tous les types.
*/
const typeDisplayName = { const typeDisplayName = {
"objet": "Objet",
"arme": "Arme",
"armure": "Armure",
"conteneur": "Conteneur",
"competence": "Compétence", "competence": "Compétence",
"sort": "Sort",
"herbe": "Plante", "herbe": "Plante",
"ingredient": "Ingrédient", "ingredient": "Ingrédient",
"livre": "Livre",
"potion": "Potion",
"munition": "Munition",
"queue": "Queue de dragon", "queue": "Queue de dragon",
"ombre": "Ombre de Thanatos", "ombre": "Ombre de Thanatos",
"souffle": "Souffle de Dragon", "souffle": "Souffle de Dragon",
@ -20,20 +18,16 @@ const typeDisplayName = {
"rencontresTMR": "Rencontre des TMR", "rencontresTMR": "Rencontre des TMR",
"competencecreature": "Compétence de créature", "competencecreature": "Compétence de créature",
"nombreastral": "Nombre astral", "nombreastral": "Nombre astral",
"casetmr": "Case des TMR", "casetmr": "Effet sur TMR",
"recettealchimique": "Recette alchimique", "recettealchimique": "Recette alchimique",
"recettecuisine": "Recette de cuisine", "recettecuisine": "Recette de cuisine",
"tarot": "Carte de tarot draconique", "tarot": "Carte de tarot draconique",
"tache": "Tâche", "tache": "Tâche",
"meditation": "Méditation", "meditation": "Méditation",
"monnaie": "Monnaie",
"musique": "Morceau de musique", "musique": "Morceau de musique",
"chant": "Chanson", "chant": "Chanson",
"danse": "Danse",
"jeu": "Jeu",
"personnage": "Personnage",
"creature": "Créature", "creature": "Créature",
"entite": "Entité de cauchemar", "entite": "Entité",
"vehicule": "Véhicule" "vehicule": "Véhicule"
} }

View File

@ -28,6 +28,7 @@ import { RddCompendiumOrganiser } from "./rdd-compendium-organiser.js";
import { ReglesOptionelles } from "./regles-optionelles.js"; import { ReglesOptionelles } from "./regles-optionelles.js";
import { TMRRencontres } from "./tmr-rencontres.js"; import { TMRRencontres } from "./tmr-rencontres.js";
import { RdDHotbar } from "./rdd-hotbar-drop.js" import { RdDHotbar } from "./rdd-hotbar-drop.js"
import { EffetsDraconiques } from "./tmr/effets-draconiques.js";
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Foundry VTT Initialization */ /* Foundry VTT Initialization */
@ -234,6 +235,7 @@ Hooks.once("init", async function () {
RdDActor.init(); RdDActor.init();
RddCompendiumOrganiser.init(); RddCompendiumOrganiser.init();
ReglesOptionelles.init(); ReglesOptionelles.init();
EffetsDraconiques.init()
TMRUtility.init(); TMRUtility.init();
TMRRencontres.init(); TMRRencontres.init();
RdDHotbar.initDropbar(); RdDHotbar.initDropbar();

View File

@ -179,12 +179,6 @@ export class RdDRoll extends Dialog {
console.log("RdDRollSelectDialog - Cout reve", ptreve); console.log("RdDRollSelectDialog - Cout reve", ptreve);
this.updateRollResult(); this.updateRollResult();
}); });
html.find('#ptreve-variable').change((event) => {
let ptreve = Misc.toInt(event.currentTarget.value);
this.rollData.selectedSort.data.ptreve_reel = ptreve; // Update the selectedCarac
console.log("RdDRollSelectDialog - Cout reve", ptreve);
this.updateRollResult();
});
html.find('#coupsNonMortels').change((event) => { html.find('#coupsNonMortels').change((event) => {
this.rollData.dmg.mortalite = event.currentTarget.checked ? "non-mortel" : "mortel"; this.rollData.dmg.mortalite = event.currentTarget.checked ? "non-mortel" : "mortel";
this.updateRollResult(); this.updateRollResult();

View File

@ -34,6 +34,11 @@ export class RdDRollTables {
return drawnItemRef.text; return drawnItemRef.text;
} }
/* -------------------------------------------- */
static async getCompetence(toChat = false) {
return await RdDRollTables.drawItemFromRollTable("Détermination aléatoire de compétence", toChat);
}
/* -------------------------------------------- */ /* -------------------------------------------- */
static async getSouffle(toChat = false) { static async getSouffle(toChat = false) {
return await RdDRollTables.drawItemFromRollTable("Souffles de Dragon", toChat); return await RdDRollTables.drawItemFromRollTable("Souffles de Dragon", toChat);

View File

@ -1,24 +1,31 @@
/**
* Extend the base Dialog entity by defining a custom window to perform spell.
* @extends {Dialog}
*/
import { RollDataAjustements } from "./rolldata-ajustements.js"; import { RollDataAjustements } from "./rolldata-ajustements.js";
import { RdDUtility } from "./rdd-utility.js"; import { RdDUtility } from "./rdd-utility.js";
import { TMRUtility } from "./tmr-utility.js"; import { TMRUtility, tmrConstants } from "./tmr-utility.js";
import { tmrConstants } from "./tmr-utility.js";
import { RdDResolutionTable } from "./rdd-resolution-table.js"; import { RdDResolutionTable } from "./rdd-resolution-table.js";
import { RdDTMRRencontreDialog } from "./rdd-tmr-rencontre-dialog.js"; import { RdDTMRRencontreDialog } from "./rdd-tmr-rencontre-dialog.js";
import { TMRRencontres } from "./tmr-rencontres.js"; import { TMRRencontres } from "./tmr-rencontres.js";
import { ChatUtility } from "./chat-utility.js"; import { ChatUtility } from "./chat-utility.js";
import { RdDRoll } from "./rdd-roll.js"; import { RdDRoll } from "./rdd-roll.js";
import { Poetique } from "./poetique.js"; import { Poetique } from "./poetique.js";
import { EffetsDraconiques } from "./tmr/effets-draconiques.js";
import { PixiTMR } from "./tmr/pixi-tmr.js";
import { Draconique } from "./tmr/draconique.js";
/* -------------------------------------------- */ /* -------------------------------------------- */
export class RdDTMRDialog extends Dialog { export class RdDTMRDialog extends Dialog {
static async create(html, actor, tmrData) {
if (tmrData.mode != 'visu') {
// Notification au MJ
ChatMessage.create({ content: actor.name + " est monté dans les TMR en mode : " + tmrData.mode, whisper: ChatMessage.getWhisperRecipients("GM") });
}
return new RdDTMRDialog(html, actor, tmrData);
}
/* -------------------------------------------- */ /* -------------------------------------------- */
constructor(html, actor, tmrData, mode) { constructor(html, actor, tmrData) {
const dialogConf = { const dialogConf = {
title: "Terres Médianes de Rêve", title: "Terres Médianes de Rêve",
content: html, content: html,
@ -38,19 +45,145 @@ export class RdDTMRDialog extends Dialog {
this.tmrdata = duplicate(tmrData); this.tmrdata = duplicate(tmrData);
this.actor = actor; this.actor = actor;
this.actor.tmrApp = this; // reference this app in the actor structure this.actor.tmrApp = this; // reference this app in the actor structure
this.viewOnly = mode == "visu" this.viewOnly = tmrData.mode == "visu"
this.fatigueParCase = this.viewOnly ? 0 : this.actor.getTMRFatigue(); this.fatigueParCase = this.viewOnly ? 0 : this.actor.getTMRFatigue();
this.cumulFatigue = 0; this.cumulFatigue = 0;
this.rencontresExistantes = duplicate(this.actor.data.data.reve.rencontre.list); this.loadRencontres();
this.sortReserves = duplicate(this.actor.data.data.reve.reserve.list); this.loadSortsReserve();
this.casesSpeciales = this.actor.data.items.filter(item => item.type == 'casetmr'); this.casesSpeciales = this.actor.data.items.filter(item => Draconique.isCaseTMR(item));
this.allTokens = []; this.allTokens = [];
this.rencontreState = 'aucune'; this.rencontreState = 'aucune';
this.pixiApp = new PIXI.Application({ width: 720, height: 860 }); this.pixiApp = new PIXI.Application({ width: 720, height: 860 });
this.pixiTMR = new PixiTMR(this, this.pixiApp);
this.callbacksOnAnimate = [];
if (!this.viewOnly) { if (!this.viewOnly) {
this.actor.setStatusDemiReve(true); this.actor.setStatusDemiReve(true);
this._tellToGM(this.actor.name + " monte dans les terres médianes (" + mode + ")"); this._tellToGM(this.actor.name + " monte dans les terres médianes (" + tmrData.mode + ")");
} }
// load the texture we need
this.pixiTMR.load((loader, resources) => this.createPixiSprites());
}
loadSortsReserve() {
this.sortsReserves = duplicate(this.actor.data.data.reve.reserve.list);
}
loadRencontres() {
this.rencontresExistantes = duplicate(this.actor.getTMRRencontres()).list;
}
/* -------------------------------------------- */
createPixiSprites() {
EffetsDraconiques.carteTmr._createSprite(this.pixiTMR);
this.updateTokens();
this.demiReve = this._tokenDemiReve();
this._updateDemiReve();
}
/* -------------------------------------------- */
_createTokens() {
let tokens = this._getTokensCasesTmr()
.concat(this._getTokensRencontres())
.concat(this._getTokensSortsReserve());
for (let t of tokens) {
this._trackToken(t);
}
}
updateTokens() {
this._removeTokens(t => true);
this.loadRencontres();
this.loadSortsReserve();
this._createTokens();
}
/* -------------------------------------------- */
_getTokensCasesTmr() {
return this.casesSpeciales.map(c => this._tokenCaseSpeciale(c)).filter(token => token);
}
_getTokensRencontres() {
return this.rencontresExistantes.map(it => this._tokenRencontre(it));
}
_getTokensSortsReserve() {
return this.sortsReserves.map(it => this._tokenSortEnReserve(it));
}
/* -------------------------------------------- */
_tokenRencontre(rencontre) {
return EffetsDraconiques.rencontre.token(this.pixiTMR, rencontre, () => rencontre.coord);
}
_tokenCaseSpeciale(caseSpeciale) {
const draconique = Draconique.get(caseSpeciale.data.specific);
return draconique?.token(this.pixiTMR, caseSpeciale, () => caseSpeciale.data.coord);
}
_tokenSortEnReserve(sortEnReserve) {
return EffetsDraconiques.sortReserve.token(this.pixiTMR, sortEnReserve.sort, () => sortEnReserve.coord);
}
_tokenDemiReve() {
return EffetsDraconiques.demiReve.token(this.pixiTMR, this.actor, () => this.actor.data.data.reve.tmrpos.coord);
}
_updateDemiReve() {
this._setTokenPosition(this.demiReve);
}
/* -------------------------------------------- */
async activateListeners(html) {
super.activateListeners(html);
document.getElementById("tmrrow1").insertCell(1).append(this.pixiApp.view);
if (this.viewOnly) {
html.find('#lancer-sort').remove();
}
else {
// Roll Sort
html.find('#lancer-sort').click((event) => {
this.actor.rollUnSort(this.actor.data.data.reve.tmrpos.coord);
});
}
if (this.viewOnly) {
return;
}
// Gestion du cout de montée en points de rêve
let reveCout = ((this.tmrdata.isRapide && !EffetsDraconiques.isDeplacementAccelere(this.actor)) ? -2 : -1)
- this.actor.countMonteeLaborieuse();
this.cumulFatigue += this.fatigueParCase;
await this.actor.reveActuelIncDec(reveCout);
// Le reste...
this.updateValuesDisplay();
let tmr = TMRUtility.getTMR(this.actor.data.data.reve.tmrpos.coord);
await this.manageRencontre(tmr, () => {
this.postRencontre(tmr);
this.actor.displayTMRQueueSouffleInformation();
});
}
/* -------------------------------------------- */
updateValuesDisplay() {
let ptsreve = document.getElementById("tmr-pointsreve-value");
ptsreve.innerHTML = this.actor.data.data.reve.reve.value;
let tmrpos = document.getElementById("tmr-pos");
let tmr = TMRUtility.getTMR(this.actor.data.data.reve.tmrpos.coord);
tmrpos.innerHTML = this.actor.data.data.reve.tmrpos.coord + " (" + tmr.label + ")";
let etat = document.getElementById("tmr-etatgeneral-value");
etat.innerHTML = this.actor.getEtatGeneral();
let refoulement = document.getElementById("tmr-refoulement-value");
refoulement.innerHTML = this.actor.data.data.reve.refoulement.value;
let fatigueItem = document.getElementById("tmr-fatigue-table");
//console.log("Refresh : ", this.actor.data.data.sante.fatigue.value);
fatigueItem.innerHTML = "<table class='table-fatigue'>" + RdDUtility.makeHTMLfatigueMatrix(this.actor.data.data.sante.fatigue.value, this.actor.data.data.sante.endurance.max).html() + "</table>";
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -63,51 +196,6 @@ export class RdDTMRDialog extends Dialog {
} }
} }
/* -------------------------------------------- */
displaySortReserve() {
console.debug("displaySortReserve", this.sortReserves);
for (let sort of this.sortReserves) {
this._trackToken(this._tokenSortEnReserve(sort));
}
}
/* -------------------------------------------- */
displaySpecificCase() {
for (let caseSpeciale of this.casesSpeciales) {
console.log("SPEC CASE ", caseSpeciale);
if (caseSpeciale.data.specific == 'trounoir') {
this._trackToken(this._tokenTrouNoir(caseSpeciale.data.coord));
} else if (caseSpeciale.data.specific == 'attache') {
this._trackToken(this._tokenTerreAttache(caseSpeciale.data.coord));
} else if (caseSpeciale.data.specific == 'debordement') {
this._trackToken(this._tokenDebordement(caseSpeciale.data.coord));
} else if (caseSpeciale.data.specific == 'maitrisee') {
this._trackToken(this._tokenMaitrisee(caseSpeciale.data.coord));
}
}
}
/* -------------------------------------------- */
displayPreviousRencontres() {
console.debug("displayPreviousRencontres", this.rencontresExistantes);
for (let rencontre of this.rencontresExistantes) {
this._trackToken(this._tokenRencontre(rencontre));
}
}
/* -------------------------------------------- */
updatePreviousRencontres() {
this._removeTokens(t => t.rencontre != undefined);
this.rencontresExistantes = duplicate(this.actor.data.data.reve.rencontre.list);
this.displayPreviousRencontres();
}
/* -------------------------------------------- */
updateSortReserve() {
this._removeTokens(t => t.sort != undefined);
this.sortReserves = duplicate(this.actor.data.data.reve.reserve.list);
this.displaySortReserve();
}
/* -------------------------------------------- */ /* -------------------------------------------- */
async derober() { async derober() {
@ -121,7 +209,7 @@ export class RdDTMRDialog extends Dialog {
this._tellToGM(this.actor.name + " a refoulé : " + this.currentRencontre.name); this._tellToGM(this.actor.name + " a refoulé : " + this.currentRencontre.name);
await this.actor.deleteTMRRencontreAtPosition(); // Remove the stored rencontre if necessary await this.actor.deleteTMRRencontreAtPosition(); // Remove the stored rencontre if necessary
await this.actor.ajouterRefoulement(this.currentRencontre.refoulement ?? 1); await this.actor.ajouterRefoulement(this.currentRencontre.refoulement ?? 1);
this.updatePreviousRencontres(); this.updateTokens();
console.log("-> refouler", this.currentRencontre) console.log("-> refouler", this.currentRencontre)
this.updateValuesDisplay(); this.updateValuesDisplay();
this.nettoyerRencontre(); this.nettoyerRencontre();
@ -131,7 +219,7 @@ export class RdDTMRDialog extends Dialog {
async ignorerRencontre() { async ignorerRencontre() {
this._tellToGM(this.actor.name + " a ignoré : " + this.currentRencontre.name); this._tellToGM(this.actor.name + " a ignoré : " + this.currentRencontre.name);
await this.actor.deleteTMRRencontreAtPosition(); // Remove the stored rencontre if necessary await this.actor.deleteTMRRencontreAtPosition(); // Remove the stored rencontre if necessary
this.updatePreviousRencontres(); this.updateTokens();
this.updateValuesDisplay(); this.updateValuesDisplay();
this.nettoyerRencontre(); this.nettoyerRencontre();
} }
@ -166,7 +254,7 @@ export class RdDTMRDialog extends Dialog {
} }
async choisirCaseType(type) { async choisirCaseType(type) {
const locList = TMRUtility.getListCoordTMR(type); const locList = TMRUtility.filterTMR(it => it.type == type).map(it => it.coord);
this.colorierZoneRencontre(locList); this.colorierZoneRencontre(locList);
} }
@ -200,9 +288,9 @@ export class RdDTMRDialog extends Dialog {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async maitriser() { async maitriserRencontre() {
this.actor.deleteTMRRencontreAtPosition(); this.actor.deleteTMRRencontreAtPosition();
this.updatePreviousRencontres(); this.updateTokens();
let rencontreData = { let rencontreData = {
actor: this.actor, actor: this.actor,
@ -218,6 +306,7 @@ export class RdDTMRDialog extends Dialog {
await this._tentativeMaitrise(rencontreData); await this._tentativeMaitrise(rencontreData);
} }
/* -------------------------------------------- */
async _tentativeMaitrise(rencontreData) { async _tentativeMaitrise(rencontreData) {
console.log("-> matriser", rencontreData); console.log("-> matriser", rencontreData);
@ -227,7 +316,6 @@ export class RdDTMRDialog extends Dialog {
RollDataAjustements.calcul(rencontreData, this.actor); RollDataAjustements.calcul(rencontreData, this.actor);
rencontreData.rolled = await RdDResolutionTable.roll(rencontreData.reve, RollDataAjustements.sum(rencontreData.ajustements)); rencontreData.rolled = await RdDResolutionTable.roll(rencontreData.reve, RollDataAjustements.sum(rencontreData.ajustements));
let postProcess = await TMRRencontres.gererRencontre(this, rencontreData); let postProcess = await TMRRencontres.gererRencontre(this, rencontreData);
ChatMessage.create({ ChatMessage.create({
@ -257,6 +345,7 @@ export class RdDTMRDialog extends Dialog {
} }
} }
/* -------------------------------------------- */
_deleteTmrMessages(actor, nbRounds = -1) { _deleteTmrMessages(actor, nbRounds = -1) {
setTimeout(() => { setTimeout(() => {
if (nbRounds < 0) { if (nbRounds < 0) {
@ -307,8 +396,12 @@ export class RdDTMRDialog extends Dialog {
if (rencontre) { if (rencontre) {
return rencontre; return rencontre;
} }
// TODO: dialog pour remplacer la rencontre par un présent
//if (this.casesSpeciales.find(c => EffetsDraconiques.isPresentCite(c, tmr.coord))) {
let myRoll = new Roll("1d7").evaluate().total; let myRoll = new Roll("1d7").evaluate().total;
if (TMRUtility.isForceRencontre() || myRoll== 7) { if (TMRUtility.isForceRencontre() || myRoll == 7) {
return await this.rencontreTMRRoll(tmr, this.actor.isRencontreSpeciale()); return await this.rencontreTMRRoll(tmr, this.actor.isRencontreSpeciale());
} }
this._tellToUser(myRoll + ": Pas de rencontre en " + tmr.label + " (" + tmr.coord + ")"); this._tellToUser(myRoll + ": Pas de rencontre en " + tmr.label + " (" + tmr.coord + ")");
@ -318,39 +411,18 @@ export class RdDTMRDialog extends Dialog {
/* -------------------------------------------- */ /* -------------------------------------------- */
async rencontreTMRRoll(tmr, isMauvaise = false) { async rencontreTMRRoll(tmr, isMauvaise = false) {
let rencontre = TMRUtility.utiliseForceRencontre() ?? let rencontre = TMRUtility.utiliseForceRencontre() ??
isMauvaise ? await TMRRencontres.getMauvaiseRencontre() (isMauvaise
: await TMRRencontres.getRencontreAleatoire(tmr.type); ? await TMRRencontres.getMauvaiseRencontre()
: await TMRRencontres.getRencontreAleatoire(tmr.type));
rencontre.coord = tmr.coord; rencontre.coord = tmr.coord;
rencontre.date = game.system.rdd.calendrier.getDateFromIndex(); rencontre.date = game.system.rdd.calendrier.getDateFromIndex();
rencontre.heure = game.system.rdd.calendrier.getCurrentHeure(); rencontre.heure = game.system.rdd.calendrier.getCurrentHeure();
return rencontre; return rencontre;
} }
/* -------------------------------------------- */
updateValuesDisplay() {
let ptsreve = document.getElementById("tmr-pointsreve-value");
ptsreve.innerHTML = this.actor.data.data.reve.reve.value;
let tmrpos = document.getElementById("tmr-pos");
let tmr = TMRUtility.getTMR(this.actor.data.data.reve.tmrpos.coord);
tmrpos.innerHTML = this.actor.data.data.reve.tmrpos.coord + " (" + tmr.label + ")";
let etat = document.getElementById("tmr-etatgeneral-value");
etat.innerHTML = this.actor.getEtatGeneral();
let refoulement = document.getElementById("tmr-refoulement-value");
refoulement.innerHTML = this.actor.data.data.reve.refoulement.value;
let fatigueItem = document.getElementById("tmr-fatigue-table");
//console.log("Refresh : ", this.actor.data.data.sante.fatigue.value);
fatigueItem.innerHTML = "<table class='table-fatigue'>" + RdDUtility.makeHTMLfatigueMatrix(this.actor.data.data.sante.fatigue.value, this.actor.data.data.sante.endurance.max).html() + "</table>";
}
/* -------------------------------------------- */ /* -------------------------------------------- */
async manageCaseSpeciale(tmr) { async manageCaseSpeciale(tmr) {
for (let caseTMR of this.casesSpeciales) { if (this.casesSpeciales.find(c => EffetsDraconiques.isCaseTrouNoir(c, tmr.coord))) {
if (caseTMR.data.coord == tmr.coord) { // Match !
if (caseTMR.data.specific == 'trounoir') {
let newTMR = TMRUtility.getTMRAleatoire(); let newTMR = TMRUtility.getTMRAleatoire();
let tmrPos = duplicate(this.actor.data.data.reve.tmrpos); let tmrPos = duplicate(this.actor.data.data.reve.tmrpos);
tmrPos.coord = newTMR.coord; tmrPos.coord = newTMR.coord;
@ -361,8 +433,6 @@ export class RdDTMRDialog extends Dialog {
}); });
} }
} }
}
}
/* -------------------------------------------- */ /* -------------------------------------------- */
async manageCaseHumide(tmr) { async manageCaseHumide(tmr) {
@ -376,7 +446,8 @@ export class RdDTMRDialog extends Dialog {
tmr: tmr, tmr: tmr,
canClose: false, canClose: false,
diffLibre: -7, diffLibre: -7,
forceCarac: { 'reve-actuel': { label: "Rêve Actuel", value: this.actor.getReveActuel() } } forceCarac: { 'reve-actuel': { label: "Rêve Actuel", value: this.actor.getReveActuel() } },
maitrise: { verbe: 'maîtriser', action: 'Maîtriser le fleuve' }
} }
rollData.competence.data.defaut_carac = 'reve-actuel'; rollData.competence.data.defaut_carac = 'reve-actuel';
@ -384,80 +455,125 @@ export class RdDTMRDialog extends Dialog {
} }
} }
/* -------------------------------------------- */
async _rollMaitriseCaseHumide(rollData) {
await this._maitriserTMR(rollData, r => this._resultatMaitriseCaseHumide(r));
}
async _resultatMaitriseCaseHumide(rollData) {
if (rollData.rolled.isETotal) {
rollData.souffle = await this.actor.ajouterSouffle({ chat: false });
}
this.toclose = rollData.rolled.isEchec;
if (rollData.rolled.isSuccess && !rollData.previous && EffetsDraconiques.isDoubleResistanceFleuve(this.actor)) {
rollData.previous = { rolled: rollData.rolled, ajustements: rollData.ajustements };
await this._rollMaitriseCaseHumide(rollData);
return;
}
rollData.poesie = Poetique.getExtrait();
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-resultat-maitrise-tmr.html`, rollData)
});
if (rollData.rolled.isEchec) {
this.close();
}
}
/* -------------------------------------------- */ /* -------------------------------------------- */
isCaseHumide(tmr) { isCaseHumide(tmr) {
if (!(TMRUtility.isCaseHumide(tmr) || this.isCaseHumideAdditionelle(tmr))) {
return undefined;
}
if (this.isCaseMaitrisee(tmr.coord)) { if (this.isCaseMaitrisee(tmr.coord)) {
ChatMessage.create({ ChatMessage.create({
content: tmr.label + ": cette case humide est déja maitrisée grâce à votre Tête <strong>Quête des Eaux</strong>", content: tmr.label + ": cette case humide est déja maitrisée grâce à votre Tête <strong>Quête des Eaux</strong>",
whisper: ChatMessage.getWhisperRecipients(game.user.name) whisper: ChatMessage.getWhisperRecipients(game.user.name)
}); });
return false; return undefined;
} }
if (this.actor.isCaseHumideAdditionelle(tmr)) { return -7;
return true;
}
return tmr.type == "lac" || tmr.type == "fleuve" || tmr.type == "marais";
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
isCaseMaitrisee(coordTMR) { isCaseHumideAdditionelle(tmr) {
return this.casesSpeciales.find(it => it.data.coord = coordTMR && it.data.specific == 'maitrisee'); if (tmr.type == 'pont' && EffetsDraconiques.isPontImpraticable(this.actor)) {
ChatMessage.create({
content: tmr.label + ": Vous êtes sous le coup d'une Impraticabilité des Ponts : ce pont doit être maîtrisé comme une case humide.",
whisper: ChatMessage.getWhisperRecipients(game.user.name)
});
return true;
}
if (this.isCaseInondee(tmr.coord)) {
ChatMessage.create({
content: tmr.label + ": cette case est inondée, elle doit être maîtrisée comme une case humide.",
whisper: ChatMessage.getWhisperRecipients(game.user.name)
});
return true;
}
return false;
} }
async _rollMaitriseCaseHumide(rollData) { /* -------------------------------------------- */
this.minimize(); // Hide async conquerirCiteFermee(tmr) {
const dialog = await RdDRoll.create(this.actor, rollData, if (this.viewOnly || this.currentRencontre) {
{ return;
html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-tmr-humide.html',
options:{ height: 350 },
close: html => { this.maximize(); } // Re-display TMR
},
{
name: 'maitrise',
label: 'Maîtriser le fleuve',
callbacks: [
this.actor.createCallbackExperience(),
{ action: r => this._maitriseCaseHumide(r) }
]
} }
); const citeFermee = this.isCiteFermee(tmr.coord);
dialog.render(true); if (citeFermee) {
let rollData = {
actor: this.actor,
competence: duplicate(this.actor.getBestDraconic()),
tmr: tmr,
canClose: false,
diffLibre: -9,
forceCarac: { 'reve-actuel': { label: "Rêve Actuel", value: this.actor.getReveActuel() } },
maitrise: { verbe: 'conquérir', action: 'Conquérir la cité' }
} }
rollData.competence.data.defaut_carac = 'reve-actuel';
async _maitriseCaseHumide(rollData) { await this._maitriserTMR(rollData, r => this._resultatConqueteCiteFermee(r));
}
}
async _resultatConqueteCiteFermee(rollData) {
if (rollData.rolled.isETotal) { if (rollData.rolled.isETotal) {
rollData.souffle = await this.actor.ajouterSouffle({ chat: false }); rollData.souffle = await this.actor.ajouterSouffle({ chat: false });
} }
this.toclose = rollData.rolled.isEchec; this.toclose = rollData.rolled.isEchec;
if (rollData.rolled.isSuccess) {
if (!rollData.previous && this.actor.isDoubleResistanceFleuve()) {
ChatMessage.create({
content: `Double résistance du fleuve: `,
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name)
});
rollData.previous = [rollData.rolled];
await this._rollMaitriseCaseHumide(rollData);
return;
}
}
rollData.poesie = Poetique.getExtrait(); rollData.poesie = Poetique.getExtrait();
ChatMessage.create({ ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name), whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-fleuve-tmr.html`, rollData) content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-resultat-maitrise-tmr.html`, rollData)
}); });
if (rollData.rolled.isEchec) { if (rollData.rolled.isEchec) {
this.close(); this.close();
} }
else {
const citeFermee = this.actor.data.items.find(it => EffetsDraconiques.isCiteFermee(it, rollData.tmr.coord));
this.actor.deleteOwnedItem(citeFermee._id);
this._removeTokens(t => t.coordTMR() == citeFermee.data.coord && t.caseSpeciale?._id == citeFermee._id);
}
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
isReserveExtensible(coordTMR) { async _maitriserTMR(rollData, callbackMaitrise) {
for (let caseTMR of this.casesSpeciales) { this.minimize(); // Hide
if (caseTMR.data.specific == 'reserve_extensible' && caseTMR.data.coord == coordTMR) const dialog = await RdDRoll.create(this.actor, rollData,
return true; {
html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-maitrise-tmr.html',
options: { height: 350 },
close: html => { this.maximize(); } // Re-display TMR
},
{
name: rollData.maitrise.verbe, label: rollData.maitrise.action,
callbacks: [
this.actor.createCallbackExperience(),
{ action: callbackMaitrise }
]
} }
return false; );
dialog.render(true);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -466,9 +582,9 @@ export class RdDTMRDialog extends Dialog {
return; return;
} }
let sortReserveList = TMRUtility.getSortReserveList(this.sortReserves, coordTMR); let sortReserveList = TMRUtility.getSortReserveList(this.sortsReserves, coordTMR);
if (sortReserveList.length > 0) { if (sortReserveList.length > 0) {
if (this.actor.isReserveEnSecurite() || this.isReserveExtensible(coordTMR)) { if (EffetsDraconiques.isReserveEnSecurite(this.actor) || this.isReserveExtensible(coordTMR)) {
let msg = "Vous êtes sur une case avec un Sort en Réserve. Grâce à votre Tête <strong>Reserve en Sécurité</strong> ou <strong>Réserve Exensible</strong>, vous pouvez contrôler le déclenchement. Cliquez si vous souhaitez le déclencher : <ul>"; let msg = "Vous êtes sur une case avec un Sort en Réserve. Grâce à votre Tête <strong>Reserve en Sécurité</strong> ou <strong>Réserve Exensible</strong>, vous pouvez contrôler le déclenchement. Cliquez si vous souhaitez le déclencher : <ul>";
for (let sortReserve of sortReserveList) { for (let sortReserve of sortReserveList) {
msg += "<li><a class='chat-card-button' id='sort-reserve' data-actor-id='" + this.actor._id + "' data-tmr-coord='" + coordTMR + "' data-sort-id='" + sortReserve.sort._id + "'>" + sortReserve.sort.name + "</a></li>"; msg += "<li><a class='chat-card-button' id='sort-reserve' data-actor-id='" + this.actor._id + "' data-tmr-coord='" + coordTMR + "' data-sort-id='" + sortReserve.sort._id + "'>" + sortReserve.sort.name + "</a></li>";
@ -483,9 +599,10 @@ export class RdDTMRDialog extends Dialog {
} }
} }
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
lancerSortEnReserve(coordTMR, sortId) { lancerSortEnReserve(coordTMR, sortId) {
let sortReserveList = TMRUtility.getSortReserveList(this.sortReserves, coordTMR); let sortReserveList = TMRUtility.getSortReserveList(this.sortsReserves, coordTMR);
let sortReserve = sortReserveList.find(sortReserve => sortReserve.sort._id == sortId); let sortReserve = sortReserveList.find(sortReserve => sortReserve.sort._id == sortId);
//console.log("SORT RESA", sortReserveList, coordTMR, sortId, sortReserve); //console.log("SORT RESA", sortReserveList, coordTMR, sortId, sortReserve);
if (sortReserve) { if (sortReserve) {
@ -535,24 +652,34 @@ export class RdDTMRDialog extends Dialog {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
isTerreAttache(coordTMR) { isCaseInondee(coord) {
for (let caseTMR of this.casesSpeciales) { return this.casesSpeciales.find(c => EffetsDraconiques.isCaseInondee(c, coord));
if (caseTMR.data.specific == 'attache' && caseTMR.data.coord == coordTMR) { // Match !
return true;
} }
}
return false; isCiteFermee(coord) {
return this.casesSpeciales.find(c => EffetsDraconiques.isCiteFermee(c, coord));
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
checkConnaissanceFleuve(currentTMR, nextTMR) { isTerreAttache(coord) {
if (this.actor.isConnaissanceFleuve()) { return this.casesSpeciales.find(c => EffetsDraconiques.isTerreAttache(c, coord));
//console.log(currentTMR, nextTMR );
if (TMRUtility.getTMR(currentTMR).type == 'fleuve' && TMRUtility.getTMR(nextTMR).type == 'fleuve') {
return true;
} }
/* -------------------------------------------- */
isCaseMaitrisee(coord) {
return this.casesSpeciales.find(c => EffetsDraconiques.isCaseMaitrisee(c, coord));
} }
return false;
/* -------------------------------------------- */
isReserveExtensible(coord) {
return this.casesSpeciales.find(c => EffetsDraconiques.isReserveExtensible(c, coord));
}
/* -------------------------------------------- */
isConnaissanceFleuve(currentTMR, nextTMR) {
return TMRUtility.getTMR(currentTMR).type == 'fleuve' &&
TMRUtility.getTMR(nextTMR).type == 'fleuve' &&
EffetsDraconiques.isConnaissanceFleuve(this.actor);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -562,7 +689,7 @@ export class RdDTMRDialog extends Dialog {
} }
let origEvent = event.data.originalEvent; let origEvent = event.data.originalEvent;
let tmrObject = event.target.tmrObject; let tmrObject = this;
let eventPos = RdDTMRDialog._computeEventPos(origEvent); let eventPos = RdDTMRDialog._computeEventPos(origEvent);
await tmrObject._onClickTMRPos(eventPos); // Vérifier l'état des compteurs reve/fatigue/vie await tmrObject._onClickTMRPos(eventPos); // Vérifier l'état des compteurs reve/fatigue/vie
@ -579,7 +706,7 @@ export class RdDTMRDialog extends Dialog {
// Validation de la case de destination (gestion du cas des rencontres qui peuvent téléporter) // Validation de la case de destination (gestion du cas des rencontres qui peuvent téléporter)
let deplacementType = 'erreur'; let deplacementType = 'erreur';
if (this.rencontreState == 'aucune') { // Pas de recontre en post-processing, donc deplacement normal if (this.rencontreState == 'aucune') { // Pas de recontre en post-processing, donc deplacement normal
if (!RdDTMRDialog._horsDePortee(currentPos, eventPos) || this.isTerreAttache(targetCoordTMR) || this.checkConnaissanceFleuve(currentCoordTMR, targetCoordTMR)) { if (this.isTerreAttache(targetCoordTMR) || this.isConnaissanceFleuve(currentCoordTMR, targetCoordTMR) || !RdDTMRDialog._horsDePortee(currentPos, eventPos)) {
deplacementType = 'normal'; deplacementType = 'normal';
} }
} else { } else {
@ -644,6 +771,7 @@ export class RdDTMRDialog extends Dialog {
async postRencontre(tmr) { async postRencontre(tmr) {
await this.manageCaseHumide(tmr); await this.manageCaseHumide(tmr);
await this.conquerirCiteFermee(tmr);
await this.declencheSortEnReserve(tmr.coord); await this.declencheSortEnReserve(tmr.coord);
await this.actor.checkSoufflePeage(tmr); await this.actor.checkSoufflePeage(tmr);
} }
@ -658,76 +786,11 @@ export class RdDTMRDialog extends Dialog {
await this.actor.updateCoordTMR(coordTMR); await this.actor.updateCoordTMR(coordTMR);
this._updateDemiReve(); this._updateDemiReve();
let tmr = TMRUtility.getTMR(coordTMR); let tmr = TMRUtility.getTMR(coordTMR);
this.manageCaseHumide(tmr); await this.manageCaseHumide(tmr);
await this.conquerirCiteFermee(tmr);
await this.declencheSortEnReserve(tmr.coord); await this.declencheSortEnReserve(tmr.coord);
} }
/* -------------------------------------------- */
async activateListeners(html) {
super.activateListeners(html);
var row = document.getElementById("tmrrow1");
var cell1 = row.insertCell(1);
cell1.append(this.pixiApp.view);
if (this.viewOnly) {
html.find('#lancer-sort').remove();
}
else {
// Roll Sort
html.find('#lancer-sort').click((event) => {
this.actor.rollUnSort(this.actor.data.data.reve.tmrpos.coord);
});
}
// load the texture we need
await this.pixiApp.loader
.add('tmr', 'systems/foundryvtt-reve-de-dragon/styles/img/ui/tmp_main_r1.webp')
.add('demi-reve', "icons/svg/sun.svg")
.load((loader, resources) => {
// This creates a texture from a TMR image
const mytmr = new PIXI.Sprite(resources.tmr.texture);
// Setup the position of the TMR
mytmr.x = 0;
mytmr.y = 0;
mytmr.width = 720;
mytmr.height = 860;
// Rotate around the center
mytmr.anchor.x = 0;
mytmr.anchor.y = 0;
mytmr.interactive = true;
mytmr.buttonMode = true;
mytmr.tmrObject = this;
if (!this.viewOnly) {
mytmr.on('pointerdown', this.onClickTMR);
}
this.pixiApp.stage.addChild(mytmr);
this._addDemiReve();
this.displayPreviousRencontres();
this.displaySortReserve();
this.displaySpecificCase();
});
if (this.viewOnly) {
return;
}
// Gestion du cout de montée en points de rêve
let reveCout = (this.tmrdata.isRapide && !this.actor.checkTeteDeplacementAccelere()) ? -2 : -1;
this.cumulFatigue += this.fatigueParCase;
reveCout -= this.actor.checkMonteeLaborieuse();
await this.actor.reveActuelIncDec(reveCout);
// Le reste...
this.updateValuesDisplay();
let tmr = TMRUtility.getTMR(this.actor.data.data.reve.tmrpos.coord);
await this.manageRencontre(tmr, () => {
this.postRencontre(tmr);
this.actor.displayTMRQueueSouffleInformation();
});
}
/* -------------------------------------------- */ /* -------------------------------------------- */
static _computeEventPos(origEvent) { static _computeEventPos(origEvent) {
let canvasRect = origEvent.target.getBoundingClientRect(); let canvasRect = origEvent.target.getBoundingClientRect();
@ -747,125 +810,16 @@ export class RdDTMRDialog extends Dialog {
|| (target.y == 0 && target.y < origin.y && target.x != origin.x && origin.x % 2 == 1); || (target.y == 0 && target.y < origin.y && target.x != origin.x && origin.x % 2 == 1);
} }
/* -------------------------------------------- */
_tokenRencontre(rencontre) {
let sprite = new PIXI.Graphics();
sprite.beginFill(0x767610, 0.6);
sprite.drawCircle(0, 0, 6);
sprite.endFill();
sprite.decallage = {
x: (tmrConstants.cellw / 2) - 16,
y: 16 - (tmrConstants.cellh / 2)
};
return { sprite: sprite, rencontre: rencontre, coordTMR: () => rencontre.coord };
}
/* -------------------------------------------- */
_tokenTrouNoir(coord) {
let sprite = new PIXI.Graphics();
sprite.beginFill(0x050505, 0.8);
sprite.drawCircle(0, 0, (tmrConstants.cellw / 2) - 2);
sprite.endFill();
sprite.decallage = {
x: 0,
y: 2
}
return { sprite: sprite, coordTMR: () => coord }
}
/* -------------------------------------------- */
_tokenDebordement(coord) {
let sprite = new PIXI.Graphics();
sprite.beginFill(0x0101FE, 0.3);
sprite.drawCircle(0, 0, (tmrConstants.cellw / 2) - 2);
sprite.endFill();
sprite.decallage = {
x: 0,
y: 2
}
return { sprite: sprite, coordTMR: () => coord }
}
/* -------------------------------------------- */
_tokenMaitrisee(coord) {
let sprite = new PIXI.Graphics();
sprite.beginFill(0x1010F0, 0.8);
sprite.drawCircle(0, 0, 6);
sprite.endFill();
sprite.decallage = {
x: 16 - (tmrConstants.cellw / 2),
y: 16 - (tmrConstants.cellh / 2)
}
return { sprite: sprite, coordTMR: () => coord }
}
/* -------------------------------------------- */
_tokenTerreAttache(coord) {
let sprite = new PIXI.Graphics();
sprite.beginFill(0x10F010, 0.8);
sprite.drawCircle(0, 0, 6);
sprite.endFill();
sprite.decallage = {
x: 16 - (tmrConstants.cellw / 2),
y: 16 - (tmrConstants.cellh / 2)
}
return { sprite: sprite, coordTMR: () => coord }
}
/* -------------------------------------------- */
_tokenSortEnReserve(sort) {
let sprite = new PIXI.Graphics();
sprite.beginFill(0x101010, 0.8);
sprite.drawCircle(0, 0, 6);
sprite.endFill();
sprite.decallage = {
x: 16 - (tmrConstants.cellw / 2),
y: 16 - (tmrConstants.cellh / 2)
}
return { sprite: sprite, sort: sort, coordTMR: () => sort.coord }
}
/* -------------------------------------------- */
_tokenDemiReve() {
let texture = PIXI.utils.TextureCache['demi-reve'];
let sprite = new PIXI.Sprite(texture);
sprite.width = tmrConstants.cellw * 0.7;
sprite.height = tmrConstants.cellh * 0.7;
sprite.anchor.set(0.5);
sprite.tint = 0x00FFEE;
return { sprite: sprite, actor: this.actor, coordTMR: () => this.actor.data.data.reve.tmrpos.coord }
}
/* -------------------------------------------- */
_addDemiReve() {
this.demiReve = this._tokenDemiReve();
this._setTokenPosition(this.demiReve);
this.pixiApp.stage.addChild(this.demiReve.sprite);
}
/* -------------------------------------------- */
_updateDemiReve() {
this._setTokenPosition(this.demiReve);
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/** Retourne les coordonnées x, h, w, h du rectangle d'une case donnée */ /** Retourne les coordonnées x, h, w, h du rectangle d'une case donnée */
_getCaseRectangleCoord(coord) { _getCaseRectangleCoord(coord) {
let coordXY = TMRUtility.convertToCellPos(coord); return this.pixiTMR.getCaseRectangle(TMRUtility.convertToCellPos(coord));
let decallagePairImpair = (coordXY.x % 2 == 0) ? tmrConstants.col1_y : tmrConstants.col2_y;
let x = tmrConstants.gridx + (coordXY.x * tmrConstants.cellw) - (tmrConstants.cellw / 2);
let y = tmrConstants.gridy + (coordXY.y * tmrConstants.cellh) - (tmrConstants.cellh / 2) + decallagePairImpair;
return { x: x, y: y, w: tmrConstants.cellw, h: tmrConstants.cellh }
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
_setTokenPosition(token) { _setTokenPosition(token) {
let coordXY = TMRUtility.convertToCellPos(token.coordTMR()); this.pixiTMR.setPosition(token.sprite, TMRUtility.convertToCellPos(token.coordTMR()));
let decallagePairImpair = (coordXY.x % 2 == 0) ? tmrConstants.col1_y : tmrConstants.col2_y;
let dx = (token.sprite.decallage == undefined) ? 0 : token.sprite.decallage.x;
let dy = (token.sprite.decallage == undefined) ? 0 : token.sprite.decallage.y;
token.sprite.x = tmrConstants.gridx + (coordXY.x * tmrConstants.cellw) + dx;
token.sprite.y = tmrConstants.gridy + (coordXY.y * tmrConstants.cellh) + dy + decallagePairImpair;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -878,10 +832,9 @@ export class RdDTMRDialog extends Dialog {
/* -------------------------------------------- */ /* -------------------------------------------- */
_trackToken(token) { _trackToken(token) {
this.allTokens.push(token) this.allTokens.push(token);
this._setTokenPosition(token); this._setTokenPosition(token);
this.pixiApp.stage.addChild(token.sprite);
} }
} }

View File

@ -9,7 +9,7 @@ export class RdDTMRRencontreDialog extends Dialog {
buttons: { buttons: {
derober: { icon: '<i class="fas fa-check"></i>', label: "Se dérober", callback: () => { this.onButtonFuir(() => tmrApp.derober()); } }, derober: { icon: '<i class="fas fa-check"></i>', label: "Se dérober", callback: () => { this.onButtonFuir(() => tmrApp.derober()); } },
refouler: { icon: '<i class="fas fa-check"></i>', label: "Refouler", callback: () => this.onButtonAction(() => tmrApp.refouler()) }, refouler: { icon: '<i class="fas fa-check"></i>', label: "Refouler", callback: () => this.onButtonAction(() => tmrApp.refouler()) },
maitiser: { icon: '<i class="fas fa-check"></i>', label: "Maîtriser", callback: () => this.onButtonAction(() => tmrApp.maitriser()) } maitiser: { icon: '<i class="fas fa-check"></i>', label: "Maîtriser", callback: () => this.onButtonAction(() => tmrApp.maitriserRencontre()) }
}, },
default: "derober" default: "derober"
}; };

View File

@ -112,8 +112,8 @@ export const referenceAjustements = {
getDescr: (rollData, actor) => RdDBonus.find(actor.getSurprise()).descr getDescr: (rollData, actor) => RdDBonus.find(actor.getSurprise()).descr
}, },
bonusCase: { bonusCase: {
isUsed: (rollData, actor) => rollData.selectedSort && rollData.coord, isUsed: (rollData, actor) => rollData.selectedSort && rollData.tmr.coord,
getDescr: (rollData, actor) => rollData.selectedSort && rollData.coord ? `Bonus de case: ${RdDItemSort.getCaseBonus(rollData.selectedSort, rollData.coord)}%` : '' getDescr: (rollData, actor) => rollData.selectedSort && rollData.tmr.coord ? `Bonus de case: ${RdDItemSort.getCaseBonus(rollData.selectedSort, rollData.tmr.coord)}%` : ''
}, },
rencontreTMR: { rencontreTMR: {
isVisible: (rollData, actor) => rollData.tmr && rollData.rencontre?.name, isVisible: (rollData, actor) => rollData.tmr && rollData.rencontre?.name,

View File

@ -86,7 +86,7 @@ const typeRencontres = {
changeur: { changeur: {
msgSucces: (data) => `Le ${data.rencontre.name} vaincu accepte de vous déplacer sur une autre ${TMRType[data.tmr.type].name} de votre choix en échange de sa liberté.`, msgSucces: (data) => `Le ${data.rencontre.name} vaincu accepte de vous déplacer sur une autre ${TMRType[data.tmr.type].name} de votre choix en échange de sa liberté.`,
msgEchec: (data) => { msgEchec: (data) => {
data.newTMR = TMRUtility.getTMRAleatoire(data.tmr.type); data.newTMR = TMRUtility.getTMRAleatoire(it => it.type = data.tmr.type);
return `Le ${data.rencontre.name} vous embobine avec des promesses, et vous transporte en ${data.newTMR.label} sans attendre votre avis.`; return `Le ${data.rencontre.name} vous embobine avec des promesses, et vous transporte en ${data.newTMR.label} sans attendre votre avis.`;
}, },
postSucces: (tmrDialog, data) => { postSucces: (tmrDialog, data) => {
@ -230,7 +230,7 @@ const typeRencontres = {
msgSucces: (data) => `A tout seigneur, tout honneur, vous faites face à un ${data.rencontre.name}. Vous le maîtrisez et récupérez ses rêves. Vous gagnez ses ${data.rencontre.force} points de rêve`, msgSucces: (data) => `A tout seigneur, tout honneur, vous faites face à un ${data.rencontre.name}. Vous le maîtrisez et récupérez ses rêves. Vous gagnez ses ${data.rencontre.force} points de rêve`,
msgEchec: (data) => `A tout seigneur, tout honneur, vous faites face à un ${data.rencontre.name}. La rencontre tourne au cauchemar, dans la lutte épique, vous subissez ${data.rolled.isETotal ? 'deux queues' : 'une queue'} de dragon!`, msgEchec: (data) => `A tout seigneur, tout honneur, vous faites face à un ${data.rencontre.name}. La rencontre tourne au cauchemar, dans la lutte épique, vous subissez ${data.rolled.isETotal ? 'deux queues' : 'une queue'} de dragon!`,
postSucces: (tmrDialog, data) => TMRRencontres.onPostSuccessReveDeDragon(tmrDialog, data), postSucces: (tmrDialog, data) => TMRRencontres.onPostSuccessReveDeDragon(tmrDialog, data),
postEchec: (tmrDialog, data) => TMRRencontres.onPostSuccessReveDeDragon(tmrDialog, data), postEchec: (tmrDialog, data) => TMRRencontres.onPostEchecReveDeDragon(tmrDialog, data),
poesieSucces: { poesieSucces: {
reference: "Rêve de Dragon, Denis Gerfaud", reference: "Rêve de Dragon, Denis Gerfaud",
extrait: `Le monde est Rêve de Dragons, mais nous ne savons extrait: `Le monde est Rêve de Dragons, mais nous ne savons
@ -475,11 +475,11 @@ export class TMRRencontres {
if (data.rolled.isPart) { if (data.rolled.isPart) {
await data.actor.appliquerExperience(data.rolled, 'reve', data.competence); await data.actor.appliquerExperience(data.rolled, 'reve', data.competence);
} }
await data.actor.resultCombattreReveDeDragon(data.rolled); await data.actor.resultCombatReveDeDragon(data);
} }
static async onPostEchecReveDeDragon(tmrDialog, data) { static async onPostEchecReveDeDragon(tmrDialog, data) {
await data.actor.resultCombattreReveDeDragon(data.rolled); await data.actor.resultCombatReveDeDragon(data);
tmrDialog.close(); tmrDialog.close();
} }
} }

View File

@ -227,68 +227,6 @@ export const TMRType = {
desolation: { name: "désolation", genre: "f" } desolation: { name: "désolation", genre: "f" }
} }
export const poesieHautReve = [
{
reference: 'Le Ratier Bretonien',
extrait: `Le courant du Fleuve
<br>Te domine et te Porte
<br>Avant que tu te moeuves
<br>Combat le, ou il t'emporte`
},
{
reference: 'Incompatibilité, Charles Beaudelaire',
extrait: `Et lorsque par hasard une nuée errante
<br>Assombrit dans son vol le lac silencieux,
<br>On croirait voir la robe ou l'ombre transparente
<br>D'un esprit qui voyage et passe dans les cieux.`
},
{
reference: 'Au fleuve de Loire, Joachim du Bellay',
extrait: `Ô de qui la vive course
<br>Prend sa bienheureuse source,
<br>Dune argentine fontaine,
<br>Qui dune fuite lointaine,
<br>Te rends au sein fluctueux
<br>De lOcéan monstrueux`
},
{
reference: 'Denis Gerfaud',
extrait: `Et l'on peut savoir qui est le maître d'Oniros, c'est le Fleuve de l'Oubli.
Et l'on sait qui est le créateur du Fleuve de l'Oubli, c'est Hypnos et Narcos.
Mais l'on ne sait pas qui est le maître du Fleuve de l'Oubli,
sinon peut-être lui-même, ou peut-être Thanatos` },
{
reference: 'Denis Gerfaud',
extrait: `Narcos est la source du Fleuve de l'Oubli et Hypnos l'embouchure
Remonter le Fleuve est la Voie de la Nuit, la Voie du Souvenir.
Descendre le Fleuve est la Voie du Jour, la Voie de l'Oubli`
},
{
reference: 'Denis Gerfaud',
extrait: `Narcos engendre le fils dont il est la mère à l'heure du Vaisseau,
car Oniros s'embarque pour redescendre le Fleuve
vers son père Hypnos sur la Voie de l'Oubli`
},
{
reference: 'Denis Gerfaud',
extrait: `Hypnos engendre le fils dont il est la mère à l'heure du Serpent, car
tel les serpents, Oniros commence à remonter le Fleuve
sur le Voie du Souvenir vers son père Narcos`
},
{
reference: 'Denis Gerfaud',
extrait: `Ainsi se cuccèdent les Jours et les Ages. Les jours des Dragons sont les Ages des Hommes`
},
{
reference: 'Denis Gerfaud',
extrait: `Ainsi parlent les sages:
&laquo;Les Dragons sont créateurs de leurs rêves, mais ils ne sont pas créateurs d'Oniros
Les Dragons ne sont pas les maîtres de leurs rêvezs, car ils ne sont pas maîtres d'Oniros.
Nul ne sait qui est le créateur des Dragons, ni qui est leur maître.
Mais l'on peut supposer qui est le maître du Rêve des Dragons, c'est Oniros&raquo;`
},
]
/* -------------------------------------------- */ /* -------------------------------------------- */
const caseSpecificModes = ["attache", "trounoir", "debordement", "reserve_extensible", "maitrisee"]; const caseSpecificModes = ["attache", "trounoir", "debordement", "reserve_extensible", "maitrisee"];
@ -309,9 +247,36 @@ export const tmrConstants = {
cellw: 55, cellw: 55,
cellh: 55, cellh: 55,
gridx: 28, gridx: 28,
gridy: 28 gridy: 28,
// tailles
third: 18,
half: 27.5,
twoThird: 36,
full: 55,
// decallages
center: { x: 0, y: 0 },
top: { x: 0, y: -11.5 },
topLeft: { x: -11.5, y: -11.5 },
left: { x: -11.5, y: 0 },
bottomLeft: { x: -11.5, y: 11.5 },
bottom: { x: 0, y: 11.5 },
bottomRight: { x: 11.5, y: 11.5 },
right: { x: 11.5, y: 0 },
topRight: { x: 11.5, y: -11.5 },
} }
// couleurs
export const tmrColors = {
sort: 0xFF8800,
tetes: 0xA000FF,
souffle: 0x804040,
trounoir: 0x401060,
demireve: 0x00FFEE,
rencontre: 0xFF0000,
casehumide: 0x1050F0,
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -355,6 +320,9 @@ export class TMRUtility {
static getTMR(coordTMR) { static getTMR(coordTMR) {
return TMRMapping[coordTMR]; return TMRMapping[coordTMR];
} }
static isCaseHumide(tmr) {
return tmr.type == 'fleuve' || tmr.type == 'lac' || tmr.type == 'marais';
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/** Some debug functions */ /** Some debug functions */
@ -416,12 +384,16 @@ export class TMRUtility {
return TMRType[terrain].list; return TMRType[terrain].list;
} }
static getListCoordTMR(terrain) { static filterTMR(filter) {
return this.getListTMR(terrain).map(it => it.coord); return Object.values(TMRMapping).filter(filter);
} }
static getTMRAleatoire(terrain = undefined) { static filterTMRCoord(filter) {
let list = terrain ? TMRUtility.getListTMR(terrain) : Object.values(TMRMapping); return TMRUtility.filterTMR(filter).map(it => it.coord);
}
static getTMRAleatoire(filter = undefined) {
let list = TMRUtility.filterTMR(filter);
let index = new Roll("1d" + list.length).evaluate().total - 1; let index = new Roll("1d" + list.length).evaluate().total - 1;
return list[index]; return list[index];
} }

20
module/tmr/carte-tmr.js Normal file
View File

@ -0,0 +1,20 @@
import { Draconique } from "./draconique.js";
export class CarteTmr extends Draconique {
constructor() {
super();
}
type() { return '' }
match(item) { return false; }
manualMessage() { return false }
async onActorCreateOwned(actor, item) { }
code() { return 'tmr' }
img() { return 'systems/foundryvtt-reve-de-dragon/styles/img/ui/tmp_main_r1.webp' }
_createSprite(pixiTMR) {
return pixiTMR.carteTmr(this.code());
}
}

31
module/tmr/debordement.js Normal file
View File

@ -0,0 +1,31 @@
import { tmrColors, tmrConstants, TMRUtility } from "../tmr-utility.js";
import { Draconique } from "./draconique.js";
export class Debordement extends Draconique {
constructor() {
super();
}
type() { return 'souffle' }
match(item) { return Draconique.isSouffleDragon(item) && item.name.toLowerCase().includes('trou noir'); }
manualMessage() { return false }
async onActorCreateOwned(actor, item) { await this._creerCaseTmr(actor); }
code() { return 'debordement' }
tooltip(linkData) { return `Débordement en ${TMRUtility.getTMR(linkData.data.coord).label}` }
img() { return 'systems/foundryvtt-reve-de-dragon/icons/svg/wave.svg' }
_createSprite(pixiTMR) {
return pixiTMR.sprite(this.code(),
{
color: tmrColors.casehumide, alpha: 0.5, taille: tmrConstants.twoThird, decallage: tmrConstants.bottom
});
}
async _creerCaseTmr(actor) {
const existants = actor.data.items.filter(it => this.isCase(it)).map(it => it.data.coord);
const tmr = TMRUtility.getTMRAleatoire(it => !(TMRUtility.isCaseHumide(it) || existants.includes(it.coord)));
await this.createCaseTmr(actor, 'Debordement: ' + tmr.label, tmr);
}
}

27
module/tmr/demi-reve.js Normal file
View File

@ -0,0 +1,27 @@
import { tmrColors, tmrConstants } from "../tmr-utility.js";
import { Draconique } from "./draconique.js";
export class DemiReve extends Draconique {
constructor() {
super();
}
type() { return '' }
match(item) { return false; }
manualMessage() { return false }
async onActorCreateOwned(actor, item) { }
code() { return 'demi-reve' }
tooltip(linkData) { return `Demi-rêve` }
img() { return 'icons/svg/sun.svg' }
_createSprite(pixiTMR) {
const sprite = pixiTMR.sprite(this.code(), {
color: tmrColors.demireve,
taille: (tmrConstants.full * 0.7)
});
pixiTMR.animate(pixiApp => pixiApp.ticker.add((delta) => sprite.rotation -= 0.01 * delta));
return sprite;
}
}

118
module/tmr/draconique.js Normal file
View File

@ -0,0 +1,118 @@
import { PixiTMR } from "./pixi-tmr.js";
const registeredEffects = [
]
/**
* Définition des informations d'une "draconique" (queue, ombre, tête, souffle) qui influence les TMR
*/
export class Draconique
{
static isCaseTMR(element) { return element.type == 'casetmr'; }
static isQueueDragon(element) { return element.type == 'queue' || element.type == 'ombre'; }
static isSouffleDragon(element) { return element.type == 'souffle'; }
static isTeteDragon(element) { return element.type == 'tete'; }
static isQueueSouffle(it) { return Draconique.isQueueDragon(it) || Draconique.isSouffleDragon(it); }
static register(draconique) {
registeredEffects[draconique.code()] = draconique;
if (draconique.img()) {
PixiTMR.register(draconique.code(), draconique.img())
}
return draconique;
}
static all() {
return Object.values(registeredEffects);
}
static get(code) {
return registeredEffects[code];
}
/**
* @param item un Item quelconque
* @returns true si l'item correspond
*/
match(item) {
return Draconique.isQueueDragon(item) || Draconique.isSouffleDragon(item) || Draconique.isTeteDragon(item);
}
/**
* @returns un message à afficher si la draconique doit être gérée manuellement.
*/
manualMessage() {
return false;
}
/**
* Méthode responsable de gérer une draconique (par exemple, ajouter des casetmr pour la fermeture des cités).
* @param actor auquel la draconique est ajoutée
*/
async onActorCreateOwned(actor) {
return false;
}
async onActorDeleteOwned(actor) {
return false;
}
/**
* @return le code interne utilisé pour les casetmr correpondant
*/
code() { return undefined }
/**
* @param {*} linkData données associées au token pixi (une casetmr, un sort en réserve, une rencontre en attente)
* @returns un tooltip à afficher au dessus du token
*/
tooltip(linkData) { return undefined }
/**
* @param {*} img l'url du fichier image à utiliser pour le token. Si indéfini (et si createSprite n'est pas surchargé),
* un disque est utilisé.
*/
img() { return undefined }
/**
* factory d'élément graphique PIXI correpsondant à l'objet draconique
* @param {*} pixiTMR instance de PixiTMR qui gère les tooltips, les méthodes de création de sprite standard, les clicks.
*/
token(pixiTMR, linkData, coordTMR, type = undefined) {
const token = {
sprite: this._createSprite(pixiTMR),
coordTMR: coordTMR
};
token[type ?? this.code()] = linkData;
pixiTMR.addTooltip(token.sprite, this.tooltip(linkData));
return token;
return sprite;
}
/**
* factory d'élément graphique PIXI correpsondant à l'objet draconique
* @param {*} pixiTMR instance de PixiTMR qui gère les tooltips, les méthodes de création de sprite standard, les clicks.
*/
_createSprite(pixiTMR) {
if (this.img()) {
return pixiTMR.sprite(this.code());
}
else{
return pixiTMR.circle()
}
}
/**
*
* @param {*} it un item à tester
* @param {*} coord les coordonnées d'une case. Si undefined toute case du type correspondra,
*/
isCase(it, coord = undefined) {
return Draconique.isCaseTMR(it) && it.data.specific == this.code() && (coord ? it.data.coord == coord : true);
}
async createCaseTmr(actor, label, tmr) {
await actor.createOwnedItem({
name: label, type: 'casetmr', img: this.img(), _id: randomID(16),
data: { coord: tmr.coord, specific: this.code() }
});
}
}

View File

@ -0,0 +1,152 @@
import { Debordement } from "./debordement.js";
import { FermetureCites } from "./fermeture-cites.js";
import { QueteEaux } from "./quete-eaux.js";
import { TerreAttache } from "./terre-attache.js";
import { ReserveExtensible } from "./reserve-extensible.js";
import { DemiReve } from "./demi-reve.js";
import { TrouNoir } from "./trou-noir.js";
import { Rencontre } from "./rencontre.js";
import { SortReserve } from "./sort-reserve.js";
import { CarteTmr } from "./carte-tmr.js";
import { PontImpraticable } from "./pont-impraticable.js";
import { Draconique } from "./draconique.js";
import { PresentCites } from "./present-cites.js";
export class EffetsDraconiques {
static carteTmr = new CarteTmr();
static demiReve = new DemiReve();
static rencontre = new Rencontre();
static sortReserve = new SortReserve();
static debordement = new Debordement();
static presentCites = new PresentCites();
static fermetureCites = new FermetureCites();
static queteEaux = new QueteEaux();
static reserveExtensible = new ReserveExtensible();
static terreAttache = new TerreAttache();
static trouNoir = new TrouNoir();
static pontImpraticable = new PontImpraticable();
static init() {
Draconique.register(EffetsDraconiques.carteTmr);
Draconique.register(EffetsDraconiques.demiReve);
Draconique.register(EffetsDraconiques.rencontre);
Draconique.register(EffetsDraconiques.sortReserve);
Draconique.register(EffetsDraconiques.debordement);
Draconique.register(EffetsDraconiques.fermetureCites);
Draconique.register(EffetsDraconiques.queteEaux);
Draconique.register(EffetsDraconiques.reserveExtensible);
Draconique.register(EffetsDraconiques.terreAttache);
Draconique.register(EffetsDraconiques.trouNoir);
Draconique.register(EffetsDraconiques.pontImpraticable);
Draconique.register(EffetsDraconiques.presentCites);
}
/* -------------------------------------------- */
static isCaseInondee(caseTMR, coord) {
return EffetsDraconiques.debordement.isCase(caseTMR, coord) || EffetsDraconiques.pontImpraticable.isCase(caseTMR, coord);
}
static isCaseTrouNoir(caseTMR, coord) {
return EffetsDraconiques.trouNoir.isCase(caseTMR, coord);
}
static isReserveExtensible(caseTMR, coord) {
return EffetsDraconiques.reserveExtensible.isCase(caseTMR, coord);
}
static isTerreAttache(caseTMR, coord) {
return EffetsDraconiques.terreAttache.isCase(caseTMR, coord);
}
static isCiteFermee(caseTMR, coord) {
return EffetsDraconiques.fermetureCites.isCase(caseTMR, coord);
}
static isPresentCite(caseTMR, coord) {
return EffetsDraconiques.presentCites.isCase(caseTMR, coord);
}
/* -------------------------------------------- */
static isMauvaiseRencontre(element) {
return EffetsDraconiques.isMatching(element, it => Draconique.isQueueSouffle(it) && it.name.toLowerCase().includes('mauvaise rencontre'));
}
static isMonteeLaborieuse(element) {
return EffetsDraconiques.isMatching(element, it => Draconique.isQueueSouffle(it) && it.name.toLowerCase().includes('montée laborieuse'));
}
static isFermetureCite(element) {
/* -------------------------------------------- */
return EffetsDraconiques.isMatching(element, it => EffetsDraconiques.fermetureCites.match(it));
}
static isPontImpraticable(element) {
return EffetsDraconiques.isMatching(element, it => EffetsDraconiques.pontImpraticable.match(it));
}
static isDoubleResistanceFleuve(element) {
return EffetsDraconiques.isMatching(element, it => Draconique.isSouffleDragon(it) && it.name.toLowerCase().includes('résistance du fleuve'));
}
static isPeage(element) {
return EffetsDraconiques.isMatching(element, it => Draconique.isSouffleDragon(it) && it.name.toLowerCase().includes('péage'));
}
static isPeriple(element) {
// TODO
return EffetsDraconiques.isMatching(element, it => Draconique.isSouffleDragon(it) && ir.name.toLowerCase() == 'périple');
}
static isDesorientation(element) {
// TODO
return EffetsDraconiques.isMatching(element, it => Draconique.isSouffleDragon(it) && it.name.toLowerCase() == 'désorientation');
}
/* -------------------------------------------- */
static isConquete(element) {
// TODO
return EffetsDraconiques.isMatching(element, it => Draconique.isQueueDragon(it) && it.name.toLowerCase() == 'conquête');
}
static isPelerinage(element) {
return EffetsDraconiques.isMatching(element, it => Draconique.isQueueDragon(it) && it.name.toLowerCase() == 'pélerinage');
}
static countInertieDraconique(element) {
return EffetsDraconiques.count(element, it => Draconique.isQueueDragon(it) && it.name.toLowerCase().includes('inertie draconique'));
}
static isUrgenceDraconique(element) {
return EffetsDraconiques.isMatching(element, it => Draconique.isQueueDragon(it) && it.name.toLowerCase() == 'urgence draconique');
}
/* -------------------------------------------- */
static isDonDoubleReve(element) {
return EffetsDraconiques.isMatching(element, it => Draconique.isTeteDragon(it) && it.name == 'Don de double-rêve');
}
static isConnaissanceFleuve(element) {
return EffetsDraconiques.isMatching(element, it => Draconique.isTeteDragon(it) && it.name.toLowerCase().includes('connaissance du fleuve'));
}
static isReserveEnSecurite(element) {
return EffetsDraconiques.isMatching(element, it => Draconique.isTeteDragon(it) && it.name.toLowerCase().includes(' en sécurité'));
}
static isDeplacementAccelere(element) {
return EffetsDraconiques.isMatching(element, it => Draconique.isTeteDragon(it) && it.name.toLowerCase().includes(' déplacement accéléré'));
}
static isMatching(element, matcher) {
return EffetsDraconiques.toItems(element).find(matcher);
}
static count(element, matcher) {
return EffetsDraconiques.toItems(element).filter(matcher).length;
}
static toItems(element) {
return (element?.entity === 'Actor') ? element.data.items : (element?.entity === 'Item') ? [element] : [];
}
}

View File

@ -0,0 +1,33 @@
import { tmrColors, tmrConstants, TMRUtility } from "../tmr-utility.js";
import { Draconique } from "./draconique.js";
export class FermetureCites extends Draconique {
constructor() {
super();
}
type() { return 'souffle' }
match(item) { return Draconique.isSouffleDragon(item) && item.name.toLowerCase() == 'fermeture des cités'; }
manualMessage() { return false }
async onActorCreateOwned(actor, item) { await this._fermerLesCites(actor); }
code() { return 'fermeture' }
tooltip(linkData) { return `La ${TMRUtility.getTMR(linkData.data.coord).label} est fermée` }
img() { return 'icons/svg/door-closed.svg' }
_createSprite(pixiTMR) {
return pixiTMR.sprite(this.code(),
{
color: tmrColors.souffle, alpha: 0.9, taille: tmrConstants.full, decallage: { x: 2, y: 0 }
});
}
async _fermerLesCites(actor) {
let existants = actor.data.items.filter(it => this.isCase(it)).map(it => it.data.coord);
let ouvertes = TMRUtility.filterTMR(it => it.type == 'cite' && !existants.includes(it.coord));
for (let tmr of ouvertes) {
await this.createCaseTmr(actor, 'Fermeture: ' + tmr.label, tmr);
}
}
}

148
module/tmr/pixi-tmr.js Normal file
View File

@ -0,0 +1,148 @@
import { tmrConstants } from "../tmr-utility.js";
const tooltipStyle = new PIXI.TextStyle({
fontFamily: 'CaslonAntique',
fontSize: 18,
fill: '#FFFFFF',
stroke: '#000000',
strokeThickness: 3
});
export class PixiTMR {
static textures = []
constructor(tmrObject, pixiApp) {
this.tmrObject = tmrObject;
this.pixiApp = pixiApp ?? tmrObject.pixiApp;
this.callbacksOnAnimate = [];
}
load(onLoad = (loader, resources) => {}) {
let loader = this.pixiApp.loader;
for (const [name, img] of Object.entries(PixiTMR.textures)) {
loader = loader.add(name, img);
}
loader.load((loader, resources) => {
onLoad(loader, resources);
for (let onAnimate of this.callbacksOnAnimate) {
onAnimate();
}
});
}
static register(name, img) {
PixiTMR.textures[name] = img;
}
animate(animation = pixiApp=>{})
{
this.callbacksOnAnimate.push(() => animation(this.pixiApp));
}
carteTmr(code) {
const carteTmr = new PIXI.Sprite(PIXI.utils.TextureCache[code]);
// Setup the position of the TMR
carteTmr.x = 0;
carteTmr.y = 0;
carteTmr.width = 720;
carteTmr.height = 860;
// Rotate around the center
carteTmr.anchor.set(0);
carteTmr.interactive = true;
carteTmr.buttonMode = true;
carteTmr.tmrObject = this;
// atténue les couleurs des TMRs
const tmrColorFilter = new PIXI.filters.ColorMatrixFilter();
tmrColorFilter.contrast(1);
tmrColorFilter.brightness(0.2);
tmrColorFilter.saturate(-0.5);
carteTmr.filters = [tmrColorFilter];
if (!this.tmrObject.viewOnly) {
carteTmr.on('pointerdown', event => this.onClickBackground(event));
}
this.pixiApp.stage.addChild(carteTmr);
return carteTmr;
}
sprite(code, options = {}) {
const texture = PIXI.utils.TextureCache[code];
if (!texture) {
console.error("Texture manquante", code)
return;
}
let sprite = new PIXI.Sprite(texture);
sprite.width = options.taille ?? tmrConstants.half;
sprite.height = options.taille ?? tmrConstants.half;
sprite.anchor.set(0.5);
sprite.tint = options.color ?? 0x000000;
sprite.alpha = options.alpha ?? 0.75;
sprite.decallage = options.decallage ?? tmrConstants.center;
this.pixiApp.stage.addChild(sprite);
return sprite;
}
circle(name, options = {}) {
let sprite = new PIXI.Graphics();
sprite.beginFill(options.color, options.opacity);
sprite.drawCircle(0, 0, (options.taille ?? 12) / 2);
sprite.endFill();
sprite.decallage = options.decallage ?? tmrConstants.topLeft;
this.pixiApp.stage.addChild(sprite);
return sprite;
}
addTooltip(sprite, text) {
if (text) {
sprite.tooltip = new PIXI.Text(text, tooltipStyle);
sprite.isOver = false;
sprite.interactive = true;
sprite.on('pointerdown', event => this.onClickBackground(event))
.on('pointerover', () => this.onShowTooltip(sprite))
.on('pointerout', () => this.onHideTooltip(sprite));
}
}
onClickBackground(event) {
this.tmrObject.onClickTMR(event)
}
onShowTooltip(sprite) {
if (sprite.tooltip) {
if (!sprite.isOver) {
sprite.tooltip.x = sprite.x;
sprite.tooltip.y = sprite.y;
this.pixiApp.stage.addChild(sprite.tooltip);
}
sprite.isOver = true;
}
}
onHideTooltip(sprite) {
if (sprite.tooltip) {
if (sprite.isOver) {
this.pixiApp.stage.removeChild(sprite.tooltip);
}
sprite.isOver = false;
}
}
setPosition( sprite, pos) {
let decallagePairImpair = (pos.x % 2 == 0) ? tmrConstants.col1_y : tmrConstants.col2_y;
let dx = (sprite.decallage == undefined) ? 0 : sprite.decallage.x;
let dy = (sprite.decallage == undefined) ? 0 : sprite.decallage.y;
sprite.x = tmrConstants.gridx + (pos.x * tmrConstants.cellw) + dx;
sprite.y = tmrConstants.gridy + (pos.y * tmrConstants.cellh) + dy + decallagePairImpair;
}
getCaseRectangle(pos) {
let decallagePairImpair = (pos.x % 2 == 0) ? tmrConstants.col1_y : tmrConstants.col2_y;
let x = tmrConstants.gridx + (pos.x * tmrConstants.cellw) - (tmrConstants.cellw / 2);
let y = tmrConstants.gridy + (pos.y * tmrConstants.cellh) - (tmrConstants.cellh / 2) + decallagePairImpair;
return { x: x, y: y, w: tmrConstants.cellw, h: tmrConstants.cellh };
}
}

View File

@ -0,0 +1,40 @@
import { tmrColors, tmrConstants, TMRUtility } from "../tmr-utility.js";
import { Draconique } from "./draconique.js";
export class PontImpraticable extends Draconique {
constructor() {
super();
}
type() { return 'souffle' }
match(item) { return Draconique.isSouffleDragon(item) && item.name.toLowerCase().includes('impraticabilité des ponts'); }
async onActorCreateOwned(actor, item) { await this._creerCaseTmr(actor); }
async onActorDeleteOwned(actor, item) { await this._supprimerCaseTmr(actor); }
code() { return 'pont-impraticable' }
tooltip(linkData) { return `${TMRUtility.getTMR(linkData.data.coord).label} impraticable` }
img() { return 'systems/foundryvtt-reve-de-dragon/icons/svg/wave.svg' }
_createSprite(pixiTMR) {
return pixiTMR.sprite(this.code(),
{
color: tmrColors.casehumide, alpha: 0.5, taille: tmrConstants.twoThird, decallage: tmrConstants.bottom
});
}
async _creerCaseTmr(actor) {
const ponts = TMRUtility.getListTMR('pont');
for (let tmr of ponts) {
await this.createCaseTmr(actor, 'Pont impraticable: ' + tmr.label, tmr);
}
}
async _supprimerCaseTmr(actor) {
const existants = actor.data.items.filter(it => this.isCase(it));
for (let caseTMR of existants) {
await actor.deleteOwnedItem(caseTMR._id);
}
}
}

View File

@ -0,0 +1,42 @@
import { ChatUtility } from "../chat-utility.js";
import { tmrColors, tmrConstants, TMRUtility } from "../tmr-utility.js";
import { Draconique } from "./draconique.js";
export class PresentCites extends Draconique {
constructor() {
super();
}
type() { return 'tete' }
match(item) { return Draconique.isTeteDragon(item) && item.name.toLowerCase() == 'présent des cités'; }
manualMessage() { return false }
async onActorCreateOwned(actor, item) { await this._ajouterPresents(actor); }
code() { return 'present-cites' }
tooltip(linkData) { return `La ${TMRUtility.getTMR(linkData.data.coord).label} a un présent` }
img() { return 'systems/foundryvtt-reve-de-dragon/icons/svg/gift.svg' }
_createSprite(pixiTMR) {
return pixiTMR.sprite(this.code(),
{
color: tmrColors.tetes, alpha: 0.7, taille: tmrConstants.third, decallage:tmrConstants.topRight
});
}
async _ajouterPresents(actor) {
let existants = actor.data.items.filter(it => this.isCase(it)).map(it => it.data.coord);
if (existants.length >0 ) {
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user),
content: "Vous avez encore des présents dans des cités, vous devrez tirer une autre tête pour remplacer celle ci!"
})
}
else{
let cites = TMRUtility.filterTMR(it => it.type == 'cite');
for (let tmr of cites) {
await this.createCaseTmr(actor, 'Présent: ' + tmr.label, tmr);
}
}
}
}

25
module/tmr/quete-eaux.js Normal file
View File

@ -0,0 +1,25 @@
import { tmrColors, tmrConstants, TMRUtility } from "../tmr-utility.js";
import { Draconique } from "./draconique.js";
export class QueteEaux extends Draconique {
constructor() {
super();
}
type() { return 'tete' }
match(item) { return Draconique.isTeteDragon(item) && item.name.toLowerCase().includes("quête des eaux"); }
manualMessage() { return "Vous devrez re-configurer votre Quête des Eaux une fois un lac ou marais vaincu" }
async onActorCreateOwned(actor, item) { await this._creerCaseTmr(actor); }
code() { return 'maitrisee' }
tooltip(linkData) { return `Quête des eaux, le ${TMRUtility.getTMR(linkData.data.coord).label} est maîtrisé` }
img() { return 'icons/svg/bridge.svg' }
_createSprite(pixiTMR) {
return pixiTMR.sprite(this.code(), { color: tmrColors.tetes, decallage: tmrConstants.topRight });
}
async _creerCaseTmr(actor) {
await this.createCaseTmr(actor, "Quête des eaux à déterminer", {coord:'A0'});
}
}

22
module/tmr/rencontre.js Normal file
View File

@ -0,0 +1,22 @@
import { tmrColors, tmrConstants } from "../tmr-utility.js";
import { Draconique } from "./draconique.js";
export class Rencontre extends Draconique {
constructor() {
super();
}
type() { return '' }
match(item) { return false; }
manualMessage() { return false }
async onActorCreateOwned(actor, item) { }
code() { return 'rencontre' }
tooltip(linkData) { return `${linkData.name} de force ${linkData.force}` }
img() { return 'systems/foundryvtt-reve-de-dragon/icons/heures/hd06.svg' }
_createSprite(pixiTMR) {
return pixiTMR.sprite(this.code(), { color: tmrColors.rencontre, taille: tmrConstants.full, decallage: { x: 2, y: 2 } });
}
}

View File

@ -0,0 +1,28 @@
import { tmrColors, tmrConstants, TMRUtility } from "../tmr-utility.js";
import { Draconique } from "./draconique.js";
export class ReserveExtensible extends Draconique {
constructor() {
super();
}
type() { return 'tete' }
match(item) { return Draconique.isTeteDragon(item) && item.name.toLowerCase().includes("réserve extensible"); }
manualMessage() { return "Vous pouvez re-configurer votre Réserve extensible" }
async onActorCreateOwned(actor, item) { await this._creerCaseTmr(actor); }
code() { return 'reserve_extensible' }
tooltip(linkData) { return `Réserve extensible en ${TMRUtility.getTMR(linkData.data.coord).label} !` }
img() { return 'icons/svg/chest.svg' }
_createSprite(pixiTMR) {
return pixiTMR.sprite(this.code(), { color: tmrColors.tetes, decallage: tmrConstants.left});
}
async _creerCaseTmr(actor) {
const existants = actor.data.items.filter(it => this.isCase(it)).map(it => it.data.coord);
const tmr = TMRUtility.getTMRAleatoire(it => !(it.type == 'fleuve' || existants.includes(it.coord)));
await this.createCaseTmr(actor, "Nouvelle Réserve extensible", tmr);
}
}

View File

@ -0,0 +1,22 @@
import { tmrColors, tmrConstants } from "../tmr-utility.js";
import { Draconique } from "./draconique.js";
export class SortReserve extends Draconique {
constructor() {
super();
}
type() { return '' }
match(item) { return false; }
manualMessage() { return false }
async onActorCreateOwned(actor, item) { }
code() { return 'sort' }
tooltip(linkData) { return `${linkData.name}, r${linkData.data.ptreve_reel}` }
img() { return 'icons/svg/book.svg' }
_createSprite(pixiTMR) {
return pixiTMR.sprite(this.code(), { color: tmrColors.sort, decallage: tmrConstants.right });
}
}

View File

@ -0,0 +1,25 @@
import { tmrColors, tmrConstants, TMRUtility } from "../tmr-utility.js";
import { Draconique } from "./draconique.js";
export class TerreAttache extends Draconique {
constructor() {
super();
}
type() { return 'tete' }
match(item) { return Draconique.isTeteDragon(item) && item.name.toLowerCase().includes("terre d'attache"); }
manualMessage() { return "Vous pouvez re-configurer votre Terre d'Attache" }
async onActorCreateOwned(actor, item) { await this._creerCaseTmr(actor); }
code() { return 'attache' }
tooltip(linkData) { return `Terre d'attache en ${TMRUtility.getTMR(linkData.data.coord).label} !` }
img() { return 'icons/svg/anchor.svg' }
_createSprite(pixiTMR) {
return pixiTMR.sprite(this.code(), { color: tmrColors.tetes, decallage: tmrConstants.topLeft });
}
async _creerCaseTmr(actor) {
await this.createCaseTmr(actor, "Terre d'attache à déterminer", {coord:'A0'});
}
}

30
module/tmr/trou-noir.js Normal file
View File

@ -0,0 +1,30 @@
import { tmrColors, tmrConstants, TMRUtility } from "../tmr-utility.js";
import { Draconique } from "./draconique.js";
export class TrouNoir extends Draconique {
constructor() {
super();
}
type() { return 'souffle' }
match(item) { return Draconique.isSouffleDragon(item) && item.name.toLowerCase().includes('trou noir'); }
manualMessage() { return false }
async onActorCreateOwned(actor, item) { await this._creerCaseTmr(actor); }
code() { return 'trounoir' }
tooltip(linkData) { return `Trou noir en ${TMRUtility.getTMR(linkData.data.coord).label} !` }
img() { return 'icons/svg/explosion.svg' }
_createSprite(pixiTMR) {
return pixiTMR.sprite(this.code(),
{
color: tmrColors.trounoir, alpha: 1, taille: tmrConstants.full, decallage: { x: 2, y: 2 },
});
}
async _creerCaseTmr(actor) {
const existants = actor.data.items.filter(it => this.isCase(it)).map(it => it.data.coord);
const tmr = TMRUtility.getTMRAleatoire(it => !(TMRUtility.isCaseHumide(it) || existants.includes(it.coord)));
await this.createCaseTmr(actor, 'Trou noir: ' + tmr.label, tmr);
}
}

View File

@ -1,10 +1,11 @@
{"_id":"5JccZSafqCXYqrwU","name":"Connaissance du fleuve","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Cette tête permet de téléporter instantanément son demi-rêve de nimporte quelle case de fleuve à nimporte quelle autre. Il sagit du fleuve seul, à lexclusion des cases de lac et marais. La téléportation remplace le mouvement normal et coûte donc un point de fatigue. Comme après un mouvement normal, une rencontre doit être tirée dans la case de fleuve darrivée, puis cette dernière doit être maîtrisée selon la règle usuelle. Noter enfin que la téléportation nest possible que si le demi-rêve est libre de son mouvement, cest-à-dire sil nest sous lemprise ni dun Reflet, ni dun Tourbillon. Unique.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"} {"_id":"5JccZSafqCXYqrwU","name":"Connaissance du fleuve","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Cette tête permet de téléporter instantanément son demi-rêve de nimporte quelle case de fleuve à nimporte quelle autre. Il sagit du fleuve seul, à lexclusion des cases de lac et marais. La téléportation remplace le mouvement normal et coûte donc un point de fatigue. Comme après un mouvement normal, une rencontre doit être tirée dans la case de fleuve darrivée, puis cette dernière doit être maîtrisée selon la règle usuelle. Noter enfin que la téléportation nest possible que si le demi-rêve est libre de son mouvement, cest-à-dire sil nest sous lemprise ni dun Reflet, ni dun Tourbillon. Unique.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
{"_id":"BT18LAdIqEgSG2Hh","name":"Réserve en sécurité","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Un haut-rêvant possédant cette tête peut pénétrer sur une case où il a un sort en réserve sans forcément le déclencher sil ne le souhaite pas. Même chose si un Tourbillon labandonne sur une case de réserve. Déclencher un sort en réserve devient un acte volontaire. Même chose pour un échec total en réserve. Cette tête sapplique à toutes les cases. Unique.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"} {"_id":"BT18LAdIqEgSG2Hh","name":"Réserve en sécurité","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Un haut-rêvant possédant cette tête peut pénétrer sur une case où il a un sort en réserve sans forcément le déclencher sil ne le souhaite pas. Même chose si un Tourbillon labandonne sur une case de réserve. Déclencher un sort en réserve devient un acte volontaire. Même chose pour un échec total en réserve. Cette tête sapplique à toutes les cases. Unique.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
{"_id":"SFxPAvWpEGYHI8mO","name":"Connaissance intuitive d'un nouveau sort","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Le haut-rêvant bénéficiaire de cette tête se retrouve en possession dun nouveau sort ou rituel, déterminé par un procédé aléatoire quelconque.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"} {"name":"Présent des cités","permission":{"default":0,"Q2G6GTdrotKzYGUC":3},"type":"tete","data":{"description":"<p>Par cette tête, chaque cité offre automatiquement soit une Fleur des rêves, soit un Passeur, soit un Messager, dès quelle est visitée par le demi-rêve du haut-rêvant. Il ny a pas de d7 à tirer, le haut-rêvant choisit librement ce quil préfère, et la rencontre est considérée automatiquement maîtrisée, Fleurs, Messagers et Passeurs ayant une force de 2d6 points. Dès quelle a offert son présent, la cité redevient normale en ce qui concerne le tirage des rencontres; il ny a quun seul présent par cité. La tête de Dragon prend fin dès que toutes les cités (22) ont été visitées; entretemps, la tête ne peut être ré-obtenue.</p>"},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[],"_id":"E4a4O1IdrgbNGpVy"}
{"_id":"VWOXA0q6GB7o8oxz","name":"Don de double-rêve","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Lors de la récupération des points de rêve, permet de jeter 1ddr toutes les demi-heures draconiques au lieu de toutes les heures. Unique.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"} {"_id":"SFxPAvWpEGYHI8mO","name":"Connaissance intuitive d'un nouveau sort","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Le haut-rêvant bénéficiaire de cette tête se retrouve en possession dun nouveau sort ou rituel, déterminé par un procédé aléatoire quelconque.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
{"_id":"YDo0a0ApM8iW9g82","name":"Terre d'attache","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Par cette tête, le haut-rêvant peut désigner une case des TMR de son choix à lexception dune case humide. Ensuite, à nimporte quel round de son périple en TMR, il peut sy téléporter directement quel que soit le nombre de cases qui len sépare. Cette téléportation instantanée remplace son mouvement normal et coûte donc un point de fatigue. De même, tout comme après un mouvement normal, 1d7 de rencontre doit être tiré dès larrivée dans la terre dattache. Enfin, le haut-rêvant ne peut sy téléporter que sil est libre de son mouvement, cest-à-dire sil nest sous lemprise ni dun Reflet dancien rêve, ni dun Tourbillon. Cumulable : on peut avoir plusieurs terres dattache.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"} {"_id":"VWOXA0q6GB7o8oxz","name":"Don de double-rêve","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Lors de la récupération des points de rêve, permet de jeter 1ddr toutes les demi-heures draconiques au lieu de toutes les heures. Unique.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
{"_id":"ZVh8PLlAzAJulr37","name":"Don de déplacement accéléré","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Permet à tout moment loption du déplacement accéléré dans les TMR sans avoir à dépenser un point de rêve supplémentaire. Unique.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"} {"_id":"YDo0a0ApM8iW9g82","name":"Terre d'attache","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Par cette tête, le haut-rêvant peut désigner une case des TMR de son choix à lexception dune case humide. Ensuite, à nimporte quel round de son périple en TMR, il peut sy téléporter directement quel que soit le nombre de cases qui len sépare. Cette téléportation instantanée remplace son mouvement normal et coûte donc un point de fatigue. De même, tout comme après un mouvement normal, 1d7 de rencontre doit être tiré dès larrivée dans la terre dattache. Enfin, le haut-rêvant ne peut sy téléporter que sil est libre de son mouvement, cest-à-dire sil nest sous lemprise ni dun Reflet dancien rêve, ni dun Tourbillon. Cumulable : on peut avoir plusieurs terres dattache.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
{"_id":"Zlt01O2sFrVR9pus","name":"Augmentation du seuil de rêve","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Permet daugmenter le seuil de rêve de 2 points. Cumulable jusquà un maximum du double de la caractéristique Rêve.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"} {"_id":"ZVh8PLlAzAJulr37","name":"Don de déplacement accéléré","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Permet à tout moment loption du déplacement accéléré dans les TMR sans avoir à dépenser un point de rêve supplémentaire. Unique.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
{"_id":"ZuTV36GyOhFgTlE7","name":"Présents de cités","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Par cette tête, chaque cité offre automatiquement soit une Fleur des rêves, soit un Passeur, soit un Messager, dès quelle est visitée par le demi-rêve du haut-rêvant. Il ny a pas de d7 à tirer, le haut-rêvant choisit librement ce quil préfère, et la rencontre est considérée automatiquement maîtrisée, Fleurs, Messagers et Passeurs ayant une force de 2d6 points. Dès quelle a offert son présent, la cité redevient normale en ce qui concerne le tirage des rencontres; il ny a quun seul présent par cité. La tête de Dragon prend fin dès que toutes les cités (22) ont été visitées; entretemps, la tête ne peut être ré-obtenue.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"} {"_id":"Zlt01O2sFrVR9pus","name":"Augmentation du seuil de rêve","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Permet daugmenter le seuil de rêve de 2 points. Cumulable jusquà un maximum du double de la caractéristique Rêve.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
{"_id":"a3Y5W0AX5EKxZRSL","name":"Quête des eaux","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Sous linfluence de cette tête, le haut-rêvant est invité à se rendre dans un lac ou dans un marais de son choix. Dès que cette case, lac ou marais, a été maîtrisée selon les règles usuelles, la victoire devient définitive, et la case naura plus jamais besoin dêtre maîtrisée. Une fois le lac ou le marais choisi, le haut-rêvant ne peut en changer, mais a droit à un nombre illimité dessais pour le maîtriser. Cumulable : on peut maîtriser définitivement plusieurs lacs ou marais.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"} {"_id":"ZuTV36GyOhFgTlE7","name":"Présents de cités","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Par cette tête, chaque cité offre automatiquement soit une Fleur des rêves, soit un Passeur, soit un Messager, dès quelle est visitée par le demi-rêve du haut-rêvant. Il ny a pas de d7 à tirer, le haut-rêvant choisit librement ce quil préfère, et la rencontre est considérée automatiquement maîtrisée, Fleurs, Messagers et Passeurs ayant une force de 2d6 points. Dès quelle a offert son présent, la cité redevient normale en ce qui concerne le tirage des rencontres; il ny a quun seul présent par cité. La tête de Dragon prend fin dès que toutes les cités (22) ont été visitées; entretemps, la tête ne peut être ré-obtenue.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
{"_id":"bFdU6ddgj4BAlJZX","name":"Réserve extensible","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Une (seule) case spécifique librement choisie par le haut-rêvant à lexception dune case de fleuve, nest plus dorénavant limitée à un seul sort en réserve. Le haut-rêvant peut y en stocker autant quil le désire (dans les limites du nombre permis par ses niveaux). En conséquence, tant que ce maximum nest pas atteint, le demi-rêve peut entrer dans la case concernée sans déclencher les sorts qui sy trouvent. Le haut-rêvant peut se contenter dy passer, ou rajouter un sort aux précédents. Dès que le maximum permis est atteint, le fait de passer par la case déclenche automatiquement lun des sorts (au choix). Les sorts peuvent être déclenchés lun après lautre (rappelons que déclencher un sort fait obligatoirement redescendre le demi-rêve), ou déclenchés ensemble (tout ou partie dentre eux) à la condition expresse quils aient la même cible (même créature, objet, ou centre de zone). Cumulable : on peut avoir plusieurs cases de réserve extensible.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp"} {"_id":"a3Y5W0AX5EKxZRSL","name":"Quête des eaux","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Sous linfluence de cette tête, le haut-rêvant est invité à se rendre dans un lac ou dans un marais de son choix. Dès que cette case, lac ou marais, a été maîtrisée selon les règles usuelles, la victoire devient définitive, et la case naura plus jamais besoin dêtre maîtrisée. Une fois le lac ou le marais choisi, le haut-rêvant ne peut en changer, mais a droit à un nombre illimité dessais pour le maîtriser. Cumulable : on peut maîtriser définitivement plusieurs lacs ou marais.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}
{"_id":"bFdU6ddgj4BAlJZX","name":"Réserve extensible","permission":{"default":0,"jOzRscDxoXZWpGS6":3},"type":"tete","data":{"description":"<p>Une (seule) case spécifique librement choisie par le haut-rêvant à lexception dune case de fleuve, nest plus dorénavant limitée à un seul sort en réserve. Le haut-rêvant peut y en stocker autant quil le désire (dans les limites du nombre permis par ses niveaux). En conséquence, tant que ce maximum nest pas atteint, le demi-rêve peut entrer dans la case concernée sans déclencher les sorts qui sy trouvent. Le haut-rêvant peut se contenter dy passer, ou rajouter un sort aux précédents. Dès que le maximum permis est atteint, le fait de passer par la case déclenche automatiquement lun des sorts (au choix). Les sorts peuvent être déclenchés lun après lautre (rappelons que déclencher un sort fait obligatoirement redescendre le demi-rêve), ou déclenchés ensemble (tout ou partie dentre eux) à la condition expresse quils aient la même cible (même créature, objet, ou centre de zone). Cumulable : on peut avoir plusieurs cases de réserve extensible.</p>","refoulement":null},"flags":{},"img":"systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp","effects":[]}

View File

@ -3,3 +3,9 @@
<option value="debordement">Débordement</option> <option value="debordement">Débordement</option>
<option value="reserve_extensible">Réserve extensible</option> <option value="reserve_extensible">Réserve extensible</option>
<option value="maitrisee">Case humide maitrisée (Quête des Eaux)</option> <option value="maitrisee">Case humide maitrisée (Quête des Eaux)</option>
<option value="fermeture">Fermeture cité</option>
<option value="pont-impraticable">Pont impraticable</option>
<option value="desorientation">Désoriantation</option>
<option value="periple">Periple</option>
<option value="pelerinage">Pèlerinage</option>
<option value="present-cites">Présents des cités</option>

View File

@ -1,20 +1,20 @@
<img class="chat-icon" src="{{competence.img}}" alt="{{competence.name}}"/> <img class="chat-icon" src="{{competence.img}}" alt="{{competence.name}}"/>
<h4 data-categorie="tmr" data-actor-id="{{actor._id}}"> <h4 data-categorie="tmr" data-actor-id="{{actor._id}}">
{{alias}} tente de maîtriser {{le tmr.genre}} {{tmr.label}} ({{tmr.coord}}) {{alias}} tente de {{maitrise.verbe}} {{le tmr.genre}} {{tmr.label}} ({{tmr.coord}})
</h4> </h4>
{{#if previous}} {{#if previous}}
{{#each previous as |rolled key|}} {{#with previous}}
{{> "systems/foundryvtt-reve-de-dragon/templates/chat-infojet.html"}} {{> "systems/foundryvtt-reve-de-dragon/templates/chat-infojet.html"}}
<br>Double résistance du fleuve! <br><strong>Double résistance du fleuve!</strong><br>
{{/each}} {{/with}}
{{/if}} {{/if}}
{{> "systems/foundryvtt-reve-de-dragon/templates/chat-infojet.html"}} {{> "systems/foundryvtt-reve-de-dragon/templates/chat-infojet.html"}}
<hr> <hr>
<span> <span>
{{#if rolled.isSuccess}} {{#if rolled.isSuccess}}
Vous avez maîtrisé {{le tmr.genre}} {{#if (eq tmr.type 'fleuve')}}Fleuve de l'Oubli{{else}}{{tmr.label}}{{/if}} ! Vous parvenez à {{maitrise.verbe}} {{le tmr.genre}} {{tmr.label}} !
{{else}} {{else}}
Vous ne parvenez pas à surmonter {{le tmr.genre}} {{#if (eq tmr.type 'fleuve')}}Fleuve de l'Oubli{{else}}{{tmr.label}}{{/if}}. Vous ne parvenez pas à {{maitrise.verbe}} {{le tmr.genre}} {{tmr.label}}.
Vous <strong>quittez les Terres Médianes</strong> ! Vous <strong>quittez les Terres Médianes</strong> !
{{#if souffle}} {{#if souffle}}
<br>De plus, votre échec total vous fait subir un Souffle de Dragon : {{souffle.name}} <br>De plus, votre échec total vous fait subir un Souffle de Dragon : {{souffle.name}}