Compare commits

...

16 Commits

Author SHA1 Message Date
7d8b5c9549 Merge pull request '10.4.0: Pour Noël, je voudrais plein de cadeaux' (#598) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: public/foundryvtt-reve-de-dragon#598
2022-12-23 09:50:26 +01:00
Vincent Vandemeulebrouck
ec58317b35 Version 10.4.0 2022-12-23 02:18:43 +01:00
Vincent Vandemeulebrouck
7c70e944b1 Ajout des "boutiques"
Une boutique est un Item service permettant de définir l'inventaire
en vente, et de le vendre facilement.

Les boutiques peuvent être accédées par les joueurs (avec le lien)
pour y faire leurs courses.
2022-12-23 02:17:37 +01:00
Vincent Vandemeulebrouck
f397c82c6d Prepare sous-classes Item 2022-12-23 00:35:44 +01:00
Vincent Vandemeulebrouck
81e3ceb4dc Lien vers Items monde/compendium 2022-12-23 00:35:44 +01:00
Vincent Vandemeulebrouck
7bec249e8d Cleanup init & start
- classe SystemReveDeDragon pour l'init/start
- déplacement de la migration 1.5.34 dans les migrations
2022-12-23 00:25:11 +01:00
Vincent Vandemeulebrouck
70b30b545b Affichage consistant prix unitaire/total 2022-12-22 00:04:49 +01:00
Vincent Vandemeulebrouck
ed2eebf99d Renommage confirmation 2022-12-21 00:49:36 +01:00
Vincent Vandemeulebrouck
4ba2c384d7 Alignement de prix/quantité/enc
- alignement à droite
- affichage des prix avec 2 décimales
2022-12-21 00:48:32 +01:00
Vincent Vandemeulebrouck
7f27399f3c comptage de monde en async
Au cas où on n'a pas de connection internet (merci la coupure Orange)
2022-12-21 00:48:32 +01:00
Vincent Vandemeulebrouck
61389e117b Séparation inventaire/monnaie 2022-12-20 01:13:51 +01:00
Vincent Vandemeulebrouck
46cc245abf Fix edition minutes du calendrier 2022-12-20 01:13:51 +01:00
Vincent Vandemeulebrouck
a372531849 Cleanup: html TMR multiligne 2022-12-20 01:12:52 +01:00
Vincent Vandemeulebrouck
8a8323ac8d Cleanup: Suppression log 2022-12-20 01:12:52 +01:00
Vincent Vandemeulebrouck
8e6d4fbb89 Fix: la valeur de l'inventaire hors fortune
La valeur de l'inventaire ne tient plus compte de la fortune
2022-12-20 01:12:51 +01:00
Vincent Vandemeulebrouck
886307f24c Fix: affichage de la fortune avec arrondis
Affichage en sols + deniers
2022-12-20 01:12:51 +01:00
35 changed files with 927 additions and 442 deletions

BIN
icons/items/services.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -27,6 +27,7 @@
"TypeArmure": "Armure", "TypeArmure": "Armure",
"TypeConteneur": "Conteneur", "TypeConteneur": "Conteneur",
"TypeNourritureboisson": "Nourriture & boisson", "TypeNourritureboisson": "Nourriture & boisson",
"TypeService": "Services/Boutique",
"TypeChant": "Chant", "TypeChant": "Chant",
"TypeDanse": "Danse", "TypeDanse": "Danse",
"TypeMusique": "Musique", "TypeMusique": "Musique",

View File

@@ -57,7 +57,7 @@ export class RdDActorSheet extends ActorSheet {
notes: await TextEditor.enrichHTML(this.object.system.notes, { async: true }), notes: await TextEditor.enrichHTML(this.object.system.notes, { async: true }),
notesmj: await TextEditor.enrichHTML(this.object.system.notesmj, { async: true }), notesmj: await TextEditor.enrichHTML(this.object.system.notesmj, { async: true }),
calc: { calc: {
fortune: Monnaie.getFortune(this.actor), fortune: Monnaie.getFortuneSolsDeniers(this.actor),
encTotal: await this.actor.computeEncombrementTotalEtMalusArmure(), encTotal: await this.actor.computeEncombrementTotalEtMalusArmure(),
surenc: this.actor.computeMalusSurEncombrement(), surenc: this.actor.computeMalusSurEncombrement(),
prixTotalEquipement: this.actor.computePrixTotalEquipement(), prixTotalEquipement: this.actor.computePrixTotalEquipement(),
@@ -137,7 +137,7 @@ export class RdDActorSheet extends ActorSheet {
RdDSheetUtility.splitItem(item, this.actor); RdDSheetUtility.splitItem(item, this.actor);
}); });
this.html.find('.item-edit').click(async event => RdDSheetUtility.getItem(event, this.actor)?.sheet.render(true)) this.html.find('.item-edit').click(async event => RdDSheetUtility.getItem(event, this.actor)?.sheet.render(true))
this.html.find('.item-delete').click(async event => RdDUtility.confirmerSuppressionItem(this, RdDSheetUtility.getItem(event, this.actor))); this.html.find('.item-delete').click(async event => RdDUtility.confirmActorItemDelete(this, RdDSheetUtility.getItem(event, this.actor)));
this.html.find('.item-vendre').click(async event => RdDSheetUtility.getItem(event, this.actor)?.proposerVente()); this.html.find('.item-vendre').click(async event => RdDSheetUtility.getItem(event, this.actor)?.proposerVente());
this.html.find('.item-montrer').click(async event => RdDSheetUtility.getItem(event, this.actor)?.postItem()); this.html.find('.item-montrer').click(async event => RdDSheetUtility.getItem(event, this.actor)?.postItem());
this.html.find('.item-action').click(async event => RdDSheetUtility.getItem(event, this.actor)?.actionPrincipale(this.actor)); this.html.find('.item-action').click(async event => RdDSheetUtility.getItem(event, this.actor)?.actionPrincipale(this.actor));

View File

@@ -188,17 +188,17 @@ export class RdDActor extends Actor {
canReceive(item) { canReceive(item) {
if (this.isCreature()) { if (this.isCreature()) {
return item.type == 'competencecreature' || RdDItem.isItemInventaire(item); return item.type == 'competencecreature' || item.isInventaire();
} }
if (this.isEntite()) { if (this.isEntite()) {
return item.type == 'competencecreature'; return item.type == 'competencecreature';
} }
if (this.isVehicule()) { if (this.isVehicule()) {
return RdDItem.isItemInventaire(item); return item.isInventaire();
} }
if (this.isPersonnage()) { if (this.isPersonnage()) {
switch (item.type) { switch (item.type) {
case 'competencecreature': case 'tarot': case 'competencecreature': case 'tarot': case 'service':
return false; return false;
} }
return true; return true;
@@ -461,7 +461,7 @@ export class RdDActor extends Actor {
selectedCaracName: 'apparence', selectedCaracName: 'apparence',
competences: this.itemTypes['competence'] competences: this.itemTypes['competence']
}; };
const dialog = await RdDRoll.create(this, rollData, const dialog = await RdDRoll.create(this, rollData,
{ html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll.html' }, { html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll.html' },
{ {
@@ -1406,6 +1406,7 @@ export class RdDActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
computePrixTotalEquipement() { computePrixTotalEquipement() {
const valeur = this.items.filter(it => it.isInventaire()) const valeur = this.items.filter(it => it.isInventaire())
.filter(it => !it.isMonnaie())
.map(it => it.valeurTotale()) .map(it => it.valeurTotale())
.reduce(Misc.sum(), 0); .reduce(Misc.sum(), 0);
return valeur; return valeur;
@@ -3769,35 +3770,32 @@ export class RdDActor extends Actor {
} }
const cout = Number(achat.prixTotal ?? 0); const cout = Number(achat.prixTotal ?? 0);
const vente = achat.vente;
const acheteur = achat.acheteurId ? game.actors.get(achat.acheteurId) : undefined;
const vendeur = achat.vendeurId ? game.actors.get(achat.vendeurId) : undefined; const vendeur = achat.vendeurId ? game.actors.get(achat.vendeurId) : undefined;
let itemVendu = vendeur?.getObjet(vente.item._id); const service = achat.serviceId ? (vendeur?.getObjet(achat.serviceId) ?? game.items.get(achat.serviceId)) : undefined;
const acheteur = achat.acheteurId ? game.actors.get(achat.acheteurId) : undefined;
if (vendeur && (itemVendu?.getQuantite() ?? 0) < achat.quantiteTotal) { const vente = achat.vente;
ChatUtility.notifyUser(achat.userId, 'warn', `Le vendeur n'a plus assez de ${vente.item.name} !`); const quantite = (achat.choix.nombreLots ?? 1) * (vente.tailleLot);
return; const itemVendu = vendeur?.getObjet(vente.item._id) ?? (await RdDItem.getCorrespondingItem(vente.item));
if (!this.verifierQuantite(service, vendeur, itemVendu, quantite)) {
ChatUtility.notifyUser(achat.userId, 'warn', `Le vendeur n'a pas assez de ${itemVendu.name} !`);
return
} }
if (Monnaie.getFortune(acheteur) < Number(cout)) { if (Monnaie.getFortune(acheteur) < Number(cout)) {
ChatUtility.notifyUser(achat.userId, 'warn', `Vous n'avez pas assez d'argent pour payer ${Math.ceil(cout / 100)} sols !`); ChatUtility.notifyUser(achat.userId, 'warn', `Vous n'avez pas assez d'argent pour payer ${Math.ceil(cout / 100)} sols !`);
return; return;
} }
await this.decrementerVente(service, vendeur, itemVendu, quantite, cout);
achat.quantiteTotal = (achat.choix.nombreLots ?? 1) * (vente.tailleLot);
if (vendeur) {
await vendeur.ajouterSols(cout);
await vendeur.decrementerQuantiteItem(itemVendu, achat.quantiteTotal,);
}
if (acheteur) { if (acheteur) {
await acheteur.depenserSols(cout); await acheteur.depenserSols(cout);
let createdItemId = await acheteur.creerQuantiteItem(vente.item, achat.quantiteTotal); let createdItemId = await acheteur.creerQuantiteItem(vente.item, quantite);
await acheteur.consommerNourritureAchetee(achat, vente, createdItemId); await acheteur.consommerNourritureAchetee(achat, vente, createdItemId);
} }
if (cout > 0) { if (cout > 0) {
RdDAudio.PlayContextAudio("argent"); RdDAudio.PlayContextAudio("argent");
} }
const chatAchatItem = duplicate(vente); const chatAchatItem = duplicate(vente);
chatAchatItem.quantiteTotal = achat.quantiteTotal; chatAchatItem.quantiteTotal = quantite;
ChatMessage.create({ ChatMessage.create({
user: achat.userId, user: achat.userId,
speaker: { alias: (acheteur ?? vendeur).name }, speaker: { alias: (acheteur ?? vendeur).name },
@@ -3809,8 +3807,8 @@ export class RdDActor extends Actor {
if (vente.quantiteNbLots <= achat.choix.nombreLots) { if (vente.quantiteNbLots <= achat.choix.nombreLots) {
ChatUtility.removeChatMessageId(achat.chatMessageIdVente); ChatUtility.removeChatMessageId(achat.chatMessageIdVente);
} }
else { else if (!service) {
vente["properties"] = new RdDItem(vente.item).getProprietes(); vente["properties"] = itemVendu.getProprietes();
vente.quantiteNbLots -= achat.choix.nombreLots; vente.quantiteNbLots -= achat.choix.nombreLots;
vente.jsondata = JSON.stringify(vente.item); vente.jsondata = JSON.stringify(vente.item);
const messageVente = game.messages.get(achat.chatMessageIdVente); const messageVente = game.messages.get(achat.chatMessageIdVente);
@@ -3820,6 +3818,21 @@ export class RdDActor extends Actor {
} }
} }
async decrementerVente(service, vendeur, itemVendu, quantite, cout) {
if (service) {
await service.venteRefItem(itemVendu, quantite, cout)
}
else if (vendeur) {
await vendeur.ajouterSols(cout);
await vendeur.decrementerQuantiteItem(itemVendu, quantite);
}
}
verifierQuantite(service, vendeur, item, quantiteTotal) {
const disponible = service ? service.getQuantiteDisponible(item, quantiteTotal) : (vendeur ? (item?.getQuantite() ?? 0) : quantiteTotal);
return disponible >= quantiteTotal;
}
async consommerNourritureAchetee(achat, vente, createdItemId) { async consommerNourritureAchetee(achat, vente, createdItemId) {
if (achat.choix.consommer && vente.item.type == 'nourritureboisson' && createdItemId != undefined) { if (achat.choix.consommer && vente.item.type == 'nourritureboisson' && createdItemId != undefined) {
achat.choix.doses = achat.choix.nombreLots; achat.choix.doses = achat.choix.nombreLots;

View File

@@ -1,12 +1,13 @@
import { Misc } from "./misc.js";
import { RdDUtility } from "./rdd-utility.js"; import { RdDUtility } from "./rdd-utility.js";
export class DialogItemAchat extends Dialog { export class DialogItemAchat extends Dialog {
static venteData(button) { static preparerAchat(chatButton) {
const vendeurId = button.attributes['data-vendeurId']?.value; const vendeurId = chatButton.attributes['data-vendeurId']?.value;
const vendeur = vendeurId ? game.actors.get(vendeurId) : undefined; const vendeur = vendeurId ? game.actors.get(vendeurId) : undefined;
const acheteur = RdDUtility.getSelectedActor(); const acheteur = RdDUtility.getSelectedActor();
const json = button.attributes['data-jsondata']?.value; const json = chatButton.attributes['data-jsondata']?.value;
if (!acheteur && !vendeur) { if (!acheteur && !vendeur) {
ui.notifications.info("Pas d'acheteur ni de vendeur, aucun changement"); ui.notifications.info("Pas d'acheteur ni de vendeur, aucun changement");
return undefined; return undefined;
@@ -16,46 +17,68 @@ export class DialogItemAchat extends Dialog {
return undefined; return undefined;
} }
const prixLot = Number(button.attributes['data-prixLot']?.value ?? 0);
return { return {
item: json ? JSON.parse(json) : undefined, item: (json ? JSON.parse(json) : undefined),
actingUserId: game.user.id, vendeur,
vendeurId: vendeurId, acheteur,
vendeur: vendeur, nbLots: parseInt(chatButton.attributes['data-quantiteNbLots']?.value),
acheteur: acheteur, tailleLot: parseInt(chatButton.attributes['data-tailleLot']?.value ?? 1),
tailleLot: parseInt(button.attributes['data-tailleLot']?.value ?? 1), prixLot: Number(chatButton.attributes['data-prixLot']?.value ?? 0),
quantiteIllimite: button.attributes['data-quantiteIllimite']?.value == 'true', quantiteIllimite: chatButton.attributes['data-quantiteIllimite']?.value == 'true',
quantiteNbLots: parseInt(button.attributes['data-quantiteNbLots']?.value), chatMessageIdVente: RdDUtility.findChatMessageId(chatButton),
choix: {
nombreLots: 1,
seForcer: false,
supprimerSiZero: true
},
prixLot: prixLot,
prixTotal: prixLot,
isVente: prixLot > 0,
chatMessageIdVente: RdDUtility.findChatMessageId(button)
}; };
} }
static async onAcheter(venteData) {
static async onAcheter({ item, vendeur, acheteur, service, tailleLot, prixLot, nbLots, quantiteIllimite, chatMessageIdVente }) {
const venteData = {
item,
actingUserId: game.user.id,
vendeurId: vendeur?.id,
vendeur,
acheteur,
service,
tailleLot,
quantiteIllimite,
quantiteNbLots: nbLots,
choix: { seForcer: false, supprimerSiZero: true },
prixLot,
isVente: prixLot > 0,
isConsommable: item.type == 'nourritureboisson' && acheteur?.isPersonnage(),
chatMessageIdVente
};
DialogItemAchat.changeNombreLots(venteData, 1);
const html = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/dialog-item-achat.html`, venteData); const html = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/dialog-item-achat.html`, venteData);
new DialogItemAchat(html, venteData).render(true); new DialogItemAchat(html, venteData).render(true);
} }
static changeNombreLots(venteData, nombreLots) {
venteData.choix.nombreLots = nombreLots;
venteData.prixTotal = (nombreLots * venteData.prixLot).toFixed(2);
if (venteData.isConsommable) {
const doses = nombreLots * venteData.tailleLot;
venteData.totalSust = Misc.keepDecimals(doses * (venteData.item.system.sust ?? 0), 2);
venteData.totalDesaltere = venteData.item.system.boisson
? Misc.keepDecimals(doses * (venteData.item.system.desaltere ?? 0), 2)
: 0;
}
}
constructor(html, venteData) { constructor(html, venteData) {
const isConsommable = venteData.item.type == 'nourritureboisson' && venteData.acheteur?.isPersonnage();
let options = { classes: ["dialogachat"], width: 400, height: 'fit-content', 'z-index': 99999 }; let options = { classes: ["dialogachat"], width: 400, height: 'fit-content', 'z-index': 99999 };
const actionAchat = venteData.prixLot > 0 ? "Acheter" : "Prendre"; const actionAchat = venteData.prixLot > 0 ? "Acheter" : "Prendre";
const buttons = {}; const buttons = {};
if (isConsommable) { if (venteData.isConsommable) {
buttons["consommer"] = { label: venteData.item.system.boisson ? "Boire" : "Manger", callback: it => this.onAchatConsommer() } buttons["consommer"] = { label: venteData.item.system.boisson ? "Boire" : "Manger", callback: it => this.onAchatConsommer() }
} }
buttons[actionAchat] = { label: actionAchat, callback: it => { this.onAchat(); } }; buttons[actionAchat] = { label: actionAchat, callback: it => { this.onAchat(); } };
buttons["decliner"] = { label: "Décliner", callback: it => { } }; buttons["decliner"] = { label: "Décliner", callback: it => { } };
const acheteur = venteData.acheteur?.name ?? 'Un acheteur';
const vendeur = (venteData.service ?? venteData.vendeur)?.name ?? 'Un vendeur';
let conf = { let conf = {
title: venteData.acheteur ? venteData.acheteur.name + " - " + actionAchat : actionAchat, title: `${acheteur} - ${actionAchat} à ${vendeur}`,
content: html, content: html,
default: actionAchat, default: actionAchat,
buttons: buttons buttons: buttons
@@ -69,6 +92,7 @@ export class DialogItemAchat extends Dialog {
await this.html.find(".nombreLots").change(); await this.html.find(".nombreLots").change();
(this.venteData.vendeur ?? this.venteData.acheteur).achatVente({ (this.venteData.vendeur ?? this.venteData.acheteur).achatVente({
userId: game.user.id, userId: game.user.id,
serviceId: this.venteData.service?.id,
vendeurId: this.venteData.vendeur?.id, vendeurId: this.venteData.vendeur?.id,
acheteurId: this.venteData.acheteur?.id, acheteurId: this.venteData.acheteur?.id,
prixTotal: this.venteData.prixTotal, prixTotal: this.venteData.prixTotal,
@@ -96,13 +120,21 @@ export class DialogItemAchat extends Dialog {
} }
setNombreLots(nombreLots) { setNombreLots(nombreLots) {
if (nombreLots > this.venteData.quantiteNbLots) {
ui.notifications.warn(`Seulement ${this.venteData.quantiteNbLots} lots disponibles, vous ne pouvez pas en prendre ${nombreLots}`) if (!this.venteData.quantiteIllimite) {
if (!this.venteData.quantiteIllimite && nombreLots > this.venteData.quantiteNbLots) {
ui.notifications.warn(`Seulement ${this.venteData.quantiteNbLots} lots disponibles, vous ne pouvez pas en prendre ${nombreLots}`)
}
nombreLots = Math.min(nombreLots, this.venteData.quantiteNbLots);
} }
this.venteData.choix.nombreLots = Math.min(nombreLots, this.venteData.quantiteNbLots);
this.venteData.prixTotal = (nombreLots * this.venteData.prixLot).toFixed(2); DialogItemAchat.changeNombreLots(this.venteData, nombreLots);
this.html.find(".nombreLots").val(this.venteData.choix.nombreLots);
this.html.find(".nombreLots").val(nombreLots);
this.html.find(".prixTotal").text(this.venteData.prixTotal); this.html.find(".prixTotal").text(this.venteData.prixTotal);
this.html.find("span.total-sust").text(this.venteData.totalSust);
this.html.find("span.total-desaltere").text(this.venteData.totalDesaltere);
} }
} }

View File

@@ -2,21 +2,24 @@ import { HtmlUtility } from "./html-utility.js";
export class DialogItemVente extends Dialog { export class DialogItemVente extends Dialog {
static async display(item, callback) { static async display({ item, callback, service = undefined, quantiteMax = undefined }) {
const quantite = item.isConteneur() ? 1 : item.system.quantite; const quantite = quantiteMax ?? item.getQuantite();
const isOwned = item.isOwned;
// const isOwned = item.isOwned || service?.actor;
const venteData = { const venteData = {
item: item, item: item,
alias: item.actor?.name ?? game.user.name, alias: item.actor?.name ?? service?.name ?? game.user.name,
vendeurId: item.actor?.id, serviceId: service?.id,
vendeurId: item.actor?.id ?? service?.actor?.id,
prixOrigine: item.system.cout, prixOrigine: item.system.cout,
prixUnitaire: item.system.cout, prixUnitaire: item.system.cout,
prixLot: item.system.cout, prixLot: item.system.cout,
tailleLot: 1, tailleLot: 1,
quantiteNbLots: quantite, quantiteNbLots: quantite,
quantiteMaxLots: quantite, quantiteMaxLots: quantite,
quantiteMax: quantite , quantiteMax: quantite,
quantiteIllimite: !item.isOwned, quantiteIllimite: service? service.system.illimite : !isOwned,
isOwned: item.isOwned, isOwned: isOwned,
}; };
const html = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/dialog-item-vente.html`, venteData); const html = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/dialog-item-vente.html`, venteData);
return new DialogItemVente(venteData, html, callback).render(true); return new DialogItemVente(venteData, html, callback).render(true);
@@ -54,7 +57,7 @@ export class DialogItemVente extends Dialog {
await this.html.find(".quantiteIllimite").change(); await this.html.find(".quantiteIllimite").change();
await this.html.find(".prixLot").change(); await this.html.find(".prixLot").change();
this.callback(this.venteData); this.callback(this.venteData);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
setPrixLot(prixLot) { setPrixLot(prixLot) {
@@ -68,13 +71,11 @@ export class DialogItemVente extends Dialog {
this.html.find(".prixLot").val(this.venteData.prixLot); this.html.find(".prixLot").val(this.venteData.prixLot);
} }
this.venteData.tailleLot = tailleLot; this.venteData.tailleLot = tailleLot;
if (this.venteData.isOwned) { // recalculer le nombre de lots max
// recalculer le nombre de lots max this.venteData.quantiteMaxLots = Math.floor(this.venteData.quantiteMax / tailleLot);
this.venteData.quantiteMaxLots = Math.floor(this.venteData.quantiteMax / tailleLot); this.venteData.quantiteNbLots = Math.min(this.venteData.quantiteMaxLots, this.venteData.quantiteNbLots);
this.venteData.quantiteNbLots = Math.min(this.venteData.quantiteMaxLots, this.venteData.quantiteNbLots); this.html.find(".quantiteNbLots").val(this.venteData.quantiteNbLots);
this.html.find(".quantiteNbLots").val(this.venteData.quantiteNbLots); this.html.find(".quantiteNbLots").attr("max", this.venteData.quantiteMaxLots)
this.html.find(".quantiteNbLots").attr("max", this.venteData.quantiteMaxLots)
}
} }
setNbLots(nbLots) { setNbLots(nbLots) {

View File

@@ -72,6 +72,14 @@ export class Monnaie {
return 0; return 0;
} }
static getFortuneSolsDeniers(actor) {
const fortune = Monnaie.getFortune(actor);
return {
sols: Math.floor(fortune),
deniers: Math.round(100 * (fortune - Math.floor(fortune)))
};
}
static async optimiserFortune(actor, fortune) { static async optimiserFortune(actor, fortune) {
let resteEnDeniers = Math.round(fortune * 100); let resteEnDeniers = Math.round(fortune * 100);
let monnaies = actor.itemTypes['monnaie']; let monnaies = actor.itemTypes['monnaie'];

View File

@@ -0,0 +1,81 @@
import { RdDItemSheet } from "./item-sheet.js";
import { Misc } from "./misc.js";
import { RdDUtility } from "./rdd-utility.js";
import { SystemCompendiums } from "./settings/system-compendiums.js";
import { DialogItemAchat } from "./dialog-item-achat.js";
import { RdDItem } from "./item.js";
import { RdDItemService } from "./item-service.js";
export class RdDServiceItemSheet extends RdDItemSheet {
static get ITEM_TYPE() { return "service" };
async getData() {
const formData = await super.getData();
formData.disabled = formData.isGM || formData.isOwned ? '' : 'disabled';
return formData;
}
activateListeners(html) {
super.activateListeners(html);
this.html.find('a.rdd-world-content-link').click(async event => {
const itemRef = this.getItemRef(event);
game.items.get(itemRef.id)?.sheet.render(true)
});
this.html.find('a.sub-item-acheter').click(async event => {
const subItem = this.item.findRefItem(this.getItemRef(event));
await this.item.acheter(RdDUtility.getSelectedActor(), subItem);
});
if (!this.options.editable) return;
this.html.find('a.sub-item-vendre').click(async event => {
const subItem = this.item.findRefItem(this.getItemRef(event));
await this.item.vendre(subItem);
});
this.html.find('a.sub-item-delete').click(async event => {
await this.item.removeRefItem(this.getItemRef(event));
});
this.html.find('a.sub-item-quantite-moins').click(async event => await this.item.increaseRefItemQuantite(this.getItemRef(event), -1))
this.html.find('a.sub-item-quantite-plus').click(async event => await this.item.increaseRefItemQuantite(this.getItemRef(event), 1))
this.html.find('input.sub-item-quantite').change(async event => {
const newQuantite = Math.max(0, Number.parseInt(this.html.find(event.currentTarget).val()));
await this.item.updateRefItem(this.getItemRef(event), it => it.system.quantite = newQuantite);
})
this.html.find('input.sub-item-cout').change(async event => {
const newCout = Math.max(0, Number(this.html.find(event.currentTarget).val()));
await this.item.updateRefItem(this.getItemRef(event), it => it.system.cout = newCout);
})
this.html.find('a.sub-item-info-add').click(__ =>
ui.notifications.info(`Utiliser le glisser-déposer pour ajouter des objets depuis un compendium ou les objets du monde`)
);
}
async _onDropItem(event, dragData) {
let linkedItem = fromUuidSync(dragData.uuid);
const existing = this.item.system.items.find(it => it.pack == linkedItem.pack && it.id == linkedItem.id && it.type == linkedItem.type);
if (existing) {
ui.notifications.warn(`${this.item.name} contient déjà un ${existing.name}`);
return;
}
if (linkedItem.pack) {
linkedItem = await SystemCompendiums.loadDocument(linkedItem);
}
if (linkedItem.isInventaire()) {
await this.item.addRefItem(RdDServiceItemSheet.createSubItem(linkedItem));
}
else {
ui.notifications.warn(`${this.item.name} ne peut pas proposer à la vente de ${Misc.typeName('Item', linkedItem.type)}: ${linkedItem.name}`);
}
}
getItemRef(event) {
const itemRow = this.html.find(event.currentTarget)?.parents('.item.service-item');
return { id: itemRow?.data("item-id"), pack: itemRow?.data("pack") ?? undefined }
}
}

140
module/item-service.js Normal file
View File

@@ -0,0 +1,140 @@
import { DialogItemAchat } from "./dialog-item-achat.js";
import { RdDItem } from "./item.js";
import { Misc } from "./misc.js";
export class RdDItemService extends RdDItem {
static get defaultIcon() {
return "systems/foundryvtt-reve-de-dragon/icons/items/services.webp";
}
/** @override*/
getUserLevel(user) {
const level = super.getUserLevel(user);
if (level == CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) {
// si quelqu'un a accès au lien d'un service, il peut le voir
return CONST.DOCUMENT_OWNERSHIP_LEVELS.LIMITED;
}
return level;
}
isService() { return true; }
getChatItemTemplate() { return 'systems/foundryvtt-reve-de-dragon/templates/post-item-service.html'; }
getProprietes() { return []; }
getServiceItem(itemRef) {
if (this.isService()) {
return this.system.items.find(it => it.id == itemRef.id && it.pack == itemRef.pack);
}
return undefined;
}
getQuantiteDisponible(itemRef, max) {
if (this.system.illimite) {
return max;
}
const subItem = this.getServiceItem(itemRef);
return subItem?.system.quantite ?? 0;
}
async venteRefItem(ref, quantite, cout) {
if (this.actor) {
await this.actor.ajouterSols(cout);
}
await this.increaseRefItemQuantite(ref, -quantite);
}
async vendre(subItem) {
const item = await RdDItem.getCorrespondingItem(subItem);
const quantiteMax = this.system.illimite ? undefined : subItem.system.quantite;
await item.proposerVente({ service: this, quantiteMax });
}
async acheter(acheteur, subItem) {
if (!acheteur) {
ui.notifications.warn(`Pas d'acheteur sélectionné`);
return;
}
const nbLots = this.system.illimite ? 1 : subItem.system.quantite;
if (nbLots <= 0) {
ui.notifications.warn(`${this.name} n'a plus de ${subItem.name} en vente`);
return;
}
await DialogItemAchat.onAcheter({
item: await RdDItem.getCorrespondingItem(subItem),
acheteur,
service: this,
quantiteIllimite: this.system.illimite,
nbLots,
tailleLot: 1,
prixLot: subItem.system.cout
});
}
static createSubItem(linkedItem) {
return {
id: linkedItem.id,
pack: linkedItem.pack,
name: linkedItem.name,
img: linkedItem.img,
system: {
quantite: 1,
cout: linkedItem.system.cout ?? 0
}
};
}
static matchRefItem({ id, pack }) {
return it => it.id == id && (pack ? (it.pack == pack) : (!it.pack));
}
findRefItem(ref) {
return this.system.items.find(RdDItemService.matchRefItem(ref));
}
async increaseRefItemQuantite(ref, quantite) {
await this.updateRefItem(ref,
it => it.system.quantite = Math.max(0, it.system.quantite + quantite)
);
}
async updateRefItem(ref, update = it => { }) {
await this.updateRefItems(RdDItemService.matchRefItem(ref), update);
}
async addRefItem(newItem) {
if (!newItem.id) {
ui.notifications.warn(`${newItem?.name ?? '??'} n'a pas d'identifiant`);
return;
}
if (this.system.items.find(RdDItemService.matchRefItem(newItem))) {
ui.notifications.warn(`${newItem?.name ?? newItem.id} est déjà présent ici`);
return;
}
await this.setRefItems([...this.system.items, newItem]);
}
async removeRefItem(ref) {
await this.removeRefItems(RdDItemService.matchRefItem(ref));
}
async removeRefItems(matcher = it => false) {
await this.setRefItems(this.system.items.filter(it => !matcher(it)));
}
async updateRefItems(matcher = it => false, update = it => { }) {
const updatedList = this.system.items.map(it => {
if (matcher(it)) {
update(it);
}
return it;
});
await this.setRefItems(updatedList);
}
async setRefItems(newItems) {
await this.update({ 'system.items': newItems.sort(Misc.ascending(it => it.type + ':' + it.name)) });
}
}

View File

@@ -209,7 +209,7 @@ export class RdDItemSheet extends ItemSheet {
this.html.find('.item-split').click(async event => RdDSheetUtility.splitItem(RdDSheetUtility.getItem(event, this.actor), this.actor, async () => this.render(true))); this.html.find('.item-split').click(async event => RdDSheetUtility.splitItem(RdDSheetUtility.getItem(event, this.actor), this.actor, async () => this.render(true)));
this.html.find('.item-edit').click(async event => RdDSheetUtility.getItem(event, this.actor)?.sheet.render(true)); this.html.find('.item-edit').click(async event => RdDSheetUtility.getItem(event, this.actor)?.sheet.render(true));
this.html.find('.item-delete').click(async event => RdDUtility.confirmerSuppressionItem(this, RdDSheetUtility.getItem(event, this.actor))); this.html.find('.item-delete').click(async event => RdDUtility.confirmActorItemDelete(this, RdDSheetUtility.getItem(event, this.actor)));
this.html.find('.item-vendre').click(async event => RdDSheetUtility.getItem(event, this.actor)?.proposerVente()); this.html.find('.item-vendre').click(async event => RdDSheetUtility.getItem(event, this.actor)?.proposerVente());
this.html.find('.item-montrer').click(async event => RdDSheetUtility.getItem(event, this.actor)?.postItem()); this.html.find('.item-montrer').click(async event => RdDSheetUtility.getItem(event, this.actor)?.postItem());
this.html.find('.item-action').click(async event => RdDSheetUtility.getItem(event, this.actor)?.actionPrincipale(this.actor, async () => this.render(true))); this.html.find('.item-action').click(async event => RdDSheetUtility.getItem(event, this.actor)?.actionPrincipale(this.actor, async () => this.render(true)));

View File

@@ -3,6 +3,7 @@ import { Grammar } from "./grammar.js";
import { Misc } from "./misc.js"; import { Misc } from "./misc.js";
import { RdDHerbes } from "./rdd-herbes.js"; import { RdDHerbes } from "./rdd-herbes.js";
import { RdDUtility } from "./rdd-utility.js"; import { RdDUtility } from "./rdd-utility.js";
import { SystemCompendiums } from "./settings/system-compendiums.js";
const typesObjetsInventaire = [ const typesObjetsInventaire = [
"arme", "arme",
@@ -57,6 +58,7 @@ export const defaultItemImg = {
poison: "systems/foundryvtt-reve-de-dragon/icons/maladies_venins/venin.webp", poison: "systems/foundryvtt-reve-de-dragon/icons/maladies_venins/venin.webp",
oeuvre: "systems/foundryvtt-reve-de-dragon/icons/competence_comedie.webp", oeuvre: "systems/foundryvtt-reve-de-dragon/icons/competence_comedie.webp",
nourritureboisson: "systems/foundryvtt-reve-de-dragon/icons/objets/provision_crue.webp", nourritureboisson: "systems/foundryvtt-reve-de-dragon/icons/objets/provision_crue.webp",
service: "systems/foundryvtt-reve-de-dragon/icons/items/services.webp",
signedraconique: "systems/foundryvtt-reve-de-dragon/icons/tmr/signe_draconique.webp", signedraconique: "systems/foundryvtt-reve-de-dragon/icons/tmr/signe_draconique.webp",
gemme: "systems/foundryvtt-reve-de-dragon/icons/gemmes/almaze.webp", gemme: "systems/foundryvtt-reve-de-dragon/icons/gemmes/almaze.webp",
possession: "systems/foundryvtt-reve-de-dragon/icons/entites/possession2.webp", possession: "systems/foundryvtt-reve-de-dragon/icons/entites/possession2.webp",
@@ -69,11 +71,7 @@ export const defaultItemImg = {
export class RdDItem extends Item { export class RdDItem extends Item {
static getDefaultImg(itemType) { static getDefaultImg(itemType) {
return defaultItemImg[itemType]; return game.system.rdd.itemClasses[itemType]?.defaultIcon ?? defaultItemImg[itemType];
}
static isItemInventaire(newLocal) {
return typesObjetsInventaire.includes(newLocal.type);
} }
static isFieldInventaireModifiable(type, field) { static isFieldInventaireModifiable(type, field) {
@@ -92,6 +90,42 @@ export class RdDItem extends Item {
return true; return true;
} }
static async getCorrespondingItem(itemRef) {
if (itemRef.pack) {
return await SystemCompendiums.loadDocument(itemRef)
}
return game.items.get(itemRef.id ?? itemRef._id);
}
static getItemTypesInventaire() {
return typesObjetsInventaire
}
static getTypesOeuvres() {
return typesObjetsOeuvres
}
constructor(docData, context = {}) {
if (!context.rdd?.ready) {
mergeObject(context, { rdd: { ready: true } });
const ItemConstructor = game.system.rdd.itemClasses[docData.type];
if (ItemConstructor) {
if (!docData.img) {
docData.img = ItemConstructor.defaultIcon;
}
return new ItemConstructor(docData, context);
}
}
if (!docData.img) {
docData.img = RdDItem.getDefaultImg(docData.type);
}
super(docData, context);
}
static get defaultIcon() {
return undefined;
}
getUniteQuantite() { getUniteQuantite() {
switch (this.type) { switch (this.type) {
case "monnaie": return "(Pièces)" case "monnaie": return "(Pièces)"
@@ -107,32 +141,17 @@ export class RdDItem extends Item {
return ''; return '';
} }
constructor(itemData, context) { isCompetencePersonnage() { return this.type == 'competence' }
if (!itemData.img) { isCompetenceCreature() { return this.type == 'competencecreature' }
itemData.img = RdDItem.getDefaultImg(itemData.type); isConteneur() { return this.type == 'conteneur'; }
} isMonnaie() { return this.type == 'monnaie'; }
super(itemData, context); isNourritureBoisson() { return this.type == 'nourritureboisson'; }
} isService() { return this.type == 'service'; }
static getItemTypesInventaire() {
return typesObjetsInventaire
}
static getTypesOeuvres() {
return typesObjetsOeuvres
}
isCompetencePersonnage() {
return this.type == 'competence'
}
isCompetenceCreature() {
return this.type == 'competencecreature'
}
isCompetence() { isCompetence() {
return typesObjetsCompetence.includes(this.type) return typesObjetsCompetence.includes(this.type)
} }
isInventaire() { isInventaire() {
return RdDItem.isItemInventaire(this) return typesObjetsInventaire.includes(this.type);
} }
isOeuvre() { isOeuvre() {
return typesObjetsOeuvres.includes(this.type) return typesObjetsOeuvres.includes(this.type)
@@ -146,12 +165,7 @@ export class RdDItem extends Item {
isConnaissance() { isConnaissance() {
return typesObjetsConnaissance.includes(this.type) return typesObjetsConnaissance.includes(this.type)
} }
isConteneur() {
return this.type == 'conteneur';
}
isMonnaie() {
return this.type == 'monnaie';
}
getItemGroup() { getItemGroup() {
if (this.isInventaire()) return "equipement"; if (this.isInventaire()) return "equipement";
@@ -175,10 +189,6 @@ export class RdDItem extends Item {
return !this.isConteneur() || (this.system.contenu?.length ?? 0) == 0; return !this.isConteneur() || (this.system.contenu?.length ?? 0) == 0;
} }
isNourritureBoisson() {
return this.type == 'nourritureboisson';
}
isComestible() { isComestible() {
switch (this.type) { switch (this.type) {
case 'nourritureboisson': return 'pret'; case 'nourritureboisson': return 'pret';
@@ -209,7 +219,7 @@ export class RdDItem extends Item {
} }
getQuantite() { getQuantite() {
return Math.round(this.isConteneur() ? 1 : (this.system.quantite ?? 0)) return Math.round(this.system.quantite ?? 0)
} }
getEncTotal() { getEncTotal() {
@@ -284,7 +294,7 @@ export class RdDItem extends Item {
} }
return undefined; return undefined;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async actionPrincipale(actor, onActionItem = async () => { }) { async actionPrincipale(actor, onActionItem = async () => { }) {
if (!this.getActionPrincipale()) { if (!this.getActionPrincipale()) {
@@ -410,31 +420,38 @@ export class RdDItem extends Item {
return [true, undefined]; return [true, undefined];
} }
async proposerVente() { async proposerVente({ service = undefined, quantiteMax = undefined }) {
console.log(this); console.log(this);
if (this.isConteneurNonVide()) { if (this.isConteneurNonVide()) {
ui.notifications.warn(`Votre ${this.name} n'est pas vide, pas possible de le proposer`); ui.notifications.warn(`Votre ${this.name} n'est pas vide, pas possible de le proposer`);
return; return;
} }
await DialogItemVente.display(this, async (vente) => { await DialogItemVente.display({
vente["properties"] = this.getProprietes(); item: this,
if (vente.isOwned) { service,
if (vente.quantiteNbLots * vente.tailleLot > vente.quantiteMax) { quantiteMax,
ui.notifications.warn(`Vous avez ${vente.quantiteMax} ${vente.item.name}, ce n'est pas suffisant pour vendre ${vente.quantiteNbLots} de ${vente.tailleLot}`) callback: async (vente) => {
return; 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;
}
} }
} vente.jsondata = JSON.stringify(vente.item);
vente.jsondata = JSON.stringify(vente.item);
console.log(vente); let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-vente-item.html', vente);
let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-vente-item.html', vente); ChatMessage.create(RdDUtility.chatDataSetup(html));
ChatMessage.create(RdDUtility.chatDataSetup(html)); }
}); });
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
getProprietes() { getProprietes() {
return this[`_${this.type}ChatData`]().filter(it => it != undefined); if (this[`_${this.type}ChatData`]) {
return this[`_${this.type}ChatData`]().filter(it => it != undefined);
}
return [];
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -452,12 +469,19 @@ export class RdDItem extends Item {
payload: chatData, payload: chatData,
}); });
renderTemplate('systems/foundryvtt-reve-de-dragon/templates/post-item.html', chatData).then(html => { renderTemplate(this.getChatItemTemplate(), chatData).then(html => {
let chatOptions = RdDUtility.chatDataSetup(html, modeOverride); let chatOptions = RdDUtility.chatDataSetup(html, modeOverride);
ChatMessage.create(chatOptions) ChatMessage.create(chatOptions)
}); });
} }
getChatItemTemplate() {
switch (this.type) {
case 'service': return 'systems/foundryvtt-reve-de-dragon/templates/post-item-service.html';
}
return 'systems/foundryvtt-reve-de-dragon/templates/post-item.html';
}
static propertyIfDefined(name, val, condition = true) { static propertyIfDefined(name, val, condition = true) {
return condition ? `<b>${name}</b>: ${val}` : undefined; return condition ? `<b>${name}</b>: ${val}` : undefined;
} }
@@ -706,5 +730,4 @@ export class RdDItem extends Item {
...this._inventaireTemplateChatData() ...this._inventaireTemplateChatData()
] ]
} }
} }

View File

@@ -29,6 +29,39 @@ class Migration {
} }
class _1_5_34_migrationPngWebp {
get code() { return "migrationPngWebp"; }
get version() { return "1.5.34"; }
async migrate() {
const regexOldPngJpg = /(systems\/foundryvtt-reve-de-dragon\/icons\/.*)\.(png|jpg)/;
const replaceWithWebp = '$1.webp';
function convertImgToWebp(img) {
return img.replace(regexOldPngJpg, replaceWithWebp);
}
function prepareDocumentsImgUpdate(documents) {
return documents.filter(it => it.img && it.img.match(regexOldPngJpg))
.map(it => {
return { _id: it.id, img: convertImgToWebp(it.img) }
});
}
const itemsUpdates = prepareDocumentsImgUpdate(game.items);
const actorsUpdates = prepareDocumentsImgUpdate(game.actors);
//Migrate system png to webp
await Item.updateDocuments(itemsUpdates);
await Actor.updateDocuments(actorsUpdates);
game.actors.forEach(actor => {
if (actor.token?.img && actor.token.img.match(regexOldPngJpg)) {
actor.update({ "token.img": convertImgToWebp(actor.token.img) });
}
const actorItemsToUpdate = prepareDocumentsImgUpdate(actor.items);
actor.updateEmbeddedDocuments('Item', actorItemsToUpdate);
});
}
}
class _10_0_16_MigrationSortsReserve extends Migration { class _10_0_16_MigrationSortsReserve extends Migration {
get code() { return "creation-item-sort-reserve"; } get code() { return "creation-item-sort-reserve"; }
get version() { return "10.0.16"; } get version() { return "10.0.16"; }
@@ -296,6 +329,7 @@ class _10_3_17_Monnaies extends Migration {
export class Migrations { export class Migrations {
static getMigrations() { static getMigrations() {
return [ return [
new _1_5_34_migrationPngWebp(),
new _10_0_16_MigrationSortsReserve(), new _10_0_16_MigrationSortsReserve(),
new _10_0_17_MigrationCompetenceCreature(), new _10_0_17_MigrationCompetenceCreature(),
new _10_0_21_VehiculeStructureResistanceMax(), new _10_0_21_VehiculeStructureResistanceMax(),

View File

@@ -29,7 +29,7 @@ export class RdDCalendrierEditeur extends Dialog {
this.html.find("input[name='nomMois']").val(this.calendrierData.moisKey); this.html.find("input[name='nomMois']").val(this.calendrierData.moisKey);
this.html.find("select[name='nomHeure']").val(this.calendrierData.heureKey); this.html.find("select[name='nomHeure']").val(this.calendrierData.heureKey);
this.html.find("select[name='jourMois']").val(this.calendrierData.jourMois); this.html.find("select[name='jourMois']").val(this.calendrierData.jourMois);
this.html.find("select[name='minutesRelative']").val(calendrierData.minutesRelative); this.html.find("select[name='minutesRelative']").val(this.calendrierData.minutesRelative);
this.html.find("select[name='annee']").val(this.calendrierData.annee); this.html.find("select[name='annee']").val(this.calendrierData.annee);
} }

View File

@@ -162,7 +162,6 @@ export class RdDCombatManager extends Combat {
if (arme.system.unemain && arme.system.deuxmains && !dommages.includes("/")) { if (arme.system.unemain && arme.system.deuxmains && !dommages.includes("/")) {
ui.notifications.info("Les dommages de l'arme à 1/2 mains " + arme.name + " ne sont pas corrects (ie sous la forme X/Y)"); ui.notifications.info("Les dommages de l'arme à 1/2 mains " + arme.name + " ne sont pas corrects (ie sous la forme X/Y)");
} }
console.log(">>>>", arme)
if ((arme.system.unemain && arme.system.competence) || if ((arme.system.unemain && arme.system.competence) ||
(arme.system.competence.toLowerCase().includes("corps à corps"))) { (arme.system.competence.toLowerCase().includes("corps à corps"))) {
actions.push(RdDCombatManager.$prepareAttaqueArme({ actions.push(RdDCombatManager.$prepareAttaqueArme({

View File

@@ -1,4 +1,3 @@
import { Misc } from "./misc.js";
export class RdDHotbar { export class RdDHotbar {

View File

@@ -1,13 +1,3 @@
/**
* RdD system
* Author: LeRatierBretonnien
* Software License: GNU GPLv3
*/
/* -------------------------------------------- */
/* -------------------------------------------- */
// Import Modules
import { SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js"; import { SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js";
import { RdDActor } from "./actor.js"; import { RdDActor } from "./actor.js";
import { RdDItemSheet } from "./item-sheet.js"; import { RdDItemSheet } from "./item-sheet.js";
@@ -44,302 +34,281 @@ import { Environnement } from "./environnement.js";
import { RdDIngredientItemSheet } from "./item-ingredient-sheet.js"; import { RdDIngredientItemSheet } from "./item-ingredient-sheet.js";
import { RdDFauneItemSheet } from "./item-faune-sheet.js"; import { RdDFauneItemSheet } from "./item-faune-sheet.js";
import { RdDConteneurItemSheet } from "./item-conteneur-sheet.js"; import { RdDConteneurItemSheet } from "./item-conteneur-sheet.js";
import { RdDServiceItemSheet } from "./item-service-sheet.js";
import { RdDItemService } from "./item-service.js";
/* -------------------------------------------- */ /**
/* Foundry VTT Initialization */ * RdD system
/* -------------------------------------------- */ * Author: LeRatierBretonnien
* Software License: GNU GPLv3
*/
export class SystemReveDeDragon {
static start() {
const system = new SystemReveDeDragon();
/************************************************************************************/ Hooks.once('init', async () => await system.onInit());
Hooks.once("init", async function () { Hooks.once('diceSoNiceReady', (dice3d) => RdDDice.diceSoNiceReady(dice3d));
console.log(`Initializing Reve de Dragon System`);
// preload handlebars templates
RdDUtility.preloadHandlebarsTemplates();
// Create useful storage space
game.system.rdd = {
TMRUtility,
RdDUtility,
RdDHotbar,
RdDPossession,
} }
/* -------------------------------------------- */ constructor() {
game.settings.register(SYSTEM_RDD, "accorder-entite-cauchemar", { this.RdDUtility = RdDUtility;
name: "Accorder le rêve aux entités", this.RdDHotbar = RdDHotbar;
hint: "A quel moment les personnages doivent accorder leur rêve aux entités de cauchemar", this.itemClasses = {
scope: "world", service: RdDItemService
config: true, }
type: String, this.actorClasses = {
choices: { // If choices are defined, the resulting setting will be a select menu
"avant-attaque": "Avant l'attaque",
"avant-defense": "Avant la défense",
"avant-encaissement": "Avant l'encaissement",
},
default: "avant-encaissement"
});
/* -------------------------------------------- */
game.settings.register(SYSTEM_RDD, "calendrier", {
name: "calendrier",
scope: "world",
config: false,
default: RdDCalendrier.createCalendrierInitial(),
type: Object
});
/* -------------------------------------------- */
game.settings.register(SYSTEM_RDD, "migration-png-webp-1.5.34", {
name: "calendrier",
scope: "world",
config: false,
default: false,
type: Boolean
});
/* -------------------------------------------- */
game.settings.register(SYSTEM_RDD, "liste-nombre-astral", {
name: "liste-nombre-astral",
scope: "world",
config: false,
default: [],
type: Object
});
/* -------------------------------------------- */
game.settings.register(SYSTEM_RDD, "calendrier-pos", {
name: "calendrierPos",
scope: "client",
config: false,
default: RdDCalendrier.createCalendrierPos(),
type: Object
});
/* -------------------------------------------- */
game.settings.register(SYSTEM_RDD, "supprimer-dialogues-combat-chat", {
name: "Supprimer les dialogues de combat",
hint: "Si désactivée, tous les dialogues de combat sont conservés dans la conversation",
scope: "world",
config: true,
default: true,
type: Boolean
});
/* -------------------------------------------- */
game.settings.register(SYSTEM_RDD, "activer-sons-audio", {
name: "Activer les bruitages intégrés",
hint: "Si activé, certaines actions en jeu déclenchent un son d'ambiance",
scope: "world",
config: true,
default: true,
type: Boolean
});
/* -------------------------------------------- */
game.settings.register(SYSTEM_RDD, "appliquer-famine-soif", {
name: "Notifier de la famine et la soif pour",
hint: "Indique si les cas de famine et de soif seront indiqués durant Château Dormant",
scope: "world",
config: true,
type: String,
choices: {
"aucun": "ni la famine, ni la soif",
"famine": "seulement la famine",
"famine-soif": "la famine et la soif",
},
default: "aucun"
});
/* -------------------------------------------- */
// Set an initiative formula for the system
CONFIG.Combat.initiative = {
formula: "1+(1d6/10)",
decimals: 2
};
/* -------------------------------------------- */
game.socket.on(SYSTEM_SOCKET_ID, async (sockmsg) => {
console.log(">>>>> MSG RECV", 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)
} }
});
/* -------------------------------------------- */
// Define custom Entity classes
CONFIG.Actor.documentClass = RdDActor;
CONFIG.Item.documentClass = RdDItem;
CONFIG.RDD = {
resolutionTable: RdDResolutionTable.resolutionTable,
carac_array: RdDUtility.getCaracArray(),
ajustementsConditions: RdDUtility.getAjustementsConditions(),
difficultesLibres: RdDUtility.getDifficultesLibres()
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
// Register sheet application classes /* Foundry VTT Initialization */
Actors.unregisterSheet("core", ActorSheet); /* -------------------------------------------- */
Actors.registerSheet(SYSTEM_RDD, RdDActorSheet, { types: ["personnage"], makeDefault: true }); async onInit() {
Actors.registerSheet(SYSTEM_RDD, RdDActorCreatureSheet, { types: ["creature"], makeDefault: true }); game.system.rdd = this;
Actors.registerSheet(SYSTEM_RDD, RdDActorVehiculeSheet, { types: ["vehicule"], makeDefault: true });
Actors.registerSheet(SYSTEM_RDD, RdDActorEntiteSheet, { types: ["entite"], makeDefault: true });
Items.unregisterSheet("core", ItemSheet);
RdDItemSheet.register(RdDSigneDraconiqueItemSheet); console.log(`Initializing Reve de Dragon System`);
RdDItemSheet.register(RdDRencontreItemSheet);
RdDItemSheet.register(RdDConteneurItemSheet);
RdDItemSheet.register(RdDHerbeItemSheet);
RdDItemSheet.register(RdDFauneItemSheet);
RdDItemSheet.register(RdDIngredientItemSheet);
Items.registerSheet(SYSTEM_RDD, RdDItemSheet, { // preload handlebars templates
types: [ RdDUtility.preloadHandlebarsTemplates();
"competence", "competencecreature",
"recettealchimique", "musique", "chant", "danse", "jeu", "recettecuisine", "oeuvre",
"objet", "arme", "armure", "livre", "potion", "munition",
"monnaie", "nourritureboisson", "gemme",
"meditation", "queue", "ombre", "souffle", "tete", "casetmr", "sort", "sortreserve",
"nombreastral", "tache", "maladie", "poison", "possession",
"tarot", "extraitpoetique"
], makeDefault: true
});
CONFIG.Combat.documentClass = RdDCombatManager;
// préparation des différents modules /* -------------------------------------------- */
SystemCompendiums.init(); game.settings.register(SYSTEM_RDD, "accorder-entite-cauchemar", {
DialogChronologie.init(); name: "Accorder le rêve aux entités",
ReglesOptionelles.init(); hint: "A quel moment les personnages doivent accorder leur rêve aux entités de cauchemar",
RdDUtility.init(); scope: "world",
RdDDice.init(); config: true,
RdDCommands.init(); type: String,
RdDCombat.init(); choices: { // If choices are defined, the resulting setting will be a select menu
RdDCombatManager.init(); "avant-attaque": "Avant l'attaque",
RdDTokenHud.init(); "avant-defense": "Avant la défense",
RdDActor.init(); "avant-encaissement": "Avant l'encaissement",
RddCompendiumOrganiser.init(); },
EffetsDraconiques.init() default: "avant-encaissement"
TMRUtility.init(); });
RdDHotbar.initDropbar();
RdDPossession.init();
TMRRencontres.init();
Environnement.init();
});
/* -------------------------------------------- */ /* -------------------------------------------- */
function messageDeBienvenue() { game.settings.register(SYSTEM_RDD, "calendrier", {
if (game.user.isGM) { name: "calendrier",
ChatUtility.removeChatMessageContaining('<div id="message-bienvenue-rdd">'); scope: "world",
ChatMessage.create({ config: false,
user: game.user.id, default: RdDCalendrier.createCalendrierInitial(),
content: `<div id="message-bienvenue-rdd"><span class="rdd-roll-part">Bienvenue dans le Rêve des Dragons !</span> type: Object
});
/* -------------------------------------------- */
game.settings.register(SYSTEM_RDD, "liste-nombre-astral", {
name: "liste-nombre-astral",
scope: "world",
config: false,
default: [],
type: Object
});
/* -------------------------------------------- */
game.settings.register(SYSTEM_RDD, "calendrier-pos", {
name: "calendrierPos",
scope: "client",
config: false,
default: RdDCalendrier.createCalendrierPos(),
type: Object
});
/* -------------------------------------------- */
game.settings.register(SYSTEM_RDD, "supprimer-dialogues-combat-chat", {
name: "Supprimer les dialogues de combat",
hint: "Si désactivée, tous les dialogues de combat sont conservés dans la conversation",
scope: "world",
config: true,
default: true,
type: Boolean
});
/* -------------------------------------------- */
game.settings.register(SYSTEM_RDD, "activer-sons-audio", {
name: "Activer les bruitages intégrés",
hint: "Si activé, certaines actions en jeu déclenchent un son d'ambiance",
scope: "world",
config: true,
default: true,
type: Boolean
});
/* -------------------------------------------- */
game.settings.register(SYSTEM_RDD, "appliquer-famine-soif", {
name: "Notifier de la famine et la soif pour",
hint: "Indique si les cas de famine et de soif seront indiqués durant Château Dormant",
scope: "world",
config: true,
type: String,
choices: {
"aucun": "ni la famine, ni la soif",
"famine": "seulement la famine",
"famine-soif": "la famine et la soif",
},
default: "aucun"
});
/* -------------------------------------------- */
// Set an initiative formula for the system
CONFIG.Combat.initiative = {
formula: "1+(1d6/10)",
decimals: 2
};
/* -------------------------------------------- */
game.socket.on(SYSTEM_SOCKET_ID, async (sockmsg) => {
console.log(">>>>> MSG RECV", 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)
}
});
/* -------------------------------------------- */
// Define custom Entity classes
CONFIG.Actor.documentClass = RdDActor;
CONFIG.Item.documentClass = RdDItem;
CONFIG.RDD = {
resolutionTable: RdDResolutionTable.resolutionTable,
carac_array: RdDUtility.getCaracArray(),
ajustementsConditions: RdDUtility.getAjustementsConditions(),
difficultesLibres: RdDUtility.getDifficultesLibres()
}
/* -------------------------------------------- */
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet(SYSTEM_RDD, RdDActorSheet, { types: ["personnage"], makeDefault: true });
Actors.registerSheet(SYSTEM_RDD, RdDActorCreatureSheet, { types: ["creature"], makeDefault: true });
Actors.registerSheet(SYSTEM_RDD, RdDActorVehiculeSheet, { types: ["vehicule"], makeDefault: true });
Actors.registerSheet(SYSTEM_RDD, RdDActorEntiteSheet, { types: ["entite"], makeDefault: true });
Items.unregisterSheet("core", ItemSheet);
RdDItemSheet.register(RdDSigneDraconiqueItemSheet);
RdDItemSheet.register(RdDRencontreItemSheet);
RdDItemSheet.register(RdDConteneurItemSheet);
RdDItemSheet.register(RdDHerbeItemSheet);
RdDItemSheet.register(RdDFauneItemSheet);
RdDItemSheet.register(RdDIngredientItemSheet);
RdDItemSheet.register(RdDServiceItemSheet);
Items.registerSheet(SYSTEM_RDD, RdDItemSheet, {
types: [
"competence", "competencecreature",
"recettealchimique", "musique", "chant", "danse", "jeu", "recettecuisine", "oeuvre",
"objet", "arme", "armure", "livre", "potion", "munition",
"monnaie", "nourritureboisson", "gemme",
"meditation", "queue", "ombre", "souffle", "tete", "casetmr", "sort", "sortreserve",
"nombreastral", "tache", "maladie", "poison", "possession",
"tarot", "extraitpoetique"
], makeDefault: true
});
CONFIG.Combat.documentClass = RdDCombatManager;
// préparation des différents modules
SystemCompendiums.init();
DialogChronologie.init();
ReglesOptionelles.init();
RdDUtility.init();
RdDDice.init();
RdDCommands.init();
RdDCombat.init();
RdDCombatManager.init();
RdDTokenHud.init();
RdDActor.init();
RddCompendiumOrganiser.init();
EffetsDraconiques.init()
TMRUtility.init();
RdDHotbar.initDropbar();
RdDPossession.init();
TMRRencontres.init();
Environnement.init();
Hooks.once('ready', () => this.onReady());
}
async onReady() {
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
// CSS patch for v9
if (game.version) {
let sidebar = document.getElementById("sidebar");
sidebar.style.width = "min-content";
}
if (Misc.isUniqueConnectedGM()) {
new Migrations().migrate();
}
StatusEffects.onReady();
RdDHerbes.initializeHerbes();
RdDDice.onReady();
/* -------------------------------------------- */
/* Affiche/Init le calendrier */
let calendrier = new RdDCalendrier();
let templatePath = "systems/foundryvtt-reve-de-dragon/templates/calendar-template.html";
let templateData = {};
renderTemplate(templatePath, templateData).then(html => {
calendrier.render(true);
});
game.system.rdd.calendrier = calendrier; // Reference;
// Avertissement si joueur sans personnage
if (!game.user.isGM && game.user.character == undefined) {
ui.notifications.info("Attention ! Vous n'êtes connecté à aucun personnage !");
ChatMessage.create({
content: "<b>ATTENTION</b> Le joueur " + game.user.name + " n'est connecté à aucun personnage !",
user: game.user.id
});
}
if (Misc.isUniqueConnectedGM()) {
this.messageDeBienvenue();
this.registerUsageCount(SYSTEM_RDD);
}
}
/* -------------------------------------------- */
messageDeBienvenue() {
if (game.user.isGM) {
ChatUtility.removeChatMessageContaining('<div id="message-bienvenue-rdd">');
ChatMessage.create({
user: game.user.id,
content: `<div id="message-bienvenue-rdd"><span class="rdd-roll-part">Bienvenue dans le Rêve des Dragons !</span>
<br>Vous trouverez quelques informations pour démarrer dans ce document : @Compendium[foundryvtt-reve-de-dragon.rappel-des-regles.7uGrUHGdPu0EmIu2]{Documentation MJ/Joueurs} <br>Vous trouverez quelques informations pour démarrer dans ce document : @Compendium[foundryvtt-reve-de-dragon.rappel-des-regles.7uGrUHGdPu0EmIu2]{Documentation MJ/Joueurs}
<br>La commande <code>/aide</code> dans le chat permet de voir les commandes spécifiques à Rêve de Dragon.</div> <br>La commande <code>/aide</code> dans le chat permet de voir les commandes spécifiques à Rêve de Dragon.</div>
` }); ` });
}
}
/* -------------------------------------------- */
// Register world usage statistics
function registerUsageCount(registerKey) {
if (game.user.isGM) {
game.settings.register("world", "world-key", {
name: "Unique world key",
scope: "world",
config: false,
default: "NONE",
type: String
});
let worldKey = game.settings.get("world", "world-key")
if (worldKey == undefined || worldKey == "") {
worldKey = randomID(32)
game.settings.set("world", "world-key", worldKey)
} }
let regURL = `https://www.uberwald.me/fvtt_appcount/count.php?name="${registerKey}"&worldKey="${worldKey}"&version="${game.release.generation}.${game.release.build}"&system="${game.system.id}"&systemversion="${game.system.version}"`
$.ajax(regURL)
/* -------------------------------------------- */
}
}
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
Hooks.once("ready", async function () {
await migrationPngWebp_1_5_34()
if (Misc.isUniqueConnectedGM()) {
new Migrations().migrate();
} }
StatusEffects.onReady();
RdDHerbes.initializeHerbes();
RdDDice.onReady();
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Affiche/Init le calendrier */ // Register world usage statistics
let calendrier = new RdDCalendrier(); async registerUsageCount(registerKey) {
let templatePath = "systems/foundryvtt-reve-de-dragon/templates/calendar-template.html"; if (game.user.isGM) {
let templateData = {}; game.settings.register("world", "world-key", {
renderTemplate(templatePath, templateData).then(html => { name: "Unique world key",
calendrier.render(true); scope: "world",
}); config: false,
game.system.rdd.calendrier = calendrier; // Reference; default: "NONE",
type: String
});
// Avertissement si joueur sans personnage let worldKey = game.settings.get("world", "world-key")
if (!game.user.isGM && game.user.character == undefined) { if (worldKey == undefined || worldKey == "") {
ui.notifications.info("Attention ! Vous n'êtes connecté à aucun personnage !"); worldKey = randomID(32)
ChatMessage.create({ game.settings.set("world", "world-key", worldKey)
content: "<b>ATTENTION</b> Le joueur " + game.user.name + " n'est connecté à aucun personnage !",
user: game.user.id
});
}
if (Misc.isUniqueConnectedGM()) {
messageDeBienvenue();
registerUsageCount(SYSTEM_RDD);
}
});
async function migrationPngWebp_1_5_34() {
if (!game.settings.get(SYSTEM_RDD, "migration-png-webp-1.5.34")) {
const regexOldPngJpg = /(systems\/foundryvtt-reve-de-dragon\/icons\/.*)\.(png|jpg)/;
const replaceWithWebp = '$1.webp';
function convertImgToWebp(img) {
return img.replace(regexOldPngJpg, replaceWithWebp);
}
function prepareDocumentsImgUpdate(documents) {
return documents.filter(it => it.img && it.img.match(regexOldPngJpg))
.map(it => {
return { _id: it.id, img: convertImgToWebp(it.img) }
});
}
const itemsUpdates = prepareDocumentsImgUpdate(game.items);
const actorsUpdates = prepareDocumentsImgUpdate(game.actors);
//Migrate system png to webp
await Item.updateDocuments(itemsUpdates);
await Actor.updateDocuments(actorsUpdates);
game.actors.forEach(actor => {
if (actor.token?.img && actor.token.img.match(regexOldPngJpg)) {
actor.update({ "token.img": convertImgToWebp(actor.token.img) });
} }
const actorItemsToUpdate = prepareDocumentsImgUpdate(actor.items); let regURL = `https://www.uberwald.me/fvtt_appcount/count.php?name="${registerKey}"&worldKey="${worldKey}"&version="${game.release.generation}.${game.release.build}"&system="${game.system.id}"&systemversion="${game.system.version}"`
actor.updateEmbeddedDocuments('Item', actorItemsToUpdate); $.ajax(regURL)
}); /* -------------------------------------------- */
game.settings.set(SYSTEM_RDD, "migration-png-webp-1.5.34", true) }
}
// CSS patch for v9
if (game.version) {
let sidebar = document.getElementById("sidebar");
sidebar.style.width = "min-content";
} }
} }
/* -------------------------------------------- */ SystemReveDeDragon.start();
/* Dice-so-nice ready */
/* -------------------------------------------- */
Hooks.once('diceSoNiceReady', (dice3d) => RdDDice.diceSoNiceReady(dice3d));

View File

@@ -203,6 +203,7 @@ export class RdDUtility {
'systems/foundryvtt-reve-de-dragon/templates/item-signedraconique-sheet.html', 'systems/foundryvtt-reve-de-dragon/templates/item-signedraconique-sheet.html',
'systems/foundryvtt-reve-de-dragon/templates/item-possession-sheet.html', 'systems/foundryvtt-reve-de-dragon/templates/item-possession-sheet.html',
'systems/foundryvtt-reve-de-dragon/templates/item-extraitpoetique-sheet.html', 'systems/foundryvtt-reve-de-dragon/templates/item-extraitpoetique-sheet.html',
'systems/foundryvtt-reve-de-dragon/templates/item-service-sheet.html',
// partial enums // partial enums
'systems/foundryvtt-reve-de-dragon/templates/enum-caracteristiques.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-caracteristiques.html',
'systems/foundryvtt-reve-de-dragon/templates/enum-base-competence.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-base-competence.html',
@@ -431,6 +432,7 @@ export class RdDUtility {
formData.oeuvres = this.arrayOrEmpty(itemTypes['oeuvre']); formData.oeuvres = this.arrayOrEmpty(itemTypes['oeuvre']);
formData.jeux = this.arrayOrEmpty(itemTypes['jeu']); formData.jeux = this.arrayOrEmpty(itemTypes['jeu']);
formData.services = this.arrayOrEmpty(itemTypes['service']);
formData.recettescuisine = this.arrayOrEmpty(itemTypes['recettecuisine']); formData.recettescuisine = this.arrayOrEmpty(itemTypes['recettecuisine']);
formData.recettesAlchimiques = this.arrayOrEmpty(itemTypes['recettealchimique']); formData.recettesAlchimiques = this.arrayOrEmpty(itemTypes['recettealchimique']);
formData.maladies = this.arrayOrEmpty(itemTypes['maladie']); formData.maladies = this.arrayOrEmpty(itemTypes['maladie']);
@@ -814,7 +816,7 @@ export class RdDUtility {
// gestion bouton tchat Acheter // gestion bouton tchat Acheter
html.on("click", '.button-acheter', event => { html.on("click", '.button-acheter', event => {
const venteData = DialogItemAchat.venteData(event.currentTarget); const venteData = DialogItemAchat.preparerAchat(event.currentTarget);
if (venteData) { if (venteData) {
DialogItemAchat.onAcheter(venteData); DialogItemAchat.onAcheter(venteData);
} }
@@ -830,6 +832,10 @@ export class RdDUtility {
ChatUtility.removeChatMessageId(RdDUtility.findChatMessageId(event.currentTarget)); ChatUtility.removeChatMessageId(RdDUtility.findChatMessageId(event.currentTarget));
} }
}); });
html.on("click", '.rdd-world-content-link', async event => {
const itemId = html.find(event.currentTarget)?.data("id");
game.items.get(itemId)?.sheet.render(true)
});
} }
static findChatMessageId(current) { static findChatMessageId(current) {
@@ -947,7 +953,7 @@ export class RdDUtility {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static async confirmerSuppressionItem(sheet, item, htmlToDelete) { static async confirmActorItemDelete(sheet, item, htmlToDelete) {
const itemId = item.id; const itemId = item.id;
const confirmationSuppression = { const confirmationSuppression = {
settingConfirmer: "confirmation-supprimer-" + item.getItemGroup(), settingConfirmer: "confirmation-supprimer-" + item.getItemGroup(),

View File

@@ -87,6 +87,11 @@ export class SystemCompendiums extends FormApplication {
return items; return items;
} }
static async loadDocument(document) {
const pack = game.packs.get(document.pack);
return await pack.getDocument(document.id ?? document._id);
}
static async getItems(compendium, itemType = undefined) { static async getItems(compendium, itemType = undefined) {
const items = await SystemCompendiums.getPackContent(compendium, 'Item'); const items = await SystemCompendiums.getPackContent(compendium, 'Item');
return (itemType ? items.filter(it => it.type == itemType) : items); return (itemType ? items.filter(it => it.type == itemType) : items);

View File

@@ -599,7 +599,7 @@ input:is(.blessure-premiers_soins, .blessure-soins_complets) {
.editor { .editor {
border: 2; border: 2;
height: fit-content; height: fit-content;
min-height: 8rem; min-height: 5rem;
padding: 0 3px; padding: 0 3px;
} }
@@ -611,7 +611,7 @@ input:is(.blessure-premiers_soins, .blessure-soins_complets) {
.small-editor { .small-editor {
border: 2; border: 2;
min-height: 4rem; min-height: 2rem;
padding: 0 3px; padding: 0 3px;
} }
@@ -771,11 +771,41 @@ div.placeholder-resolution span.table-proba-reussite{
input[type="date"], input[type="date"],
input[type="time"]) { input[type="time"]) {
color: rgba(255, 255, 255, 0.75); color: rgba(255, 255, 255, 0.75);
background: rgba(255, 255, 255, 0.05); background: rgba(255, 255, 255, 0.1);
border: 0 none; border: 0 none;
margin-bottom: 0.2rem; margin-bottom: 0.2rem;
} }
input[type="number"] {
text-align: right;
padding-right: 0.5rem;
max-width: 7rem;
}
select:is(.number-x,.number-x2,.number-x3,.number-x4,.number-x5) {
padding-right: 0.2rem;
text-align: center;
}
input:is(.number,.number-x,.number-x2,.number-x3,.number-x4,.number-x5) {
padding-right: 0.2rem;
text-align: right;
}
:is(input,select).number-x {
max-width: 1.4rem;
}
:is(input,select).number-x2 {
max-width: 2.4rem;
}
:is(input,select).number-x3 {
max-width: 3.4rem;
}
:is(input,select).number-x4 {
max-width: 4.4rem;
}
:is(input,select).number-x5 {
max-width: 4.4rem;
}
form.rdddialogchrono input[type=datetime-local] { form.rdddialogchrono input[type=datetime-local] {
min-width: 20px; min-width: 20px;
padding: 0; padding: 0;
@@ -818,7 +848,21 @@ section.sheet-body:after {
display: block; display: block;
clear: both; clear: both;
} }
a.rdd-world-content-link i {
color: var(--color-text-dark-inactive);
margin-right: 0.25em;
}
a.rdd-world-content-link {
background: hsla(280, 50%, 50%, 0.1);
padding: 1px 4px;
border: 1px solid var(--color-border-dark-tertiary);
border-radius: 2px;
white-space: nowrap;
word-break: break-all;
}
a.content-link {
background: hsla(45, 100%, 80%, 0.2);
}
li label.compteur { li label.compteur {
display: inline-block; display: inline-block;

View File

@@ -1,8 +1,8 @@
{ {
"id": "foundryvtt-reve-de-dragon", "id": "foundryvtt-reve-de-dragon",
"title": "Rêve de Dragon", "title": "Rêve de Dragon",
"version": "10.3.17", "version": "10.4.0",
"download": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/archive/foundryvtt-reve-de-dragon-10.3.17.zip", "download": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/archive/foundryvtt-reve-de-dragon-10.4.0.zip",
"manifest": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/raw/v10/system.json", "manifest": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/raw/v10/system.json",
"compatibility": { "compatibility": {
"minimum": "10", "minimum": "10",

View File

@@ -562,6 +562,7 @@
"recettealchimique", "musique", "chant", "danse", "jeu", "recettecuisine", "oeuvre", "recettealchimique", "musique", "chant", "danse", "jeu", "recettecuisine", "oeuvre",
"objet", "arme", "armure", "conteneur", "herbe", "ingredient", "faune", "livre", "potion", "munition", "objet", "arme", "armure", "conteneur", "herbe", "ingredient", "faune", "livre", "potion", "munition",
"monnaie", "nourritureboisson", "gemme", "monnaie", "nourritureboisson", "gemme",
"service",
"meditation", "rencontre", "queue", "ombre", "souffle", "tete", "casetmr", "signedraconique", "sort", "sortreserve", "meditation", "rencontre", "queue", "ombre", "souffle", "tete", "casetmr", "signedraconique", "sort", "sortreserve",
"nombreastral", "tache", "maladie", "poison", "possession", "nombreastral", "tache", "maladie", "poison", "possession",
"tarot", "extraitpoetique" "tarot", "extraitpoetique"
@@ -741,6 +742,12 @@
"prpermanent": false, "prpermanent": false,
"prdate": 0 "prdate": 0
}, },
"service": {
"templates": [ "description"],
"illimite": false,
"items": [],
"services": []
},
"musique": { "musique": {
"templates": [ "description" ], "templates": [ "description" ],
"niveau": "", "niveau": "",

View File

@@ -60,6 +60,7 @@
{{!-- Equipment Tab --}} {{!-- Equipment Tab --}}
<div class="tab items" data-group="primary" data-tab="items"> <div class="tab items" data-group="primary" data-tab="items">
{{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire-monnaie.html"}}
{{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire.html"}} {{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire.html"}}
</div> </div>

View File

@@ -119,7 +119,7 @@
{{!-- Equipment Tab --}} {{!-- Equipment Tab --}}
<div class="tab items" data-group="primary" data-tab="items"> <div class="tab items" data-group="primary" data-tab="items">
{{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire-monnaie.html"}}
{{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire.html"}} {{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire.html"}}
{{> "systems/foundryvtt-reve-de-dragon/templates/actor/liens-animaux.html"}} {{> "systems/foundryvtt-reve-de-dragon/templates/actor/liens-animaux.html"}}
{{> "systems/foundryvtt-reve-de-dragon/templates/actor/liens-suivants.html"}} {{> "systems/foundryvtt-reve-de-dragon/templates/actor/liens-suivants.html"}}

View File

@@ -91,6 +91,7 @@
{{!-- Equipment Tab --}} {{!-- Equipment Tab --}}
<div class="tab items" data-group="primary" data-tab="items"> <div class="tab items" data-group="primary" data-tab="items">
{{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire-monnaie.html"}}
{{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire.html"}} {{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire.html"}}
</div> </div>

View File

@@ -1,10 +1,14 @@
{{#if system.attributs.hautrevant.value}} {{#if system.attributs.hautrevant.value}}
<div class="tmr-buttons"> <div class="tmr-buttons">
<span class="monte-tmr"> <span class="monte-tmr">
<a title="Montée dans les Terres M&eacute;dianes !" {{#if hautreve.isDemiReve}}disabled{{/if}}><img class="button-img" src="systems/foundryvtt-reve-de-dragon/styles/img/ui/icon-tmr-normal.svg" alt="Montée dans les Terres M&eacute;dianes !"/></a> <a title="Montée dans les Terres M&eacute;dianes !" {{#if hautreve.isDemiReve}}disabled{{/if}}>
<img class="button-img" src="systems/foundryvtt-reve-de-dragon/styles/img/ui/icon-tmr-normal.svg" alt="Montée dans les Terres M&eacute;dianes !"/>
</a>
</span> </span>
<span class="monte-tmr-rapide"> <span class="monte-tmr-rapide">
<a title="Montée accélérée dans les Terres M&eacute;dianes !" {{#if hautreve.isDemiReve}}disabled{{/if}}><img class="button-img" src="systems/foundryvtt-reve-de-dragon/styles/img/ui/icon-tmr-rapide.svg" alt="Montée accélérée dans les Terres M&eacute;dianes !"/></a> <a title="Montée accélérée dans les Terres M&eacute;dianes !" {{#if hautreve.isDemiReve}}disabled{{/if}}>
<img class="button-img" src="systems/foundryvtt-reve-de-dragon/styles/img/ui/icon-tmr-rapide.svg" alt="Montée accélérée dans les Terres M&eacute;dianes !"/>
</a>
</span> </span>
<span class="visu-tmr"> <span class="visu-tmr">
<a title="Regarder les Terres M&eacute;dianes"><img class="button-img" src="systems/foundryvtt-reve-de-dragon/styles/img/ui/icon-tmr-view.svg" alt="Regarder les Terres M&eacute;dianes"/></a> <a title="Regarder les Terres M&eacute;dianes"><img class="button-img" src="systems/foundryvtt-reve-de-dragon/styles/img/ui/icon-tmr-view.svg" alt="Regarder les Terres M&eacute;dianes"/></a>

View File

@@ -1,4 +1,4 @@
<span class="item-name"><h4>Argent et Monnaies (fortune: {{calc.fortune}} sols)</h4></span> <span class="item-name"><h4>Argent et Monnaies (fortune: {{calc.fortune.sols}} sols {{calc.fortune.deniers}} deniers)</h4></span>
<ul class="item-list alterne-list"> <ul class="item-list alterne-list">
{{#each monnaie as |piece id|}} {{#each monnaie as |piece id|}}
<li class="item flexrow list-item" data-item-id="{{piece._id}}"> <li class="item flexrow list-item" data-item-id="{{piece._id}}">

View File

@@ -1,5 +1,3 @@
{{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire-monnaie.html" monnaie=monnaie}}
<h4>Equipement</h4> <h4>Equipement</h4>
<span class="item-name"> <span class="item-name">
<a class="chat-card-button creer-un-objet">Nouvel objet</a> <a class="chat-card-button creer-un-objet">Nouvel objet</a>

View File

@@ -1 +1,6 @@
<a class="content-link" draggable="true" data-pack="{{pack}}" data-uuid="Compendium.{{pack}}.{{id}}" data-id="{{id}}"><i class="fas fa-suitcase"></i>{{name}}</a> {{#if pack}}
{{!-- draggable="true" --}}
<a class="content-link" data-pack="{{pack}}" data-uuid="Compendium.{{pack}}.{{id}}" data-id="{{id}}"><i class="fas fa-suitcase"></i>{{name}}</a>
{{else}}
<a class="rdd-world-content-link" data-id="{{id}}"><i class="fas fa-suitcase"></i>{{name}}</a>
{{/if}}

View File

@@ -2,15 +2,17 @@
<div> <div>
<div class="flexrow flex-center"> <div class="flexrow flex-center">
<div> <div>
{{#if vendeur}} {{#if service}}
<img class="chat-icon" src="{{service.img}}" title="{{service.name}}" alt="{{service.name}}" />
{{else if vendeur}}
<img class="chat-icon" src="{{vendeur.img}}" title="{{vendeur.name}}" alt="{{vendeur.name}}" /> <img class="chat-icon" src="{{vendeur.img}}" title="{{vendeur.name}}" alt="{{vendeur.name}}" />
{{else}} {{else}}
<img class="chat-icon" src="systems/foundryvtt-reve-de-dragon/styles/img/ui/icon_echoppe.webp" title="Un commerçant" alt="Vendeur MJ" /> <img class="chat-icon" src="systems/foundryvtt-reve-de-dragon/styles/img/ui/icon_echoppe.webp" title="Un commerçant" alt="Vendeur MJ" />
{{/if}} {{/if}}
</div> </div>
<div><i class="fas fa-sign-out-alt"></i></div> <div><i class="fa-solid fa-arrow-right-long"></i></div>
<div><img class="chat-icon" src="{{item.img}}" title="{{item.name}}" alt="{{item.name}}" /></div> <div><img class="chat-icon" src="{{item.img}}" title="{{item.name}}" alt="{{item.name}}" /></div>
<div><i class="fas fa-sign-in-alt"></i></div> <div><i class="fa-solid fa-arrow-right-long"></i></div>
<div> <div>
{{#if acheteur}} {{#if acheteur}}
<img class="chat-icon" src="{{acheteur.img}}" title="{{acheteur.name}}" alt="{{acheteur.name}}" /> <img class="chat-icon" src="{{acheteur.img}}" title="{{acheteur.name}}" alt="{{acheteur.name}}" />
@@ -40,8 +42,10 @@
{{else}}Quantité{{/if}} {{else}}Quantité{{/if}}
</label> </label>
<div class="flexrow"> <div class="flexrow">
<input name="nombreLots" class="nombreLots flex-shrink" type="number" min="1" max="{{quantiteNbLots}}" <input name="nombreLots" class="nombreLots flex-shrink number-x2" type="number" min="1"
value="{{choix.nombreLots}}" data-dtype="Number" /> {{#unless quantiteIllimite}} max="{{quantiteNbLots}}" {{/unless}}
value="{{choix.nombreLots}}"
data-dtype="Number" />
</div> </div>
</div> </div>
@@ -50,8 +54,7 @@
{{#if item.system.sust}} {{#if item.system.sust}}
<p>Cette {{#if item.system.boisson}}boisson{{else}}nourriture{{/if}} vous apportera <p>Cette {{#if item.system.boisson}}boisson{{else}}nourriture{{/if}} vous apportera
<span class="total-sust">{{totalSust}}</span> <span class="total-sust">{{totalSust}}</span> de sustantation.</p>
de sustantation.</p>
{{/if}} {{/if}}
{{#if item.system.boisson}} {{#if item.system.boisson}}
<p> <p>
@@ -86,9 +89,9 @@
</div> </div>
<div class="flexrow flex-group-left"> <div class="flexrow flex-group-left">
<label>Prix total</label> <label>Prix total</label>
<span class="flexrow"> <span>
<span class="prixTotal">{{prixTotal}}</span> <span class="prixTotal">{{prixTotal}}</span>
<span>Sols</span> Sols
</span> </span>
</div> </div>
{{/if}} {{/if}}

View File

@@ -3,7 +3,7 @@
<h4>{{item.name}}</h4> <h4>{{item.name}}</h4>
<div class="flexrow"> <div class="flexrow">
<label class="flex-grow">Quantité à {{#if item.system.boisson}}boire{{else}}manger{{/if}}</label> <label class="flex-grow">Quantité à {{#if item.system.boisson}}boire{{else}}manger{{/if}}</label>
<input class="attribute-value consommer-doses flex-shrink" type="number" name="doses" value="{{choix.doses}}" <input class="attribute-value consommer-doses flex-shrink number-x2" type="number" name="doses" value="{{choix.doses}}"
min="0" max="{{item.system.quantite}}" data-dtype="Number" /> min="0" max="{{item.system.quantite}}" data-dtype="Number" />
</div> </div>
{{#if item.system.sust}} {{#if item.system.sust}}

View File

@@ -4,7 +4,7 @@
<label>Quantité totale : {{item.system.quantite}}</label> <label>Quantité totale : {{item.system.quantite}}</label>
<div class="flexrow"> <div class="flexrow">
<label class="flex-grow">Quantité à séparer</label> <label class="flex-grow">Quantité à séparer</label>
<input class="attribute-value choix-quantite flex-shrink" type="number" name="choix.quantite" value="{{choix.quantite}}" <input class="attribute-value choix-quantite flex-shrink number-x2" type="number" name="choix.quantite" value="{{choix.quantite}}"
min="1" max="{{choix.max}}" data-dtype="Number" /> min="1" max="{{choix.max}}" data-dtype="Number" />
</div> </div>
</form> </form>

View File

@@ -16,13 +16,13 @@
quantiteIllimite}}checked{{/if}} /> quantiteIllimite}}checked{{/if}} />
<label class="label-quantiteIllimite flex-shrink">Illimités</label> <label class="label-quantiteIllimite flex-shrink">Illimités</label>
{{/unless}} {{/unless}}
<input name="quantiteNbLots" class="quantiteNbLots flex-shrink" type="number" min="1" <input name="quantiteNbLots" class="quantiteNbLots flex-shrink number-x2" type="number" min="1"
max="{{quantiteMaxLots}}" value="{{quantiteNbLots}}" data-dtype="Number" /> max="{{quantiteMaxLots}}" value="{{quantiteNbLots}}" data-dtype="Number" />
</div> </div>
</div> </div>
<div class="flexrow flex-group-left"> <div class="flexrow flex-group-left">
<label for="tailleLot">Taille d'un lot</label> <label for="tailleLot">Taille d'un lot</label>
<input name="tailleLot" class="tailleLot flex-shrink" type="number" min="1" <input name="tailleLot" class="tailleLot flex-shrink number-x2" type="number" min="1"
max="{{quantiteMax}}" value="{{tailleLot}}" data-dtype="Number" /> max="{{quantiteMax}}" value="{{tailleLot}}" data-dtype="Number" />
</div> </div>
<div class="flexrow flex-group-left"> <div class="flexrow flex-group-left">

View File

@@ -0,0 +1,103 @@
<form class="{{cssClass}}" autocomplete="off">
{{>"systems/foundryvtt-reve-de-dragon/templates/header-item.html"}}
<section class="sheet-body">
<div class="flexcol form-group small-editor">
{{editor description target="system.description" button=true owner=owner editable=(or isGM isOwner) engine="prosemirror"}}
</div>
{{!--
<div class="flexcol">
<ul class="item-list alterne-list">
<li class="item flexrow list-item">
<label class="flex-grow">Service</label>
<label>Moral</label>
<label>Qualité</label>
<label>Prix (sols)</label>
<label>
{{#unless disabled}}
<a class="service-add"><i class="fas fa-plus-circle"></i></a>
{{/unless}}
</label>
</li>
{{#each system.services as |service key|}}
<li class="item flexrow list-item" data-key="{{key}}">
<input {{@root.disabled}} type="text" name="services[{{key}}].name" value="{{service.name}}" data-dtype="String" />
<input {{@root.disabled}} type="checkbox" name="services[{{key}}].system.moral" {{#if service.system.moral}}checked{{/if}} />
<input {{@root.disabled}} type="number" name="services[{{key}}].system.qualite" value="{{service.system.qualite}}" data-dtype="Number" min="-10" max="10"/>
<input {{@root.disabled}} type="number" class="input-prix" name="services[{{key}}].system.cout" value="{{numberFormat service.system.cout decimals=2 sign=false}}" data-dtype="Number" min="0" />
<div class="item-controls">
<a class="service-acheter" title="Acheter"><i class="fa-sharp fa-solid fa-coins"></i></a>
{{#unless @root.disabled}}
<a class="service-vendre" title="Proposer"><i class="fas fa-comments-dollar"></i></a>
<a class="service-delete" title="Supprimer"><i class="fas fa-trash"></i></a>
{{/unless}}
</div>
</li>
{{/each}}
</ul>
</div>
<br>
--}}
<div class="form-group">
<input {{@root.disabled}} class="attribute-value" type="checkbox" name="system.illimite" {{#if system.illimite}}checked{{/if}}/>
<span for="system.illimite">Quantité en vente illimitée</span>
</div>
<div class="flexcol">
<ul class="item-list alterne-list">
<li class="item flexrow list-item">
<label class="flex-grow">A vendre</label>
{{#unless system.illimite}}
<label>Quantite</label>
{{/unless}}
<label>Prix (sols)</label>
<div class="item-controls">
{{#unless disabled}}
<a class="sub-item-info-add" title="Utiliser le drag&amp;drop pour ajouter un objet dans la liste">
<i class="fa-solid fa-circle-info"></i>
</a>
{{/unless}}
</div>
</li>
{{#each system.items as |item key|}}
<li class="item flexrow list-item service-item" data-item-id="{{item.id}}" data-pack="{{item.pack}}" data-key="{{key}}">
<label class="flex-grow">
{{#if item.img}}<img class="sheet-competence-img" src="{{item.img}}" title="{{item.name}}"/>{{/if}}
{{> 'systems/foundryvtt-reve-de-dragon/templates/common/compendium-link.hbs'
pack=item.pack id=item.id name=item.name}}
</label>
{{#unless @root.system.illimite}}
<span class="flexrow">
{{#unless @root.disabled}}
<a class="sub-item-quantite-moins"><i class="fas fa-minus-square"></i></a>
{{/unless}}
<input {{@root.disabled}} type="number" class="sub-item-quantite" name="items[{{key}}].system.quantite" value="{{item.system.quantite}}" data-dtype="Number" />
{{#unless @root.disabled}}
<a class="sub-item-quantite-plus"><i class="fas fa-plus-square"></i></a>
{{/unless}}
</span>
{{/unless}}
<span class="flexrow">
<input {{@root.disabled}} type="number" class="input-prix number-x3 sub-item-cout" name="items[{{key}}].system.cout" value="{{numberFormat item.system.cout decimals=2 sign=false}}" data-dtype="Number" />
</span>
<div class="item-controls">
<a class="sub-item-acheter" title="Acheter"><i class="fa-regular fa-coins"></i></a>
{{#unless @root.disabled}}
<a class="sub-item-vendre" title="Vendre"><i class="fas fa-comments-dollar"></i></a>
<a class="sub-item-delete" title="Supprimer"><i class="fas fa-trash"></i></a>
{{/unless}}
</div>
</li>
{{/each}}
</ul>
</div>
{{#if isGM}}
<br>
<div class="flexcol">
<span><label>Description (MJ seulement): </label></span>
<div class="form-group medium-editor">
{{editor descriptionmj target="system.descriptionmj" button=true owner=owner editable=true engine="prosemirror"}}
</div>
</div>
{{/if}}
</section>
</form>

View File

@@ -1,25 +1,25 @@
<div class="form-group"> <div class="form-group">
<label for="system.qualite">Qualité</label> <label for="system.qualite">Qualité</label>
<input class="attribute-value" type="text" name="system.qualite" value="{{system.qualite}}" data-dtype="Number" <input class="attribute-value number-x3" type="number" name="system.qualite" value="{{system.qualite}}" data-dtype="Number"
{{#unless (isFieldInventaireModifiable type 'qualite')}}disabled{{/unless}}/> {{#unless (isFieldInventaireModifiable type 'qualite')}}disabled{{/unless}}/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="system.encombrement">Encombrement</label> <label for="system.encombrement">Encombrement</label>
<input class="attribute-value" type="text" name="system.encombrement" value="{{system.encombrement}}" data-dtype="Number" <input class="attribute-value number-x3" type="number" name="system.encombrement" value="{{system.encombrement}}" data-dtype="Number"
{{#unless (isFieldInventaireModifiable type 'encombrement')}}disabled{{/unless}}/> {{#unless (isFieldInventaireModifiable type 'encombrement')}}disabled{{/unless}}/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="system.quantite">Quantité {{uniteQuantite id actorId}}</label> <label for="system.quantite">Quantité {{uniteQuantite id actorId}}</label>
<input class="attribute-value" type="text" name="system.quantite" value="{{system.quantite}}" data-dtype="Number" <input class="attribute-value number-x3" type="number" name="system.quantite" value="{{system.quantite}}" data-dtype="Number"
{{#unless (isFieldInventaireModifiable type 'quantite')}}disabled{{/unless}}/> {{#unless (isFieldInventaireModifiable type 'quantite')}}disabled{{/unless}}/>
</div> </div>
<div class="form-group item-cout"> <div class="form-group item-cout">
<label for="system.cout">Prix (sols)</label> <label for="system.cout">Prix (sols)</label>
{{#if (or (ne type 'monnaie') (gt system.cout 0))}} {{#if (or (ne type 'monnaie') (gt system.cout 0))}}
<input class="attribute-value" type="text" name="system.cout" value="{{system.cout}}" data-dtype="Number" <input class="input-prix attribute-value number-x3" type="number" name="system.cout" value="{{numberFormat system.cout decimals=2 sign=false}}" data-dtype="Number"
{{#unless (isFieldInventaireModifiable type 'cout')}}disabled{{/unless}}/> {{#unless (isFieldInventaireModifiable type 'cout')}}disabled{{/unless}}/>
{{else}} {{else}}
<input class="attribute-value field-error" type="text" name="system.cout" value="{{system.cout}}" data-dtype="Number" <input class="input-prix attribute-value field-error number-x3" type="number" name="system.cout" value="{{numberFormat system.cout decimals=2 sign=false}}" data-dtype="Number"
{{#unless (isFieldInventaireModifiable type 'cout')}}disabled{{/unless}}/> {{#unless (isFieldInventaireModifiable type 'cout')}}disabled{{/unless}}/>
{{/if}} {{/if}}
</div> </div>

View File

@@ -0,0 +1,8 @@
<div class="post-item" data-transfer="{{transfer}}">
{{#if img}}
<img class="chat-icon" src="{{img}}" title="{{name}}" />
{{/if}}
<p>{{> 'systems/foundryvtt-reve-de-dragon/templates/common/compendium-link.hbs' pack=pack id=_id name=name}}</p>
<p class="card-content">{{{system.description}}}</p>
</div>