Compare commits

..

16 Commits

Author SHA1 Message Date
232f414a62 Ajout distance 2022-10-09 13:44:40 +02:00
14e76ac631 Merge pull request 'v10' (#564) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #564
2022-10-09 13:43:27 +02:00
43763dbe3a Jets d'encaissement validés par le MJ
* ajout d'une option pour activer la validation par le MJ
* lors d'un jet d'encaissement, une fenêtre s'ouvre chez le MJ
  avec le résultat d'encaissement
* le MJ peut changer le jet d'encaissement
* si le MJ annule, l'encaissement n'a pas lieu
* Attention, si plusieurs MJ, un seul doit valider, sinon
  encaissements multiples
2022-10-09 02:19:33 +02:00
81ae15a6a2 Simuler les lancers de dés
Lorsqu'on force le résultat des dés, on force l'affichage d'un lancer
donnant ce résultat avec DiceSoNice
2022-10-08 17:37:39 +02:00
6dc7272ef6 Corrections mineures
Plus besoin de vérifier game.versions pour utiliser game.users
Plus besoin d'une indirection Misc.getUsers

defender.system.name => defender.name
2022-10-08 17:37:32 +02:00
d75eef1926 Correction configuration affichage prix
L'option de configuration étaity toujours vrai quand vue par le MJ
2022-10-08 17:37:32 +02:00
18039e905b Compétences & herbes personalisées
* permettre d'ajouter des compétences dans un monde, qui seront
  ajoutés aux acteurs créés dans ce monde
* les herbes de repos/soins du monde sont bien considérées comme
  des herbes pour les potions
2022-10-08 17:37:32 +02:00
6d0e5321a2 Nommage homogène 2022-10-08 11:56:52 +02:00
3073670afa Ajout visibilité 2022-10-07 23:46:36 +02:00
690dd1f0a2 Merge pull request 'Monnaies et armes à distance' (#563) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #563
2022-10-07 23:35:40 +02:00
0dcce5456b Inatteignable à -10
au lieu de hyphen non affiché
2022-10-07 23:31:57 +02:00
35f1f2437c Garde fous
En cas d'exception dans le traitement d'un message websocket,
faire un catch pour être sûr de ne pas réémettre si l'exception
revient à l'émetteur (boucle infinie d'envois sinon)
2022-10-07 23:30:06 +02:00
3e17dd9b7e Ajout d'informations pour armes à distance
* prise en compte de la taille de la cible
* prise en compte de l'activité
* valeur indicative
2022-10-07 23:28:41 +02:00
4f8406360f Suppression de création de personnage
la feuille n'est pas encore prête
2022-10-07 19:07:25 +02:00
d316bba8fa Déplacement de méthode compendium 2022-10-07 19:07:25 +02:00
9621d72f92 Correction des monnaies
* Des deniers sont créés si on n'a rien d'autre
* Gagner ou dépenser de l'argent fonctionne même si on n'a pas
  tous les types de pièces
* Tous les acteurs peuvent acheter/vendre s'ils ont de l'argent
  => Pratique pour créer une auberge!
* Seuls les personnages peuvent boire et manger
* plus de problèmes de monnaies manquantes
2022-10-07 19:07:25 +02:00
27 changed files with 745 additions and 459 deletions

View File

@ -33,8 +33,9 @@ import { RollDataAjustements } from "./rolldata-ajustements.js";
import { DialogItemAchat } from "./dialog-item-achat.js";
import { RdDItem } from "./item.js";
import { RdDPossession } from "./rdd-possession.js";
import { ENTITE_BLURETTE, ENTITE_INCARNE, ENTITE_NONINCARNE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js";
import { ENTITE_BLURETTE, ENTITE_INCARNE, ENTITE_NONINCARNE, HIDE_DICE, SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js";
import { RdDConfirm } from "./rdd-confirm.js";
import { DialogValidationEncaissement } from "./dialog-validation-encaissement.js";
const POSSESSION_SANS_DRACONIC = {
img: 'systems/foundryvtt-reve-de-dragon/icons/entites/possession.webp',
@ -116,17 +117,13 @@ export class RdDActor extends Actor {
const isPersonnage = actorData.type == "personnage";
// If the created actor has items (only applicable to duplicated actors) bypass the new actor creation logic
if (actorData.items) {
let actor = await super.create(actorData, options);
if (isPersonnage) {
await actor.checkMonnaiePresence();
}
return actor;
return await super.create(actorData, options);
}
if (isPersonnage) {
const competences = await RdDUtility.loadCompendium(RdDItemCompetence.actorCompendium(actorData.type));
const competences = await RdDUtility.loadItems(it => it.isCompetencePersonnage(), 'foundryvtt-reve-de-dragon.competences');
actorData.items = competences.map(i => i.toObject());
actorData.items = actorData.items.concat(Monnaie.monnaiesData());
actorData.items = actorData.items.concat(Monnaie.monnaiesStandard());
}
else {
actorData.items = [];
@ -143,9 +140,9 @@ export class RdDActor extends Actor {
// Make separate methods for each Actor type (character, npc, etc.) to keep
// things organized.
if (this.type === 'personnage') this._prepareCharacterData(this)
if (this.type === 'creature') this._prepareCreatureData(this)
if (this.type === 'vehicule') this._prepareVehiculeData(this)
if (this.isPersonnage()) this._prepareCharacterData(this)
if (this.isCreature()) this._prepareCreatureData(this)
if (this.isVehicule()) this._prepareVehiculeData(this)
}
/* -------------------------------------------- */
@ -180,8 +177,6 @@ export class RdDActor extends Actor {
await this.cleanupConteneurs();
await this.computeEncombrementTotalEtMalusArmure();
this.computeEtatGeneral();
// Sanity check
await this.checkMonnaiePresence();
}
/* -------------------------------------------- */
@ -194,15 +189,6 @@ export class RdDActor extends Actor {
}
}
/* -------------------------------------------- */
async checkMonnaiePresence() { // Ajout opportuniste si les pièces n'existent pas.
if (!this.items) return; // Sanity check during import
let manquantes = Monnaie.monnaiesManquantes(this);
if (manquantes.length > 0) {
await this.createEmbeddedDocuments('Item', manquantes, { renderSheet: false });
}
}
/* -------------------------------------------- */
isCreature() {
return this.type == 'creature' || this.type == 'entite';
@ -211,6 +197,9 @@ export class RdDActor extends Actor {
isPersonnage() {
return this.type == 'personnage';
}
isVehicule() {
return this.type == 'vehicule';
}
/* -------------------------------------------- */
isHautRevant() {
return this.isPersonnage() && this.system.attributs.hautrevant.value != ""
@ -355,7 +344,7 @@ export class RdDActor extends Actor {
}
/* -------------------------------------------- */
getDraconicList() {
return this.items.filter(it => it.type == 'competence' && it.system.categorie == 'draconic')
return this.items.filter(it => it.isCompetencePersonnage() && it.system.categorie == 'draconic')
}
/* -------------------------------------------- */
getBestDraconic() {
@ -2329,7 +2318,7 @@ export class RdDActor extends Actor {
case "detection d'aura":
return draconicList;
case "annulation de magie":
return draconicList.filter(it => !Grammar.toLowerCaseNoAccent(it.name).includes('thanatos'));
return draconicList.filter(it => !RdDItemCompetence.isThanatos(it));
}
return [RdDItemCompetence.getVoieDraconic(draconicList, sort.system.draconic)];
}
@ -3342,20 +3331,39 @@ export class RdDActor extends Actor {
}
/* -------------------------------------------- */
async encaisserDommages(rollData, attacker = undefined, defenderRoll = undefined) {
async encaisserDommages(rollData, attacker = undefined, show = undefined) {
if (attacker && !await attacker.accorder(this, 'avant-encaissement')) {
return;
}
this.validerEncaissement(rollData, show);
}
console.log("encaisserDommages", rollData)
async validerEncaissement(rollData, show) {
if (ReglesOptionelles.isUsing('validation-encaissement-gr') && !game.user.isGM) {
RdDActor.remoteActorCall({
actorId: this.id,
method: 'validerEncaissement',
args: [rollData, show]
});
return;
}
const armure = await this.computeArmure(rollData);
if (ReglesOptionelles.isUsing('validation-encaissement-gr')) {
DialogValidationEncaissement.validerEncaissement(this, rollData, armure, show, (encaissement, show) => this._appliquerEncaissement(encaissement, show));
}
else {
let encaissement = await RdDUtility.jetEncaissement(rollData, armure, { showDice: SHOW_DICE });
await this._appliquerEncaissement(encaissement, show)
}
}
async _appliquerEncaissement(encaissement, show) {
let santeOrig = duplicate(this.system.sante);
let encaissement = await this.jetEncaissement(rollData);
this.ajouterBlessure(encaissement); // Will upate the result table
const perteVie = this.isEntite()
? { 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);
this.computeEtatGeneral();
@ -3368,7 +3376,7 @@ export class RdDActor extends Actor {
jetEndurance: perteEndurance.jetEndurance,
endurance: santeOrig.endurance.value - perteEndurance.newValue,
vie: this.isEntite() ? 0 : (santeOrig.vie.value - perteVie.newValue),
show: defenderRoll?.show ?? {}
show: show ?? {}
});
await ChatUtility.createChatWithRollMode(this.name, {
@ -3386,65 +3394,6 @@ export class RdDActor extends Actor {
}
}
/* -------------------------------------------- */
async jetEncaissement(rollData) {
let formula = "2d10";
// Chaque dé fait au minmum la difficulté libre
if (ReglesOptionelles.isUsing('degat-minimum-malus-libre')) {
if (rollData.diffLibre < 0) {
let valeurMin = Math.abs(rollData.diffLibre);
formula += "min" + valeurMin;
}
}
// Chaque dé fait au minmum la difficulté libre
if (ReglesOptionelles.isUsing('degat-ajout-malus-libre')) {
if (rollData.diffLibre < 0) {
let valeurMin = Math.abs(rollData.diffLibre);
formula += "+" + valeurMin;
}
}
let roll = await RdDDice.roll(formula);
// 1 dé fait au minmum la difficulté libre
if (ReglesOptionelles.isUsing('degat-minimum-malus-libre-simple')) {
if (rollData.diffLibre < 0) {
let valeurMin = Math.abs(rollData.diffLibre);
if (roll.terms[0].results[0].result < valeurMin) {
roll.terms[0].results[0].result = valeurMin;
} else if (roll.terms[0].results[1].result < valeurMin) {
roll.terms[0].results[1].result = valeurMin;
}
roll._total = roll.terms[0].results[0].result + roll.terms[0].results[1].result;
}
}
const armure = await this.computeArmure(rollData);
const jetTotal = roll.total + rollData.dmg.total - armure;
let encaissement = RdDUtility.selectEncaissement(jetTotal, rollData.dmg.mortalite)
let over20 = Math.max(jetTotal - 20, 0);
encaissement.dmg = rollData.dmg;
encaissement.dmg.loc = rollData.dmg.loc ?? await RdDUtility.getLocalisation(this.type);
encaissement.dmg.loc.label = encaissement.dmg.loc.label ?? 'Corps;'
encaissement.roll = roll;
encaissement.armure = armure;
encaissement.total = jetTotal;
encaissement.vie = await RdDActor._evaluatePerte(encaissement.vie, over20);
encaissement.endurance = await RdDActor._evaluatePerte(encaissement.endurance, over20);
encaissement.penetration = rollData.arme?.system.penetration ?? 0;
return encaissement;
}
/* -------------------------------------------- */
static async _evaluatePerte(formula, over20) {
let perte = new Roll(formula, { over20: over20 });
await perte.evaluate({ async: true });
return perte.total;
}
/* -------------------------------------------- */
ajouterBlessure(encaissement) {
if (this.type == 'entite') return; // Une entité n'a pas de blessures
@ -3610,33 +3559,11 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */
getFortune() {
let monnaies = this.itemTypes['monnaie'];
if (monnaies.length < 4) {
ui.notifications.error("Problème de monnaies manquantes, impossible de payer correctement!")
throw "Problème de monnaies manquantes, impossible de payer correctement!";
}
return monnaies.map(m => Number(m.system.valeur_deniers) * Number(m.system.quantite))
return this.itemTypes['monnaie']
.map(m => Number(m.system.valeur_deniers) * Number(m.system.quantite))
.reduce(Misc.sum(), 0);
}
/* -------------------------------------------- */
async optimizeArgent(fortuneTotale) {
let monnaies = this.itemTypes['monnaie'];
let parValeur = Misc.classifyFirst(monnaies, it => it.system.valeur_deniers);
let nouvelleFortune = {
1000: Math.floor(fortuneTotale / 1000), // or
100: Math.floor(fortuneTotale / 100) % 10, // argent
10: Math.floor(fortuneTotale / 10) % 10, // bronze
1: fortuneTotale % 10 // étain
}
console.log('RdDActor.optimizeArgent', fortuneTotale, 'nouvelleFortune', nouvelleFortune, 'monnaie_par_valeur', parValeur);
let updates = [];
for (const [valeur, nombre] of Object.entries(nouvelleFortune)) {
updates.push({ _id: parValeur[valeur].id, 'system.quantite': nombre });
}
await this.updateEmbeddedDocuments('Item', updates);
}
/* -------------------------------------------- */
async depenserDeniers(depense, dataObj = undefined, quantite = 1, toActorId) {
depense = Number(depense);
@ -3653,10 +3580,9 @@ export class RdDActor extends Actor {
}
else {
if (fortune >= depense) {
fortune -= depense;
const toActor = game.actors.get(toActorId)
await toActor?.ajouterDeniers(depense, this.id);
await this.optimizeArgent(fortune);
await Monnaie.optimiser(this, fortune - depense);
msg = `Vous avez payé <strong>${depense} Deniers</strong>${toActor ? " à " + toActor.name : ''}, qui ont été soustraits de votre argent.`;
RdDAudio.PlayContextAudio("argent"); // Petit son
@ -3679,18 +3605,19 @@ export class RdDActor extends Actor {
}
async depenser(depense) {
depense = Number(depense);
let fortune = this.getFortune();
let reste = fortune - depense;
let reste = this.getFortune() - Number.parseInt(depense);
if (reste >= 0) {
fortune -= depense;
await this.optimizeArgent(fortune);
await Monnaie.optimiser(this, reste);
}
return reste;
}
async ajouterDeniers(gain, fromActorId = undefined) {
gain = Number.parseInt(gain);
if (gain < 0) {
ui.notifications.error(`Impossible d'ajouter un gain de ${gain} <0`);
return;
}
if (gain == 0) {
return;
}
@ -3703,9 +3630,7 @@ export class RdDActor extends Actor {
}
else {
const fromActor = game.actors.get(fromActorId)
let fortune = this.getFortune();
fortune += gain;
await this.optimizeArgent(fortune);
await Monnaie.optimiser(this, gain + this.getFortune());
RdDAudio.PlayContextAudio("argent"); // Petit son
ChatMessage.create({
@ -3735,8 +3660,7 @@ export class RdDActor extends Actor {
actorId: achat.vendeurId ?? achat.acheteurId,
method: 'achatVente',
args: [achat]
},
);
});
return;
}
@ -3744,8 +3668,8 @@ export class RdDActor extends Actor {
const vendeur = achat.vendeurId ? game.actors.get(achat.vendeurId) : undefined;
const messageVente = game.messages.get(achat.chatMessageIdVente);
const html = await messageVente.getHTML();
const buttonAcheter = html.find(".button-acheter")[0];
const vente = DialogItemAchat.prepareVenteData(buttonAcheter, achat.vendeurId, vendeur, acheteur);
const button = html.find(".button-acheter")[0];
const vente = DialogItemAchat.venteData(button);
const itemId = vente.item._id;
const isItemEmpilable = "quantite" in vente.item.system;
@ -4146,7 +4070,7 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */
async setEffect(statusId, status) {
if (this.isEntite() || this.type == 'vehicule') {
if (this.isEntite() || this.isVehicule()) {
return;
}
console.log("setEffect", statusId, status)
@ -4176,7 +4100,7 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */
async onPreUpdateItem(item, change, options, id) {
if (item.type == 'competence' && item.system.defaut_carac && item.system.xp) {
if (item.isCompetencePersonnage() && item.system.defaut_carac && item.system.xp) {
await this.checkCompetenceXP(item.name, item.system.xp);
}
}

View File

@ -129,7 +129,7 @@ export class ChatUtility {
/* -------------------------------------------- */
static getUsers(filter) {
return Misc.getUsers().filter(filter).map(user => user.id);
return game.users.filter(filter).map(user => user.id);
}
/* -------------------------------------------- */

View File

@ -1,34 +1,52 @@
import { Monnaie } from "./item-monnaie.js";
import { Misc } from "./misc.js";
import { RdDUtility } from "./rdd-utility.js";
export class DialogItemAchat extends Dialog {
static async onButtonAcheter(event) {
const buttonAcheter = event.currentTarget;
if (!buttonAcheter.attributes['data-jsondata']?.value) {
ui.notifications.warn("Impossible d'acheter: informations sur l'objet manquantes")
return;
}
const chatMessageIdVente = RdDUtility.findChatMessageId(buttonAcheter);
const vendeurId = buttonAcheter.attributes['data-vendeurId']?.value;
static venteData(button) {
const vendeurId = button.attributes['data-vendeurId']?.value;
const vendeur = vendeurId ? game.actors.get(vendeurId) : undefined;
const acheteur = RdDUtility.getSelectedActor();
const json = button.attributes['data-jsondata']?.value;
if (!acheteur && !vendeur) {
ui.notifications.info("Pas d'acheteur ni de vendeur, aucun changement");
return;
return undefined;
}
if (!json) {
ui.notifications.warn("Impossible d'acheter: informations sur l'objet manquantes")
return undefined;
}
let venteData = DialogItemAchat.prepareVenteData(buttonAcheter, vendeurId, vendeur, acheteur);
const prixLot = Monnaie.arrondiDeniers(button.attributes['data-prixLot']?.value ?? 0);
return {
item: json ? JSON.parse(json) : undefined,
vendeurId: vendeurId,
vendeur: vendeur,
acheteur: acheteur,
tailleLot: parseInt(button.attributes['data-tailleLot']?.value ?? 1),
quantiteIllimite: button.attributes['data-quantiteIllimite']?.value == 'true',
quantiteNbLots: parseInt(button.attributes['data-quantiteNbLots']?.value),
choix: {
nombreLots: 1,
seForcer: false,
supprimerSiZero: true
},
prixLot: prixLot,
prixTotal: prixLot,
isVente: prixLot > 0,
chatMessageIdVente: RdDUtility.findChatMessageId(button)
};
}
static async onAcheter(venteData) {
const html = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/dialog-item-achat.html`, venteData);
const dialog = new DialogItemAchat(html, vendeur, acheteur, venteData, chatMessageIdVente);
const dialog = new DialogItemAchat(html, venteData);
dialog.render(true);
}
constructor(html, vendeur, acheteur, venteData, chatMessageIdVente) {
const isConsommable = venteData.item.type == 'nourritureboisson';
constructor(html, venteData) {
const isConsommable = venteData.item.type == 'nourritureboisson' && venteData.acheteur?.isPersonnage();
let options = { classes: ["dialogachat"], width: 400, height: isConsommable ? 450 : 350, 'z-index': 99999 };
const actionAchat = venteData.prixLot > 0 ? "Acheter" : "Prendre";
@ -47,42 +65,17 @@ export class DialogItemAchat extends Dialog {
super(conf, options);
this.vendeur = vendeur;
this.acheteur = acheteur;
this.chatMessageIdVente = chatMessageIdVente;
this.venteData = venteData;
}
static prepareVenteData(buttonAcheter, vendeurId, vendeur, acheteur) {
const jsondata = buttonAcheter.attributes['data-jsondata']?.value;
const prixLot = parseInt(buttonAcheter.attributes['data-prixLot']?.value ?? 0);
return {
item: JSON.parse(jsondata),
vendeurId: vendeurId,
vendeur: vendeur,
acheteur: acheteur,
tailleLot: parseInt(buttonAcheter.attributes['data-tailleLot']?.value ?? 1),
quantiteIllimite: buttonAcheter.attributes['data-quantiteIllimite']?.value == 'true',
quantiteNbLots: parseInt(buttonAcheter.attributes['data-quantiteNbLots']?.value),
choix: {
nombreLots: 1,
seForcer: false,
supprimerSiZero: true
},
prixLot: prixLot,
prixTotal: prixLot,
isVente: prixLot > 0
};
}
async onAchat() {
await $(".nombreLots").change();
(this.vendeur ?? this.acheteur).achatVente({
(this.venteData.vendeur ?? this.venteData.acheteur).achatVente({
userId: game.user.id,
vendeurId: this.vendeur?.id,
acheteurId: this.acheteur?.id,
vendeurId: this.venteData.vendeur?.id,
acheteurId: this.venteData.acheteur?.id,
prixTotal: this.venteData.prixTotal,
chatMessageIdVente: this.chatMessageIdVente,
chatMessageIdVente: this.venteData.chatMessageIdVente,
choix: this.venteData.choix
});
}

View File

@ -3,7 +3,7 @@ import { Misc } from "./misc.js";
export class DialogItemVente extends Dialog {
static async create(item, callback) {
static async display(item, callback) {
const quantite = item.isConteneur() ? 1 : item.system.quantite;
const venteData = {
item: item,
@ -20,7 +20,7 @@ export class DialogItemVente extends Dialog {
isOwned: item.isOwned,
};
const html = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/dialog-item-vente.html`, venteData);
return new DialogItemVente(venteData, html, callback);
return new DialogItemVente(venteData, html, callback).render(true);
}
constructor(venteData, html, callback) {

View File

@ -0,0 +1,70 @@
import { HIDE_DICE, SHOW_DICE } from "./constants.js";
import { RdDUtility } from "./rdd-utility.js";
/**
* Extend the base Dialog entity by defining a custom window to perform roll.
* @extends {Dialog}
*/
export class DialogValidationEncaissement extends Dialog {
static async validerEncaissement(actor, rollData, armure, show, onEncaisser) {
let encaissement = await RdDUtility.jetEncaissement(rollData, armure, { showDice: HIDE_DICE });
const html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-validation-encaissement.html', {
actor: actor,
rollData: rollData,
encaissement: encaissement,
show: show
});
const dialog = new DialogValidationEncaissement(html, actor, rollData, armure, encaissement, show, onEncaisser);
dialog.render(true);
}
/* -------------------------------------------- */
constructor(html, actor, rollData, armure, encaissement, show, onEncaisser) {
// Common conf
let buttons = {
"valider": { label: "Valider", callback: html => this.validerEncaissement() },
"annuler": { label: "Annuler", callback: html => { } },
};
let dialogConf = {
title: "Validation d'encaissement",
content: html,
buttons: buttons,
default: "valider"
}
let dialogOptions = {
classes: ["rdddialog"],
width: 350,
height: 290
}
// Select proper roll dialog template and stuff
super(dialogConf, dialogOptions);
this.actor = actor
this.rollData = rollData;
this.armure = armure;
this.encaissement = encaissement;
this.show = show;
this.onEncaisser = onEncaisser;
this.forceDiceResult = {total: encaissement.roll.result };
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
html.find('input.encaissement-roll-result').keyup(async event => {
this.forceDiceResult.total = event.currentTarget.value;
this.encaissement = await RdDUtility.jetEncaissement(this.rollData, this.armure, { showDice: HIDE_DICE, forceDiceResult: this.forceDiceResult});
$('label.encaissement-total').text(this.encaissement.total);
$('label.encaissement-blessure').text(this.encaissement.blessures)
});
}
async validerEncaissement() {
this.encaissement = await RdDUtility.jetEncaissement(this.rollData, this.armure, { showDice: SHOW_DICE, forceDiceResult: this.forceDiceResult});
this.onEncaisser(this.encaissement, this.show)
}
}

View File

@ -57,8 +57,8 @@ const competence_xp_cumul = _buildCumulXP();
export class RdDItemCompetence extends Item {
/* -------------------------------------------- */
static actorCompendium(actorType) {
return compendiumCompetences[actorType];
static actorCompendium(actorType = undefined) {
return compendiumCompetences[actorType ?? 'personnage'];
}
/* -------------------------------------------- */
@ -89,22 +89,28 @@ export class RdDItemCompetence extends Item {
/* -------------------------------------------- */
static isCompetenceArme(competence) {
switch (competence.system.categorie) {
case 'melee':
return competence.name != 'Esquive';
case 'tir':
case 'lancer':
return true;
if (competence.isCompetence()) {
switch (competence.system.categorie) {
case 'melee':
return !Grammar.toLowerCaseNoAccent(competence.name).includes('esquive');
case 'tir':
case 'lancer':
return true;
}
}
return false;
}
/* -------------------------------------------- */
static isArmeUneMain(competence) {
return competence.name.toLowerCase().includes("1 main");
return RdDItemCompetence.isCompetenceArme(competence) && competence.name.toLowerCase().includes("1 main");
}
static isArme2Main(competence) {
return competence.name.toLowerCase().includes("2 main");
return RdDItemCompetence.isCompetenceArme(competence) && competence.name.toLowerCase().includes("2 main");
}
static isThanatos(competence) {
return competence.isCompetencePersonnage() && Grammar.toLowerCaseNoAccent(competence.name).includes('thanatos');
}
/* -------------------------------------------- */
@ -133,7 +139,7 @@ export class RdDItemCompetence extends Item {
/* -------------------------------------------- */
static computeXP(competence) {
const factor = competence.name.includes('Thanatos') ? 2 : 1; // Thanatos compte double !
const factor = RdDItemCompetence.isThanatos(competence) ? 2 : 1; // Thanatos compte double !
const xpNiveau = RdDItemCompetence.computeDeltaXP(competence.system.base, competence.system.niveau ?? competence.system.base);
const xp = competence.system.xp ?? 0;
const xpSort = competence.system.xp_sort ?? 0;
@ -213,21 +219,14 @@ export class RdDItemCompetence extends Item {
if (idOrName == undefined) {
return undefined;
}
options = mergeObject(options, {
preFilter: it => RdDItemCompetence.isCompetence(it),
description: 'compétence',
});
return list.find(it => it.id == idOrName && RdDItemCompetence.isCompetence(it))
options = mergeObject(options, { preFilter: it => it.isCompetence(), description: 'compétence', });
return list.find(it => it.id == idOrName && it.isCompetence())
?? Misc.findFirstLike(idOrName, list, options);
}
/* -------------------------------------------- */
static findCompetences(list, name) {
return Misc.findAllLike(name, list, { filter: it => RdDItemCompetence.isCompetence(it), description: 'compétence' });
}
static isCompetence(item) {
return item.type == 'competence' || item.type == 'competencecreature';
return Misc.findAllLike(name, list, { filter: it => it.isCompetence(), description: 'compétence' });
}
/* -------------------------------------------- */

View File

@ -1,44 +1,32 @@
import { Misc } from "./misc.js";
import { LOG_HEAD, SYSTEM_RDD } from "./constants.js";
import { LOG_HEAD } from "./constants.js";
const MONNAIES_STANDARD = [
{
name: "Etain (1 denier)", type: 'monnaie',
img: "systems/foundryvtt-reve-de-dragon/icons/objets/piece_etain_poisson.webp",
system: { quantite: 0, valeur_deniers: 1, encombrement: 0.001, description: "" }
},
{
name: "Bronze (10 deniers)", type: 'monnaie',
img: "systems/foundryvtt-reve-de-dragon/icons/objets/piece_bronze_epees.webp",
system: { quantite: 0, valeur_deniers: 10, encombrement: 0.002, description: "" }
},
{
name: "Argent (1 sol)", type: 'monnaie',
img: "systems/foundryvtt-reve-de-dragon/icons/objets/piece_argent_sol.webp",
system: { quantite: 0, valeur_deniers: 100, encombrement: 0.003, description: "" }
},
{
name: "Or (10 sols)", type: 'monnaie',
img: "systems/foundryvtt-reve-de-dragon/icons/objets/piece_or_sol.webp",
system: { quantite: 0, valeur_deniers: 1000, encombrement: 0.004, description: "" }
}
]
const VALEURS_STANDARDS = MONNAIES_STANDARD.map(it =>it.system.valeur_deniers);
const MONNAIE_ETAIN = {
name: "Etain (1 denier)", type: 'monnaie',
img: "systems/foundryvtt-reve-de-dragon/icons/objets/piece_etain_poisson.webp",
system: { quantite: 0, valeur_deniers: 1, encombrement: 0.001, description: "" }
};
const MONNAIE_BRONZE = {
name: "Bronze (10 deniers)", type: 'monnaie',
img: "systems/foundryvtt-reve-de-dragon/icons/objets/piece_bronze_epees.webp",
system: { quantite: 0, valeur_deniers: 10, encombrement: 0.002, description: "" }
};
const MONNAIE_ARGENT = {
name: "Argent (1 sol)", type: 'monnaie',
img: "systems/foundryvtt-reve-de-dragon/icons/objets/piece_argent_sol.webp",
system: { quantite: 0, valeur_deniers: 100, encombrement: 0.003, description: "" }
};
const MONNAIE_OR = {
name: "Or (10 sols)", type: 'monnaie',
img: "systems/foundryvtt-reve-de-dragon/icons/objets/piece_or_sol.webp",
system: { quantite: 0, valeur_deniers: 1000, encombrement: 0.004, description: "" }
};
const MONNAIES_STANDARD = [MONNAIE_ETAIN, MONNAIE_BRONZE, MONNAIE_ARGENT, MONNAIE_OR];
export class Monnaie {
static isSystemMonnaie(item, items) {
if (item.type == 'monnaie') {
const valeur = item.system.valeur_deniers;
if (VALEURS_STANDARDS.includes(valeur)) {
const monnaiesDeValeur = items.filter(it => it.type == 'monnaie' && it.system.valeur_deniers == valeur)
return monnaiesDeValeur.length<=1;
}
}
return false;
}
static monnaiesData() {
static monnaiesStandard() {
return MONNAIES_STANDARD;
}
@ -56,10 +44,47 @@ export class Monnaie {
}
static arrondiDeniers(sols) {
return sols.toFixed(2);
return Number(sols).toFixed(2);
}
static triValeurDenier() {
return Misc.ascending(item => item.system.valeur_deniers)
}
static async creerMonnaiesStandard(actor) {
await actor.createEmbeddedDocuments('Item', MONNAIES_STANDARD, { renderSheet: false });
}
static async creerMonnaiesDeniers(actor, fortune) {
await actor.createEmbeddedDocuments('Item', [Monnaie.creerDeniers(fortune)], { renderSheet: false });
}
static creerDeniers(fortune) {
const deniers = duplicate(MONNAIE_ETAIN);
deniers.system.quantite = fortune;
return deniers;
}
static async optimiser(actor, fortune) {
let reste = fortune;
let monnaies = actor.itemTypes['monnaie'];
let updates = [];
let parValeur = Misc.classifyFirst(monnaies, it => it.system.valeur_deniers);
for (let valeur of [1000, 100, 10, 1]) {
if (parValeur[valeur]) {
const piecesDeCetteValeur = Math.floor(reste / valeur);
updates.push({ _id: parValeur[valeur].id, 'system.quantite': piecesDeCetteValeur });
reste -= piecesDeCetteValeur*valeur;
}
}
console.log('Monnaie.optimiser', actor.name, 'total', fortune, 'parValeur', parValeur, 'updates', updates, 'reste', reste);
if (updates.length > 0) {
await actor.updateEmbeddedDocuments('Item', updates);
}
if (reste>0){
// créer le reste en deniers fortune en deniers
await Monnaie.creerMonnaiesDeniers(actor, reste);
}
}
}

View File

@ -90,11 +90,10 @@ export class RdDItemSheet extends ItemSheet {
if (this.item.type == 'tache' || this.item.type == 'livre' || this.item.type == 'meditation' || this.item.type == 'oeuvre') {
formData.caracList = duplicate(game.system.model.Actor.personnage.carac)
formData.caracList["reve-actuel"] = duplicate(game.system.model.Actor.personnage.reve.reve)
formData.competences = await RdDUtility.loadCompendium('foundryvtt-reve-de-dragon.competences')
formData.competences = await RdDUtility.loadItems(it => it.isCompetencePersonnage(), RdDItemCompetence.actorCompendium(this.actor?.type))
}
if (this.item.type == 'arme') {
formData.competences = await RdDUtility.loadCompendium('foundryvtt-reve-de-dragon.competences', it => RdDItemCompetence.isCompetenceArme(it));
console.log(formData.competences)
formData.competences = await RdDUtility.loadItems(it => RdDItemCompetence.isCompetenceArme(it), RdDItemCompetence.actorCompendium(this.actor?.type))
}
if (this.item.type == 'recettecuisine') {
formData.ingredients = await TextEditor.enrichHTML(this.object.system.ingredients, {async: true})
@ -115,7 +114,7 @@ export class RdDItemSheet extends ItemSheet {
formData.system.prdate = this.dateUpdated;
this.dateUpdated = undefined;
}
RdDHerbes.updatePotionData(formData);
await RdDHerbes.updatePotionData(formData);
}
if (formData.isOwned && this.item.type == 'herbe' && (formData.system.categorie == 'Soin' || formData.system.categorie == 'Repos')) {
formData.isIngredientPotionBase = true;
@ -160,7 +159,7 @@ export class RdDItemSheet extends ItemSheet {
html.find(".categorie").change(event => this._onSelectCategorie(event));
html.find('.sheet-competence-xp').change((event) => {
if (this.item.type == 'competence') {
if (this.item.isCompetencePersonnage()) {
RdDUtility.checkThanatosXP(this.item.name);
}
});

View File

@ -21,7 +21,7 @@ const typesObjetsOeuvres = ["oeuvre", "recettecuisine", "musique", "chant", "dan
const typesObjetsDraconiques = ["queue", "ombre", "souffle", "tete", "signedraconique", "sortreserve"]
const typesObjetsConnaissance = ["meditation", "recettealchimique", "sort"]
const typesObjetsEffet = ["possession", "poison", "maladie"]
const typesObjetsCompetence = ["competence", "compcreature"]
const typesObjetsCompetence = ["competence", "competencecreature"]
const encBrin = 0.00005; // un brin = 1 décigramme = 1/10g = 1/10000kg = 1/20000 enc
const encPepin = 0.0007; /* un pépin de gemme = 1/10 cm3 = 1/1000 l = 3.5/1000 kg = 7/2000 kg = 7/1000 enc
densité 3.5 (~2.3 à 4, parfois plus) -- https://www.juwelo.fr/guide-des-pierres/faits-et-chiffres/
@ -29,7 +29,7 @@ densité 3.5 (~2.3 à 4, parfois plus) -- https://www.juwelo.fr/guide-des-pierre
export const defaultItemImg = {
competence: "systems/foundryvtt-reve-de-dragon/icons/competence_defaut.webp",
compcreature: "systems/foundryvtt-reve-de-dragon/icons/competence_defaut.webp",
competencecreature: "systems/foundryvtt-reve-de-dragon/icons/competence_defaut.webp",
arme: "systems/foundryvtt-reve-de-dragon/icons/armes_armures/epee_gnome.webp",
armure: "systems/foundryvtt-reve-de-dragon/icons/armes_armures/armure_plaques.webp",
conteneur: "systems/foundryvtt-reve-de-dragon/icons/objets/sac_a_dos.webp",
@ -77,6 +77,9 @@ export class RdDItem extends Item {
return typesObjetsOeuvres
}
isCompetencePersonnage() {
return this.type == 'competence'
}
isCompetence() {
return typesObjetsCompetence.includes(this.type)
}
@ -180,8 +183,8 @@ export class RdDItem extends Item {
this.system.magique = categorie.includes('enchante');
if (this.system.magique) {
if (categorie.includes('soin') || categorie.includes('repos')) {
// TODO: utiliser calculePointsRepos / calculePointsGuerison
this.system.puissance = RdDHerbes.calculePuissancePotion(this);
// TODO: utiliser calculPointsRepos / calculPointsGuerison
this.system.puissance = RdDHerbes.calculPuissancePotion(this);
}
}
}
@ -268,26 +271,23 @@ export class RdDItem extends Item {
async proposerVente() {
console.log(this);
if (this.isConteneurNonVide()) {
ui.notifications.warn(`Votre ${this.name} n'est pas vide, pas possible de le donner ou le vendre`);
ui.notifications.warn(`Votre ${this.name} n'est pas vide, pas possible de le proposer`);
return;
}
const dialog = await DialogItemVente.create(this, (vente) => this._onProposerVente(vente))
dialog.render(true);
}
async _onProposerVente(venteData) {
venteData["properties"] = this.getProprietes();
if (venteData.isOwned) {
if (venteData.quantiteNbLots * venteData.tailleLot > venteData.quantiteMax) {
ui.notifications.warn(`Vous avez ${venteData.quantiteMax} ${venteData.item.name}, ce n'est pas suffisant pour vendre ${venteData.quantiteNbLots} de ${venteData.tailleLot}`)
return;
await DialogItemVente.display(this, async (vente) => {
vente["properties"] = this.getProprietes();
if (vente.isOwned) {
if (vente.quantiteNbLots * vente.tailleLot > vente.quantiteMax) {
ui.notifications.warn(`Vous avez ${vente.quantiteMax} ${vente.item.name}, ce n'est pas suffisant pour vendre ${vente.quantiteNbLots} de ${vente.tailleLot}`)
return;
}
}
}
venteData.jsondata = JSON.stringify(venteData.item);
console.log(venteData);
let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-vente-item.html', venteData);
ChatMessage.create(RdDUtility.chatDataSetup(html));
vente.jsondata = JSON.stringify(vente.item);
console.log(vente);
let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-vente-item.html', vente);
ChatMessage.create(RdDUtility.chatDataSetup(html));
});
}
/* -------------------------------------------- */

View File

@ -105,17 +105,6 @@ export class Misc {
return params.reduce((a, b) => a + separator + b);
}
static getEntityTypeLabel(entity) {
const documentName = entity?.documentName
const type = entity?.type
if (documentName === 'Actor' || documentName === 'Item') {
const label = CONFIG[documentName]?.typeLabels?.[type] ?? type;
return game.i18n.has(label) ? game.i18n.localize(label) : t;
}
return type;
}
static connectedGMOrUser(ownerId = undefined) {
if (ownerId && game.user.id == ownerId) {
return ownerId;
@ -123,16 +112,12 @@ export class Misc {
return Misc.firstConnectedGM()?.id ?? game.user.id;
}
static getUsers() {
return game.version ? game.users : game.users.entities;
}
static getActiveUser(id) {
return Misc.getUsers().find(u => u.id == id && u.active);
return game.users.find(u => u.id == id && u.active);
}
static firstConnectedGM() {
return Misc.getUsers().filter(u => u.isGM && u.active).sort(Misc.ascending(u => u.id)).find(u => u.isGM && u.active);
return game.users.filter(u => u.isGM && u.active).sort(Misc.ascending(u => u.id)).find(u => u.isGM && u.active);
}
@ -153,7 +138,7 @@ export class Misc {
/* -------------------------------------------- */
static findPlayer(name) {
return Misc.findFirstLike(name, Misc.getUsers(), { description: 'joueur' });
return Misc.findFirstLike(name, game.users, { description: 'joueur' });
}
/* -------------------------------------------- */

View File

@ -1,5 +1,5 @@
import { ChatUtility } from "./chat-utility.js";
import { ENTITE_INCARNE, ENTITE_NONINCARNE, HIDE_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js";
import { ENTITE_BLURETTE, ENTITE_INCARNE, ENTITE_NONINCARNE, HIDE_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js";
import { Grammar } from "./grammar.js";
import { RdDItemArme } from "./item-arme.js";
import { RdDItemCompetence } from "./item-competence.js";
@ -688,27 +688,81 @@ export class RdDCombat {
}
/* -------------------------------------------- */
verifierDistance( rollData ) {
if ( rollData.competence.system.categorie == "tir" ||
rollData.competence.system.categorie == "lancer" ) {
const defenderToken = canvas.tokens.get(this.defenderTokenId)
let dist = canvas.grid.measureDistances([{ ray: new Ray(_token.center, defenderToken.center) }], { gridSpaces: false })
dist = Number(dist).toPrecision(5)
//let ray = new Ray( {x: _token.x, y: _token.y}, {x: defenderToken.x, y:defenderToken.y} )
let msgPortee = "portée est courte (0)"
if (dist > rollData.arme.system.portee_courte && dist <= rollData.arme.system.portee_moyenne) {
msgPortee = "portée est moyenne (-3)"
} else if (dist > rollData.arme.system.portee_moyenne && dist <= rollData.arme.system.portee_extreme) {
msgPortee = "portée est extrême (-5)"
} else if ( dist > rollData.arme.system.portee_extreme) {
msgPortee = "cible est inateignable"
async proposerAjustementTirLancer( rollData ) {
if (['tir', 'lancer'].includes(rollData.competence.system.categorie)) {
if (this.defender.isEntite([ENTITE_BLURETTE])){
ChatMessage.create( {
content: `<strong>La cible est une blurette, l'arme à distance sera perdue dans le blurêve`,
whisper: ChatMessage.getWhisperRecipients("GM")})
}
else {
const defenderToken = canvas.tokens.get(this.defenderTokenId);
const dist = this.distance(_token, defenderToken)
const isVisible = this.isVisible(_token, defenderToken)
const portee = this._ajustementPortee(dist, rollData.arme)
const taille = this._ajustementTaille(this.defender)
const activite = this._ajustementMouvement(this.defender)
const total = [portee, taille, activite].map(it=>it.diff).filter(d => !Number.isNaN(d)).reduce(Misc.sum(), 0)
ChatMessage.create({
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-info-distance.html', {
rollData: rollData,
attacker: _token,
isVisible: isVisible,
defender: defenderToken,
distance: dist,
portee: portee,
taille: taille,
activite: activite,
total: total
}),
whisper: ChatMessage.getWhisperRecipients("GM")
})
}
ChatMessage.create( { content: `<strong>Indication MJ</strong> : La cible est à une distance indicative de : ${dist} mètres. Pour l'arme ${rollData.arme.name}, la ${msgPortee}.`, whisper: ChatMessage.getWhisperRecipients("GM") } )
}
}
isVisible(token, defenderToken) {
return canvas.effects.visibility.testVisibility(defenderToken.center, { object: token })
}
distance(token, defenderToken) {
return Number(canvas.grid.measureDistances([{ ray: new Ray(token.center, defenderToken.center) }], { gridSpaces: false })).toFixed(1);
}
_ajustementPortee(dist, arme) {
if (dist <= arme.system.portee_courte) return {msg:"courte", diff:0};
if (dist <= arme.system.portee_moyenne) return {msg: "moyenne" , diff: -3};
if (dist <= arme.system.portee_extreme) return {msg: "extrême", diff:-5};
return {msg: "inatteignable", diff: -10};
}
_ajustementTaille(actor) {
if (actor.isVehicule()) return {msg: "véhicule", diff: 0}
const taille = actor.getCaracByName('TAILLE')?.value ?? 1;
if (taille <= 1) return {msg: "souris", diff: -8};
if (taille <= 3) return {msg: "chat", diff: -4};
if (taille <= 5) return {msg: "chien", diff: -2};
if (taille <= 15) return {msg: "humanoïde", diff: 0};
if (taille <= 20) return {msg: "ogre", diff: 2};
return {msg: "gigantesque", diff: 4};
}
_ajustementMouvement(defender) {
if (defender.getSurprise(true)) return {msg: "immobile (surprise)", diff: 0};
if (game.combat?.combatants.find(it => it.actorId == defender.id)) return {msg: "en mouvement (combat)", diff: -4};
return {msg: "à déterminer (0 immobile, -3 actif, -4 en mouvement, -5 en zig-zag)", diff: -3};
}
/* -------------------------------------------- */
async attaque(competence, arme) {
// const nonIncarnee = this.defender.isEntite([ENTITE_NONINCARNE])
// const blurette = this.defender.isEntite([ENTITE_BLURETTE])
// if (nonIncarnee || blurette) {
// ChatMessage.create( {
// content: `<strong>La cible est ${nonIncarnee ? 'non incarnée' : 'une blurette'}.
// Il est impossible de l'atteindre.`,
// whisper: ChatMessage.getWhisperRecipients("GM")})
// }
if (!await this.accorderEntite('avant-attaque')) {
return;
}
@ -729,7 +783,7 @@ export class RdDCombat {
if (arme) {
this.attacker.verifierForceMin(arme);
}
this.verifierDistance(rollData)
await this.proposerAjustementTirLancer(rollData)
const dialog = await RdDRoll.create(this.attacker, rollData,
{
@ -824,7 +878,7 @@ export class RdDCombat {
attackerRoll.dmg = RdDBonus.dmg(attackerRoll, this.attacker.getBonusDegat(), this.defender.isEntite());
let defenderRoll = { attackerRoll: attackerRoll, passeArme: attackerRoll.passeArme, show: {} }
attackerRoll.show = {
cible: this.target ? this.defender.system.name : 'la cible',
cible: this.target ? this.defender.name : 'la cible',
isRecul: (attackerRoll.particuliere == 'force' || attackerRoll.tactique == 'charge')
}
await RdDResolutionTable.displayRollData(attackerRoll, this.attacker, 'chat-resultat-attaque.html');
@ -1249,7 +1303,7 @@ export class RdDCombat {
attackerRoll.defenderTokenId = defenderTokenId;
await this.computeRecul(defenderRoll);
this.defender.encaisserDommages(attackerRoll, this.attacker, defenderRoll);
this.defender.encaisserDommages(attackerRoll, this.attacker, defenderRoll?.show);
}
else { // envoi à un GM: les joueurs n'ont pas le droit de modifier les personnages qu'ils ne possèdent pas
game.socket.emit(SYSTEM_SOCKET_ID, {

View File

@ -1,5 +1,4 @@
import { SYSTEM_RDD } from "./constants.js";
import { Misc } from "./misc.js";
export class RddCompendiumOrganiser {
static init() {
@ -17,7 +16,7 @@ export class RddCompendiumOrganiser {
}
static async setEntityTypeName(pack, element) {
const label = Misc.getEntityTypeLabel(await pack.getDocument(element.dataset.documentId));
const label = RddCompendiumOrganiser.getEntityTypeLabel(await pack.getDocument(element.dataset.documentId));
RddCompendiumOrganiser.insertEntityType(element, label);
}
@ -27,4 +26,16 @@ export class RddCompendiumOrganiser {
}
}
static getEntityTypeLabel(entity) {
const documentName = entity?.documentName
const type = entity?.type
if (documentName === 'Actor' || documentName === 'Item') {
const label = CONFIG[documentName]?.typeLabels?.[type] ?? type;
return game.i18n.has(label) ? game.i18n.localize(label) : t;
}
return type;
}
}

View File

@ -132,18 +132,15 @@ export class RdDDice {
}
}
static async roll(formula, options = { showDice: SHOW_DICE, rollMode: undefined }) {
const roll = new Roll(formula);
await roll.evaluate({ async: true });
if (options.showDice != HIDE_DICE) {
await this.showDiceSoNice(roll, options.rollMode ?? game.settings.get("core", "rollMode"));
}
return roll;
static async rollTotal(formula, options = { showDice: HIDE_DICE }) {
return (await RdDDice.roll(formula, options)).total;
}
static async rollTotal(formula, options = { showDice: HIDE_DICE}) {
const roll = await RdDDice.roll(formula, options);
return roll.total;
static async roll(formula, options = { showDice: SHOW_DICE, rollMode: undefined }) {
const roll = new Roll(RdDDice._formulaOrFake(formula, options));
await roll.evaluate({ async: true });
await this.showDiceSoNice(roll, options);
return roll;
}
static async rollOneOf(array) {
@ -160,27 +157,106 @@ export class RdDDice {
}
/* -------------------------------------------- */
static async showDiceSoNice(roll, rollMode) {
if (game.modules.get("dice-so-nice")?.active) {
if (game.dice3d) {
let whisper = null;
let blind = false;
rollMode = rollMode ?? game.settings.get("core", "rollMode");
switch (rollMode) {
case "blindroll": //GM only
blind = true;
case "gmroll": //GM + rolling player
whisper = ChatUtility.getUsers(user => user.isGM);
break;
case "roll": //everybody
whisper = ChatUtility.getUsers(user => user.active);
break;
case "selfroll":
whisper = [game.user.id];
break;
}
await game.dice3d.showForRoll(roll, game.user, true, whisper, blind);
static async showDiceSoNice(roll, options) {
if (options.showDice == HIDE_DICE || !game.modules.get("dice-so-nice")?.active || !game.dice3d) {
return;
}
let { whisper, blind } = RdDDice._getWhisperBlind(options);
if (options.forceDiceResult?.total) {
let terms = await RdDDice._getForcedTerms(options);
if (terms) {
await game.dice3d.show({ throws: [{ dice: terms }] })
return;
}
}
await game.dice3d.showForRoll(roll, game.user, true, whisper, blind);
}
static _formulaOrFake(formula, options) {
if (options?.forceDiceResult?.total) {
options.forceDiceResult.formula = formula;
return options.forceDiceResult.total.toString()
}
return formula;
}
static async _getForcedTerms(options) {
const total = options.forceDiceResult.total;
switch (options.forceDiceResult.formula) {
case '1d100':
return terms1d100(total);
case "2d10":
return await terms2d10(total);
}
return undefined;
function terms1d100(total) {
const unites = total % 10;
const dizaines = Math.floor(total / 10);
return [{
resultLabel: dizaines * 10,
d100Result: total,
result: dizaines,
type: "d100",
vectors: [],
options: {}
},
{
resultLabel: unites,
d100Result: total,
result: unites,
type: "d10",
vectors: [],
options: {}
}];
}
async function terms2d10(total) {
if (total>20 || total<2) { return undefined }
let first = await RdDDice.d10();
let second = Math.min(total-first, 10);
first = Math.max(first, total-second);
return [{
resultLabel:first,
result: first,
type: "d10",
vectors: [],
options: {}
},
{
resultLabel: second,
result: second,
type: "d10",
vectors: [],
options: {}
}];
}
}
static async d10() {
let roll = new Roll('1d10');
await roll.evaluate({ async: true });
return roll.total;
}
static _getWhisperBlind(options) {
let whisper = null;
let blind = false;
let rollMode = options.rollMode ?? game.settings.get("core", "rollMode");
switch (rollMode) {
case "blindroll": //GM only
blind = true;
case "gmroll": //GM + rolling player
whisper = ChatUtility.getUsers(user => user.isGM);
break;
case "roll": //everybody
whisper = ChatUtility.getUsers(user => user.active);
break;
case "selfroll":
whisper = [game.user.id];
break;
}
return { whisper, blind };
}
}

View File

@ -1,76 +1,75 @@
import { RdDUtility } from "./rdd-utility.js";
import { RdDCalendrier } from "./rdd-calendrier.js";
import { Grammar } from "./grammar.js";
/* -------------------------------------------- */
export class RdDHerbes extends Item {
/* -------------------------------------------- */
static isHerbeSoin( botaniqueItem ) {
return botaniqueItem.categorie == 'Soin';
}
/* -------------------------------------------- */
static isHerbeRepos( botaniqueItem ) {
return botaniqueItem.categorie == 'Repos';
static async initializeHerbes() {
this.herbesSoins = await RdDHerbes.listCategorieHerbes('Soin');
this.herbesRepos = await RdDHerbes.listCategorieHerbes('Repos');
}
/* -------------------------------------------- */
static async initializeHerbes( ) {
this.herbesSoins = await RdDUtility.loadCompendium('foundryvtt-reve-de-dragon.botanique', item => this.isHerbeSoin(item));
this.herbesRepos = await RdDUtility.loadCompendium('foundryvtt-reve-de-dragon.botanique', item => this.isHerbeRepos(item));
static async listCategorieHerbes(categorie) {
return await RdDUtility.loadItems(
it => it.type == 'herbe' && it.system.categorie.toLowerCase() == categorie.toLowerCase(),
'foundryvtt-reve-de-dragon.botanique');
}
/* -------------------------------------------- */
static buildHerbesList(listeHerbes, max) {
let list = {}
for ( let herbe of listeHerbes) {
for (let herbe of listeHerbes) {
let brins = max - herbe.system.niveau;
list[herbe.system.name] = `${herbe.system.name} (Bonus: ${herbe.system.niveau}, Brins: ${brins})`;
list[herbe.name] = `${herbe.name} (Bonus: ${herbe.system.niveau}, Brins: ${brins})`;
}
list['Autre'] = 'Autre (Bonus: variable, Brins: variable)'
return list;
}
/* -------------------------------------------- */
static updatePotionData( formData ) {
formData.herbesSoins = this.buildHerbesList(this.herbesSoins, 12);
formData.herbesRepos = this.buildHerbesList(this.herbesRepos, 7);
static async updatePotionData(formData) {
formData.isSoins = formData.system.categorie.includes('Soin');
formData.isRepos = formData.system.categorie.includes('Repos');
if (formData.isSoins) {
RdDHerbes.calculBonusHerbe(formData, this.herbesSoins, 12);
}
if (formData.isRepos) {
RdDHerbes.calculBonusHerbe(formData, this.herbesRepos, 7);
}
formData.herbesSoins = RdDHerbes.buildHerbesList(this.herbesSoins, 12);
formData.herbesRepos = RdDHerbes.buildHerbesList(this.herbesRepos, 7);
formData.jourMoisOptions = RdDCalendrier.buildJoursMois();
formData.dateActuelle = game.system.rdd.calendrier.getDateFromIndex();
formData.splitDate = game.system.rdd.calendrier.getNumericDateFromIndex(formData.system.prdate);
if (formData.system.categorie.includes('Soin') ) {
formData.isHerbe = true;
this.computeHerbeBonus(formData, this.herbesSoins, 12);
} else if (formData.system.categorie.includes('Repos')) {
formData.isRepos = true;
this.computeHerbeBonus(formData, this.herbesRepos, 7);
}
}
/* -------------------------------------------- */
static calculePuissancePotion( potion ) {
static calculPuissancePotion(potion) {
return potion.system.herbebonus * potion.system.pr;
}
/* -------------------------------------------- */
static calculePointsRepos( potion ) {
static calculPointsRepos(potion) {
return potion.system.herbebonus * potion.system.pr;
}
/* -------------------------------------------- */
static calculePointsGuerison( potion ){
static calculPointsGuerison(potion) {
return potion.system.herbebonus * potion.system.pr;
}
/* -------------------------------------------- */
static computeHerbeBonus( formData, herbesList, max) {
if ( Number(formData.system.herbebrins) ) {
let herbe = herbesList.find(item => item.name.toLowerCase() == formData.system.herbe.toLowerCase() );
if( herbe ) {
let brinsBase = max - herbe.system.niveau;
formData.system.herbebonus = Math.max(herbe.system.niveau - Math.max(brinsBase - formData.system.herbebrins, 0), 0);
static calculBonusHerbe(formData, herbesList, max) {
if (Number(formData.system.herbebrins)) {
let herbe = herbesList.find(item => item.name.toLowerCase() == formData.system.herbe.toLowerCase());
if (herbe) {
const brinsRequis = max - herbe.system.niveau;
const brinsManquants = Math.max(brinsRequis - formData.system.herbebrins, 0);
formData.system.herbebonus = Math.max(herbe.system.niveau - brinsManquants, 0);
}
}
}
}
}

View File

@ -150,11 +150,14 @@ Hooks.once("init", async function () {
/* -------------------------------------------- */
game.socket.on(SYSTEM_SOCKET_ID, sockmsg => {
console.log(">>>>> MSG RECV", sockmsg);
RdDUtility.onSocketMessage(sockmsg);
RdDCombat.onSocketMessage(sockmsg);
ChatUtility.onSocketMessage(sockmsg);
RdDActor.onSocketMessage(sockmsg);
try {
RdDUtility.onSocketMessage(sockmsg);
RdDCombat.onSocketMessage(sockmsg);
ChatUtility.onSocketMessage(sockmsg);
RdDActor.onSocketMessage(sockmsg);
} catch(e) {
console.error('game.socket.on(SYSTEM_SOCKET_ID) Exception: ', sockmsg,' => ', e)
}
});
/* -------------------------------------------- */

View File

@ -142,10 +142,8 @@ export class RdDResolutionTable {
/* -------------------------------------------- */
static async rollChances(chances, diviseur, forceDiceResult = -1) {
if (forceDiceResult <= 0 || forceDiceResult > 100) {
forceDiceResult = -1;
}
chances.roll = await RdDDice.rollTotal((forceDiceResult == -1) ? "1d100" : `${forceDiceResult}`, chances);
chances.forceDiceResult = forceDiceResult <= 0 || forceDiceResult > 100 ? undefined : {total: forceDiceResult};
chances.roll = await RdDDice.rollTotal( "1d100", chances);
mergeObject(chances, this.computeReussite(chances, chances.roll, diviseur), { overwrite: true });
return chances;
}

View File

@ -1,4 +1,4 @@
import { ENTITE_BLURETTE, ENTITE_INCARNE, ENTITE_NONINCARNE } from "./constants.js";
import { ENTITE_BLURETTE, ENTITE_INCARNE} from "./constants.js";
/**
* Extend the base Dialog entity by defining a custom window to perform roll.
@ -33,7 +33,7 @@ export class RdDEncaisser extends Dialog {
let dialogOptions = {
classes: ["rdddialog"],
width: 320,
height: 240
height: 260
}
// Select proper roll dialog template and stuff

View File

@ -144,6 +144,7 @@ export class RdDRoll extends Dialog {
/* -------------------------------------------- */
async onAction(action, html) {
this.rollData.forceDiceResult = Number.parseInt($('#force-dice-result').val()) ?? -1;
await RdDResolutionTable.rollData(this.rollData);
console.log("RdDRoll -=>", this.rollData, this.rollData.rolled);
this.actor.setRollWindowsOpened(false);

View File

@ -2,16 +2,22 @@ export class RdDRollTables {
/* -------------------------------------------- */
static async genericGetTableResult(tableName, toChat) {
let table = game.tables.find(table => table.name.toLowerCase() == tableName.toLowerCase())
if ( !table) {
const pack = game.packs.get("foundryvtt-reve-de-dragon.tables-diverses");
const index = await pack.getIndex();
const entry = index.find(e => e.name === tableName);
table = await pack.getDocument(entry._id);
}
let table = RdDRollTables.getWorldTable() ?? (await RdDRollTables.getSystemTable(tableName));
const draw = await table.draw({ displayChat: toChat, rollMode: "gmroll"});
//console.log("RdDRollTables", tableName, toChat, ":", draw);
return draw.results.length > 0 ? draw.results[0] : undefined;
}
static getWorldTable() {
return game.tables.find(table => table.name.toLowerCase() == tableName.toLowerCase());
}
static async getSystemTable(tableName) {
const pack = game.packs.get("foundryvtt-reve-de-dragon.tables-diverses");
const index = await pack.getIndex();
const entry = index.find(e => e.name === tableName);
return await pack.getDocument(entry._id);
}
/* -------------------------------------------- */

View File

@ -13,6 +13,7 @@ import { Monnaie } from "./item-monnaie.js";
import { RdDPossession } from "./rdd-possession.js";
import { RdDNameGen } from "./rdd-namegen.js";
import { RdDConfirm } from "./rdd-confirm.js";
import { RdDActor } from "./actor.js";
/* -------------------------------------------- */
// This table starts at 0 -> niveau -10
@ -112,7 +113,6 @@ export class RdDUtility {
static async preloadHandlebarsTemplates() {
const templatePaths = [
//Character Sheets
'systems/foundryvtt-reve-de-dragon/templates/actor-creation-sheet.html',
'systems/foundryvtt-reve-de-dragon/templates/actor-sheet.html',
'systems/foundryvtt-reve-de-dragon/templates/actor-creature-sheet.html',
'systems/foundryvtt-reve-de-dragon/templates/actor-entite-sheet.html',
@ -214,6 +214,7 @@ export class RdDUtility {
'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html',
'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-sort.html',
'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-encaisser.html',
'systems/foundryvtt-reve-de-dragon/templates/dialog-validation-encaissement.html',
'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-meditation.html',
'systems/foundryvtt-reve-de-dragon/templates/dialog-tmr.html',
'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-alchimie.html',
@ -242,6 +243,7 @@ export class RdDUtility {
'systems/foundryvtt-reve-de-dragon/templates/chat-infojet.html',
'systems/foundryvtt-reve-de-dragon/templates/chat-description.html',
'systems/foundryvtt-reve-de-dragon/templates/chat-info-appel-au-moral.html',
'systems/foundryvtt-reve-de-dragon/templates/chat-info-distance.html',
'systems/foundryvtt-reve-de-dragon/templates/chat-demande-defense.html',
'systems/foundryvtt-reve-de-dragon/templates/chat-demande-attaque-particuliere.html',
'systems/foundryvtt-reve-de-dragon/templates/chat-demande-attaque-etotal.html',
@ -652,7 +654,68 @@ export class RdDUtility {
}
/* -------------------------------------------- */
static selectEncaissement(degats, mortalite) {
static async jetEncaissement(rollData, armure, options = { showDice: HIDE_DICE }) {
let formula = "2d10";
// Chaque dé fait au minmum la difficulté libre
if (ReglesOptionelles.isUsing('degat-minimum-malus-libre')) {
if (rollData.diffLibre < 0) {
let valeurMin = Math.abs(rollData.diffLibre);
formula += "min" + valeurMin;
}
}
// Chaque dé fait au minmum la difficulté libre
if (ReglesOptionelles.isUsing('degat-ajout-malus-libre')) {
if (rollData.diffLibre < 0) {
let valeurMin = Math.abs(rollData.diffLibre);
formula += "+" + valeurMin;
}
}
let roll = await RdDDice.roll(formula, options);
// 1 dé fait au minmum la difficulté libre
if (ReglesOptionelles.isUsing('degat-minimum-malus-libre-simple')) {
if (rollData.diffLibre < 0) {
let valeurMin = Math.abs(rollData.diffLibre);
if (roll.terms[0].results[0].result < valeurMin) {
roll.terms[0].results[0].result = valeurMin;
} else if (roll.terms[0].results[1].result < valeurMin) {
roll.terms[0].results[1].result = valeurMin;
}
roll._total = roll.terms[0].results[0].result + roll.terms[0].results[1].result;
}
}
return await RdDUtility.prepareEncaissement(rollData, roll, armure);
}
/* -------------------------------------------- */
static async prepareEncaissement(rollData, roll, armure) {
const jetTotal = roll.total + rollData.dmg.total - armure;
let encaissement = RdDUtility._selectEncaissement(jetTotal, rollData.dmg.mortalite);
let over20 = Math.max(jetTotal - 20, 0);
encaissement.dmg = rollData.dmg;
encaissement.dmg.loc = rollData.dmg.loc ?? await RdDUtility.getLocalisation(this.type);
encaissement.dmg.loc.label = encaissement.dmg.loc.label ?? 'Corps;';
encaissement.roll = roll;
encaissement.armure = armure;
encaissement.total = jetTotal;
encaissement.vie = await RdDUtility._evaluatePerte(encaissement.vie, over20);
encaissement.endurance = await RdDUtility._evaluatePerte(encaissement.endurance, over20);
encaissement.penetration = rollData.arme?.system.penetration ?? 0;
encaissement.blessures = (
encaissement.critiques> 0 ? "Critique":
encaissement.graves> 0 ? "Grave":
encaissement.legeres> 0 ? "Légère":
encaissement.eraflures>0 ? "Contusions/Eraflures":
'Aucune'
);
return encaissement;
}
/* -------------------------------------------- */
static _selectEncaissement(degats, mortalite) {
const table = definitionsEncaissement[mortalite] === undefined ? definitionsEncaissement["mortel"] : definitionsEncaissement[mortalite];
for (let encaissement of table) {
if ((encaissement.minimum === undefined || encaissement.minimum <= degats)
@ -663,6 +726,13 @@ export class RdDUtility {
return duplicate(table[0]);
}
/* -------------------------------------------- */
static async _evaluatePerte(formula, over20) {
let perte = new Roll(formula, { over20: over20 });
await perte.evaluate({ async: true });
return perte.total;
}
/* -------------------------------------------- */
static currentFatigueMalus(value, max) {
if (ReglesOptionelles.isUsing("appliquer-fatigue")) {
@ -683,17 +753,28 @@ export class RdDUtility {
}
/* -------------------------------------------- */
static async loadCompendiumData(compendium) {
const pack = game.packs.get(compendium);
return await pack?.getDocuments() ?? [];
static async loadItems(filter, compendium) {
let items = game.items.filter(filter);
if (compendium) {
const ids = items.map(it => it.id);
const names = items.map(it => it.name.toLowerCase());
items = items.concat(await RdDUtility.loadCompendium(compendium, it => !ids.includes(it.id) && !names.includes(it.name.toLowerCase()) && filter(it)));
}
return items;
}
/* -------------------------------------------- */
static async loadCompendium(compendium, filter = item => true) {
static async loadCompendium(compendium, filter = it => true) {
let compendiumData = await RdDUtility.loadCompendiumData(compendium);
return compendiumData.filter(filter);
}
/* -------------------------------------------- */
static async loadCompendiumData(compendium) {
const pack = game.packs.get(compendium);
return await pack?.getDocuments() ?? [];
}
/* -------------------------------------------- */
static async responseNombreAstral(callData) {
let actor = game.actors.get(callData.id);
@ -750,7 +831,12 @@ export class RdDUtility {
});
// gestion bouton tchat Acheter
html.on("click", '.button-acheter', event => DialogItemAchat.onButtonAcheter(event));
html.on("click", '.button-acheter', event => {
const venteData = DialogItemAchat.venteData(event.currentTarget);
if (venteData) {
DialogItemAchat.onAcheter(venteData);
}
});
html.on("click", '.button-creer-acteur', event => RdDNameGen.onCreerActeur(event));
// Gestion du bouton payer
@ -888,11 +974,6 @@ export class RdDUtility {
/* -------------------------------------------- */
static async confirmerSuppressionItem(sheet, item, htmlToDelete) {
const itemId = item.id;
if (Monnaie.isSystemMonnaie(item, sheet.actor.items)) {
ui.notifications.warn("Suppression des monnaies de base impossible");
return;
}
const confirmationSuppression = {
settingConfirmer: "confirmation-supprimer-" + item.getItemGroup(),
content: `<p>Etes vous certain de vouloir supprimer: ${item.name}?</p>`,

View File

@ -2,29 +2,32 @@ import { SYSTEM_RDD } from "./constants.js";
import { Misc } from "./misc.js";
const listeReglesOptionelles = [
{ name: 'recul', group: 'Règles de combat', descr: "Appliquer le recul en cas de particulière en force ou de charge" },
{ name: 'resistanceArmeParade', group: 'Règles de combat', descr: "Faire le jet de résistance des armes lors de parades pouvant les endommager" },
{ name: 'deteriorationArmure', group: 'Règles de combat', descr: "Tenir compte de la détérioration des armures" },
{ name: 'defenseurDesarme', group: 'Règles de combat', descr: "Le défenseur peut être désarmé en parant une particulière en force ou une charge avec une arme autre qu'un bouclier" },
{ name: 'categorieParade', group: 'Règles de combat', descr: "Le défenseur doit obtenir une significative en cas de parade avec des armes de catégories différentes" },
{ name: 'tripleSignificative', group: 'Règles de combat', descr: "En cas de demi-surprise, d'attaque particulière en finesse, et de catégories d'armes différentes, le défenseur doit obtenir 1/8 des chances de succès" },
{ name: 'degat-minimum-malus-libre-simple', group: 'Règles de combat', descr: "Le malus libre d'attaque remplace une des valeurs de dés d'encaissement si elle est plus petite. Exemple : la difficulté libre de l'attaquant est de -4. Sur le jet d'encaissement, si 1 résultat est inférieur à 4, alors il devient 4.", default: false },
{ name: 'degat-minimum-malus-libre', group: 'Règles de combat', descr: "Le malus libre d'attaque remplace une valeur de dés d'encaissement si elle est plus petite. Exemple : la difficulté libre de l'attaquant est de -4. Sur le jet d'encaissement, tout résultat inférieur à 4 devient 4.", default: false },
{ name: 'degat-ajout-malus-libre', group: 'Règles de combat', descr: "Le malus libre d'attaque s'ajoute au jet d'encaissement et aux autres bonus. Exemple : la difficulté libre de l'attaquant est de -4. Le jet d'encaissement est effectué à 2d10+4, plus les bonus de situation et d'armes.", default: false },
{ name: 'astrologie', group: 'Règles générales', descr: "Appliquer les ajustements astrologiques aux jets de chance et aux rituels"},
{ name: 'afficher-prix-joueurs', group: 'Règles générales', descr: "Afficher le prix de l'équipement des joueurs", uniquementJoueur: true},
{ name: 'appliquer-fatigue', group: 'Règles générales', descr: "Appliquer les règles de fatigue"},
{ name: 'afficher-colonnes-reussite', group: 'Règles générales', descr: "Afficher le nombre de colonnes de réussite ou d'échec", default: false },
{ name: 'confirmation-tmr', group: 'Confirmations', descr: "Confirmer pour monter dans les TMR", scope: "client"},
{ name: 'confirmation-vider', group: 'Confirmations', descr: "Confirmer pour vider l'équipement", scope: "client"},
{ name: 'confirmation-supprimer-lien-acteur', group: 'Confirmations', descr: "Confirmer pour détacher un animal/suivant/véhicule", scope: "client"},
{ name: 'confirmation-supprimer-equipement', group: 'Confirmations', descr: "Confirmer la suppression des équipements", scope: "client"},
{ name: 'confirmation-supprimer-oeuvre', group: 'Confirmations', descr: "Confirmer la suppression des oeuvres", scope: "client"},
{ name: 'confirmation-supprimer-connaissance', group: 'Confirmations', descr: "Confirmer la suppression des connaissances", scope: "client"},
{ name: 'confirmation-supprimer-draconique', group: 'Confirmations', descr: "Confirmer la suppression des queues, souffles, têtes", scope: "client"},
{ name: 'confirmation-supprimer-effet', group: 'Confirmations', descr: "Confirmer la suppression des effets", scope: "client"},
{ name: 'confirmation-supprimer-competence', group: 'Confirmations', descr: "Confirmer la suppression des compétences", scope: "client"},
{ name: 'confirmation-supprimer-autres', group: 'Confirmations', descr: "Confirmer la suppression des autres types d'Objets", scope: "client"},
{ group: 'Règles de combat', name: 'recul', descr: "Appliquer le recul en cas de particulière en force ou de charge" },
{ group: 'Règles de combat', name: 'resistanceArmeParade', descr: "Faire le jet de résistance des armes lors de parades pouvant les endommager" },
{ group: 'Règles de combat', name: 'deteriorationArmure', descr: "Tenir compte de la détérioration des armures" },
{ group: 'Règles de combat', name: 'defenseurDesarme', descr: "Le défenseur peut être désarmé en parant une particulière en force ou une charge avec une arme autre qu'un bouclier" },
{ group: 'Règles de combat', name: 'categorieParade', descr: "Le défenseur doit obtenir une significative en cas de parade avec des armes de catégories différentes" },
{ group: 'Règles de combat', name: 'tripleSignificative', descr: "En cas de demi-surprise, d'attaque particulière en finesse, et de catégories d'armes différentes, le défenseur doit obtenir 1/8 des chances de succès" },
{ group: 'Règles de combat', name: 'degat-minimum-malus-libre-simple', descr: "Le malus libre d'attaque remplace une des valeurs de dés d'encaissement si elle est plus petite. Exemple : la difficulté libre de l'attaquant est de -4. Sur le jet d'encaissement, si le plus petit dé est inférieur à 4, alors il devient 4.", default: false },
{ group: 'Règles de combat', name: 'degat-minimum-malus-libre', descr: "Le malus libre d'attaque remplace une valeur de dés d'encaissement si elle est plus petite. Exemple : la difficulté libre de l'attaquant est de -4. Sur le jet d'encaissement, tout résultat inférieur à 4 devient 4.", default: false },
{ group: 'Règles de combat', name: 'degat-ajout-malus-libre', descr: "Le malus libre d'attaque s'ajoute au jet d'encaissement et aux autres bonus. Exemple : la difficulté libre de l'attaquant est de -4. Le jet d'encaissement est effectué à 2d10+4, plus les bonus de situation et d'armes.", default: false },
{ group: 'Règles de combat', name: 'validation-encaissement-gr', descr: "Le Gardien des Rêves doit valider les jets d'encaissement et peut les changer.", default: false },
{ group: 'Règles générales', name: 'astrologie', descr: "Appliquer les ajustements astrologiques aux jets de chance et aux rituels"},
{ group: 'Règles générales', name: 'afficher-prix-joueurs', descr: "Afficher le prix de l'équipement des joueurs", uniquementJoueur: true},
{ group: 'Règles générales', name: 'appliquer-fatigue', descr: "Appliquer les règles de fatigue"},
{ group: 'Règles générales', name: 'afficher-colonnes-reussite', descr: "Afficher le nombre de colonnes de réussite ou d'échec", default: false },
{ group: 'Confirmations', name: 'confirmation-tmr', descr: "Confirmer pour monter dans les TMR", scope: "client"},
{ group: 'Confirmations', name: 'confirmation-vider', descr: "Confirmer pour vider l'équipement", scope: "client"},
{ group: 'Confirmations', name: 'confirmation-supprimer-lien-acteur', descr: "Confirmer pour détacher un animal/suivant/véhicule", scope: "client"},
{ group: 'Confirmations', name: 'confirmation-supprimer-equipement', descr: "Confirmer la suppression des équipements", scope: "client"},
{ group: 'Confirmations', name: 'confirmation-supprimer-oeuvre', descr: "Confirmer la suppression des oeuvres", scope: "client"},
{ group: 'Confirmations', name: 'confirmation-supprimer-connaissance', descr: "Confirmer la suppression des connaissances", scope: "client"},
{ group: 'Confirmations', name: 'confirmation-supprimer-draconique', descr: "Confirmer la suppression des queues, souffles, têtes", scope: "client"},
{ group: 'Confirmations', name: 'confirmation-supprimer-effet', descr: "Confirmer la suppression des effets", scope: "client"},
{ group: 'Confirmations', name: 'confirmation-supprimer-competence', descr: "Confirmer la suppression des compétences", scope: "client"},
{ group: 'Confirmations', name: 'confirmation-supprimer-autres', descr: "Confirmer la suppression des autres types d'Objets", scope: "client"},
];
const uniquementJoueur = listeReglesOptionelles.filter(it => it.uniquementJoueur).map(it=>it.name);
@ -73,7 +76,7 @@ export class ReglesOptionelles extends FormApplication {
const regles = listeReglesOptionelles.filter(it => game.user.isGM || it.scope == "client").map(it => {
it = duplicate(it);
it.id = ReglesOptionelles._getIdRegle(it.name);
it.active = ReglesOptionelles.isUsing(it.name);
it.active = ReglesOptionelles.isSet(it.name);
return it;
});
formData.regles = regles;
@ -85,11 +88,11 @@ export class ReglesOptionelles extends FormApplication {
if (game.user.isGM && uniquementJoueur.includes(name)) {
return true;
}
return game.settings.get(SYSTEM_RDD, ReglesOptionelles._getIdRegle(name));
return ReglesOptionelles.isSet(name);
}
static isSet(name) {
return ReglesOptionelles.isUsing(name);
return game.settings.get(SYSTEM_RDD, ReglesOptionelles._getIdRegle(name));
}
static set(name, value) {

View File

@ -34,10 +34,10 @@
],
"url": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/",
"license": "LICENSE.txt",
"version": "10.0.25",
"version": "10.0.27",
"compatibility": {
"minimum": "10",
"verified": "10.286"
"verified": "10.287"
},
"esmodules": [
"module/rdd-main.js"
@ -333,7 +333,7 @@
],
"socket": true,
"manifest": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/raw/v10/system.json",
"download": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/archive/foundryvtt-reve-de-dragon-10.0.25.zip",
"download": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/archive/foundryvtt-reve-de-dragon-10.0.27.zip",
"gridDistance": 1,
"gridUnits": "m",
"primaryTokenAttribute": "sante.vie",

View File

@ -0,0 +1,14 @@
<img class="chat-icon" src="{{rollData.arme.img}}" alt="{{rollData.arme.name}}" />
<strong>Ajustement de tir/lancer</strong> proposé de <strong>{{total}}</strong>
<ul>
<li>{{defender.name}} est à une distance indicative de {{distance}} mètres.</li>
{{#if isVisible}}
<li>Selon les murs et la lumière, la cible {{defender.name}} est visible de {{attacker.name}}.</li>
{{else}}
<li>Selon les murs et la lumière, la cible {{defender.name}} n'est pas visible de {{attacker.name}}.</li>
{{/if}}
{{log defender}}
<li>Portée {{portee.msg}} pour l'arme {{rollData.arme.name}} : {{portee.diff}}</li>
<li>De taille {{taille.msg}}: {{taille.diff}}</li>
<li>Mouvement {{activite.msg}}: {{activite.diff}}</li>
</ul>

View File

@ -45,38 +45,38 @@
</div>
</div>
{{#if (eq item.type 'nourritureboisson')}}
<p>
Si vous souhaitez {{#if item.system.boisson}}boire{{else}}manger{{/if}}:
</p>
{{#if (and (eq item.type 'nourritureboisson') (eq acheteur.type 'personnage'))}}
<p>Si vous souhaitez {{#if item.system.boisson}}boire{{else}}manger{{/if}}:</p>
{{#if item.system.sust}}
<p>Cette {{#if item.system.boisson}}boisson{{else}}nourriture{{/if}} vous apportera <span
class="total-sust">{{totalSust}}</span> de sustantation.</p>
{{/if}}
{{#if item.system.boisson}}
<p>{{#if item.system.alcoolise}}
C'est une boisson alcoolisée de force {{item.system.force}}, vous effectuerez un jet d'éthylisme.
{{/if}}
Cette boisson vous apportera <span class="total-desaltere">{{totalDesaltere}}</span> unités d'eau.
</p>
{{/if}}
{{#if (gt item.system.qualite 0)}}
{{#if (gt item.system.qualite cuisine.system.niveau)}}
<p>La qualité du plat est telle qu'un jet de Goût/Cuisine à {{numberFormat item.system.qualite decimals=0 sign=true}}
vous permettra un jet de moral heureux.</p>
{{/if}}
{{/if}}
{{#if (or (lt item.system.qualite 0) (lt item.system.exotisme 0))}}
<p>
Pour surmonter {{#if (lt item.system.qualite 0)}}le mauvais goût{{else}}l'exotisme{{/if}}, vous devez effectuer un jet de Volonté/Cuisine à {{numberFormat (min item.system.exotisme item.system.qualite) decimals=0 sign=true}}.
<br/>
<input class="attribute-value se-forcer" type="checkbox" name="se-forcer" {{#if choix.seForcer}}checked{{/if}}>
<label for="se-forcer">En cas d'échec, voulez-vous vous forcer à manger (et subir un jet de moral en situation malheureuse)?</label>
</input>
</p>
{{/if}}
{{#if item.system.sust}}
<p>Cette {{#if item.system.boisson}}boisson{{else}}nourriture{{/if}} vous apportera
<span class="total-sust">{{totalSust}}</span>
de sustantation.</p>
{{/if}}
{{#if item.system.boisson}}
<p>
{{#if item.system.alcoolise}}
C'est une boisson alcoolisée de force {{item.system.force}}, vous effectuerez un jet d'éthylisme.
{{/if}}
Cette boisson vous apportera <span class="total-desaltere">{{totalDesaltere}}</span> unités d'eau.
</p>
{{/if}}
{{#if (gt item.system.qualite 0)}}
{{#if (gt item.system.qualite cuisine.system.niveau)}}
<p>La qualité du plat est telle qu'un jet de Goût/Cuisine à {{numberFormat item.system.qualite decimals=0 sign=true}}
vous permettra un jet de moral heureux.</p>
{{/if}}
{{/if}}
{{#if (or (lt item.system.qualite 0) (lt item.system.exotisme 0))}}
<p>
Pour surmonter {{#if (lt item.system.qualite 0)}}le mauvais goût{{else}}l'exotisme{{/if}}, vous devez effectuer un jet de Volonté/Cuisine à {{numberFormat (min item.system.exotisme item.system.qualite) decimals=0 sign=true}}.
<br/>
<input class="attribute-value se-forcer" type="checkbox" name="se-forcer" {{#if choix.seForcer}}checked{{/if}}>
<label for="se-forcer">En cas d'échec, voulez-vous vous forcer à manger (et subir un jet de moral en situation malheureuse)?</label>
</input>
</p>
{{/if}}
{{/if}}
{{#if isVente}}

View File

@ -1,8 +1,8 @@
<form class="encaisse-roll-dialog">
<h2 class="encaisserdialog" id="encaisserTitle"></h2>
<div class="form-group">
<label class="competence-label">Modificateurs aux Dommages:</label>
<select class="competence-value" name="modificateurDegats" id="modificateurDegats" data-dtype="number">
<div class="flexrow">
<label>Modificateurs aux Dommages:</label>
<select class="competence-value flex-shrink" name="modificateurDegats" id="modificateurDegats" data-dtype="number">
{{#select modificateurDegats}}
{{#each ajustementsEncaissement as |key|}}
<option value={{key}}>{{numberFormat key decimals=0 sign=true}}</option>
@ -10,9 +10,9 @@
{{/select}}
</select>
</div>
<div class="form-group">
<label class="competence-label">Cas particuliers:</label>
<select class="competence-value" name="encaisserSpecial" id="encaisserSpecial" data-dtype="String">
<div class="flexcol">
<label >Cas particuliers:</label>
<select name="encaisserSpecial" id="encaisserSpecial" data-dtype="String">
<option value="aucun">Aucun</option>
<option value="noarmure">Ne pas compter les Armures</option>
<option value="chute">Chute : Limiter les armures à 2 PA</option>

View File

@ -0,0 +1,45 @@
<form class="dialog-validation-encaissement">
<div class="flexrow flex-center">
<div class="flex-shrink"><img class="chat-icon" src="icons/svg/bones.svg" alt="Encaisser des dommages" /></div>
<div><h4>Encaissement de {{actor.name}}</h4></div>
<div class="flex-shrink"><img class="chat-icon" src="{{actor.img}}" title="{{actor.name}}" alt="{{actor.name}}" /></div>
</div>
<ul>
<li class="flexrow flex-group-left">
<label>Jet d'encaissement</label>
<input class="encaissement-roll-result" type="number" value="{{encaissement.roll.result}}" min="2" max="20" data-dtype="Number" />
</li>
<li class="flexrow flex-group-left">
<label>Total</label>
<span class="tooltip tooltip-dotted">
<label class="encaissement-total">{{encaissement.total}}</label>
<div class="tooltiptext ttt-ajustements">
<div>Armure: {{encaissement.armure}}</div>
{{#if rollData.dmg.penetration}}
<div>Pénétration: -{{rollData.dmg.penetration}}</div>
{{/if}}
<hr>
{{#if rollData.dmg.dmgArme}}
<div>+dom arme: {{numberFormat rollData.dmg.dmgArme decimals=0 sign=true}}</div>
{{/if}}
{{#if rollData.dmg.dmgActor}}
<div>+dom attaquant: {{numberFormat rollData.dmg.dmgActor decimals=0 sign=true}}</div>
{{/if}}
{{#if rollData.dmg.dmgParticuliere}}
<div>+dom particulière: {{numberFormat rollData.dmg.dmgParticuliere decimals=0 sign=true}}</div>
{{/if}}
{{#if rollData.dmg.dmgTactique}}
<div>+dom tactique: {{numberFormat rollData.dmg.dmgTactique decimals=0 sign=true}}</div>
{{/if}}
{{#if rollData.dmg.dmgSurprise}}
<div>+dom surprise: {{numberFormat rollData.dmg.dmgSurprise decimals=0 sign=true}}</div>
{{/if}}
</div>
</span>
</li>
<li class="flexrow flex-group-left">
<label>Blessure ({{rollData.dmg.mortalite}})</label>
<label class="encaissement-blessure">{{encaissement.blessures}}</label>
</li>
</ul>
</form>

View File

@ -41,7 +41,7 @@
{{/select}}
</select>
</div>
{{#if isHerbe}}
{{#if isSoins}}
<div class="form-group">
<label>Herbe</label>
<select name="system.herbe" class="herbe" data-dtype="String">
@ -86,7 +86,7 @@
<label for="xp">Permanente ? </label>
<input class="attribute-value" type="checkbox" name="system.prpermanent" {{#if system.prpermanent}}checked{{/if}}/>
</div>
{{#if isHerbe}}
{{#if isSoins}}
<div class="form-group">
<label for="xp">Points de guérison </label>
<label for="xp">{{pointsGuerison}}</label>