Compare commits

...

512 Commits

Author SHA1 Message Date
e226af5ac5 Merge pull request 'v11.0.14' (#665) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #665
2023-07-30 09:25:10 +02:00
d8be37a2ec v11.0.14 2023-07-29 23:47:08 +02:00
ba2d9de7b6 Version 11.0.13
Les pincettes de Khrachtchoum le Problémeux
- Correction du calcul de la place restante lors de l'ajout dans un conteneur
2023-07-29 23:44:56 +02:00
6b8fb3267a Fix: calcul de la place dans un conteneur
Correction de l'ajout dans un conteneur, en faisant un calcul arrondi
pour éviter les problèmes de virgule flotante
2023-07-29 23:42:22 +02:00
05d6f64a31 Merge pull request 'v11.0.13 - La multiplication de l'eau de Khrachtchoum le Problémeux' (#664) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #664
2023-07-28 10:11:26 +02:00
c0570e58b4 Version 11.0.13 2023-07-27 19:21:48 +02:00
8389d578bc Fix: achat d'objet illimités
Lors de l'achat à un commerce avec une quantité illimité, on était
tout de même limité à la quantité de l'objet, même si elle n'était
pas diminuée
2023-07-27 19:17:57 +02:00
f05ef79b97 Merge pull request 'v11.0.12 - Les poids de la mesure de Khrachtchoum le Problémeux' (#663) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #663
2023-07-14 00:36:37 +02:00
d2a8bfae4f v11.0.12 - Les poids de la mesure
- Correction des malus de surencombrement
- Le malus armure est correctement affiché dans l'onglet des
  caractéristiques
2023-07-13 01:02:03 +02:00
d54834fa71 Amélioration des messages d'art 2023-07-13 01:02:03 +02:00
c898bf5212 Merge pull request 'Encombrement et malus armure' (#661) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #661
2023-07-11 14:46:14 +02:00
a118dc7334 Correction sur-Encombrement v2
Le malus appliqué est maintenant correct.

Avant, le malus pouvait avoir une virgule, et donc être arrondi
dans le malus vraiment appliqué même si affiché correctement.
2023-07-11 02:53:34 +02:00
46401e5d63 Cleanup: simplification _computeFinalLevel
méthode supprimée dasn RdDRoll (car basée sur les ajustements),
pas utile ici non plus.
2023-07-11 02:36:39 +02:00
7eb1d9f838 Fix affichage Malus Armure
Le malus armure est correctement affiché dans l'onglet caractéristiques.

Il n'est plus stocké dans l'acteur mais calculé.
2023-07-11 02:36:39 +02:00
1d8f4ebb88 Merge pull request 'Fix: sélection de compendiums que pour le MJ' (#660) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #660
2023-07-09 11:09:28 +02:00
145a92f462 Fix: sélection de compendiums que pour le MJ 2023-07-07 00:47:05 +02:00
5148d02314 Correction sur arrondi sur-encombrement 2023-07-01 21:28:18 +02:00
000c89b11a Merge pull request 'V11.0.11 - Les bleus de Khrachtchoum le Problémeux' (#659) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #659
2023-06-25 09:48:17 +02:00
5d4a18aac5 Version 11.0.11 2023-06-24 23:59:25 +02:00
34b5df637f Notifier le joueur qu'il peut passer sa nuit
Lorsque le MJ configure la nuit des personnages, une notification est
envoyée aux joueurs pour leur dire de gérer leur nuit.
2023-06-24 23:50:05 +02:00
22572ca98c Support des armes non mortelles 2023-06-24 23:40:07 +02:00
a6a1c1009e Merge pull request 'v11.0.10 - Les Songes de Khrachtchoum le Problémeux' (#658) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #658
2023-06-24 09:22:24 +02:00
288cf9162c Version v11.0.10 2023-06-24 01:48:34 +02:00
4b1381e535 Decompte des heure en cas de réveil
Si le gardien gère la durée des nuits, en cas de rêve de dragon,
les heures restant à dormir sont mises à jour.
2023-06-24 01:41:12 +02:00
78336a3f45 Fix: affichage sommeil avec RdD première heure
Le message ne s'affichait pas en cas de rêve de dragon dès la
première récupération
2023-06-24 01:19:54 +02:00
3001ae44de Endurance limitée en cas d'insomnie 2023-06-24 01:19:54 +02:00
a43ceb3188 Fix: lire depuis un livre 2023-06-24 01:19:54 +02:00
9e95a44efd Fix: click pour se déplacer dans les TMR 2023-06-24 01:19:54 +02:00
179ea0c85b Fix: selection de TMR des signes draconiques 2023-06-24 01:19:53 +02:00
3821b5bea1 Merge pull request 'v11.0.9 - Les Souvenirs de Khrachtchoum le Problémeux' (#657) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #657
2023-06-21 10:40:17 +02:00
2739c650d9 Version 11.0.9 - Les Souvenirs de Khrachtchoum 2023-06-20 23:43:59 +02:00
b101d58bee Correction typo "optionnelle" 2023-06-20 23:43:58 +02:00
0e3d721fc7 Nouvelle incarnation de l'archétype 2023-06-20 23:35:38 +02:00
e1e45a83be Correction titre des fenêtres Item 2023-06-20 23:35:38 +02:00
3b14e54829 Mode de saisie d'archétype 2023-06-20 23:35:38 +02:00
615a3f1315 Organisation des règles optionelles 2023-06-20 03:05:48 +02:00
ca72d088cc Merge pull request 'v11 - report v10.7.20' (#656) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #656
2023-06-19 15:36:53 +02:00
ac89b948ca Mise à jour du système
- lien vers le change log
- organisation des compendiums par défaut
2023-06-16 23:33:44 +02:00
f22c4d2ec8 Fin d'empoignade
En cours de round en atteignant 2 points d'empoignade, on peut
uniquement entraîner au sol.

En fin de round, si la victime est immobilisée, on peut projeter, ou
faire perdre de l'endurance
2023-06-15 02:03:49 +02:00
ffd80394d3 Restreindre les actions d'empoignade
Seul le propriétaire du défenseur peut effecuer les contres
d'empoignade ou tenter de se libérer.

Seul le propriétaire de l'empoigneur peut tenter d'empêcher
la libération du défenseur, de projeter au sol, ou de faire perdre
de l'endurance.
2023-06-15 02:03:43 +02:00
0959d1b18e Correction malus de taille empoignade 2023-06-15 02:03:37 +02:00
24e58abfcc Fix empoignade
- les items d'empoignade sont ajoutés par le MJ
- les caractéristiques du défenseur sont utilisées pour la défense
- la difficulté d'attaque est imposée au défenseur
- les particulières sont en finesse (p133)
2023-06-15 02:03:30 +02:00
7d292c009d Update v11 branch 2023-06-14 08:31:14 +02:00
4bf075786c Merge pull request 'v11 report v10.7.19' (#654) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #654
2023-06-14 08:28:49 +02:00
a2a04ade4c Migration des compendiums 2023-06-14 02:12:32 +02:00
2db6895c76 Gestion des difficultés de Possession
- gestion de la difficulté imposée sur la défense
- gestion des particulières en attaque considérées en finesse
- utilisation du rêve actuel pour les personnages
2023-06-14 02:12:28 +02:00
e46f35ef92 Catégories des compétences de créatures
Les créatures peuvent avoir des compétences d'armes (lancer,
mêlée, tir, armes naturelles), de parade, de possession, et générales.

Les initiatives sont cohérentes. Les possessions sont en phase 10
d'initiative.
2023-06-14 02:12:22 +02:00
9e050d3e12 Fix commerce achat MJ sans personnage
Correction sur les achats: l'objet acheté vient forcément soit d'un
personnage-vendeur, soit des Items globaux.

Ne pas essayer d'acheter autrement car on aurait des données d'item
non structurées, et donc ça ne fonctionnerait pas.
2023-06-14 02:12:15 +02:00
81492f2857 Merge pull request 'v11.0.6 - Report v10.7.18' (#652) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #652
2023-06-08 06:51:57 +02:00
959e751e48 Correction rendu changelog en ligne 2023-06-08 01:47:04 +02:00
b6a124b57d v11.0.6 2023-06-08 01:44:17 +02:00
db2ca945a8 Fix: la date des blessures ne marchait plus
la liste des types (pour aider à la saisie) est une idée mitigée

- ça évite les fautes d'orthographe dans les constantes de types
Mais:
- on peut oublier un type
- si le type n'est pas défini, il est undefined
  (donc risques de regressions)

Saisie de tous les types du template.
2023-06-08 01:35:06 +02:00
667d764db1 Cherry pick v10 gixes 2023-06-05 20:19:42 +02:00
4273f5f48f Fix: validation encaissement MJ 2023-06-05 20:18:05 +02:00
6c4a8eb70f Correction de liens
Quelques liens pointaient sur les icones du bazaar de forge-vtt
2023-06-05 20:17:40 +02:00
eb029e8d66 Fix affichage des "objets" dans les commerces
La confusion entre les Item "objets" et le champ formData.objets
rendait les Item "objets" indisponibles (par exemple, les vêtements)
2023-06-05 20:17:19 +02:00
9ee14b3b56 Add .db again? 2023-06-04 22:17:02 +02:00
3203fae111 Add .db again? 2023-06-04 20:53:15 +02:00
755301275b Add .db again? 2023-06-04 20:51:49 +02:00
f6d5dc9d7c Add .db again? 2023-06-04 20:49:55 +02:00
5e9b47af1f Add .db again? 2023-06-04 20:47:47 +02:00
18dfb36a78 v11 release 2023-05-29 13:47:08 +02:00
febccd508f v11 release 2023-05-29 13:40:38 +02:00
b147f8937b v11 release 2023-05-29 13:33:31 +02:00
55a93b317a v10/v11 compat 2023-05-29 08:50:13 +02:00
02a630152d v10/v11 compat 2023-05-29 08:50:08 +02:00
ad8ac0de77 v10/v11 compat 2023-05-29 08:42:16 +02:00
dceb6f026f v10/v11 compat 2023-05-29 08:41:59 +02:00
ad37ee1b8e v10/v11 compat 2023-05-29 08:38:22 +02:00
4d40e2fe59 Merge pull request 'cherry-pick changements v10' (#647) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #647
2023-05-29 07:47:58 +02:00
9d7e57d9fb Changelog v11 2023-05-28 22:11:16 +02:00
717029e572 Tri des listes d'items 2023-05-28 22:11:16 +02:00
d6f8976298 Utilisation de la dateReel du calendrier 2023-05-28 22:11:16 +02:00
92e93cbbea Refonte du journal d'expérience
Reprise du journal d'expérience pour:
- afficher ancienne/nouvelle valeur
- la valeur du changement
- si c'est manuel / automatique
- identifier les dépenses de stress
- identifier les augmentations de compétences
- les changements des compteurs
2023-05-28 22:11:16 +02:00
994eaad9a9 Ajout des acteurs accordés aux entités 2023-05-28 22:11:16 +02:00
cfed7e2afa v10/v11 compat 2023-05-25 21:16:59 +02:00
436f9e6fa4 v10/v11 compat 2023-05-25 21:06:03 +02:00
18a50197cd v10/v11 compat 2023-05-25 21:05:43 +02:00
b6910612f8 v10/v11 compat 2023-05-25 21:04:27 +02:00
4ba1ec8a2e v10/v11 compat 2023-05-25 21:04:02 +02:00
75a0f7c322 v10/v11 compat 2023-05-25 20:40:59 +02:00
e2a9f55740 Initial commit for v11 support 2023-05-25 20:39:53 +02:00
1e0788cde8 Merge pull request 'v11' (#645) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #645
2023-05-25 20:37:01 +02:00
c10195f753 Version 10.7.13 2023-05-25 20:23:54 +02:00
4d7317b964 Gestion de l'armure
Correction de la détérioration d'une armure variable

Séparation du code d'armure dans l'Item RdDArmureItem
2023-05-25 20:20:43 +02:00
7b1fa009bb Utilisation de constantes pour les types
(sans effet)
2023-05-12 22:56:59 +02:00
71bb8a14e1 Fix sieste 2023-04-28 08:35:26 +02:00
5444bc7fd8 Merge pull request 'Changelog et fix mineur' (#643) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #643
2023-04-28 08:33:59 +02:00
debaf1da34 Changelog v10.6 & v10.7
Récupéré des messages postés
2023-04-27 14:20:35 +02:00
69e2ab9000 Fix: sélection de sieste pour le repos
Quand le MJ gère les changements de jours, la fenêtyre de repos ne
proposait pas les options dormir/chateau dormant, mais utilisait
l'option "dormir" par défaut
2023-04-27 14:20:35 +02:00
24218ed062 Changelog 2023-04-22 20:34:30 +02:00
da4e18dc3c Gestion de l'empoignade 2023-04-22 07:55:26 +02:00
154a9b7a37 Gestion de l'empoignade 2023-04-21 23:09:40 +02:00
fb8189606b Merge pull request 'v10: pinaillages mineurs' (#642) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #642
2023-04-21 23:08:10 +02:00
cfbfad27bd cleanup Empoignade 2023-04-21 22:30:21 +02:00
f541849306 Centralisation du message empoignade
Déplacement dans le module empoignade du message et de
la vérification d'empoignade en cours.

Le message est donc centralisé (possible de le modifier une fois pour
toutes les utilisations)
2023-04-21 22:30:21 +02:00
d2d1891838 cleanup itemTypes
Utilisation de itemTypes plutôt que de méthode listItems ou de
filtrer les items par type.

Potentiellement, itemTypes peut être précalculé par Foundry
C'est aussi un peu plus lisibles (conditions du filter moins longues,
et le filtrage par type est mis en avant en premier)
2023-04-21 22:30:21 +02:00
5580b6d59c Debug: à la recherche de l'Item qui ne se vend pas 2023-04-21 21:38:21 +02:00
40c45c30de Equiper des vêtements, bijoux, ...
Permettre d'équiper les objets "de base" en plus des armes et armures
2023-04-21 21:38:21 +02:00
1b6c5256cc Gestion de l'empoignade 2023-04-21 18:18:41 +02:00
c0e6759164 Gestion de l'empoignade 2023-04-21 18:18:20 +02:00
a096590a07 Merge pull request 'Version 10.7.7 - Les bobos de Sémolosse' (#641) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #641
2023-04-15 07:42:39 +02:00
9883048fb5 Version 10.7.7
Les bobos de Sémolosse
2023-04-14 20:57:16 +02:00
7bec42c4cf Fix Mise à jour du texte de l'heure joueurs 2023-04-14 20:31:47 +02:00
6708dc0da9 Fix rituels Lecture/Détection aura Hypnos 2023-04-14 20:31:15 +02:00
96c2da0d05 Fix: thème astral par un joueur
La recherche des chjiffres astrologiques par les joueurs ne marchait
plus
2023-04-14 01:19:44 +02:00
2b19250e8b Log: debug cas d'échec achatVente 2023-04-07 01:14:50 +02:00
957eabcac7 Fix: sélection sous l'horloge 2023-04-07 01:14:50 +02:00
285937c201 Merge pull request 'Version 10.7.6 - L'origine des maux de Sémolosse' (#640) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #640
2023-03-31 07:22:28 +02:00
93965ce91a Version 10.7.6 2023-03-31 01:42:25 +02:00
18aba9adff Pas de bordure pour les boutons avance rapide 2023-03-31 01:32:52 +02:00
0a5662ff71 Couleurs jour nuit plus visibles 2023-03-31 01:32:24 +02:00
bf98e4eae2 Fix toggle horloge
Affiche/masque correctement l'horloge pour le MJ et les joueurs
2023-03-31 01:31:09 +02:00
e32a1d25f6 Ne pas fermer l'horloge sur escape
Surcharge de la méthode close pour empêcher la fermeture
2023-03-31 00:51:05 +02:00
327943c4aa Fix mise à jour sur action des herbes/potions
Par exemple, la mise à jour de quantité d'herbe ne se faisait pas
dans les liste d'équipement des feuilles de conteneurs, lors d'une
fabrication de potion dans les items
2023-03-31 00:47:25 +02:00
de56fa909a Ajout bouton Item diminuer quantité
Pour diminuer la quantité des objets multiples sans avoir à éditer
2023-03-30 02:56:06 +02:00
2aa62cffe9 Bonus de sorts en fleuve
Le bonus de sort en fleuve est identique pour toutes les cases fleuve.
2023-03-30 01:31:41 +02:00
c57f140c54 Ajout de l'origine d'une blessure
En combat, indication de l'origine des blessures
2023-03-30 00:19:18 +02:00
9cf14f8b75 Merge pull request 'Version 10.7.5 - La montre-gousset de Sémolosse' (#639) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #639
2023-03-29 23:06:31 +02:00
b8e7b21e14 Version 10.7.5
La montre-gousset de Sémolosse
2023-03-29 23:00:23 +02:00
c1d02d9fda Ajout d'une horloge analogique
Amélioration de la fenêtre calendrier:
* plus compacte
* horloge analogique
* normalement compatible pop-out
* minimisable (juste la barre de titre)
2023-03-29 23:00:23 +02:00
23af30a538 Merge pull request 'Version 10.7.4 - Les ligatures de Sémolosse' (#638) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #638
2023-03-24 23:16:49 +01:00
4e1b663dec Version 10.7.4
Les ligatures de Sémolosse
2023-03-24 21:37:32 +01:00
c9d98c57da Cleanup: templates non internes 2023-03-24 21:26:42 +01:00
0aef139cf8 Fix: boutons pour réserve en sécurité 2023-03-24 21:26:42 +01:00
9c85293714 Fix: bouton pour jet de vie sur critique 2023-03-24 21:26:42 +01:00
ab1c04ae17 Fix: La chirurgie dans les tâches
Re-déplacement des tâches de chirurgie, maintenant que les soins de
blessures sont facilités (par les tokens)
2023-03-24 21:26:42 +01:00
a585e0faba Fix: affichage bonus de cases
Les bonus de cases sont visibles pour les sorts attachés à un personnage
2023-03-24 21:26:42 +01:00
50c730ba72 Fix: queues non refoulables 2023-03-24 21:26:42 +01:00
928a60f092 Add flags 2023-03-23 17:03:40 +01:00
e5c2f52b0e Merge pull request 'Version 10.7.3 - Les tisanes de Sémolosse' (#637) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #637
2023-03-23 17:02:56 +01:00
f9391523f8 Version 10.7.3
Les tisanes de Sémolosse
2023-03-23 01:44:35 +01:00
55434762f4 Masquer bonus premiers soins si soins complets 2023-03-23 01:43:41 +01:00
5e6ffc7846 Fix: bonus d'herbe de potion non enchantée 2023-03-23 01:42:48 +01:00
3344e20936 Fix: consommation potion enchantée 2023-03-23 01:41:37 +01:00
5a66e4e741 Fix horloge aiguille des heures
L'aiguille des heures pointe sur l'heure au début de l'heure
draconique, comme pour une montre.

Début couronne, l'aiguille des heures et des minutes sont donc
toutes les deux en haut.
2023-03-21 22:00:24 +01:00
d329724d63 Merge pull request '10.7.2 - les maux de dents de Semolosse' (#636) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #636
2023-03-20 23:32:08 +01:00
8a5405c9f5 Version 10.7.2
les maux de dents de Semolosse
2023-03-20 23:29:46 +01:00
ea992aae46 Fix: récupération des blessures 2023-03-20 23:28:25 +01:00
0bfcfec58f Fix jet carac 2023-03-16 23:17:52 +01:00
88e00c59bc Fix actor 2023-03-16 23:12:08 +01:00
d314dc39a0 Fix actor 2023-03-16 23:10:17 +01:00
30bb803da2 Merge pull request 'v10.7.0 - l'os de Sémolosse' (#635) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #635
2023-03-16 23:09:10 +01:00
dd79e16ea5 Version 10.7.0 2023-03-15 01:29:55 +01:00
83e2d56fd4 Boutons soins&blessures 2023-03-15 01:29:48 +01:00
afc23dfa7b Fix: message sans jet d'endurance 2023-03-15 01:29:48 +01:00
3e189cbe5f Ajout des blessures sur encaissement 2023-03-15 01:29:48 +01:00
d0475e8677 Suppression anciennes blessures 2023-03-15 00:41:48 +01:00
a3694c1673 Cleanup 2023-03-15 00:41:47 +01:00
9e6d5856b1 Gestion des soins avancés
- raccourci soins HUD
- edition rapide des Item blessure
- gestion des taches de soins liées aux blessures
2023-03-15 00:41:47 +01:00
acc880b53f Preparation soins HUD 2023-03-15 00:41:47 +01:00
2598ae3489 Ajout item Blessure 2023-03-15 00:35:22 +01:00
4f5fb63751 Tri alphabétique des constantes 2023-03-15 00:35:21 +01:00
d739a7993a rollCaracCompetence avec dialog 2023-03-15 00:35:21 +01:00
41335cd433 Renommage méthode jet sans dialog 2023-03-15 00:35:21 +01:00
e470d76ea0 Méthode de fenêtre de jet partagée 2023-03-15 00:35:21 +01:00
ffccc819f1 Fix: merge d'options sur token 2023-03-15 00:35:21 +01:00
45bfc69b39 Ajout d'option pour informer si pas de cible
Pour le HUD, on peut vouloir utiliser la cible (soins)
2023-03-15 00:23:21 +01:00
79e9358072 Préparation Calendrier avant migration
Les migrations peuvent avoir besoin du temps courant.
Le calendrier est recréé après.
2023-03-15 00:23:21 +01:00
5cab418e62 Merge pull request 'v10.6.25 - fix erreur synchro astrologie/heure' (#634) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #634
2023-03-14 22:41:45 +01:00
7809f7c21a Version 10.6.25 2023-03-14 22:14:04 +01:00
1bac204071 Fix astrologie 2023-03-14 21:56:59 +01:00
3bf5beb67b Sync merge manuel 2023-03-09 23:49:37 +01:00
ac1da6e979 Update archetype/theme 2023-03-09 23:25:34 +01:00
67735197bc Merge pull request 'Version 10.6.21 - La théière de Pralinor' (#632) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #632
2023-03-08 23:17:45 +01:00
f81dc9a5f5 Version 10.6.21
La théière de Pralinor
2023-03-08 23:08:40 +01:00
a563233d6b Fix: les PNJs peuvent de nouveau dormir 2023-03-08 23:08:40 +01:00
0c11013694 Ajout Aiguille Horloge 2023-03-08 22:48:40 +01:00
77cf72a752 Intégration astrologie
Intégration du thème astral dans les fenêtres d'astrologie
2023-03-08 22:34:05 +01:00
42ed5da2d4 Création de tableaux d'entiers
ajout d'une méthode propre pour construire un tableau d'entiers
consécutifs
2023-03-08 02:00:38 +01:00
5fd3a43b2a Merge pull request 'Version 10.6.20 - Les Oracles de Pralinor: vous mangerez à Couronne' (#631) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #631
2023-03-07 13:32:21 +01:00
1bb710ce83 Version 10.6.20
Les Oracles de Pralinor: vous mangerez à Couronne
2023-03-07 11:13:56 +01:00
80579032ea Ajout de la fenêtre de thème astral
Accessible par macro/depuis les fenêtres d'astrologie
2023-03-07 10:46:31 +01:00
a810e20eca Merge pull request 'Version 10.6.19 - La cerise de Pralinor' (#630) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #630
2023-02-24 09:56:29 +01:00
c6f0aaeba0 Version 10.6.19 - La cerise de Pralinor 2023-02-24 00:50:06 +01:00
3c3be7409d Fix: problème de nombre d'utilisations 2023-02-24 00:38:14 +01:00
f97345e407 Fix: recherche avec droits limités 2023-02-24 00:33:49 +01:00
4cc50fc190 Ajout Haubert d'Oniros 2023-02-18 12:26:58 +01:00
26967fe1cd Merge pull request '10.6.18 - Les insomnies de Pralinor' (#629) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #629
2023-02-17 10:16:29 +01:00
b23dcaace5 Version 10.6.18
Les insomnies de Pralinor
2023-02-17 01:31:18 +01:00
045ee76013 Fix: transfert du moral à chateau dormant 2023-02-17 01:29:21 +01:00
7fed3844b5 Fix: calcul du nombre d'heures dormi
Le calcul était incorrect (5 au lieu de 4), du coup Chateau Dormant
ne se déclenchait pas.
2023-02-17 01:28:30 +01:00
b5571e6c7a Merge pull request 'v10.6.17 - Les désordres de Pralinor' (#628) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #628
2023-02-14 08:13:44 +01:00
b5eac8ccfa Version 10.6.17
Les désordres de Pralinor
2023-02-13 22:49:21 +01:00
673196c644 Fix: trier le contenu 2023-02-11 21:20:12 +01:00
9741028914 Fix: suppression doublons 2023-02-11 20:52:29 +01:00
10daab2bcd Merge pull request 'v10.6.16' (#627) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #627
2023-02-11 17:05:42 +01:00
99d2d38279 Version 10.6.16 2023-02-11 01:24:58 +01:00
e30825db6b Commerce "Liste d'équipement" 2023-02-11 01:24:58 +01:00
10ed4d9382 Corrections d'équipements
Séparation de:
- flèches et carreaux (qui deviennent des munitions)
- Luth et viole
- Burin, gouge, ciseau
- Lime, Râpe
- Alène, poinçon

Ajout de l'herbe à pipe
Ajout de prix à l'herbe de lune (qualité 3 par défaut)

Sang et Liquide deviennent de bêtes objets
2023-02-11 01:24:58 +01:00
2ffa0d8dc7 Fix: utiliser les compétences des humanoïdes
Utiliser les compétences de l'acteur empêchait l'édition des armes
(et objets liés à une compétence) stockées sur un animal, dans un
véhicule, ou dans un commerce
2023-02-11 01:24:58 +01:00
f229537206 Merge pull request 'v10.6.15' (#626) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #626
2023-02-10 21:37:33 +01:00
5c31c7bd7f Version 10.6.15 2023-02-10 21:30:30 +01:00
4cf428f630 Fix: messages et insomnie
Amélioration des messages de sommeil (nombre d'heure, seulement les
récupérations de rêve effectives, ...)

Les insomnies ne durent bien que 12h draconique à partir du prochain
chateau dormant (elles pouvaient durer 3 jours par erreur).
2023-02-10 02:01:55 +01:00
0c1b70f3f0 Fix: recherche dans arbre de conteneurs
On affiche bien tous les conteneurs intermédiaires
2023-02-10 02:01:55 +01:00
b7ea857bb4 Merge pull request '10.6.14' (#625) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #625
2023-02-09 07:22:14 +01:00
0238976ce8 Version 10.6.14 2023-02-08 21:17:20 +01:00
31fd91336a Correction affichage calendrier
Correction de l'affichage incorrect pour Chateau Dormant et
Poisson Acrobate

Nettoyage du css
2023-02-08 21:09:07 +01:00
c70b6c9d5f Dialog chateau-dormant
Pour permettre de pré-remplir les infos de chateau dormant
2023-02-08 21:09:07 +01:00
07d0d92f57 Suppression code commentaire 2023-02-08 00:24:05 +01:00
a2968697f4 Jet de moral neutre sans effet si moral à 0 2023-02-07 21:37:43 +01:00
fb7dbe6ea0 Merge pull request 'v10.6.13' (#624) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #624
2023-02-07 19:28:34 +01:00
59641bf5cf Version 10.6.13
Recherche dans les commerces
2023-02-07 18:08:13 +01:00
4ee4445836 Fix: message sur jet de moral neutre 2023-02-07 18:07:26 +01:00
1a346a21c3 Recherche dans les commerces 2023-02-07 18:07:26 +01:00
c6c0dd43fd Merge pull request 'Recherche dans l'inventaire' (#623) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #623
2023-02-05 23:00:20 +01:00
d69a07d4dd Version 10.6.12 2023-02-05 20:32:47 +01:00
56379a9234 Fix: bouton d'ajout d'objet 2023-02-05 20:30:19 +01:00
fbcc167272 Recherche dans l'inventaire
Par nom ou par type, et dans les contenants
2023-02-05 20:26:51 +01:00
703ab2579d Merge P3 2023-02-03 08:35:02 +01:00
6b7e881bf2 Merge pull request 'v10.6.11' (#621) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #621
2023-02-03 08:34:06 +01:00
dc3ceb1732 Déplacements des effets de rencontre
classe placée dans le dossier "tmr"
2023-02-03 02:23:46 +01:00
7a0132cf8d La griffe morbide donne un poison
Ajouter l'effet "poison: griffe morbide de thanatos" à la victime pour
agir "comme" un poison (sauf qu'un jet de vie jamais positif remplace
l'habituel jet de constitution).
2023-02-03 02:23:46 +01:00
ca1ea5a854 La récupération est bloquée par les maladies
Pas de récupération de vie ou de blessures possibles sous l'effet d'un
poison ou d'une maladie
2023-02-03 02:23:46 +01:00
9bdde92d61 Minot fixes 2023-02-02 08:56:00 +01:00
74313b7fd3 Merge pull request 'fixes' (#620) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #620
2023-02-02 08:55:24 +01:00
9a9399a581 Alchimie: températures et difficulté
- toujours afficher les difficultés des températures/consistances
- renommer "couleur" en "température" à l'affichage
- mise à jour des recettes du compendium
2023-02-02 01:24:42 +01:00
95a7ac73d0 Fix: description en mode edition 2023-02-02 01:21:06 +01:00
ab4c118d12 Fix: Sustentation consommée parfois concaténée 2023-02-02 00:53:32 +01:00
681358238d Merge branch 2023-02-01 10:11:33 +01:00
5511acc876 Merge pull request 'Alignement description en haut' (#619) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #619
2023-02-01 10:10:42 +01:00
2e144851d4 Alignement description en haut 2023-01-31 21:40:35 +01:00
9f24aee1f3 Merge pull request 'Recherche par nom' (#618) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #618
2023-01-31 07:32:09 +01:00
e605ab405a Version 10.6.8 2023-01-30 22:08:11 +01:00
86f69566a6 Recherche par nom
Dans la fenêtre de recherche, possibilité de chercher sur le nom
des objets en plus des autres critères
2023-01-30 22:08:11 +01:00
2561a658f2 Merge pull request 'v10.6.7' (#617) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #617
2023-01-28 20:50:52 +01:00
8a1d1fd253 Version 10.6.7 2023-01-28 18:08:47 +01:00
e42f384d7f Masquer le bouton si pas utilisable
Pour créer un nouvel objet, il faut être "owner"
2023-01-28 16:46:17 +01:00
d381191692 Monnaie pour gérer la fortune 2023-01-28 16:35:45 +01:00
53a7230f1d Fix: Actions dans un conteneur
Les actions dans un conteneur (ouvrir un des sous conteneurs par
exemple) n'étaient pas disponibles.
Ceci était lié à la structure des données contenant les droits
différente pour les items et les acteurs.

La gestion des droits était mélangées et faite de façons différentes.
Maintenant, les "options" dans les données du formulaire contiennent
les informations de droits d'accès et sont utilisées.
2023-01-28 16:34:09 +01:00
4d5651c2d3 Reprise de l'arbre d'inventaire
Soucis lors de l'affichage dans la fenêtre d'un conteneur
2023-01-28 15:43:31 +01:00
61474172a7 Fix: gestion de la fenêtre de vente
les nombre de lots ne marchaient plus (toujourts 1 de proposé)
2023-01-28 11:36:22 +01:00
083806612b Merge pull request 'v10.6.7' (#616) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #616
2023-01-25 10:26:26 +01:00
1c417d02a1 Version 10.6.6 2023-01-24 22:35:39 +01:00
ee9e7d2adb Messages compétences troncs en notifications
Lors d'une création de perso, ça spamme trop le tchat
2023-01-24 22:33:45 +01:00
fa3334273d Fix: ligne de carac vide à cause de la beauté
Une lighne de carac était ajouée lors de l'édition de la beauté.
2023-01-24 22:33:45 +01:00
05b73a41a5 Inversion: Taille puis poids
C'est dans cet ordre dans les scénarios
2023-01-24 22:33:45 +01:00
842243485a Corrections d'armes rudimentaires 2023-01-24 22:12:43 +01:00
1e53f2d9e8 Minot fix 2023-01-24 18:13:27 +01:00
792fcc1b39 Merge pull request 'v10' (#615) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #615
2023-01-24 15:22:36 +01:00
3d914a29c3 Fix: édition de carac créature 2023-01-24 01:43:10 +01:00
7fe7c1e9bd Fix: +dom pas affiché 2023-01-21 19:54:04 +01:00
d86b81fbbc Merge pull request 'v10.6.4' (#614) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #614
2023-01-21 16:57:37 +01:00
22da2807d8 Fix2: update de stress transformé 2023-01-21 00:17:36 +01:00
76e03b07ed v10.6.4 2023-01-20 23:53:03 +01:00
e4098c813b Les remèdes sont bien catégorisés 2023-01-20 23:53:03 +01:00
90aecd6cc0 Suppression compendium taches de soins
Avantageusement remplacé par les boutons de soins
2023-01-20 23:53:03 +01:00
254709f55b Recherche de potion par catégorie 2023-01-20 23:53:02 +01:00
064472dc11 Ajout de l'utilisation 'cuisine' 2023-01-20 23:52:37 +01:00
7a67cb7cea Prise en compte des milieux
Les filtrages par milieux prennent en compte la rareté/fréquence
du milieu
2023-01-20 23:52:09 +01:00
7f051e76be Tri par ordre alphabétique (tous compendiums)
Le tri étati fait séparément pas compendium, du coup, la présentation
des résultats n'était pas toujours pratique
2023-01-20 23:52:09 +01:00
38db7fb7c8 Cas où le stress peut être concaténé
Vu deux fois:
- passage de "29" à "290" en ajoutant 0
- valeur "00"
2023-01-20 23:52:08 +01:00
c448f32bb8 Merge pull request 'v10.6.3' (#613) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #613
2023-01-19 10:50:27 +01:00
e194514965 Taille de titre soins 2023-01-19 02:46:54 +01:00
a790c36618 Soins des blessures
Maintenant, les tâches peuvent être créées directement
dans l'onglet combat, à côté des blessures.

Les tâches des oins sont dans cet onglet
2023-01-19 02:42:25 +01:00
f221bb31eb Version 10.6.3 2023-01-19 01:50:02 +01:00
bdc2d8db3a Fix blocage du round
Les ActiveEffect n'ont pas de system, et causaint une exception
dans la méthode de passage de fin de round
2023-01-19 01:48:41 +01:00
782dc38268 Ajout de message si rencontre perdue
Ajout d'un message pour comprendre comment une rencontre peut
être perdue ( Issue #612)
2023-01-19 01:48:41 +01:00
024e355586 Fix: Affichage d'anciens objets temporels
Les objets temporels créés avant la 10.5.0 ne pouvaient pas être édités
2023-01-19 01:48:41 +01:00
37704558e0 Combat bloqué en cas d'init négative
Le code essayait de modifier le total d'un roll, qui n'est pas un
champ mais une methode get de la classe Roll
2023-01-19 01:48:41 +01:00
d816490839 Expérience sur jets de résistance
Une réussite particulière à un JR de difficulté négative fait
gagner un point d’expérience. On ne gagne qu’un seul point
quelle que soit la difficulté du JR, et cet unique point va
toujours en caractéristique RÊVE, jamais en Draconic.

Pour l'éthylisme, le jet de vie n'est pas un jet d'action, mais de
résistance, limitation à 1 point d'expérience pour lutter contre
l'alcoolisme optimisateur.
2023-01-19 01:48:41 +01:00
a4186da540 Merge pull request '10.6.2' (#611) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #611
2023-01-18 08:40:40 +01:00
b8b4a1ff14 Version 10.6.2 2023-01-18 02:01:53 +01:00
8925a4979d Ajout de fréquence aux équipements 2023-01-18 02:01:27 +01:00
851a208aee Généralisation des fréquences
Tout objet d'inventaire a une fréquence (en ville/village, ou autre
milieu)
2023-01-18 02:01:27 +01:00
4df04fe06f Configuration de la recherche 2023-01-18 00:12:34 +01:00
f4d074fa31 Paramétrage des compendiums de recherche 2023-01-17 21:51:49 +01:00
f7595a1bfe Taille Quantité dans commerces
Agrandissement pour mieux afficher jusqu'à 4 chiffres au cas
en cas d'objets en grande quantité, avec d'autres limités
2023-01-16 23:20:29 +01:00
86feb12811 Fix: /table milieu
régression causée par les changements de la fenêtre de recherche
2023-01-16 23:17:17 +01:00
2383094254 Increase relese 2023-01-15 23:37:06 +01:00
2a72f6ffea Merge pull request 'v10.6.1' (#610) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #610
2023-01-15 23:36:12 +01:00
3cc56c615c Amélioration "effacer les filtres" 2023-01-15 23:30:32 +01:00
948309bb4f Drag depuis la fenêtre de recherche 2023-01-15 23:21:19 +01:00
40fdff4057 Image du token pour les Commerces non liés 2023-01-15 21:40:18 +01:00
c972913a67 Amélioration des plantes comestibles&poisons 2023-01-15 15:55:35 +01:00
0d5c8e5304 Amélioration de recherches 2023-01-15 15:54:40 +01:00
0aba6d2f0a Fix: Typo monnaie "Dragon" 2023-01-15 12:42:53 +01:00
df3e181d7f Merge pull request 'Les classifications de Pralinor' (#609) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #609
2023-01-14 08:54:27 +01:00
8877939b9f Version 10.6.0 2023-01-14 01:56:47 +01:00
7334f2d8e6 Liens des plantes toxiques vers leur poisons 2023-01-14 01:56:47 +01:00
ceff3d1b50 Le retour du poisson acrobate 2023-01-14 01:56:47 +01:00
a4802d1113 Séparation ingrédients/plantes 2023-01-14 01:18:17 +01:00
162a6a04b8 Fenetre de recherche et tirage 2023-01-14 01:18:17 +01:00
0c36cf3c47 Fix ouverture de conteneurs 2023-01-13 01:53:25 +01:00
969e5f6b2d Rename: isAgiliteOuDerobee
Plutôt que isAgiliteOuDerivee qui pourrait inclure la Mêlée
2023-01-13 01:53:25 +01:00
306ecacc98 Les commerces ne gèrent pas d'encombrement 2023-01-13 01:53:25 +01:00
2dc86e6679 Fix: jet de volonté éthylisme
Dans certains cas, les jets d'éthylisme concaténaient
moral et éthylisme
2023-01-13 01:53:25 +01:00
484530d67d Fix: chronologie si le journal est supprimé 2023-01-13 01:53:25 +01:00
a08890dcb0 min-width calendrier pour titres courts
Pour certaines dates, les boutons lyre / vaisseau s'affichaient
sur 2 lignes
2023-01-12 20:53:35 +01:00
1956fdd755 Merge pull request 'Version 10.5.4 - le pense-bête d'Astrobazzarh' (#608) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #608
2023-01-10 23:11:49 +01:00
651356159a Version 10.5.4 - le pense-bête d'Astrobazzarh 2023-01-10 23:06:18 +01:00
ca304109d5 Cleanup 2023-01-10 23:06:18 +01:00
9c0d08cb6f Organiser la chronologie par date 2023-01-10 23:06:18 +01:00
f9fd8a1e24 Message si le journal est inaccessible 2023-01-10 23:06:18 +01:00
03f84eb3f7 Chronologie +/-
Quand le journal est sélectionné, la fenêtre de chronologie s'ouvre avec
les informations présaisies masquées.

Le bouton + permet de les afficher. Le bouton - de les masquer.
2023-01-10 23:06:18 +01:00
05e48b61ff Renommage showControlWhen
Vu que la méthode est publique
2023-01-10 22:17:30 +01:00
f20788d6d9 Merge pull request 'v10.5.3' (#607) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #607
2023-01-10 07:49:17 +01:00
009639dd5f Version 10.5.3 2023-01-09 23:23:29 +01:00
14542cf7cd Suppression template inutile
N'est plus utilisé, maintenant que les services sont des objets
dans les boutiques
2023-01-09 23:22:20 +01:00
f4f2db68e0 Table d'astrologie avec toutes les heures 2023-01-09 23:22:20 +01:00
7e75715d88 Merge pull request 'v10.5.2' (#606) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #606
2023-01-08 23:59:34 +01:00
d62d1c45bc Version 10.5.2 2023-01-08 23:36:53 +01:00
62914c343f Fix: à l'heure de => heure 2023-01-08 23:27:08 +01:00
9349e81580 Reformatage du calendrier 2023-01-08 23:21:14 +01:00
404539a004 Fix: ajout d'heure ajoute des minutes
Lors du passage à Lyre, on ajoutait 6 minutes
2023-01-08 23:20:54 +01:00
9eaeceafc4 Autofocus sur le texte pour la chronologie 2023-01-08 20:42:28 +01:00
1cd3bdef25 Amélioration titre du calendrier 2023-01-08 20:42:28 +01:00
fa356bd7f8 Fix calcul des heures de chance 2023-01-08 20:42:28 +01:00
771c8c9c8e Merge pull request 'Fix: migration automatique de calendrier quand l'heure est à Vaisseau+0 minutes' (#605) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #605
2023-01-08 18:44:42 +01:00
ca860a0243 Version 10.5.1 2023-01-08 16:37:38 +01:00
2e36221018 Fix de la migration quand l'heure est à 0 2023-01-08 16:36:54 +01:00
07e203513a Merge pull request '10.5.0' (#604) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #604
2023-01-08 09:49:45 +01:00
31960ce940 La périodicité est migrée 2023-01-08 01:03:03 +01:00
19118911b9 Version 10.5.0 2023-01-08 00:58:44 +01:00
652ae4cf51 Fix: enlever le bloc noir en pause 2023-01-08 00:57:50 +01:00
cf340f73f1 Migration maladies et poisons
Séparation de l'incubation / la périodicité
2023-01-08 00:57:05 +01:00
3782ed9055 Gestion d'update de timestamp 2023-01-07 23:58:33 +01:00
c3076fdbfc Gestion de la périodicité/temporalité 2023-01-07 23:28:30 +01:00
739fcbdf09 Calendrier/timestamp, suite
Correction autour des éditeurs/affichages
Migration de la date du monde dans les settings
2023-01-07 20:06:21 +01:00
19b3adc222 Cleanup logs 2023-01-07 20:06:21 +01:00
388629d36e Fix: édition des compétences de créatures 2023-01-07 20:06:21 +01:00
912b1d3df3 Gestion d'items temporels
- Séparation des timestamp / calendrier

Les poisons/maladies/souffles/queues/rencontres/signes peuvent
être temporaires.

- Ajout de champs pour stocker les timestamps de début et fin
- définition de la durée (selon les items)
- extraction des classes spécialisées des items
- initialisation des dates de début/fin des effets temporaires à
  l'ajout d'un item temporel
- préparation de la suppression automatique
- Fix de mauvaise présentations sur les dialog d'astrologie
  et d'édition du calendrier
2023-01-07 02:22:44 +01:00
11e4ad09d3 Ignore jsconfig.json and modules 2023-01-05 22:23:47 +01:00
8844f76b65 Les joueurs ne peuvent plus créer de signes
Les commandes /signe+ / /signe
2023-01-05 20:56:42 +01:00
ec24126e75 Cleanup: Suppression listes inutiles 2023-01-05 20:31:29 +01:00
b3c6845e68 Fix: la maladie bloque le calendrier
Le postItem d'une maladie bloquait le raffraîchissement du calendrier
2023-01-05 20:31:29 +01:00
cf8df182ba Merge pull request 'v10.4.9' (#603) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #603
2023-01-05 16:11:17 +01:00
30cc8419c5 Version 10.4.9 2023-01-05 14:44:38 +01:00
5ab6c5989e Fix: name unique pour tabulations 2023-01-05 14:36:01 +01:00
46df3d9f11 Fix: regression sur les ventes 2023-01-05 14:32:35 +01:00
6a6759087c Merge commerces fixes 2023-01-04 21:17:50 +01:00
a7ce1db7c5 Merge pull request 'Corrections mineurs commerces et ventes' (#602) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #602
2023-01-04 21:17:10 +01:00
a0efefad3f Suppression template inutile
Les herbes de soins sont mieux gérées
2023-01-04 00:25:23 +01:00
8e0825b6b9 Fix: vente depuis un commerce
La quantité proposée est limitée à la quantité disponible
ou illimité si le commerce est illimité
2023-01-03 23:46:08 +01:00
39d14c8496 Fix: boutons des commerces
- pas de vente des "étagères"

Les contenants groupes dans les commerces ne sont plus
montrables/vendables. Le MJ peut les éditer (pour changer le nom
et l'image, principalement), et c'est tout.

- le MJ peut acheter un objet de quantité 0 dans une boutique illimitée

le bouton n'était pas affiché...
2023-01-03 23:02:07 +01:00
89442ea6c6 Merge permissions fix 2023-01-03 16:12:23 +01:00
a66fe122c4 Merge pull request 'Le GR a toujours le droit de voir les acteurs' (#601) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #601
2023-01-03 16:11:34 +01:00
2e0abaa284 Les services sont inquantifiables
Ne pas décrémenter leur quantité, et leur quantite disponible est
undefined (ie: infinie)
2023-01-03 13:38:04 +01:00
5bddc548de Le GR a toujours le droit de voir
Change-Id: I3b58fddc3ce6765e6dcf59dc20d81cdadf381761
2023-01-03 13:26:44 +01:00
4acd0879b0 Merge pull request 'Les auberges sont des commerces' (#600) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #600
2023-01-03 10:43:25 +01:00
060943ee53 Version 10.4.6 2023-01-03 02:20:28 +01:00
80be0490eb Permettre d'éditer les conteneurs de regroupement 2023-01-03 02:20:16 +01:00
ceacee8e6c Les commerces peuvent appliquer un pourcentage 2023-01-03 02:08:27 +01:00
d5453c9b04 Ajout d'images de services 2023-01-03 01:37:07 +01:00
7ca3306c6f Fix: cacher quantite illimite dans les contenants 2023-01-03 01:19:38 +01:00
aa5d175027 Formatage de prix 2023-01-03 01:03:05 +01:00
d4ddc4e940 Services pour Commerces
Les services sont modifiés pour correspondre aux nouveaux commerces
2023-01-03 01:02:47 +01:00
87f12019ac Cleanup partials 2023-01-03 00:32:00 +01:00
128d7adf89 Migration des services en commerces 2023-01-03 00:32:00 +01:00
ee42bdcf83 Ajout d'un Actor commerce 2023-01-03 00:32:00 +01:00
5972db035d Permettre un arbre d'inventaire alternatif 2023-01-02 01:41:56 +01:00
92388df5c7 déplacement gestion equipement
Déplacement de la gestion d'équipement dans base-actor
2023-01-02 01:41:56 +01:00
274009d3fa Suppression de vente depuis un "service" 2023-01-02 01:40:48 +01:00
d7e5a09540 Permettre de regrouper des herbes
L'environnement est un tableau, dur à comparer
2023-01-02 01:40:14 +01:00
7f5b2e0abf Cleanup 2023-01-02 01:40:14 +01:00
fbd3aa7121 Extraction des settings dans une méthode dédiée 2023-01-02 01:40:14 +01:00
2ca601b5f8 onDrop depuis compendium async
Permettre de retrouver l'Item du compendium pour tester si un acteur
peut le recevoir
2023-01-02 01:40:13 +01:00
d77ecee9bd Fix: exaltation/dissolution 2022-12-31 18:57:59 +01:00
c2a3b5e246 Fix stress & achats 2022-12-31 11:45:54 +01:00
09bf28eed1 Update release + ajout Vincent 2022-12-30 09:36:06 +01:00
33c20dd2b7 Merge pull request 'Fixes préparatoire à actor échoppe' (#599) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #599
2022-12-30 09:35:01 +01:00
c933810b6b Fix: tab order milieux 2022-12-30 02:59:50 +01:00
df92a65f5d Fix: choix autocomplete sous le champ input 2022-12-30 02:55:11 +01:00
dbcab12f24 Rituels: Lecture et détection d'aura
Incorrectement déclarés comme sortilèges en oniros/narcos/thanatos
2022-12-30 02:38:03 +01:00
454193490d Fix astrologie joueur 2022-12-30 02:31:53 +01:00
c79298b60a Post to chat pour les acteurs 2022-12-30 02:31:53 +01:00
26808d7b49 Controle visibilite
Ne pas afficher les boutons non accessibles
2022-12-30 02:31:53 +01:00
2062ff0777 Support de plusieurs actors 2022-12-30 02:31:52 +01:00
6adeb790a0 Fix: pas d'actions pour les véhicules 2022-12-30 02:30:54 +01:00
2546b89b44 Fix: vente de service avec quantité 2022-12-30 02:30:49 +01:00
84f4a152a8 Fixed 2022-12-24 09:47:06 +01:00
d6f8698189 Fix repos 2022-12-23 23:18:40 +01:00
4f80c719c2 Fix services/boutique 2022-12-23 14:26:23 +01:00
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: #598
2022-12-23 09:50:26 +01:00
ec58317b35 Version 10.4.0 2022-12-23 02:18:43 +01:00
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
f397c82c6d Prepare sous-classes Item 2022-12-23 00:35:44 +01:00
81e3ceb4dc Lien vers Items monde/compendium 2022-12-23 00:35:44 +01:00
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
70b30b545b Affichage consistant prix unitaire/total 2022-12-22 00:04:49 +01:00
ed2eebf99d Renommage confirmation 2022-12-21 00:49:36 +01:00
4ba2c384d7 Alignement de prix/quantité/enc
- alignement à droite
- affichage des prix avec 2 décimales
2022-12-21 00:48:32 +01:00
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
61389e117b Séparation inventaire/monnaie 2022-12-20 01:13:51 +01:00
46cc245abf Fix edition minutes du calendrier 2022-12-20 01:13:51 +01:00
a372531849 Cleanup: html TMR multiligne 2022-12-20 01:12:52 +01:00
8a8323ac8d Cleanup: Suppression log 2022-12-20 01:12:52 +01:00
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
886307f24c Fix: affichage de la fortune avec arrondis
Affichage en sols + deniers
2022-12-20 01:12:51 +01:00
f40dbd5d7b Merge pull request '10.3.17 - petits fixes' (#597) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #597
2022-12-18 17:08:21 +01:00
66da7d5eb4 Version 10.3.17 2022-12-17 20:08:11 +01:00
b2d8c2439a Fix: affichage des descriptions
Pour les feuilles dérivées de RdDItemSheet, il faut appeler
super.getData() pour préparer les données à afficher dans le formulaire.
2022-12-17 20:08:11 +01:00
ceb4095c31 Fix: escaping dans les messages de rencontres 2022-12-17 20:08:11 +01:00
2af37cf98f Fix: l'ajout de queues fonctionne de nouveau 2022-12-17 20:08:11 +01:00
fd156960a7 Fix: les rencontres persistantes disparaissent
Après un échec, il était impossible de se débarasser d'une rencontre
persistante. Maintenant, les rencontres persistantes vaincues sont
bien supprimées.
2022-12-17 20:08:03 +01:00
80a904e533 Fix: Surencombrement toujours calculé
Sur de vieux personnages, le sur-encombrement stocké pouvait
être affiché (et incorrect).
2022-12-17 17:49:11 +01:00
c9dc847440 Migration des monnaies par nom
Vu que dans les migrations précédentes, le "cout" avait une valeur
(pas undefined), les migrations ont échoué
2022-12-17 17:26:39 +01:00
4e382d405e Merge pull request 'Ils sont bons mes Maquereaux, elles sont bien mes macros' (#596) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #596
2022-12-17 11:33:44 +01:00
6a12dc97f6 Version 10.3.16 2022-12-17 01:39:22 +01:00
e0b6957bc6 Ajout de macros RdD 2022-12-17 01:37:54 +01:00
944120b524 Fix commande /rdd sans compétence
On peut maintenant faire /rdd Vue -2
2022-12-17 00:55:13 +01:00
bb6bf3387e Malus fatigue indiqué sur la fatigue 2022-12-17 00:55:13 +01:00
467a4d53a4 Séparation des boutons Haut-rêve 2022-12-17 00:55:13 +01:00
d5635b27fe Suppression du test 2022-12-15 22:40:23 +01:00
3a33f7c4fc Fix actor sheet 2022-12-15 10:41:19 +01:00
d2e77dc61c Merge pull request 'Version 10.3.14' (#595) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #595
2022-12-15 07:36:04 +01:00
320bc471e3 Version 10.3.14
- Améliorations cosmétiques
- Affichage de la fortune
- Fix dissolution
- Fix des monnaies dans les compendiums
- Nettoyage des compendiums
2022-12-15 01:03:57 +01:00
090241f0f5 Alignement des champs d'environement 2022-12-15 01:00:28 +01:00
ddc37c6969 Ajustement champs description 2022-12-15 00:56:23 +01:00
6af6e41bc9 Reprise des attributs secondaires 2022-12-15 00:56:23 +01:00
0f7f609a2a Fix dissolution
La dissolution n'est que de 1 point
2022-12-15 00:56:23 +01:00
a58f701ca6 Centrage seuil de rêve 2022-12-15 00:56:23 +01:00
757b46080a Suppression compendiums inutilisés 2022-12-15 00:56:23 +01:00
df44cd66c7 Nettoyage compendiums
- correction des monnaies des acteurs par défaut
- re-export pour garder les données systeme
- suppression des champs "competencecreature[0]" incorrects
- suppression de champs data non utilisés
2022-12-15 00:56:23 +01:00
1040ec1be2 Auto reformat 2022-12-15 00:56:23 +01:00
92643d1c46 Autocomplete Main directrice
Permet de choisir rapidement dans les valeurs proposées
2022-12-15 00:56:22 +01:00
31c4aa32d9 Affichage de la fortune 2022-12-14 19:58:52 +01:00
f5431b58fb Merge pull request 'Non à la dévaluation' (#594) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #594
2022-12-13 22:28:42 +01:00
bd32e1039a Mettre en valeur l'argent sans valeur 2022-12-13 22:27:36 +01:00
6b8f0ed51e Version 10.3.13 2022-12-12 23:33:53 +01:00
10681b3f61 DialogItemSplit height fit-content 2022-12-12 23:32:35 +01:00
bbde3b73fe Gestion des monnaies de valeur 0
Ajout d'un message d'erreur quand on met la valeur d'une monnaie à 0.
Ajout d'une notification quand on détecte des monnaies de valeur 0

Pour les pièces en bois, les créer comme objets, si on ne veut pas de
messages d'avertissement.
2022-12-12 23:32:35 +01:00
57d52c1966 Inc release 2022-12-11 10:35:51 +01:00
e3a29cdab5 Merge pull request 'Fix taille du texte des boutons' (#593) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #593
2022-12-11 10:34:36 +01:00
10e4f14eb2 Fix taille du texte des boutons 2022-12-11 02:10:39 +01:00
04273dfcf1 Inc release 2022-12-10 18:14:52 +01:00
8c5c01114e Merge pull request 'Corrections esthétiques' (#592) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #592
2022-12-10 18:13:44 +01:00
19e6124330 Suppression scripts vides 2022-12-10 16:38:18 +01:00
1c908b50cb Dialog repos 2022-12-10 16:38:18 +01:00
969cedfc3d Fix espace avant boutons 2022-12-10 16:28:08 +01:00
830e66749d Lien des label vers les champs 2022-12-10 16:09:55 +01:00
df26e654ae Fix des tailles de polices 2022-12-10 16:09:35 +01:00
153bfe2e75 Simplification hbs pour la vue détaillée 2022-12-10 16:07:33 +01:00
f6d42875ae Stress et archétype
Déplacement du stress et de l'archétype avant les compétences

Les lignes d'archétypes totalement réparties disparaissent
2022-12-10 15:49:09 +01:00
450cb8e899 Largeur des compétences
Ajustement de la largeur des colonnes de compétenes en vue
simplifiée

Déplacement de l'en-tête dans la première ligne de la liste (avec les
boutons en vue détaillée)
2022-12-10 15:44:08 +01:00
0202938910 Ajout faune 2022-12-09 22:50:23 +01:00
512a056e59 Merge pull request 'Poissons, coquillages, crustacés' (#591) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #591
2022-12-09 22:49:29 +01:00
214377c66d Poissons, coquillages, crustacés
Ils sont frais, mes poissons!
2022-12-09 22:36:17 +01:00
752e534350 Ajout faune 2022-12-09 21:50:12 +01:00
c85a544cc9 Ajout faune + fix sur suppression milieux 2022-12-09 12:01:44 +01:00
3a90c693d9 Release 2022-12-09 10:39:21 +01:00
c04b179176 Merge pull request 'Permettre d'avoir plusieurs fenêtres' (#590) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #590
2022-12-09 10:36:48 +01:00
63770790b9 Fix multi-dialogs
Arrêter d'utiliser le jQuery $(selector) qui cause des effets de bord si
plusieurs élements de la page (ie: foundry) correspondent
au selector.

Stocker le html dans les Sheet/Dialogs lors de l'appel
activateListeners  afin de pouvoir s'y référer ensuite.

Utiliser this.html.find pour chercher dans le html de la fenêtre
courante.

Eliminer les référence par id html car l'id est unique (donc ne marche
pas en multi-fenêtres)
2022-12-09 02:07:59 +01:00
aefc7a434b Fix: lien vers rencontre du compendium 2022-12-09 02:07:59 +01:00
f02959adee Cleanup
Construction du message de jet de constitution par template hbs
2022-12-09 02:07:59 +01:00
e652027b02 Cleanup accorder entité
Méthode pour accorder une entité en double (dont une version sans xp,
et message mal formaté)
2022-12-09 02:07:45 +01:00
2122a54db7 Cleanup roll windows
- permettre plusieurs fenêtres de jets en même temps en éliminant les
  id dans le html et les jquery sur id pour éviter les interactions
- génération de la table par handlebars
2022-12-06 01:30:31 +01:00
f027e3318b Merge pull request 'Ragoût de Klampin' (#588) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #588
2022-12-05 16:50:33 +01:00
31b4d1cfcc Version 10.3.6 2022-12-05 16:39:24 +01:00
5056c35038 Faune et flore comestibles
- permettre de cuisiner les ingrédients (faune & flore)
- permettre de manger des ingrédients "crus"
2022-12-05 16:38:26 +01:00
7b58407634 Cleanup 2022-12-05 16:36:27 +01:00
7efa7be1c0 L'art ne s'encombre de rien
Ne pas appliquer le surencombrement (ou l'encombrement) aux oeuvres
d'art: on considère que pour faire de l'art, on pose son sac.
2022-12-05 16:36:27 +01:00
717bb6fc6e Fix ouverture conteneur 2022-12-05 16:36:27 +01:00
51273bcc3e Merge pull request 'Gestion de la Faune' (#587) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #587
2022-12-03 23:16:36 +01:00
b8f3a9af27 Version 10.3.5 2022-12-03 22:31:56 +01:00
ab704c46d2 Ajout de l'Item "faune" pour tables environnement 2022-12-03 22:31:42 +01:00
db8fd6dbf8 Meilleur post to tchat 2022-12-03 22:27:21 +01:00
d998a4cb08 Amélioration Classes ItemSheet séparées
Meilleur support des feuilles ItemSheet séparés
Séparation de la feuille Conteneurs

Mise en commun de la logique drag&drop
2022-12-03 22:27:20 +01:00
b1e27a9597 On ne peut pas tout donner
Limitation des types d'objets pouvant être donnés à différents acteurs
2022-12-03 18:31:14 +01:00
eaac9564b4 Merge pull request 'La recherche par milieu fonctionnelle' (#586) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #586
2022-12-03 10:05:49 +01:00
0826c7e9e3 Version 10.3.4 2022-12-03 01:26:39 +01:00
bdd3802e72 Amélioration de la recherche dans un milieu
- gestion correcte de la case
- recherche insensitive
- correction de la liste des milieux à ajouter dans la feuille
- si aucun milieu ne correspond à la recherche, affiche la
  liste des milieux disponibles
2022-12-03 01:25:24 +01:00
b07cea40e2 Fallback sur compendium system
quand le compendium configuré n'existe plus, fallback sur le
compendium du système
2022-12-03 01:25:23 +01:00
bb7f4c42ad Feuilles Ingrédient&Herbe extends RdDItemSheet
Pour bénéficier de certains comportements (description...)
2022-12-03 01:25:23 +01:00
ac15a022df Fréquences dans les milieux standard
Saisie dans le compendiums de fréquences pour la liste de milieux
par défaut
2022-12-03 01:25:23 +01:00
a43c725b06 Merge pull request '10.3.3' (#585) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #585
2022-12-02 23:51:15 +01:00
1276c64835 Version 10.3.3 2022-12-02 21:22:43 +01:00
4b4d778d9c Entrée ajoute le milieu aux fréquences 2022-12-02 21:22:11 +01:00
86f9c37b30 Liste des milieux par défaut 2022-12-02 21:22:11 +01:00
ac77c6da9e Fix calcul valeur équipement 2022-12-02 21:22:11 +01:00
344540ea8e Merge pull request 'Fix initiative' (#584) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #584
2022-12-01 08:05:14 +01:00
8a5cf3cb09 Version 10.3.2 2022-12-01 01:25:06 +01:00
3a29570bae Fix message d'expérience invisibles
Les messages d'expérience sur mêlée/tir/lancer (à répartir)
n'étaient plus visibles
2022-12-01 01:22:25 +01:00
02c48f4796 Fix initiative 2022-12-01 00:28:23 +01:00
ee01878fae Fixes suite à dernières modifs 2022-11-30 16:41:39 +01:00
2e1005e909 Merge pull request 'Fixes mineurs' (#583) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #583
2022-11-30 16:40:35 +01:00
98c016696d Correction de l'ajout de milieux parfois KO
Pas sûr du pourquoi, il semble qu'il y a un souci jQuery
(avec deux fenêtres?)
2022-11-30 16:03:56 +01:00
e67ac96e93 Fix: /roll
Regression sur /roll causée par méthode async:
return Promise(false)=>true au lieu de false...
2022-11-30 14:28:11 +01:00
979a48e4d9 Amélioration recherche dans un milieu
Si la recherche est sur un milieu exact, seul ce millieu est considéré.
ie: chercher en "Forêts" ne cherchera pas en "Forêts humides".

Le recherche en "for" cherchera dans tous les milieux contenant "for".
Un message d'avertissement est affiché, et la description de la table
contient la liste des milieux correspondants.

Si plusieurs milieux cherchés ont une fréquence pour une ressource,
la fréquence la plus élevée est utilisée.
2022-11-30 14:24:09 +01:00
faff6e54ef Fix arborescence de conteneur 2022-11-30 14:24:08 +01:00
d493e99bcb Merge pull request 'Tables de compendiums et feuilles de personnages' (#582) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #582
2022-11-30 09:37:49 +01:00
92ae0b7431 Correction aide /tirer milieu 2022-11-30 02:35:12 +01:00
13135ff2ca Version 10.3.0 2022-11-30 02:16:59 +01:00
453e7da848 Création du compendium Faune, Flore, Minéraux
- les tables d'environnement se basent sur ce seul compendium
- les tables de compendiums peuvent chercher plusieurs types d'items
- déplacement de la botanique
- déplacement des sels alchimiques depuis l'équipement
- modification de l'équipement de départ
- modification des acteurs (lien vers compendium de l'item source)
2022-11-30 02:12:31 +01:00
b7a0e5d034 Fréquences par milieu pour l'environnement
Les herbes et les ingrédients peuvent être cherchées/tirées
2022-11-30 02:06:46 +01:00
b7a8b0c08d Standardisation inventaire et prix
Les objets d'inventaire ont maintenant tous:
coût, encombrement, qualité, quantité

Le coût est toujours exprimé en sols, y compris pour les monnaies.
Les paiements et achat-ventes sont fait en sols.
2022-11-29 11:37:49 +01:00
7557d33c73 Feuille de véhicule avec compteurs 2022-11-29 11:37:49 +01:00
e35f77b5a8 Cleanup
- Partage méthode pour label des types
- Methode joining pour concaténation
- suppression template obsolete
- Déplacement de singleton rencontre dans game.system.rdd
- init des commandes lazy
2022-11-29 11:37:11 +01:00
dd4484c17b En-tête de feuille de personnage
Affichage des portraits plus grands
Les boutons sont sur la ligne du nom du personnage
Les états sont affichés sur une colonne
2022-11-29 00:04:50 +01:00
4bd2c1c2b4 Notion d'équipement/inventaire 2022-11-29 00:04:38 +01:00
42c4fe0b29 Standardisation des descriptions
Utilisation des templates sur les items pour la description
2022-11-28 21:02:32 +01:00
39c6478422 Affichage des images de tarot 2022-11-28 21:02:32 +01:00
c06491eb2f Ne pas tronquer les images
Corrections des tailles d'images pour éviter de tronquer
2022-11-28 21:02:32 +01:00
e869d15b24 Correction mise à jour état général 2022-11-28 21:02:32 +01:00
7045b6d8e1 Amélioration du message de confirmation 2022-11-28 21:02:32 +01:00
431d3199db Merge pull request 'v10.2.10: Gestion des tables depuis les compendiums' (#581) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #581
2022-11-26 18:42:10 +01:00
5555705912 10.2.10 2022-11-26 18:12:44 +01:00
4a51f698ab Fix regressions liste dans conteneurs
Les lignes étaient affichées de manière étrange et peu lisible
2022-11-26 18:12:31 +01:00
029bece877 Gestion des désir lancinant/idée fixe
Ajout d'un flag pour les identifier.
2022-11-26 16:58:53 +01:00
31eabbce23 Fix fréquence Mauvais reflet d'ancien rêve 2022-11-26 16:58:53 +01:00
7200ff529f Cleanup 2022-11-26 16:58:53 +01:00
0dacbefd6b Tirer dans les compendiums selon les fréquences 2022-11-26 16:58:53 +01:00
970be67537 Increase release, VincentVK fixes 2022-11-25 07:35:19 +01:00
53aa9cd643 Merge pull request 'Corrections des jets de dés' (#580) from VincentVk/foundryvtt-reve-de-dragon:v10 into v10
Reviewed-on: #580
2022-11-25 07:33:53 +01:00
c435bfa343 Chronologie et année
Fix jour dans chronologie
Permettre de saisir l'année
2022-11-25 03:17:27 +01:00
8667d77169 Ajout de champs pour les tables-compendium
Le champ "frequence" sert à rêgler le nombre d'occurences d'une entrée
du compendium. Le total des fréquences donnera le dé à lancer pour
tirer dans la table. Il servira pour réguler l'apparition des
queues/souffles/... d'un compendium sans avoir besoin de déclarer une
RollTable.

Le champ "hautrevant" permet d'indiquer les queues/souffles/...
réservés aux haut-rêvants.
2022-11-25 03:17:27 +01:00
5e7fcf3c9b fit-content pour toutes les fenêtres roll
C'est le comportement par défaut, donc enlever les surcharges
Corrections pour les fenêtres de combat
2022-11-25 03:17:27 +01:00
e78ae3b292 Chargement depuis les compendium sélectionnés
Fix, les compendiums systèmes n'étaient pas utilisés pour les herbes
et les compétences
2022-11-25 03:17:27 +01:00
97ee5bc331 Amélioration possession
- Créer la possession lors de la première attaque
- Le personnage ciblé par la possession est affiché
2022-11-25 03:17:27 +01:00
c3c0bbc922 Fix jets de caracs dérivées
Appel à la chance, jets de rêve actuel: pas de liste de compétences
2022-11-25 03:17:26 +01:00
9992b64cae Corrections sur les ajustements
- le malus de sur-encombrement est correctement calculé (dans la
  zone d'état)
- par défaut, le sur-encombrement est appliqué
- le sur-encombrement est affiché sur les actions physiques
- l'encombrement s'applique à agilité/dérobée, avec natation/acrobatie
  (par défaut)
- le moral est géré dans le noeud 'use' du rollData
- le moral est associé aux actions physiques
2022-11-25 03:17:26 +01:00
7698147e97 Affichage de la cible dans la fenêtre d'attaque 2022-11-23 22:42:18 +01:00
386 changed files with 14488 additions and 9206 deletions

9
.gitignore vendored
View File

@ -1,5 +1,14 @@
.vscode/settings.json
.idea
.history
todo.md
/.vscode
/ignored/
/node_modules/
/jsconfig.json
/package.json
/package-lock.json
/packs/*/
/packs/*/CURRENT
/packs/*/LOG
/packs/*/LOCK

View File

@ -1,10 +1,57 @@
==================================================================
v0.9.2 - 05/09/2020
# v11.0
Erreur de calcul sur points de vie
Gestion différente des compétences "troncs"
## v11.0.14 - Les pincettes de Khrachtchoum le Problémeux
- Correction du calcul de la place restante lors de l'ajout dans un conteneur
==================================================================
v0.9.1 - 03/09/2020
## v11.0.13 - La multiplication de l'eau de Khrachtchoum le Problémeux
- Correction de la vente depuis un commerce ayant des quantités illimitées
## v11.0.12 - Les poids de la mesure de Khrachtchoum le Problémeux
- Correction des malus de surencombrement
- Le malus armure est correctement affiché dans l'onglet des caractéristiques
- Correction d'orthographe et amélioration des messages des oeuvres d'art
## v11.0.11 - Les bleus de Khrachtchoum le Problémeux
- si le gardien configure le sommeil, les joueurs sont notifiés que chateau dormant vient de passer
- possibilité de créer des armes et des compétences de créatures non-mortelles.
## v11.0.10 - Les Songes de Khrachtchoum le Problémeux
- on peut de nouveau se déplacer dans les TMRs d'un clic sur la case à atteindre
- Lire un livre depuis l'inventaire permet de nouveau de faire un jet de la tâche
créée au lieu de créer toujours une nouvelle tâche
- La sélection des TMR pour la création de signes draconiques ne cause plus d'erreurs
- la récupération d'endurance en cas d'insomnie est limitée à la moitié
- le résultat du sommeil lors d'un rêve de dragon à la première heure s'affiche normalement
- lorsque le gardien gère la durée des nuits, en cas de rêve de dragon,
les heures dormies sont déduites des heures restant à dormir
## v11.0.9 - Les Souvenirs de Khrachtchoum le Problémeux
- mode de saisie de l'archétype en vue détaillée
- création une nouvelle incarnation depuis l'archétype
- réorganisation de la fenêtre de sélection des règles optionnelles
- correction de l'affichage du type dans les fenêtres d'objets
## v11.0.8 - la poigne de Sémolosse
- lien vers le changelog
- organisation des compendiums du système
- correction de l'empoignade
- les items d'empoignade sont ajoutés par le MJ quand nécessaire
- seul le joueur propriétaire du personnage peut effectuer ses choix et actions d'empoignade
- les caractéristiques du défenseur sont utilisées pour la défense
- la difficulté d'attaque est imposée au défenseur
- les attaques particulières sont en finesse (p133)
- on peut entraîner au sol dès 2 points d'empoignade
- les actions liée à l'immobilisation sont proposées en fin de round
# v11.0.7
- les créatures ont maintenant le droit d'avoir des compétences de tir, lancer, mêlée, armes naturelles, parade.
- les créatures armées utilisent la bonne phase d'initiative
- correction des possessions
- la difficulté de la défense est imposée par l'attaque
- une attaque particulière de possession est en finesse
- le rêve actuel des personnages est bien utilisé
- correction des achats par le MJ sans acteur sélectionné
Cf branche v10 pour l'historique des versions 10
Initial official release

BIN
icons/faune/Escargot.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
icons/faune/andurak.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
icons/faune/barbon.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
icons/faune/brocart.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
icons/faune/cancre.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
icons/faune/cancrelas.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
icons/faune/cerf.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
icons/faune/chamois.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
icons/faune/chevre.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
icons/faune/colimace.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
icons/faune/coquille.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
icons/faune/crabe.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
icons/faune/fretin.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
icons/faune/lapin.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
icons/faune/oie.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
icons/faune/oiseau.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
icons/faune/ours.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
icons/faune/padongre.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
icons/faune/poisson.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
icons/faune/rongeur.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
icons/faune/sanglier.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
icons/faune/saumon.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
icons/faune/singe-vert.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
icons/faune/soldieze.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
icons/faune/ver.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
icons/faune/wolf-head.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
icons/sante/blessure.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
icons/sante/eraflure.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
icons/sante/mort.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
icons/services/biere.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
icons/services/lit.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
icons/services/repas.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
icons/services/verre.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
icons/services/vin.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -1,49 +1,56 @@
{
"ACTOR": {
"TypePersonnage": "Personnage",
"TypeCreature": "Créature",
"TypeEntite": "Entité de cauchemar",
"TypeVehicule": "Véhicule"
},
"ITEM": {
"TypeObjet": "Objet",
"TypeGemme": "Gemme",
"TypeCompetence": "Compétence",
"TypeCompetencecreature": "Compétence de créature",
"TypeMaladie": "Maladie",
"TypePoison": "Poison",
"TypeNombreastral": "Nombre astral",
"TypeTarot": "Carte de tarot",
"TypeCasetmr": "TMR spéciale",
"TypeRencontre": "Rencontre TMR",
"TypeMunition": "Munition",
"TypeMonnaie": "Monnaie",
"TypeHerbe": "Herbe ou plante",
"TypeIngredient": "Ingrédient",
"TypeLivre": "Livre",
"TypePotion": "Potion",
"TypeArme": "Arme",
"TypeArmure": "Armure",
"TypeConteneur": "Conteneur",
"TypeNourritureboisson": "Nourriture & boisson",
"TypeChant": "Chant",
"TypeDanse": "Danse",
"TypeMusique": "Musique",
"TypeOeuvre": "Oeuvre",
"TypeTache": "Tâche",
"TypeJeu": "Jeu",
"TypeRecettealchimique": "Recette alchimique",
"TypeRecettecuisine": "Recette de cuisine",
"TypeSort": "Sort",
"TypeMeditation": "Méditation",
"TypeSignedraconique": "Signe draconique",
"TypeQueue": "Queue de Dragon",
"TypeOmbre": "Ombre de Thanatos",
"TypeSouffle": "Souffle de Dragon",
"TypeTete": "te de Dragon",
"TypePossession": "Possession",
"TypeSortreserve": "Sort en réserve",
"TypeExtraitpoetique": "Extrait poetique"
"TYPES": {
"Actor": {
"Personnage": "Personnage",
"Creature": "Créature",
"Entite": "Entité de cauchemar",
"Commerce": "Commerce",
"Vehicule": "Véhicule"
},
"Item": {
"Arme": "Arme",
"Armure": "Armure",
"Blessure": "Blessure",
"Casetmr": "TMR spéciale",
"Chant": "Chant",
"Competence": "Compétence",
"Competencecreature": "Compétence de créature",
"Conteneur": "Conteneur",
"Danse": "Danse",
"Extraitpoetique": "Extrait poetique",
"Faune": "Faune",
"Gemme": "Gemme",
"Herbe": "Herbe",
"Ingredient": "Ingrédient",
"Jeu": "Jeu",
"Livre": "Livre",
"Maladie": "Maladie",
"Meditation": "Méditation",
"Monnaie": "Monnaie",
"Munition": "Munition",
"Musique": "Musique",
"Nombreastral": "Nombre astral",
"Nourritureboisson": "Nourriture & boisson",
"Objet": "Objet",
"Oeuvre": "Oeuvre",
"Ombre": "Ombre de Thanatos",
"Plante": "Plante",
"Poison": "Poison",
"Possession": "Possession",
"Potion": "Potion",
"Queue": "Queue de Dragon",
"Recettealchimique": "Recette alchimique",
"Recettecuisine": "Recette de cuisine",
"Rencontre": "Rencontre TMR",
"Service": "Service",
"Signedraconique": "Signe draconique",
"Sort": "Sort",
"Sortreserve": "Sort en réserve",
"Souffle": "Souffle de Dragon",
"Tache": "Tâche",
"Tarot": "Carte de tarot",
"Tete": "Tête de Dragon"
}
},
"EFFECT": {
"StatusStunned": "Sonné",

View File

@ -27,24 +27,17 @@ export class RdDActorCreatureSheet extends RdDActorSheet {
if (!this.options.editable) return;
// On competence change
html.find('.creature-carac').change(async event => {
this.html.find('.creature-carac').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCreatureCompetence(compName, "carac_value", parseInt(event.target.value));
});
html.find('.creature-niveau').change(async event => {
this.html.find('.creature-niveau').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCreatureCompetence(compName, "niveau", parseInt(event.target.value));
});
html.find('.creature-dommages').change(async event => {
this.html.find('.creature-dommages').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCreatureCompetence(compName, "dommages", parseInt(event.target.value));
});
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
// Update the Actor
return this.object.update(formData);
}
}

View File

@ -1,40 +1,65 @@
import { RdDActorSheet } from "./actor-sheet.js";
import { RdDSheetUtility } from "./rdd-sheet-utility.js";
import { RdDUtility } from "./rdd-utility.js";
export class RdDActorEntiteSheet extends RdDActorSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["rdd", "sheet", "actor"],
template: "systems/foundryvtt-reve-de-dragon/templates/actor-entite-sheet.html",
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["rdd", "sheet", "actor"],
template: "systems/foundryvtt-reve-de-dragon/templates/actor-entite-sheet.html",
width: 640,
height: 720,
tabs: [{navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "carac"}],
dragDrop: [{dragSelector: ".item-list .item", dropSelector: undefined}]
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "carac" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: undefined }]
});
}
async getData() {
let formData = await super.getData();
formData.resonances = this.actor.system.sante.resonnance.actors.map(actorId => game.actors.get(actorId))
.map(actor => { return { id: actor.id, name: actor.name, img: actor.img } })
return formData
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// On competence change
html.find('.creature-carac').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCreatureCompetence( compName, "carac_value", parseInt(event.target.value) );
} );
html.find('.creature-niveau').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCreatureCompetence( compName, "niveau", parseInt(event.target.value) );
} );
html.find('.creature-dommages').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCreatureCompetence( compName, "dommages", parseInt(event.target.value) );
} );
this.html.find('.creature-carac').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCreatureCompetence(compName, "carac_value", parseInt(event.target.value));
});
this.html.find('.creature-niveau').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCreatureCompetence(compName, "niveau", parseInt(event.target.value));
});
this.html.find('.creature-dommages').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCreatureCompetence(compName, "dommages", parseInt(event.target.value));
});
this.html.find('.resonance-delete').click(async event => {
const li = RdDSheetUtility.getEventElement(event);
const actorId = li.data("actor-id");
if (actorId) {
const actorResonance = game.actors.get(actorId);
RdDUtility.confirmerSuppressionSubacteur(this, actorResonance, li, () => {
console.log('Delete : ', actorId);
this.removeSubacteur(actorId);
RdDUtility.slideOnDelete(this, li);
});
}
});
}
async removeSubacteur(actorId) {
let newResonances = this.actor.system.sante.resonnance.actors.filter(id => id != actorId);
await this.actor.update({ 'system.sante.resonnance.actors': newResonances }, { renderSheet: false });
}
}

View File

@ -7,17 +7,21 @@ import { Misc } from "./misc.js";
import { RdDCombatManager } from "./rdd-combat.js";
import { RdDCarac } from "./rdd-carac.js";
import { DialogSplitItem } from "./dialog-split-item.js";
import { ReglesOptionelles } from "./settings/regles-optionelles.js";
import { DialogRepos } from "./dialog-repos.js";
import { ReglesOptionnelles } from "./settings/regles-optionnelles.js";
import { RdDSheetUtility } from "./rdd-sheet-utility.js";
import { STATUSES } from "./settings/status-effects.js";
import { MAINS_DIRECTRICES } from "./actor.js";
import { RdDBaseActorSheet } from "./actor/base-actor-sheet.js";
import { RdDItem } from "./item.js";
import { RdDItemBlessure } from "./item/blessure.js";
import { RdDEmpoignade } from "./rdd-empoignade.js";
/* -------------------------------------------- */
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
export class RdDActorSheet extends ActorSheet {
export class RdDActorSheet extends RdDBaseActorSheet {
/** @override */
static get defaultOptions() {
@ -25,60 +29,50 @@ export class RdDActorSheet extends ActorSheet {
return mergeObject(super.defaultOptions, {
classes: ["rdd", "sheet", "actor"],
template: "systems/foundryvtt-reve-de-dragon/templates/actor-sheet.html",
width: 640,
width: 550,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "carac" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: undefined }],
showCompNiveauBase: false,
vueDetaillee: false
vueDetaillee: false,
vueArchetype: false,
});
}
/* -------------------------------------------- */
async getData() {
let formData = await super.getData();
mergeObject(formData,
{
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
effects: this.actor.effects.map(e => foundry.utils.deepClone(e)),
limited: this.actor.limited,
owner: this.actor.isOwner,
biographie: await TextEditor.enrichHTML(this.actor.system.biographie, { async: true }),
notes: await TextEditor.enrichHTML(this.actor.system.notes, { async: true }),
});
mergeObject(formData.calc, {
surenc: this.actor.computeMalusSurEncombrement(),
surprise: RdDBonus.find(this.actor.getSurprise(false)).descr,
resumeBlessures: this.actor.computeResumeBlessure(this.actor.system.blessures),
caracTotal: RdDCarac.computeTotal(this.actor.system.carac, this.actor.system.beaute),
surEncombrementMessage: this.actor.getMessageSurEncombrement(),
malusArmure: this.actor.getMalusArmure()
})
this.timerRecherche = undefined;
let formData = {
title: this.title,
id: this.actor.id,
type: this.actor.type,
img: this.actor.img,
name: this.actor.name,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
system: foundry.utils.deepClone(this.actor.system),
effects: this.actor.effects.map(e => foundry.utils.deepClone(e)),
limited: this.actor.limited,
options: this.options,
owner: this.actor.isOwner,
description: await TextEditor.enrichHTML(this.object.system.description, {async: true}),
biographie: await TextEditor.enrichHTML(this.object.system.biographie, {async: true}),
notes: await TextEditor.enrichHTML(this.object.system.notes, {async: true}),
notesmj: await TextEditor.enrichHTML(this.object.system.notesmj, {async: true}),
calc: {
encTotal: await this.actor.computeEncombrementTotalEtMalusArmure(),
prixTotalEquipement: this.actor.computePrixTotalEquipement(),
surprise: RdDBonus.find(this.actor.getSurprise(false)).descr,
resumeBlessures: this.actor.computeResumeBlessure(this.actor.system.blessures),
caracTotal: RdDCarac.computeTotal(this.actor.system.carac, this.actor.system.beaute),
surEncombrementMessage: this.actor.getMessageSurEncombrement(),
},
}
formData.options.isGM = game.user.isGM;
RdDUtility.filterItemsPerTypeForSheet(formData, this.actor.itemTypes);
this.objetVersConteneur = RdDUtility.buildArbreDeConteneurs(formData.conteneurs, formData.objets);
formData.conteneurs = RdDUtility.conteneursRacine(formData.conteneurs);
if (formData.type == 'personnage') {
formData.options.mainsDirectrices = MAINS_DIRECTRICES;
formData.byCateg = Misc.classify(formData.competences, it => it.system.categorie)
formData.calc.comptageArchetype = RdDItemCompetence.computeResumeArchetype(formData.competences);
formData.calc.competenceXPTotal= RdDItemCompetence.computeTotalXP(formData.competences);
formData.calc.fatigue= RdDUtility.calculFatigueHtml(formData.system.sante.fatigue.value, formData.system.sante.endurance.max);
formData.calc.comptageArchetype = RdDItemCompetence.computeResumeArchetype(formData.competences);
formData.calc.competenceXPTotal = RdDItemCompetence.computeTotalXP(formData.competences);
formData.calc.fatigue = RdDUtility.calculFatigueHtml(formData.system.sante.fatigue.value, formData.system.sante.endurance.max);
formData.competences.forEach(item => {
item.system.isVisible = this.options.recherche
? RdDItemCompetence.nomContientTexte(item, this.options.recherche.text)
: (!this.options.showCompNiveauBase || !RdDItemCompetence.isNiveauBase(item));
item.system.isHidden = this.options.recherche
? !item.isNomLike(this.options.recherche.text)
: (this.options.showCompNiveauBase && RdDItemCompetence.isNiveauBase(item));
RdDItemCompetence.levelUp(item, formData.system.compteurs.experience.value);
});
@ -92,6 +86,7 @@ export class RdDActorSheet extends ActorSheet {
RdDItemArme.ajoutCorpsACorps(formData.combat, formData.competences, formData.system.carac);
formData.esquives = this.actor.getCompetences("Esquive");
formData.combat = RdDCombatManager.listActionsArmes(formData.combat, formData.competences, formData.system.carac);
formData.empoignades = this.actor.getEmpoignades();
this.armesList = formData.combat;
@ -117,6 +112,346 @@ export class RdDActorSheet extends ActorSheet {
return formData;
}
/* -------------------------------------------- */ /** @override */
activateListeners(html) {
super.activateListeners(html);
HtmlUtility.showControlWhen(this.html.find(".appliquerFatigue"), ReglesOptionnelles.isUsing("appliquer-fatigue"));
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
this.html.find('.item-action').click(async event => {
const item = RdDSheetUtility.getItem(event, this.actor);
item?.actionPrincipale(this.actor, async () => this.render())
});
this.html.find('.subacteur-delete').click(async event => {
const li = RdDSheetUtility.getEventElement(event);
const actorId = li.data("actor-id");
if (actorId) {
const subActor = game.actors.get(actorId);
RdDUtility.confirmerSuppressionSubacteur(this, subActor, li, () => {
console.log('Delete : ', subActor.id);
this.actor.removeSubacteur(subActor.id);
RdDUtility.slideOnDelete(this, li);
});
}
});
this.html.find('.experiencelog-delete').click(async event => {
const li = this.html.find(event.currentTarget)?.parents(".experiencelog");
const key = Number(li.data("key") ?? -1);
await this.actor.deleteExperienceLog(key, 1);
});
this.html.find('.experiencelog-delete-previous').click(async event => {
const li = this.html.find(event.currentTarget)?.parents(".experiencelog");
const key = Number(li.data("key") ?? -1);
await this.actor.deleteExperienceLog(0, key + 1);
});
this.html.find("input.derivee-value[name='system.compteurs.stress.value']").change(async event => {
this.actor.updateCompteurValue("stress", parseInt(event.target.value));
});
this.html.find("input.derivee-value[name='system.compteurs.experience.value']").change(async event => {
this.actor.updateCompteurValue("experience", parseInt(event.target.value));
});
this.html.find('.encaisser-direct').click(async event => {
this.actor.encaisser();
})
this.html.find('.sheet-possession-attack').click(async event => {
const poss = RdDSheetUtility.getItem(event, this.actor)
this.actor.conjurerPossession(poss)
})
this.html.find('.remise-a-neuf').click(async event => {
if (game.user.isGM) {
this.actor.remiseANeuf();
}
});
this.html.find('.creer-tache').click(async event => {
this.createEmptyTache();
});
this.html.find('.creer-tache-blessure-legere').click(async event => RdDItemBlessure.createTacheSoinBlessure(this.actor, 2));
this.html.find('.creer-tache-blessure-grave').click(async event => RdDItemBlessure.createTacheSoinBlessure(this.actor, 4));
this.html.find('.creer-tache-blessure-critique').click(async event => RdDItemBlessure.createTacheSoinBlessure(this.actor, 6));
this.html.find('.creer-blessure-legere').click(async event => RdDItemBlessure.createBlessure(this.actor, 2));
this.html.find('.creer-blessure-grave').click(async event => RdDItemBlessure.createBlessure(this.actor, 4));
this.html.find('.creer-blessure-critique').click(async event => RdDItemBlessure.createBlessure(this.actor, 6));
this.html.find('.creer-une-oeuvre').click(async event => {
this.selectTypeOeuvreToCreate();
});
this.html.find('.blessure-premierssoins-done').change(async event => {
const blessure = this.getBlessure(event);
await blessure?.setSoinsBlessure({ premierssoins: { done: event.currentTarget.checked } });
});
this.html.find('.blessure-soinscomplets-done').change(async event => {
const blessure = this.getBlessure(event);
await blessure?.setSoinsBlessure({ soinscomplets: { done: event.currentTarget.checked } })
});
this.html.find('.blessure-premierssoins-bonus').change(async event => {
const blessure = this.getBlessure(event);
await blessure?.setSoinsBlessure({ premierssoins: { bonus: Number(event.currentTarget.value) } })
});
this.html.find('.blessure-soinscomplets-bonus').change(async event => {
const blessure = this.getBlessure(event);
await blessure?.setSoinsBlessure({ soinscomplets: { bonus: Number(event.currentTarget.value) } })
});
// Equip Inventory Item
this.html.find('.item-equip').click(async event => {
this.actor.equiperObjet(RdDSheetUtility.getItemId(event));
});
// Roll Carac
this.html.find('.carac-label a').click(async event => {
let caracName = event.currentTarget.attributes.name.value;
this.actor.rollCarac(caracName.toLowerCase());
});
this.html.find('.chance-actuelle').click(async event => {
this.actor.rollCarac('chance-actuelle');
});
this.html.find('.chance-appel').click(async event => {
this.actor.rollAppelChance();
});
this.html.find('[name="jet-astrologie"]').click(async event => {
this.actor.astrologieNombresAstraux();
});
// Roll Skill
this.html.find('a.competence-label').click(async event => {
this.actor.rollCompetence(RdDSheetUtility.getItemId(event));
});
this.html.find('.tache-label a').click(async event => {
this.actor.rollTache(RdDSheetUtility.getItemId(event));
});
this.html.find('.meditation-label a').click(async event => {
this.actor.rollMeditation(RdDSheetUtility.getItemId(event));
});
this.html.find('.chant-label a').click(async event => {
this.actor.rollChant(RdDSheetUtility.getItemId(event));
});
this.html.find('.danse-label a').click(async event => {
this.actor.rollDanse(RdDSheetUtility.getItemId(event));
});
this.html.find('.musique-label a').click(async event => {
this.actor.rollMusique(RdDSheetUtility.getItemId(event));
});
this.html.find('.oeuvre-label a').click(async event => {
this.actor.rollOeuvre(RdDSheetUtility.getItemId(event));
});
this.html.find('.jeu-label a').click(async event => {
this.actor.rollJeu(RdDSheetUtility.getItemId(event));
});
this.html.find('.recettecuisine-label a').click(async event => {
this.actor.rollRecetteCuisine(RdDSheetUtility.getItemId(event));
});
this.html.find('.subacteur-label a').click(async event => {
let actorId = RdDSheetUtility.getEventItemData(event, 'actor-id');
let actor = game.actors.get(actorId);
if (actor) {
actor.sheet.render(true);
}
});
// Boutons spéciaux MJs
this.html.find('.forcer-tmr-aleatoire').click(async event => {
this.actor.reinsertionAleatoire("Action MJ");
});
this.html.find('.afficher-tmr').click(async event => {
this.actor.changeTMRVisible();
});
// Points de reve actuel
this.html.find('.ptreve-actuel a').click(async event => {
this.actor.rollCarac('reve-actuel', true);
});
// Suite empoignade
this.html.find('.empoignade-label a').click(async event => {
let emp = RdDSheetUtility.getItem(event, this.actor)
RdDEmpoignade.onAttaqueEmpoignadeFromItem(emp)
});
// Roll Weapon1
this.html.find('.arme-label a').click(async event => {
let arme = this._getEventArmeCombat(event);
this.actor.rollArme(duplicate(arme));
});
// Initiative pour l'arme
this.html.find('.arme-initiative a').click(async event => {
let combatant = game.combat.combatants.find(c => c.actor.id == this.actor.id);
if (combatant) {
let action = this._getEventArmeCombat(event);
RdDCombatManager.rollInitiativeAction(combatant._id, action);
} else {
ui.notifications.info("Impossible de lancer l'initiative sans être dans un combat.");
}
});
// Display TMR, visualisation
this.html.find('.visu-tmr').click(async event => {
this.actor.displayTMR("visu");
});
// Display TMR, normal
this.html.find('.monte-tmr').click(async event => {
this.actor.displayTMR("normal");
});
// Display TMR, fast
this.html.find('.monte-tmr-rapide').click(async event => {
this.actor.displayTMR("rapide");
});
this.html.find('.repos').click(async event => {
await this.actor.repos();
});
this.html.find('.delete-active-effect').click(async event => {
if (game.user.isGM) {
let effect = this.html.find(event.currentTarget).parents(".active-effect").data('effect');
this.actor.removeEffect(effect);
}
});
this.html.find('.enlever-tous-effets').click(async event => {
if (game.user.isGM) {
await this.actor.removeEffects();
}
});
this.html.find('.carac-xp-augmenter').click(async event => {
let caracName = event.currentTarget.name.replace("augmenter.", "");
this.actor.updateCaracXPAuto(caracName);
});
this.html.find('.competence-xp-augmenter').click(async event => {
this.actor.updateCompetenceXPAuto(RdDSheetUtility.getItemId(event));
});
this.html.find('.competence-stress-augmenter').click(async event => {
this.actor.updateCompetenceStress(RdDSheetUtility.getItemId(event));
});
if (this.options.vueDetaillee) {
// On carac change
this.html.find('.carac-value').change(async event => {
let caracName = event.currentTarget.name.replace(".value", "").replace("system.carac.", "");
this.actor.updateCarac(caracName, parseInt(event.target.value));
});
this.html.find('input.carac-xp').change(async event => {
let caracName = event.currentTarget.name.replace(".xp", "").replace("system.carac.", "");
this.actor.updateCaracXP(caracName, parseInt(event.target.value));
});
// On competence change
this.html.find('.competence-value').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
//console.log("Competence changed :", compName);
this.actor.updateCompetence(compName, parseInt(event.target.value));
});
// On competence xp change
this.html.find('input.competence-xp').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCompetenceXP(compName, parseInt(event.target.value));
});
// On competence xp change
this.html.find('input.competence-xp-sort').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCompetenceXPSort(compName, parseInt(event.target.value));
});
this.html.find('.toggle-archetype').click(async event => {
this.options.vueArchetype = !this.options.vueArchetype;
this.render(true);
});
// On competence archetype change
this.html.find('.competence-archetype').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCompetenceArchetype(compName, parseInt(event.target.value));
});
}
this.html.find('.nouvelle-incarnation').click(async event => {
this.actor.nouvelleIncarnation();
});
this.html.find('.show-hide-competences').click(async event => {
this.options.showCompNiveauBase = !this.options.showCompNiveauBase;
this.render(true);
});
this.html.find('.vue-detaillee').click(async event => {
this.options.vueDetaillee = !this.options.vueDetaillee;
this.render(true);
});
// On pts de reve change
this.html.find('.pointsreve-value').change(async event => {
let reveValue = event.currentTarget.value;
this.actor.update({ "system.reve.reve.value": reveValue });
});
// On seuil de reve change
this.html.find('.seuil-reve-value').change(async event => {
console.log("seuil-reve-value", event.currentTarget)
this.actor.setPointsDeSeuil(event.currentTarget.value);
});
// On stress change
this.html.find('.compteur-edit').change(async event => {
let fieldName = event.currentTarget.attributes.name.value;
this.actor.updateCompteurValue(fieldName, parseInt(event.target.value));
});
this.html.find('.stress-test').click(async event => {
this.actor.transformerStress();
});
this.html.find('.moral-malheureux').click(async event => {
this.actor.jetDeMoral('malheureuse');
});
this.html.find('.moral-neutre').click(async event => {
this.actor.jetDeMoral('neutre');
});
this.html.find('.moral-heureux').click(async event => {
this.actor.jetDeMoral('heureuse');
});
this.html.find('.ethylisme-test').click(async event => {
this.actor.jetEthylisme();
});
this.html.find('.jet-vie').click(async event => {
this.actor.jetVie();
});
this.html.find('.jet-endurance').click(async event => {
this.actor.jetEndurance();
});
this.html.find('.vie-plus').click(async event => {
this.actor.santeIncDec("vie", 1);
});
this.html.find('.vie-moins').click(async event => {
this.actor.santeIncDec("vie", -1);
});
this.html.find('.endurance-plus').click(async event => {
this.actor.santeIncDec("endurance", 1);
});
this.html.find('.endurance-moins').click(async event => {
this.actor.santeIncDec("endurance", -1);
});
this.html.find('.ptreve-actuel-plus').click(async event => {
this.actor.reveActuelIncDec(1);
});
this.html.find('.ptreve-actuel-moins').click(async event => {
this.actor.reveActuelIncDec(-1);
});
this.html.find('.fatigue-plus').click(async event => {
this.actor.santeIncDec("fatigue", 1);
});
this.html.find('.fatigue-moins').click(async event => {
this.actor.santeIncDec("fatigue", -1);
});
}
getBlessure(event) {
const itemId = this.html.find(event.currentTarget).parents(".item-blessure").data('item-id');
const blessure = this.actor.getItem(itemId, 'blessure');
return blessure;
}
isCompetenceAffichable(competence) {
return !this.options.showCompNiveauBase || !RdDItemCompetence.isNiveauBase(competence);
}
@ -129,422 +464,34 @@ export class RdDActorSheet extends ActorSheet {
}
/* -------------------------------------------- */
async _onDropItem(event, dragData) {
const destItemId = $(event.target)?.closest('.item').attr('data-item-id')
const dropParams = RdDSheetUtility.prepareItemDropParameters(destItemId, this.actor.id, dragData, this.objetVersConteneur)
const callSuper = await this.actor.processDropItem(dropParams)
if (callSuper) {
await super._onDropItem(event, dragData)
async selectTypeOeuvreToCreate() {
let types = RdDItem.getTypesOeuvres();
let content = `<span class="competence-label">Selectionnez le type d'oeuvre</span><select class="item-type">`;
for (let typeName of types) {
content += `<option value="${typeName}">${Misc.typeName('Item', typeName)}</option>`
}
}
/* -------------------------------------------- */
async createItem(name, type) {
await this.actor.createEmbeddedDocuments('Item', [{ name: name, type: type }], { renderSheet: true });
content += '</select>';
let dialog = new Dialog({
title: "Créer une oeuvre",
content: content,
buttons: {
create: {
icon: '<i class="fas fa-check"></i>',
label: "Créer l'oeuvre",
callback: () => this.actor.createItem($(".item-type").val())
}
}
});
dialog.render(true);
}
/* -------------------------------------------- */
async createEmptyTache() {
await this.createItem('Nouvelle tache', 'tache');
}
/* -------------------------------------------- */ /** @override */
activateListeners(html) {
super.activateListeners(html);
HtmlUtility._showControlWhen($(".appliquerFatigue"), ReglesOptionelles.isUsing("appliquer-fatigue"));
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
html.find('.item-split').click(async event => {
const item = RdDSheetUtility.getItem(event, this.actor);
RdDSheetUtility.splitItem(item, this.actor);
});
html.find('.item-edit').click(async event => {
const item = RdDSheetUtility.getItem(event, this.actor)
item.sheet.render(true)
})
html.find('.display-label a').click(async event => {
const item = RdDSheetUtility.getItem(event, this.actor);
item.sheet.render(true);
});
html.find('.item-delete').click(async event => {
const li = RdDSheetUtility.getEventElement(event);
const item = this.actor.getObjet(li.data("item-id"));
RdDUtility.confirmerSuppressionItem(this, item, li);
});
html.find('.item-vendre').click(async event => {
const item = RdDSheetUtility.getItem(event, this.actor);
item?.proposerVente();
});
html.find('.item-montrer').click(async event => {
const item = RdDSheetUtility.getItem(event, this.actor);
item?.postItem();
});
html.find('.item-action').click(async event => {
const item = RdDSheetUtility.getItem(event, this.actor)
this.actor.actionItem(item);
});
html.find('.subacteur-delete').click(async event => {
const li = RdDSheetUtility.getEventElement(event);
const actorId = li.data("actor-id");
if (actorId) {
const subActor = game.actors.get(actorId);
RdDUtility.confirmerSuppressionSubacteur(this, subActor, li);
}
});
html.find('.experiencelog-delete').click(async event => {
const li = $(event.currentTarget)?.parents(".experiencelog");
const key = Number(li.data("key") ?? -1);
await this.actor.deleteExperienceLog(key, 1);
});
html.find('.experiencelog-delete-previous').click(async event => {
const li = $(event.currentTarget)?.parents(".experiencelog");
const key = Number(li.data("key") ?? -1);
await this.actor.deleteExperienceLog(0, key + 1);
});
html.find('.encaisser-direct').click(async event => {
this.actor.encaisser();
})
html.find('.sheet-possession-attack').click(async event => {
const poss = RdDSheetUtility.getItem(event, this.actor)
this.actor.conjurerPossession(poss)
})
html.find('.remise-a-neuf').click(async event => {
if (game.user.isGM) {
this.actor.remiseANeuf();
}
});
html.find('.creer-tache').click(async event => {
this.createEmptyTache();
});
html.find('.creer-un-objet').click(async event => {
RdDUtility.selectObjetType(this);
});
html.find('.creer-une-oeuvre').click(async event => {
RdDUtility.selectTypeOeuvre(this);
});
html.find('.nettoyer-conteneurs').click(async event => {
this.actor.nettoyerConteneurs();
});
// Blessure control
html.find('.blessure-control').click(async event => {
const tr = $(event.currentTarget).parents(".item");
let btype = tr.data("blessure-type");
let index = tr.data('blessure-index');
let active = $(event.currentTarget).data('blessure-active');
//console.log(btype, index, active);
await this.actor.manageBlessureFromSheet(btype, index, active);
});
// Blessure data
html.find('.blessure-soins').change(async event => {
const tr = $(event.currentTarget).parents(".item");
let btype = tr.data('blessure-type');
let index = tr.data('blessure-index');
let psoins = tr.find('.blessure-premiers_soins').val();
let pcomplets = tr.find('.blessure-soins_complets').val();
let jours = tr.find('.blessure-jours').val();
let loc = tr.find('.blessure-localisation').val();
let psdone = tr.find('.blessure-psdone:checked').val();
let scdone = tr.find('.blessure-scdone:checked').val();
console.log(btype, index, psoins, pcomplets, jours, loc, psdone, scdone);
await this.actor.setDataBlessureFromSheet(btype, index, psoins, pcomplets, jours, loc, psdone, scdone);
});
// Equip Inventory Item
html.find('.item-equip').click(async event => {
this.actor.equiperObjet(RdDSheetUtility.getItemId(event));
});
// Roll Carac
html.find('.carac-label a').click(async event => {
let caracName = event.currentTarget.attributes.name.value;
this.actor.rollCarac(caracName.toLowerCase());
});
html.find('.chance-actuelle').click(async event => {
this.actor.rollCarac('chance-actuelle');
});
html.find('.chance-appel').click(async event => {
this.actor.rollAppelChance();
});
html.find('#jet-astrologie').click(async event => {
this.actor.astrologieNombresAstraux();
});
// Roll Skill
html.find('a.competence-label').click(async event => {
this.actor.rollCompetence(RdDSheetUtility.getItemId(event));
});
html.find('.tache-label a').click(async event => {
this.actor.rollTache(RdDSheetUtility.getItemId(event));
});
html.find('.meditation-label a').click(async event => {
this.actor.rollMeditation(RdDSheetUtility.getItemId(event));
});
html.find('.chant-label a').click(async event => {
this.actor.rollChant(RdDSheetUtility.getItemId(event));
});
html.find('.danse-label a').click(async event => {
this.actor.rollDanse(RdDSheetUtility.getItemId(event));
});
html.find('.musique-label a').click(async event => {
this.actor.rollMusique(RdDSheetUtility.getItemId(event));
});
html.find('.oeuvre-label a').click(async event => {
this.actor.rollOeuvre(RdDSheetUtility.getItemId(event));
});
html.find('.jeu-label a').click(async event => {
this.actor.rollJeu(RdDSheetUtility.getItemId(event));
});
html.find('.recettecuisine-label a').click(async event => {
this.actor.rollRecetteCuisine(RdDSheetUtility.getItemId(event));
});
html.find('.subacteur-label a').click(async event => {
let actorId = RdDSheetUtility.getEventItemData(event, 'actor-id');
let actor = game.actors.get(actorId);
if (actor) {
actor.sheet.render(true);
}
});
// Boutons spéciaux MJs
html.find('.forcer-tmr-aleatoire').click(async event => {
this.actor.reinsertionAleatoire("Action MJ");
});
html.find('.afficher-tmr').click(async event => {
this.actor.changeTMRVisible();
});
// Points de reve actuel
html.find('.ptreve-actuel a').click(async event => {
this.actor.rollCarac('reve-actuel');
});
// Roll Weapon1
html.find('.arme-label a').click(async event => {
let arme = this._getEventArmeCombat(event);
this.actor.rollArme(duplicate(arme));
});
// Initiative pour l'arme
html.find('.arme-initiative a').click(async event => {
let combatant = game.combat.combatants.find(c => c.actor.id == this.actor.id);
if (combatant) {
let action = this._getEventArmeCombat(event);
RdDCombatManager.rollInitiativeAction(combatant._id, action);
} else {
ui.notifications.info("Impossible de lancer l'initiative sans être dans un combat.");
}
});
// Display TMR, visualisation
html.find('.visu-tmr').click(async event => {
this.actor.displayTMR("visu");
});
// Display TMR, normal
html.find('.monte-tmr').click(async event => {
this.actor.displayTMR("normal");
});
// Display TMR, fast
html.find('.monte-tmr-rapide').click(async event => {
this.actor.displayTMR("rapide");
});
html.find('.repos').click(async event => {
await DialogRepos.create(this.actor);
});
html.find('.delete-active-effect').click(async event => {
if (game.user.isGM) {
let effect = $(event.currentTarget).parents(".active-effect").data('effect');
this.actor.removeEffect(effect);
}
});
html.find('.enlever-tous-effets').click(async event => {
if (game.user.isGM) {
await this.actor.removeEffects();
}
});
html.find('.conteneur-name a').click(async event => {
RdDUtility.toggleAfficheContenu(RdDSheetUtility.getItemId(event));
this.render(true);
});
html.find('.carac-xp-augmenter').click(async event => {
let caracName = event.currentTarget.name.replace("augmenter.", "");
this.actor.updateCaracXPAuto(caracName);
});
html.find('.competence-xp-augmenter').click(async event => {
this.actor.updateCompetenceXPAuto(RdDSheetUtility.getItemId(event));
});
html.find('.competence-stress-augmenter').click(async event => {
this.actor.updateCompetenceStress(RdDSheetUtility.getItemId(event));
});
if (this.options.vueDetaillee) {
// On carac change
html.find('.carac-value').change(async event => {
let caracName = event.currentTarget.name.replace(".value", "").replace("system.carac.", "");
this.actor.updateCarac(caracName, parseInt(event.target.value));
});
html.find('input.carac-xp').change(async event => {
let caracName = event.currentTarget.name.replace(".xp", "").replace("system.carac.", "");
this.actor.updateCaracXP(caracName, parseInt(event.target.value));
});
// On competence change
html.find('.competence-value').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
//console.log("Competence changed :", compName);
this.actor.updateCompetence(compName, parseInt(event.target.value));
});
// On competence xp change
html.find('input.competence-xp').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCompetenceXP(compName, parseInt(event.target.value));
});
// On competence xp change
html.find('input.competence-xp-sort').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCompetenceXPSort(compName, parseInt(event.target.value));
});
// On competence archetype change
html.find('.competence-archetype').change(async event => {
let compName = event.currentTarget.attributes.compname.value;
this.actor.updateCompetenceArchetype(compName, parseInt(event.target.value));
});
}
html.find('.show-hide-competences').click(async event => {
this.options.showCompNiveauBase = !this.options.showCompNiveauBase;
this.render(true);
});
html.find('.recherche')
.each((index, field) => {
if (this.options.recherche) {
field.focus();
field.setSelectionRange(this.options.recherche.start, this.options.recherche.end);
}
})
.keyup(async event => {
const nouvelleRecherche = this._optionRecherche(event.currentTarget);
if (this.options.recherche?.text != nouvelleRecherche?.text){
this.options.recherche = nouvelleRecherche;
if (this.timerRecherche) {
clearTimeout(this.timerRecherche);
}
this.timerRecherche = setTimeout(() => {
this.timerRecherche = undefined;
this.render(true);
}, 500);
}
})
.change(async event =>
this.options.recherche = this._optionRecherche(event.currentTarget)
);
html.find('.vue-detaillee').click(async event => {
this.options.vueDetaillee = !this.options.vueDetaillee;
this.render(true);
});
// On pts de reve change
html.find('.pointsreve-value').change(async event => {
let reveValue = event.currentTarget.value;
this.actor.update({ "system.reve.reve.value": reveValue });
});
// On seuil de reve change
html.find('.seuil-reve-value').change(async event => {
console.log("seuil-reve-value", event.currentTarget)
this.actor.setPointsDeSeuil(event.currentTarget.value);
});
html.find('#attribut-protection-edit').change(async event => {
this.actor.updateAttributeValue(event.currentTarget.attributes.name.value, parseInt(event.target.value));
});
// On stress change
html.find('.compteur-edit').change(async event => {
let fieldName = event.currentTarget.attributes.name.value;
this.actor.updateCompteurValue(fieldName, parseInt(event.target.value));
});
html.find('#ethylisme').change(async event => {
this.actor.setEthylisme(parseInt(event.target.value));
});
html.find('.stress-test').click(async event => {
this.actor.transformerStress();
});
html.find('.moral-malheureux').click(async event => {
this.actor.jetDeMoral('malheureuse');
});
html.find('.moral-neutre').click(async event => {
this.actor.jetDeMoral('neutre');
});
html.find('.moral-heureux').click(async event => {
this.actor.jetDeMoral('heureuse');
});
html.find('.ethylisme-test').click(async event => {
this.actor.jetEthylisme();
});
html.find('.jet-vie').click(async event => {
this.actor.jetVie();
});
html.find('.jet-endurance').click(async event => {
this.actor.jetEndurance();
});
html.find('.monnaie-plus').click(async event => {
this.actor.monnaieIncDec(RdDSheetUtility.getItemId(event), 1);
});
html.find('.monnaie-moins').click(async event => {
this.actor.monnaieIncDec(RdDSheetUtility.getItemId(event), -1);
});
html.find('.vie-plus').click(async event => {
this.actor.santeIncDec("vie", 1);
});
html.find('.vie-moins').click(async event => {
this.actor.santeIncDec("vie", -1);
});
html.find('.endurance-plus').click(async event => {
this.actor.santeIncDec("endurance", 1);
});
html.find('.endurance-moins').click(async event => {
this.actor.santeIncDec("endurance", -1);
});
html.find('.ptreve-actuel-plus').click(async event => {
this.actor.reveActuelIncDec(1);
});
html.find('.ptreve-actuel-moins').click(async event => {
this.actor.reveActuelIncDec(-1);
});
html.find('.fatigue-plus').click(async event => {
this.actor.santeIncDec("fatigue", 1);
});
html.find('.fatigue-moins').click(async event => {
this.actor.santeIncDec("fatigue", -1);
});
}
_optionRecherche(target) {
if (!target.value?.length){
return undefined;
}
return {
text: target.value,
start: target.selectionStart,
end: target.selectionEnd,
};
await this.actor.createItem('tache', 'Nouvelle tache');
}
_getEventArmeCombat(event) {
const li = $(event.currentTarget)?.parents(".item");
const li = this.html.find(event.currentTarget)?.parents(".item");
let armeName = li.data("arme-name");
let compName = li.data('competence-name');
const arme = this.armesList.find(a => a.name == armeName && a.system.competence == compName);
@ -562,8 +509,8 @@ export class RdDActorSheet extends ActorSheet {
const sheetTabs = this.element.find(".sheet-tabs");
const sheetBody = this.element.find(".sheet-body");
let bodyHeight = position.height - sheetHeader[0].clientHeight;
if (sheetTabs.length>0) {
bodyHeight -= sheetTabs[0].clientHeight;
if (sheetTabs.length > 0) {
bodyHeight -= sheetTabs[0].clientHeight;
}
sheetBody.css("height", bodyHeight);
return position;

View File

@ -18,5 +18,22 @@ export class RdDActorVehiculeSheet extends RdDActorSheet {
});
}
activateListeners(html) {
super.activateListeners(html);
if (!this.options.editable) return;
this.html.find('.resistance-moins').click(async event => {
this.actor.vehicleIncDec("resistance", -1);
});
this.html.find('.resistance-plus').click(async event => {
this.actor.vehicleIncDec("resistance", 1);
});
this.html.find('.structure-moins').click(async event => {
this.actor.vehicleIncDec("structure", -1);
});
this.html.find('.structure-plus').click(async event => {
this.actor.vehicleIncDec("structure", 1);
});
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,301 @@
import { RdDUtility } from "../rdd-utility.js";
import { Misc } from "../misc.js";
import { DialogSplitItem } from "../dialog-split-item.js";
import { RdDSheetUtility } from "../rdd-sheet-utility.js";
import { Monnaie } from "../item-monnaie.js";
import { RdDItem, TYPES } from "../item.js";
import { RdDItemCompetenceCreature } from "../item-competencecreature.js";
/* -------------------------------------------- */
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
export class RdDBaseActorSheet extends ActorSheet {
/** @override */
static get defaultOptions() {
RdDUtility.initAfficheContenu();
return mergeObject(super.defaultOptions, {
classes: ["rdd", "sheet", "actor"],
template: "systems/foundryvtt-reve-de-dragon/templates/actor-sheet.html",
width: 550,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "carac" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: undefined }],
showCompNiveauBase: false,
vueDetaillee: false
});
}
/* -------------------------------------------- */
async getData() {
Monnaie.validerMonnaies(this.actor.itemTypes['monnaie']);
this.actor.recompute();
let formData = {
title: this.title,
id: this.actor.id,
type: this.actor.type,
img: this.actor.img,
name: this.actor.name,
system: this.actor.system,
description: await TextEditor.enrichHTML(this.actor.system.description, { async: true }),
notesmj: await TextEditor.enrichHTML(this.actor.system.notesmj, { async: true }),
options: RdDSheetUtility.mergeDocumentRights(this.options, this.actor, this.isEditable)
}
RdDBaseActorSheet.filterItemsPerTypeForSheet(formData, this.actor.itemTypes);
formData.calc = {
fortune: Monnaie.toSolsDeniers(this.actor.getFortune()),
prixTotalEquipement: this.actor.computePrixTotalEquipement(),
encTotal: await this.actor.computeEncTotal(),
}
this.objetVersConteneur = RdDUtility.buildArbreDeConteneurs(formData.conteneurs, formData.inventaires);
this._appliquerRechercheObjets(formData.conteneurs, formData.inventaires);
formData.conteneurs = RdDUtility.conteneursRacine(formData.conteneurs);
formData.competences.filter(it => it.type == TYPES.competencecreature)
.forEach(it => it.isdommages = RdDItemCompetenceCreature.isDommages(it))
return formData;
}
_appliquerRechercheObjets(conteneurs, inventaires) {
if (this.options.recherche?.text) {
const recherche = this.options.recherche;
const allVisible = inventaires.filter(it => it.isNomTypeLike(recherche.text)).map(it => it.id);
let addVisible = conteneurs.filter(it => it.isNomTypeLike(recherche.text)).map(it => it.id)
do {
allVisible.push(...addVisible)
const parentsIds = conteneurs.filter(it => it.system.contenu.find(id => allVisible.includes(id))).map(it => it.id)
addVisible = parentsIds.filter(id => !allVisible.includes(id))
}
while (addVisible.length > 0)
inventaires.forEach(it => it.system.isHidden = !allVisible.includes(it.id))
conteneurs.forEach(it => it.system.isHidden = !allVisible.includes(it.id))
}
else {
inventaires.forEach(it => it.system.isHidden = false)
conteneurs.forEach(it => it.system.isHidden = false)
}
}
/* -------------------------------------------- */
static filterItemsPerTypeForSheet(formData, itemTypes) {
formData.blessures = Misc.arrayOrEmpty(itemTypes['blessure']);
formData.recettescuisine = Misc.arrayOrEmpty(itemTypes['recettecuisine']);
formData.recettesAlchimiques = Misc.arrayOrEmpty(itemTypes['recettealchimique']);
formData.maladies = Misc.arrayOrEmpty(itemTypes['maladie']);
formData.poisons = Misc.arrayOrEmpty(itemTypes['poison']);
formData.possessions = Misc.arrayOrEmpty(itemTypes['possession']);
formData.maladiesPoisons = formData.maladies.concat(formData.poisons);
formData.competences = (itemTypes['competence'] ?? []).concat(itemTypes['competencecreature'] ?? []);
formData.sortsReserve = Misc.arrayOrEmpty(itemTypes['sortreserve']);
formData.sorts = Misc.arrayOrEmpty(itemTypes['sort']);
formData.rencontres = Misc.arrayOrEmpty(itemTypes['rencontre']);
formData.casestmr = Misc.arrayOrEmpty(itemTypes['casetmr']);
formData.signesdraconiques = Misc.arrayOrEmpty(itemTypes['signedraconique']);
formData.queues = Misc.arrayOrEmpty(itemTypes['queue']);
formData.souffles = Misc.arrayOrEmpty(itemTypes['souffle']);
formData.ombres = Misc.arrayOrEmpty(itemTypes['ombre']);
formData.tetes = Misc.arrayOrEmpty(itemTypes['tete']);
formData.taches = Misc.arrayOrEmpty(itemTypes['tache']);
formData.meditations = Misc.arrayOrEmpty(itemTypes['meditation']);
formData.chants = Misc.arrayOrEmpty(itemTypes['chant']);
formData.danses = Misc.arrayOrEmpty(itemTypes['danse']);
formData.musiques = Misc.arrayOrEmpty(itemTypes['musique']);
formData.oeuvres = Misc.arrayOrEmpty(itemTypes['oeuvre']);
formData.jeux = Misc.arrayOrEmpty(itemTypes['jeu']);
formData.services = Misc.arrayOrEmpty(itemTypes['service']);
formData.conteneurs = Misc.arrayOrEmpty(itemTypes['conteneur']);
formData.materiel = Misc.arrayOrEmpty(itemTypes['objet']);
formData.armes = Misc.arrayOrEmpty(itemTypes['arme']);
formData.armures = Misc.arrayOrEmpty(itemTypes['armure']);
formData.munitions = Misc.arrayOrEmpty(itemTypes['munition']);
formData.livres = Misc.arrayOrEmpty(itemTypes['livre']);
formData.potions = Misc.arrayOrEmpty(itemTypes['potion']);
formData.plantes = Misc.arrayOrEmpty(itemTypes['plante']);
formData.ingredients = Misc.arrayOrEmpty(itemTypes['ingredient']);
formData.faunes = Misc.arrayOrEmpty(itemTypes['faune']);
formData.herbes = Misc.arrayOrEmpty(itemTypes['herbe']);
formData.nourritureboissons = Misc.arrayOrEmpty(itemTypes['nourritureboisson']);
formData.gemmes = Misc.arrayOrEmpty(itemTypes['gemme']);
formData.monnaies = Misc.arrayOrEmpty(itemTypes['monnaie']).sort(Monnaie.triValeurEntiere());
formData.objets = Misc.arrayOrEmpty(itemTypes['objet'])
formData.inventaires = RdDItem.getItemTypesInventaire('all')
.map(t => Misc.arrayOrEmpty(itemTypes[t]))
.reduce((a, b) => a.concat(b), [])
.sort(Misc.ascending(it => it.name));
}
/* -------------------------------------------- */ /** @override */
activateListeners(html) {
super.activateListeners(html);
this.html = html;
this.html.find('.conteneur-name a').click(async event => {
RdDUtility.toggleAfficheContenu(this.getItemId(event));
this.render(true);
});
this.html.find('.item-edit').click(async event => this.getItem(event)?.sheet.render(true))
this.html.find('.item-montrer').click(async event => this.getItem(event)?.postItemToChat());
this.html.find('.actor-montrer').click(async event => this.actor.postActorToChat());
this.html.find('.recherche')
.each((index, field) => {
this._rechercheSelectArea(field);
})
.keyup(async event => this._rechercherKeyup(event))
.change(async event => this._rechercherKeyup(event));
this.html.find('.recherche').prop("disabled", false);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
this.html.find('.item-split').click(async event => {
const item = this.getItem(event);
RdDSheetUtility.splitItem(item, this.actor);
});
this.html.find('.item-quantite-plus').click(async event => this.actor.itemQuantiteIncDec(this.getItemId(event), 1));
this.html.find('.item-quantite-moins').click(async event => this.actor.itemQuantiteIncDec(this.getItemId(event), -1));
this.html.find('.item-delete').click(async event => RdDUtility.confirmActorItemDelete(this, this.getItem(event)));
this.html.find('.item-vendre').click(async event => this.vendre(this.getItem(event)));
this.html.find('.creer-un-objet').click(async event => {
this.selectObjetTypeToCreate();
});
this.html.find('.nettoyer-conteneurs').click(async event => {
this.actor.nettoyerConteneurs();
});
}
_rechercherKeyup(event) {
const currentTarget = event.currentTarget;
const nouvelleRecherche = this._optionRecherche(currentTarget);
if (this.options.recherche?.text != nouvelleRecherche?.text) {
this.options.recherche = nouvelleRecherche;
if (this.timerRecherche) {
clearTimeout(this.timerRecherche);
}
this.timerRecherche = setTimeout(() => {
this.timerRecherche = undefined;
this.render(true);
}, 500);
}
}
_rechercheSelectArea(field) {
if (this.options.recherche) {
field.focus();
field.setSelectionRange(this.options.recherche.start, this.options.recherche.end);
}
}
getItemId(event) {
return RdDSheetUtility.getItemId(event);
}
getItem(event) {
return RdDSheetUtility.getItem(event, this.actor);
}
_optionRecherche(target) {
if (!target.value?.length) {
return undefined;
}
return {
text: target.value,
start: target.selectionStart,
end: target.selectionEnd,
};
}
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
buttons.unshift({
class: "montrer",
icon: "fas fa-comment",
onclick: ev => this.actor.postActorToChat()
});
return buttons
}
/* -------------------------------------------- */
async _onDropItem(event, dragData) {
const destItemId = this.html.find(event.target)?.closest('.item').attr('data-item-id')
const dropParams = await RdDSheetUtility.prepareItemDropParameters(destItemId, this.actor, dragData, this.objetVersConteneur)
if (dropParams) {
const callSuper = await this.actor.processDropItem(dropParams)
if (callSuper) {
await super._onDropItem(event, dragData)
}
}
}
/* -------------------------------------------- */
async selectObjetTypeToCreate() {
let types = this.getTypesInventaire().sort(Misc.ascending(type => Misc.typeName('Item', type)));
let content = `<span class="competence-label">Selectionnez le type d'équipement</span><select class="item-type">`;
for (let typeName of types) {
content += `<option value="${typeName}">${Misc.typeName('Item', typeName)}</option>`
}
content += '</select>';
let d = new Dialog({
title: "Créer un équipement",
content: content,
buttons: {
create: {
icon: '<i class="fas fa-check"></i>',
label: "Créer l'objet",
callback: () => this.actor.createItem($(".item-type").val())
}
}
});
d.render(true);
}
getTypesInventaire() {
return RdDItem.getItemTypesInventaire();
}
/** @override */
setPosition(options = {}) {
const position = super.setPosition(options);
const sheetHeader = this.element.find(".sheet-header");
const sheetTabs = this.element.find(".sheet-tabs");
const sheetBody = this.element.find(".sheet-body");
let bodyHeight = position.height - sheetHeader[0].clientHeight;
if (sheetTabs.length > 0) {
bodyHeight -= sheetTabs[0].clientHeight;
}
sheetBody.css("height", bodyHeight);
return position;
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
// Update the Actor
return this.actor.update(formData);
}
async splitItem(item) {
const dialog = await DialogSplitItem.create(item, (item, split) => this._onSplitItem(item, split));
dialog.render(true);
}
async _onSplitItem(item, split) {
if (split >= 1 && split < item.system.quantite) {
await item.diminuerQuantite(split);
const splitItem = duplicate(item);
splitItem.system.quantite = split;
await this.actor.createEmbeddedDocuments('Item', [splitItem])
}
}
vendre(item) {
item?.proposerVente(this.actor.getQuantiteDisponible(item));
}
}

615
module/actor/base-actor.js Normal file
View File

@ -0,0 +1,615 @@
import { ChatUtility } from "../chat-utility.js";
import { SYSTEM_SOCKET_ID } from "../constants.js";
import { Monnaie } from "../item-monnaie.js";
import { Misc } from "../misc.js";
import { RdDAudio } from "../rdd-audio.js";
import { RdDConfirm } from "../rdd-confirm.js";
import { RdDUtility } from "../rdd-utility.js";
import { SystemCompendiums } from "../settings/system-compendiums.js";
import { APP_ASTROLOGIE_REFRESH } from "../sommeil/app-astrologie.js";
export class RdDBaseActor extends Actor {
static getDefaultImg(itemType) {
return game.system.rdd.actorClasses[itemType]?.defaultIcon ?? defaultItemImg[itemType];
}
/* -------------------------------------------- */
static init() {
Hooks.on("preUpdateItem", (item, change, options, id) => RdDBaseActor.getParentActor(item)?.onPreUpdateItem(item, change, options, id));
Hooks.on("createItem", (item, options, id) => RdDBaseActor.getParentActor(item)?.onCreateItem(item, options, id));
Hooks.on("deleteItem", (item, options, id) => RdDBaseActor.getParentActor(item)?.onDeleteItem(item, options, id));
Hooks.on("updateActor", (actor, change, options, actorId) => actor.onUpdateActor(change, options, actorId));
}
static onSocketMessage(sockmsg) {
switch (sockmsg.msg) {
case "msg_remote_actor_call":
return RdDBaseActor.onRemoteActorCall(sockmsg.data, sockmsg.userId);
case "msg_reset_nombre_astral":
game.user.character.resetNombresAstraux();
game.system.rdd.calendrier.notifyChangeNombresAstraux();
return;
case "msg_refresh_nombre_astral":
Hooks.callAll(APP_ASTROLOGIE_REFRESH);
return;
}
}
static remoteActorCall(callData, userId = undefined) {
userId = userId ?? Misc.firstConnectedGMId();
if (userId == game.user.id) {
RdDBaseActor.onRemoteActorCall(callData, userId);
return false;
}
else {
game.socket.emit(SYSTEM_SOCKET_ID, { msg: "msg_remote_actor_call", data: callData, userId: userId });
return true;
}
}
static onRemoteActorCall(callData, userId) {
if (userId == game.user.id) {
const actor = game.actors.get(callData?.actorId);
if (Misc.isOwnerPlayerOrUniqueConnectedGM(actor)) { // Seul le joueur choisi effectue l'appel: le joueur courant si propriétaire de l'actor, ou le MJ sinon
const args = callData.args;
console.info(`RdDBaseActor.onRemoteActorCall: pour l'Actor ${callData.actorId}, appel de RdDBaseActor.${callData.method}(`, ...args, ')');
actor[callData.method](...args);
}
}
}
static getParentActor(document) {
return document?.parent instanceof Actor ? document.parent : undefined
}
/**
* Cet methode surcharge Actor.create() pour ajouter si besoin des Items par défaut:
* compétences et monnaies.
*
* @param {Object} actorData template d'acteur auquel ajouter des informations.
* @param {Object} options optionspour customiser la création
*/
static async create(actorData, options) {
// import depuis un compendium
if (actorData instanceof Array) {
return super.create(actorData, options);
}
// Création d'un acteur avec des items (uniquement en cas de duplication): pas besoin d'ajouter d'items
if (actorData.items) {
return await super.create(actorData, options);
}
actorData.items = [];
if (actorData.type == "personnage") {
const competences = await SystemCompendiums.getCompetences(actorData.type);
actorData.items = actorData.items.concat(competences.map(i => i.toObject()))
.concat(Monnaie.monnaiesStandard());
}
else if (actorData.type == "commerce") {
actorData.items = actorData.items.concat(Monnaie.monnaiesStandard());
}
return super.create(actorData, options);
}
constructor(docData, context = {}) {
if (!context.rdd?.ready) {
mergeObject(context, { rdd: { ready: true } });
const ActorConstructor = game.system.rdd.actorClasses[docData.type];
if (ActorConstructor) {
if (!docData.img) {
docData.img = ActorConstructor.defaultIcon;
}
return new ActorConstructor(docData, context);
}
}
super(docData, context);
}
isCreatureEntite() { return this.type == 'creature' || this.type == 'entite'; }
isCreature() { return this.type == 'creature'; }
isEntite() { return this.type == 'entite'; }
isPersonnage() { return this.type == 'personnage'; }
isVehicule() { return this.type == 'vehicule'; }
getItem(id, type = undefined) {
const item = this.items.get(id);
if (type == undefined || (item?.type == type)) {
return item;
}
return undefined;
}
listItems(type = undefined) { return (type ? this.itemTypes[type] : this.items); }
filterItems(filter, type = undefined) { return (type ? this.itemTypes[type] : this.items)?.filter(filter) ?? []; }
findItemLike(idOrName, type) {
return this.getItem(idOrName, type)
?? Misc.findFirstLike(idOrName, this.listItems(type), { description: Misc.typeName('Item', type) });
}
getMonnaie(id) { return this.findItemLike(id, 'monnaie'); }
recompute() { }
/* -------------------------------------------- */
async onPreUpdateItem(item, change, options, id) { }
async onCreateItem(item, options, id) { }
async onDeleteItem(item, options, id) { }
async onUpdateActor(update, options, actorId) { }
async onTimeChanging(oldTimestamp, newTimestamp) {
this.items.filter(it => it.isFinPeriode(oldTimestamp, newTimestamp))
.forEach(async it => await it.onFinPeriodeTemporel(oldTimestamp, newTimestamp))
}
async creerObjetParMJ(object) {
if (!Misc.isUniqueConnectedGM()) {
RdDBaseActor.remoteActorCall({
actorId: this.id,
method: 'creerObjetParMJ',
args: [object]
});
return;
}
await this.createEmbeddedDocuments('Item', [object])
}
/* -------------------------------------------- */
getFortune() {
return Monnaie.getFortune(this.itemTypes['monnaie']);
}
/* -------------------------------------------- */
async itemQuantiteIncDec(id, value) {
let item = this.getItem(id);
if (item && item.isInventaire()) {
const quantite = Math.max(0, item.system.quantite + value);
await this.updateEmbeddedDocuments('Item', [{ _id: item.id, 'system.quantite': quantite }]);
}
}
computePrixTotalEquipement() {
return this.items.filter(it => it.isInventaire())
.filter(it => !it.isMonnaie())
.map(it => it.valeurTotale())
.reduce(Misc.sum(), 0);
}
async payerSols(depense) {
depense = Number(depense);
if (depense == 0) {
return;
}
let fortune = this.getFortune();
console.log("payer", game.user.character, depense, fortune);
let msg = "";
if (fortune >= depense) {
await Monnaie.optimiserFortune(this, fortune - depense);
msg = `Vous avez payé <strong>${depense} Sols</strong>, qui ont été soustraits de votre argent.`;
RdDAudio.PlayContextAudio("argent"); // Petit son
} else {
msg = "Vous n'avez pas assez d'argent pour payer cette somme !";
}
let message = {
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
content: msg
};
ChatMessage.create(message);
}
async depenserSols(sols) {
let reste = this.getFortune() - Number(sols);
if (reste >= 0) {
await Monnaie.optimiserFortune(this, reste);
}
return reste;
}
async ajouterSols(sols, fromActorId = undefined) {
sols = Number(sols);
if (sols == 0) {
return;
}
if (sols < 0) {
ui.notifications.error(`Impossible d'ajouter un gain de ${sols} <0`);
return;
}
if (fromActorId && !game.user.isGM) {
RdDBaseActor.remoteActorCall({
userId: Misc.connectedGMOrUser(),
actorId: this.id,
method: 'ajouterSols', args: [sols, fromActorId]
});
}
else {
const fromActor = game.actors.get(fromActorId)
await Monnaie.optimiserFortune(this, sols + this.getFortune());
RdDAudio.PlayContextAudio("argent"); // Petit son
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
content: `Vous avez reçu <strong>${sols} Sols</strong> ${fromActor ? " de " + fromActor.name : ''}, qui ont été ajoutés à votre argent.`
});
}
}
/* -------------------------------------------- */
getQuantiteDisponible(item) {
return item?.isService() ? undefined : item?.getQuantite();
}
/* -------------------------------------------- */
async achatVente(achat) {
if (achat.vendeurId == achat.acheteurId) {
ui.notifications.info("Inutile de se vendre à soi-même");
return;
}
if (!Misc.isUniqueConnectedGM()) {
RdDBaseActor.remoteActorCall({
actorId: achat.vendeurId ?? achat.acheteurId,
method: 'achatVente',
args: [achat]
});
return;
}
const cout = Number(achat.prixTotal ?? 0);
const vendeur = achat.vendeurId ? game.actors.get(achat.vendeurId) : undefined;
const acheteur = achat.acheteurId ? game.actors.get(achat.acheteurId) : undefined;
const quantite = (achat.choix.nombreLots ?? 1) * (achat.vente.tailleLot);
const itemVendu = vendeur?.getItem(achat.vente.item._id) ?? game.items.get(achat.vente.item._id);
if (!itemVendu) {
ChatUtility.notifyUser(achat.userId, 'warn', vendeur ? `Le vendeur n'a pas plus de ${achat.vente.item.name} !` : `Impossible de retrouver: ${achat.vente.item.name} !`);
return;
}
if (vendeur && !vendeur.verifierQuantite(itemVendu, quantite)) {
ChatUtility.notifyUser(achat.userId, 'warn', `Le vendeur n'a pas assez de ${itemVendu.name} !`);
return
}
if (acheteur && !acheteur.verifierFortune(cout)) {
ChatUtility.notifyUser(achat.userId, 'warn', `Vous n'avez pas assez d'argent pour payer ${Math.ceil(cout / 100)} sols !`);
return;
}
await this.decrementerVente(vendeur, itemVendu, quantite, cout);
if (acheteur) {
await acheteur.depenserSols(cout);
const createdItemId = await acheteur.creerQuantiteItem(itemVendu, quantite);
await acheteur.consommerNourritureAchetee(achat, achat.vente, createdItemId);
}
if (cout > 0) {
RdDAudio.PlayContextAudio("argent");
}
const chatAchatItem = duplicate(achat.vente);
chatAchatItem.quantiteTotal = quantite;
ChatMessage.create({
user: achat.userId,
speaker: { alias: (acheteur ?? vendeur).name },
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-achat-item.html', chatAchatItem)
});
if (!achat.vente.quantiteIllimite) {
if (achat.vente.quantiteNbLots <= achat.choix.nombreLots) {
ChatUtility.removeChatMessageId(achat.chatMessageIdVente);
}
else if (achat.chatMessageIdVente) {
achat.vente.properties = itemVendu.getProprietes();
achat.vente.quantiteNbLots -= achat.choix.nombreLots;
achat.vente.jsondata = JSON.stringify(achat.vente.item);
const messageVente = game.messages.get(achat.chatMessageIdVente);
messageVente.update({ content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-vente-item.html', achat.vente) });
messageVente.render(true);
}
}
}
async decrementerVente(vendeur, itemVendu, quantite, cout) {
if (vendeur) {
await vendeur.ajouterSols(cout);
await vendeur.decrementerQuantiteItem(itemVendu, quantite);
}
}
verifierFortune(cout) {
return this.getFortune() >= cout;
}
verifierQuantite(item, quantiteDemande) {
const disponible = this.getQuantiteDisponible(item);
return disponible == undefined || disponible >= quantiteDemande;
}
async consommerNourritureAchetee(achat, vente, createdItemId) {
if (achat.choix.consommer && vente.item.type == 'nourritureboisson' && createdItemId != undefined) {
achat.choix.doses = achat.choix.nombreLots;
await this.consommerNourritureboisson(createdItemId, achat.choix, vente.actingUserId);
}
}
async decrementerQuantiteItem(item, quantite, options = { supprimerSiZero: true }) {
if (item.isService()) {
return;
}
let resteQuantite = (item.system.quantite ?? 1) - quantite;
if (resteQuantite <= 0) {
if (options.supprimerSiZero) {
await this.deleteEmbeddedDocuments("Item", [item.id]);
}
else {
await this.updateEmbeddedDocuments("Item", [{ _id: item.id, 'system.quantite': 0 }]);
}
if (resteQuantite < 0) {
ui.notifications.warn(`La quantité de ${item.name} était insuffisante, l'objet a donc été supprimé`)
}
}
else if (resteQuantite > 0) {
await this.updateEmbeddedDocuments("Item", [{ _id: item.id, 'system.quantite': resteQuantite }]);
}
}
async creerQuantiteItem(item, quantite) {
if (this.canReceive(item)) {
const isItemEmpilable = "quantite" in item.system;
const baseItem = {
type: item.type,
img: item.img,
name: item.name,
system: mergeObject(item.system, { quantite: isItemEmpilable ? quantite : undefined })
};
const newItems = isItemEmpilable ? [baseItem] : Array.from({ length: quantite }, (_, i) => baseItem);
const items = await this.createEmbeddedDocuments("Item", newItems);
return items.length > 0 ? items[0].id : undefined;
}
}
/* -------------------------------------------- */
computeMalusSurEncombrement() {
return 0;
}
getEncombrementMax() {
return 0;
}
async computeEncTotal() {
if (!this.pack) {
this.encTotal = this.items.map(it => it.getEncTotal()).reduce(Misc.sum(), 0);
return this.encTotal;
}
return 0;
}
async createItem(type, name = undefined) {
if (!name) {
name = 'Nouveau ' + Misc.typeName('Item', type);
}
await this.createEmbeddedDocuments('Item', [{ name: name, type: type }], { renderSheet: true });
}
canReceive(item) {
return false;
}
async processDropItem(params) {
const targetActorId = this.id;
const sourceActorId = params.sourceActorId;
const itemId = params.itemId;
const destId = params.destId;
const srcId = params.srcId;
if (sourceActorId && sourceActorId != targetActorId) {
console.log("Moving objects", sourceActorId, targetActorId, itemId);
this.moveItemsBetweenActors(itemId, sourceActorId);
return false;
}
let result = true;
const item = this.getItem(itemId);
if (item?.isInventaire('all') && sourceActorId == targetActorId) {
// rangement
if (srcId != destId && itemId != destId) { // déplacement de l'objet
const src = this.getItem(srcId);
const dest = this.getItem(destId);
const cible = this.getContenantOrParent(dest);
const [empilable, message] = item.isInventaireEmpilable(dest);
if (empilable) {
await dest.empiler(item)
result = false;
}
// changer de conteneur
else if (!cible || this.conteneurPeutContenir(cible, item)) {
await this.enleverDeConteneur(item, src, params.onEnleverConteneur);
await this.ajouterDansConteneur(item, cible, params.onAjouterDansConteneur);
if (message && !dest.isConteneur()) {
ui.notifications.info(cible
? `${message}<br>${item.name} a été déplacé dans: ${cible.name}`
: `${message}<br>${item.name} a été sorti du conteneur`);
}
}
}
}
await this.computeEncTotal();
return result;
}
getContenantOrParent(dest) {
if (!dest || dest.isConteneur()) {
return dest;
}
return this.getContenant(dest);
}
getContenant(item) {
return this.itemTypes['conteneur'].find(it => it.system.contenu.includes(item.id));
}
/* -------------------------------------------- */
conteneurPeutContenir(dest, moved) {
if (!dest) {
return true;
}
if (!dest.isConteneur()) {
return false;
}
if (moved.isConteneurContenu(dest)) {
ui.notifications.warn(`Impossible de déplacer un conteneur parent (${moved.name}) dans un de ses contenus ${dest.name} !`);
return false;
}
// Calculer le total actuel des contenus
const encContenu = dest.getEncContenu();
const newEnc = moved.getEncTotal(); // Calculer le total actuel du nouvel objet
const placeDisponible = Math.roundDecimals(dest.system.capacite - encContenu - newEnc, 4)
// Teste si le conteneur de destination a suffisament de capacité pour recevoir le nouvel objet
if (placeDisponible < 0) {
ui.notifications.warn(
`Le conteneur ${dest.name} a une capacité de ${dest.system.capacite}, et contient déjà ${encContenu}.
Impossible d'y ranger: ${moved.name} d'encombrement ${newEnc}!`);
return false;
}
return true;
}
/* -------------------------------------------- */
/** Ajoute un item dans un conteneur, sur la base
* de leurs ID */
async ajouterDansConteneur(item, conteneur, onAjouterDansConteneur) {
if (!conteneur) {
// TODO: afficher
item.estContenu = false;
}
else if (conteneur.isConteneur()) {
item.estContenu = true;
await this.updateEmbeddedDocuments('Item', [{
_id: conteneur.id,
'system.contenu': [...conteneur.system.contenu, item.id]
}]);
onAjouterDansConteneur(item.id, conteneur.id);
}
}
/* -------------------------------------------- */
/** Fonction de remise à plat de l'équipement (ie vide les champs 'contenu') */
async nettoyerConteneurs() {
RdDConfirm.confirmer({
settingConfirmer: "confirmation-vider",
content: `<p>Etes vous certain de vouloir vider tous les conteneurs ?</p>`,
title: 'Vider les conteneurs',
buttonLabel: 'Vider',
onAction: async () => {
const corrections = [];
for (let item of this.items) {
if (item.estContenu) {
item.estContenu = undefined;
}
if (item.type == 'conteneur' && item.system.contenu.length > 0) {
corrections.push({ _id: item.id, 'system.contenu': [] });
}
}
if (corrections.length > 0) {
await this.updateEmbeddedDocuments('Item', corrections);
}
}
});
}
/* -------------------------------------------- */
buildSubConteneurObjetList(conteneurId, deleteList) {
let conteneur = this.getItem(conteneurId);
if (conteneur?.type == 'conteneur') { // Si c'est un conteneur
for (let subId of conteneur.system.contenu) {
let subObj = this.getItem(subId);
if (subObj) {
if (subObj.type == 'conteneur') {
this.buildSubConteneurObjetList(subId, deleteList);
}
deleteList.push({ id: subId, conteneurId: conteneurId });
}
}
}
}
/* -------------------------------------------- */
async deleteAllConteneur(itemId, options) {
let list = [];
list.push({ id: itemId, conteneurId: undefined }); // Init list
this.buildSubConteneurObjetList(itemId, list);
await this.deleteEmbeddedDocuments('Item', list.map(it => it.id), options);
}
/* -------------------------------------------- */
/** Supprime un item d'un conteneur, sur la base
* de leurs ID */
async enleverDeConteneur(item, conteneur, onEnleverDeConteneur) {
if (conteneur?.isConteneur()) {
item.estContenu = false;
await this.updateEmbeddedDocuments('Item', [{
_id: conteneur.id,
'system.contenu': conteneur.system.contenu.filter(id => id != item.id)
}]);
onEnleverDeConteneur();
}
}
/* -------------------------------------------- */
async moveItemsBetweenActors(itemId, sourceActorId) {
let itemsList = []
let sourceActor = game.actors.get(sourceActorId);
itemsList.push({ id: itemId, conteneurId: undefined }); // Init list
sourceActor.buildSubConteneurObjetList(itemId, itemsList); // Get itemId list
const itemsDataToCreate = itemsList.map(it => sourceActor.getItem(it.id))
.map(it => duplicate(it))
.map(it => { it.system.contenu = []; return it; });
let newItems = await this.createEmbeddedDocuments('Item', itemsDataToCreate);
let itemMap = this._buildMapOldNewId(itemsList, newItems);
for (let item of itemsList) { // Second boucle pour traiter la remise en conteneurs
// gestion conteneur/contenu
if (item.conteneurId) { // l'Objet était dans un conteneur
let newConteneurId = itemMap[item.conteneurId]; // Get conteneur
let newConteneur = this.getItem(newConteneurId);
let newItemId = itemMap[item.id]; // Get newItem
console.log('New conteneur filling!', newConteneur, newItemId, item);
let contenu = duplicate(newConteneur.system.contenu);
contenu.push(newItemId);
await this.updateEmbeddedDocuments('Item', [{ _id: newConteneurId, 'system.contenu': contenu }]);
}
}
for (let item of itemsList) {
await sourceActor.deleteEmbeddedDocuments('Item', [item.id]);
}
}
_buildMapOldNewId(itemsList, newItems) {
let itemMap = {};
for (let i = 0; i < itemsList.length; i++) {
itemMap[itemsList[i].id] = newItems[i].id; // Pour garder le lien ancien / nouveau
}
return itemMap;
}
/* -------------------------------------------- */
async postActorToChat(modeOverride) {
let chatData = {
doctype: 'Actor',
id: this.id,
type: this.type,
img: this.img,
pack: this.pack,
name: this.name,
system: { description: this.system.description }
}
renderTemplate('systems/foundryvtt-reve-de-dragon/templates/post-actor.html', chatData)
.then(html => ChatMessage.create(RdDUtility.chatDataSetup(html, modeOverride)));
}
}

View File

@ -0,0 +1,95 @@
import { DialogItemAchat } from "../dialog-item-achat.js";
import { RdDItem } from "../item.js";
import { RdDSheetUtility } from "../rdd-sheet-utility.js";
import { RdDUtility } from "../rdd-utility.js";
import { RdDBaseActorSheet } from "./base-actor-sheet.js";
import { RdDCommerce } from "./commerce.js";
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
export class RdDCommerceSheet extends RdDBaseActorSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["rdd", "sheet", "actor"],
template: "systems/foundryvtt-reve-de-dragon/templates/actor/commerce-actor-sheet.html",
width: 600,
height: 720,
tabs: [],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: undefined }]
});
}
get title() {
if (this.actor.token && this.actor.token != this.actor.prototypeToken) {
return this.actor.token.name;
}
return super.title
}
async getData() {
const formData = await super.getData();
if (this.actor.token && this.actor.token != this.actor.prototypeToken) {
mergeObject(formData,
{
title: this.actor.token.name,
token: {
img: this.actor.token.texture.src
}
},
{ overwrite: true });
}
return formData;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
this.html.find('a.item-acheter').click(async event => await this.vente(this.getItem(event)));
if (!this.options.editable) return;
this.html.find('a.item-quantite-moins').click(async event => await this.getItem(event)?.quantiteIncDec(-1, { supprimerSiZero: false }));
this.html.find('a.item-quantite-plus').click(async event => await this.getItem(event)?.quantiteIncDec(1));
this.html.find('input.item-quantite').change(async event => {
const newQuantite = Math.max(0, Number.parseInt(this.html.find(event.currentTarget).val()));
await this.getItem(event)?.update({ "system.quantite": newQuantite });
})
this.html.find('input.item-cout').change(async event => {
const newCout = Math.max(0, Number(this.html.find(event.currentTarget).val()));
await this.getItem(event)?.update({ "system.cout": newCout });
})
}
getTypesInventaire() {
return RdDItem.getItemTypesInventaire('all');
}
async vente(item) {
const acheteur = RdDUtility.getSelectedActor();
if (!acheteur) {
ui.notifications.warn(`Pas d'acheteur sélectionné`);
return;
}
const disponible = this.actor.getQuantiteDisponible(item)
if (disponible == 0) {
ui.notifications.warn(`${this.name} n'a plus de ${item.name} en vente`);
return;
}
await DialogItemAchat.onAcheter({
item,
vendeur: this.actor,
acheteur,
quantiteIllimite: disponible == undefined,
nbLots: disponible ?? 1,
tailleLot: 1,
prixLot: item.calculerPrixCommercant()
});
}
}

54
module/actor/commerce.js Normal file
View File

@ -0,0 +1,54 @@
import { Misc } from "../misc.js";
import { RdDBaseActor } from "./base-actor.js";
export class RdDCommerce extends RdDBaseActor {
static get defaultIcon() {
return "systems/foundryvtt-reve-de-dragon/icons/services/commerce.webp";
}
prepareData() {
super.prepareData();
}
prepareDerivedData() {
super.prepareDerivedData();
}
canReceive(item) {
if (item.isInventaire('all')) {
return true;
}
return super.canReceive(item);
}
getQuantiteDisponible(item) {
return (this.system.illimite || item?.isService()) ? undefined : item.getQuantite();
}
verifierFortune(cout) {
return this.system.illimite || super.verifierFortune(cout);
}
async depenserSols(cout) {
if (this.system.illimite) {
return
}
await super.depenserSols(cout)
}
async consommerNourritureAchetee(achat, vente, createdItemId) {
// ne pas consommer pour un commerce
}
async decrementerQuantiteItem(item, quantite) {
if (this.system.illimite) {
return;
}
await super.decrementerQuantiteItem(item, quantite, { supprimerSiZero: false });
}
calculerPrix(item) {
const pourcentage = this.system.pourcentage ?? 100;
return Misc.keepDecimals(Math.ceil(item.system.cout * pourcentage) / 100, 2);
}
}

View File

@ -0,0 +1,39 @@
export const XP_TOPIC = {
XP: { code: 'xp', label: 'xp' },
XPSORT: { code: 'xpsort', label: 'xp sort' },
NIVEAU: { code: 'niveau', label: 'Niveau' },
XPCARAC: { code: 'xpcarac', label: 'xp carac' },
CARAC: { code: 'carac', label: 'Carac' },
STRESS: { code: 'stress', label: 'Stress' },
TRANSFORM: { code: 'xps', label: 'Transformé' },
}
export class ExperienceLog {
static async add(actor, topic, from, to, raison, manuel = false) {
if (!actor.hasPlayerOwner || !actor.isPersonnage()) {
return
}
if (from == to) {
return
}
const newXpLog = {
mode: topic?.code ?? topic,
raison: (manuel ? '(manuel) ' : '') + raison,
from: from,
to: to,
valeur: to - from,
daterdd: game.system.rdd.calendrier.dateCourante(),
datereel: game.system.rdd.calendrier.dateReel().replace('T', ' ')
};
console.log('ExperienceLog.add', newXpLog)
const newExperienceLog = (actor.system.experiencelog ?? []).concat([newXpLog]);
await actor.update({ [`system.experiencelog`]: newExperienceLog });
}
static labelTopic(topic) {
const xpt = Object.values(XP_TOPIC).find(it => it.code == topic);
return xpt?.label ?? xpt?.code ?? topic;
}
}

View File

@ -78,11 +78,7 @@ export class ChatUtility {
/* -------------------------------------------- */
static async createChatWithRollMode(name, chatOptions) {
return await ChatUtility.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions);
}
/* -------------------------------------------- */
static async createChatMessage(name, rollMode, chatOptions) {
let rollMode = game.settings.get("core", "rollMode")
switch (rollMode) {
case "blindroll": // GM only
if (!game.user.isGM) {

View File

@ -1,5 +1,7 @@
import { SYSTEM_RDD } from "./constants.js";
import { Grammar } from "./grammar.js";
import { HtmlUtility } from "./html-utility.js";
import { RdDTimestamp } from "./time/rdd-timestamp.js";
const LATEST_USED_JOURNAL_ID = "chronologie-dernier-journal";
@ -22,96 +24,113 @@ export class DialogChronologie extends Dialog {
information: "",
journalId: game.settings.get(SYSTEM_RDD, LATEST_USED_JOURNAL_ID),
journaux: game.journal.filter(it => it.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)),
dateRdD: game.system.rdd.calendrier.getCalendrier(),
heureRdD: game.system.rdd.calendrier.getCurrentHeure(),
dateReel: DialogChronologie.getCurrentDateTime()
timestamp: game.system.rdd.calendrier.timestamp,
dateReel: game.system.rdd.calendrier.dateReel()
};
const html = await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/dialog-chronologie.html", dialogData);
const dialog = new DialogChronologie(html);
const dialog = new DialogChronologie(html, dialogData);
dialog.render(true);
}
constructor(html) {
constructor(html, dialogData) {
const options = {
classes: ["DialogChronologie"],
width: 500,
height: 'fit-content',
'z-index': 99999
};
const timeData = dialogData.timestamp.toCalendrier()
const conf = {
title: "Chronologie",
title: `Chronologie - ${timeData.jourDuMois} ${timeData.mois.label} - Heure ${timeData.heure.label}`,
content: html,
buttons: {
ajout: { label: "Ajouter", callback: it => this.ajouter() },
}
};
super(conf, options);
}
static getCurrentDateTime() {
return new Date().toLocaleString("sv-SE", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit"
}).replace(" ", "T");
this.dialogData = dialogData;
}
activateListeners(html) {
this.html = html;
super.activateListeners(html);
const journalPrecedent = game.journal.get(this.dialogData.journalId);
this.showChronologiePreset(!(journalPrecedent?.canUserModify(game.user)))
this.html.find("a.chronologie-preset-show").click(event => this.showChronologiePreset(true));
this.html.find("a.chronologie-preset-hide").click(event => this.showChronologiePreset(false));
this.html.find("button.chronologie-ajouter").click(event => this.ajouter());
}
showChronologiePreset(showPreset) {
HtmlUtility.showControlWhen(this.html.find(".chronologie-preset-show"), !showPreset);
HtmlUtility.showControlWhen(this.html.find(".chronologie-preset-hide"), showPreset);
HtmlUtility.showControlWhen(this.html.find(".chronologie-preset"), showPreset);
}
async ajouter() {
await this.forceValidation();
const { journalId, journalEntry } = this.findJournal();
// ajouter à la page ou créer une page
this.addContentToJournal(journalEntry, await this.prepareChronologieEntry());
if (journalEntry?.canUserModify(game.user)) {
const journalParameters = this.extractJournalParameters();
this.storeLatestUsedJournalEntry(journalId);
const jour = journalParameters.dateRdD.jour;
const mois = journalParameters.dateRdD.mois.label;
const annee = journalParameters.dateRdD.annee;
const section = `${jour} ${mois} ${annee}`
const content = await this.prepareChronologieEntry(journalParameters);
// ajouter à la page ou créer une page
this.addContentToJournal(journalEntry, section, content);
this.storeLatestUsedJournalEntry(journalId);
this.close();
}
else {
const journal = this.html.find("form.rdddialogchrono select[name='journalId']").val();
ui.notifications.warn(`Le journal ${journal} n'est pas accessible`);
}
}
async forceValidation() {
await $("form.rdddialogchrono :input").change();
await this.html.find("form.rdddialogchrono :input").change();
}
findJournal() {
const journalId = $("form.rdddialogchrono :input[name='journalId']").val();
const journalId = this.html.find("form.rdddialogchrono :input[name='journalId']").val();
const journalEntry = game.journal.get(journalId);
return { journalId, journalEntry };
}
async prepareChronologieEntry() {
return await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/chronologie-entry.html", this.extractJournalParameters());
async prepareChronologieEntry(journalParameters) {
return await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/chronologie-entry.html", journalParameters);
}
extractJournalParameters() {
return {
auteur: $("form.rdddialogchrono :input[name='auteur']").val(),
information: $("form.rdddialogchrono :input[name='information']").val(),
auteur: this.html.find("form.rdddialogchrono :input[name='auteur']").val(),
information: this.html.find("form.rdddialogchrono :input[name='information']").val(),
dateRdD: {
jour: $("form.rdddialogchrono :input[name='dateRdD.jour']").val(),
moisRdD: $("form.rdddialogchrono :input[name='dateRdD.moisRdD.key']").val(),
annee: $("form.rdddialogchrono :input[name='dateRdD.annee']").val()
jour: this.html.find("form.rdddialogchrono :input[name='chronologie.jourDuMois']").val(),
mois: RdDTimestamp.definition(this.html.find("form.rdddialogchrono :input[name='chronologie.mois']").val()),
annee: this.html.find("form.rdddialogchrono :input[name='chronologie.annee']").val(),
heure: RdDTimestamp.definition(this.html.find("form.rdddialogchrono :input[name='chronologie.heure']").val()),
minute: this.html.find("form.rdddialogchrono :input[name='chronologie.minute']").val(),
},
heureRdD: $("form.rdddialogchrono :input[name='heureRdD']").val(),
dateReel: $("form.rdddialogchrono :input[name='dateReel']").val().replace('T', ' ')
dateReel: this.html.find("form.rdddialogchrono :input[name='dateReel']").val()
}
}
addContentToJournal(journalEntry, content) {
let page = journalEntry.pages.find(p => p.type == 'text' && Grammar.equalsInsensitive(p.name, 'Chronologie'));
addContentToJournal(journalEntry, section, content) {
let page = journalEntry.pages.find(p => p.type == 'text' && Grammar.equalsInsensitive(p.name, section));
if (page) {
page.update({ 'text.content': content + '\n' + page.text.content });
page.update({ 'text.content': page.text.content + '\n' + content });
}
else {
journalEntry.createEmbeddedDocuments('JournalEntryPage', [this.newPageChronologie(content)]);
journalEntry.createEmbeddedDocuments('JournalEntryPage', [this.newPageChronologie(section, content)]);
}
}
newPageChronologie(content) {
newPageChronologie(section, content) {
return new JournalEntryPage({
name: 'Chronologie',
name: section,
type: 'text',
title: { show: true, level: 1 },
text: { content: content, format: 1 }

View File

@ -1,6 +1,6 @@
import { ChatUtility } from "./chat-utility.js";
import { HtmlUtility } from "./html-utility.js";
import { RdDItemSigneDraconique } from "./item-signedraconique.js";
import { RdDItemSigneDraconique } from "./item/signedraconique.js";
import { TMRUtility } from "./tmr-utility.js";
export class DialogCreateSigneDraconique extends Dialog {
@ -35,16 +35,16 @@ export class DialogCreateSigneDraconique extends Dialog {
super(conf, options);
this.dialogData = dialogData;
}
async _onCreerSigneActeurs() {
await $("[name='signe.system.ephemere']").change();
await $(".signe-xp-sort").change();
await this.html.find("[name='signe.system.ephemere']").change();
await this.html.find(".signe-xp-sort").change();
this.validerSigne();
this.dialogData.actors.filter(it => it.selected)
.map(it => game.actors.get(it.id))
.forEach(actor => this._createSigneForActor(actor, this.dialogData.signe));
.map(it => game.actors.get(it.id))
.forEach(actor => this._createSigneForActor(actor, this.dialogData.signe));
}
async _createSigneForActor(actor, signe) {
actor.createEmbeddedDocuments("Item", [signe]);
ChatMessage.create({
@ -57,19 +57,20 @@ export class DialogCreateSigneDraconique extends Dialog {
}
validerSigne() {
this.dialogData.signe.name = $("[name='signe.name']").val();
this.dialogData.signe.system.valeur.norm = $("[name='signe.system.valeur.norm']").val();
this.dialogData.signe.system.valeur.sign = $("[name='signe.system.valeur.sign']").val();
this.dialogData.signe.system.valeur.part = $("[name='signe.system.valeur.part']").val();
this.dialogData.signe.system.difficulte = $("[name='signe.system.difficulte']").val();
this.dialogData.signe.system.ephemere = $("[name='signe.system.ephemere']").prop("checked");
this.dialogData.signe.system.duree = $("[name='signe.system.duree']").val();
this.dialogData.signe.name = this.html.find("[name='signe.name']").val();
this.dialogData.signe.system.valeur.norm = this.html.find("[name='signe.system.valeur.norm']").val();
this.dialogData.signe.system.valeur.sign = this.html.find("[name='signe.system.valeur.sign']").val();
this.dialogData.signe.system.valeur.part = this.html.find("[name='signe.system.valeur.part']").val();
this.dialogData.signe.system.difficulte = this.html.find("[name='signe.system.difficulte']").val();
this.dialogData.signe.system.ephemere = this.html.find("[name='signe.system.ephemere']").prop("checked");
this.dialogData.signe.system.duree = this.html.find("[name='signe.system.duree']").val();
this.dialogData.signe.system.typesTMR = TMRUtility.buildListTypesTMRSelection(this.dialogData.tmrs);
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
this.html = html;
this.setEphemere(this.dialogData.signe.system.ephemere);
html.find(".signe-aleatoire").click(event => this.setSigneAleatoire());
html.find("[name='signe.system.ephemere']").change((event) => this.setEphemere(event.currentTarget.checked));
@ -81,27 +82,27 @@ export class DialogCreateSigneDraconique extends Dialog {
async setSigneAleatoire() {
const newSigne = await RdDItemSigneDraconique.randomSigneDraconique({ephemere: true});
$("[name='signe.name']").val(newSigne.name);
$("[name='signe.system.valeur.norm']").val(newSigne.system.valeur.norm);
$("[name='signe.system.valeur.sign']").val(newSigne.system.valeur.sign);
$("[name='signe.system.valeur.part']").val(newSigne.system.valeur.part);
$("[name='signe.system.difficulte']").val(newSigne.system.difficulte);
$("[name='signe.system.duree']").val(newSigne.system.duree);
$("[name='signe.system.ephemere']").prop("checked", newSigne.system.ephemere);
this.html.find("[name='signe.name']").val(newSigne.name);
this.html.find("[name='signe.system.valeur.norm']").val(newSigne.system.valeur.norm);
this.html.find("[name='signe.system.valeur.sign']").val(newSigne.system.valeur.sign);
this.html.find("[name='signe.system.valeur.part']").val(newSigne.system.valeur.part);
this.html.find("[name='signe.system.difficulte']").val(newSigne.system.difficulte);
this.html.find("[name='signe.system.duree']").val(newSigne.system.duree);
this.html.find("[name='signe.system.ephemere']").prop("checked", newSigne.system.ephemere);
this.dialogData.tmrs = TMRUtility.buildSelectionTypesTMR(newSigne.system.typesTMR);
this.dialogData.tmrs.forEach(t => {
$(`[data-tmr-name='${t.name}']`).prop( "checked", t.selected);
this.html.find(`[data-tmr-name='${t.name}']`).prop( "checked", t.selected);
})
this.setEphemere(newSigne.system.ephemere);
}
async setEphemere(ephemere) {
this.dialogData.signe.system.ephemere = ephemere;
HtmlUtility._showControlWhen($(".signe-system-duree"), ephemere);
HtmlUtility.showControlWhen(this.html.find(".signe-system-duree"), ephemere);
}
async onSelectActor(event) {
const actorId = $(event.currentTarget)?.data("actor-id");
const actorId = this.html.find(event.currentTarget)?.data("actor-id");
const actor = this.dialogData.actors.find(it => it.id == actorId);
if (actor) {
actor.selected = event.currentTarget.checked;
@ -109,8 +110,8 @@ export class DialogCreateSigneDraconique extends Dialog {
}
onSelectTmr(event) {
const tmrName = $(event.currentTarget)?.data("tmr-name");
const onTmr = this.tmrs.find(it => it.name == tmrName);
const tmrName = this.html.find(event.currentTarget)?.data("tmr-name");
const onTmr = this.dialogData.tmrs.find(it => it.name == tmrName);
if (onTmr){
onTmr.selected = event.currentTarget.checked;
}

View File

@ -5,7 +5,7 @@ import { RdDUtility } from "./rdd-utility.js";
export class DialogFabriquerPotion extends Dialog {
/* -------------------------------------------- */
static async create(actor, item, dialogConfig) {
static async create(actor, item, onActionItem) {
const min = DialogFabriquerPotion.nombreBrinsMinimum(item);
if (item.system.quantite < min) {
ui.notifications.warn(`Vous avez ${item.system.quantite} brins de ${item.name}, il en faut au moins ${min} pour faire une potion!`);
@ -13,18 +13,10 @@ export class DialogFabriquerPotion extends Dialog {
}
let potionData = DialogFabriquerPotion.prepareData(actor, item);
let conf = {
title: `Fabriquer une potion de ${potionData.system.categorie}`,
content: await renderTemplate(dialogConfig.html, potionData),
default: potionData.buttonName,
};
const html = await renderTemplate( 'systems/foundryvtt-reve-de-dragon/templates/dialog-fabriquer-potion-base.html', potionData);
let options = { classes: ["dialogfabriquerpotion"], width: 600, height: 160, 'z-index': 99999 };
mergeObject(options, dialogConfig.options ?? {}, { overwrite: true })
const dialog = new DialogFabriquerPotion(actor, potionData, conf, options);
dialog.render(true);
return dialog;
new DialogFabriquerPotion(actor, potionData, onActionItem, html, options).render(true);
}
/* -------------------------------------------- */
@ -40,10 +32,15 @@ export class DialogFabriquerPotion extends Dialog {
}
/* -------------------------------------------- */
constructor(actor, potionData, conf, options) {
conf.buttons = {
[potionData.buttonName]: {
label: potionData.buttonName, callback: it => this.onFabriquer(it)
constructor(actor, potionData, onActionItem, html, options) {
const conf = {
title: `Fabriquer une potion de ${potionData.system.categorie}`,
content: html,
default: 'fabriquer',
buttons: {
'fabriquer': {
label: potionData.buttonName, callback: it => this.onFabriquer()
}
}
};
@ -51,6 +48,26 @@ export class DialogFabriquerPotion extends Dialog {
this.actor = actor;
this.potionData = potionData;
this.onActionItem = onActionItem;
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
this.html = html;
this.html.find("[name='nbBrins']").change(event => {
this.potionData.nbBrins = Misc.toInt(event.currentTarget.value);
const brinsManquants = Math.max(0, DialogFabriquerPotion.nombreBrinsOptimal(this.potionData) - this.potionData.nbBrins);
this.potionData.herbebonus = Math.max(0, this.potionData.system.niveau - brinsManquants)
});
}
/* -------------------------------------------- */
async onFabriquer() {
await this.html.find("[name='nbBrins']").change();
await this.actor.fabriquerPotion(this.potionData);
this.close();
await this.onActionItem()
}
static nombreBrinsMinimum(herbeData) {
@ -68,22 +85,4 @@ export class DialogFabriquerPotion extends Dialog {
}
return 1;
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
html.find("#nbBrins").change(event => {
this.potionData.nbBrins = Misc.toInt(event.currentTarget.value);
const brinsManquants = Math.max(0, DialogFabriquerPotion.nombreBrinsOptimal(this.potionData) - this.potionData.nbBrins);
this.potionData.herbebonus = Math.max(0, this.potionData.system.niveau - brinsManquants)
});
}
/* -------------------------------------------- */
async onFabriquer(it) {
await $("#nbBrins").change();
this.actor.fabriquerPotion(this.potionData);
this.close();
}
}

View File

@ -1,15 +1,13 @@
import { Monnaie } from "./item-monnaie.js";
import { Misc } from "./misc.js";
import { RdDUtility } from "./rdd-utility.js";
export class DialogItemAchat extends Dialog {
static venteData(button) {
const vendeurId = button.attributes['data-vendeurId']?.value;
static preparerAchat(chatButton) {
const vendeurId = chatButton.attributes['data-vendeurId']?.value;
const vendeur = vendeurId ? game.actors.get(vendeurId) : undefined;
const acheteur = RdDUtility.getSelectedActor();
const json = button.attributes['data-jsondata']?.value;
const json = chatButton.attributes['data-jsondata']?.value;
if (!acheteur && !vendeur) {
ui.notifications.info("Pas d'acheteur ni de vendeur, aucun changement");
return undefined;
@ -19,59 +17,77 @@ export class DialogItemAchat extends Dialog {
return undefined;
}
const prixLot = Monnaie.arrondiDeniers(button.attributes['data-prixLot']?.value ?? 0);
return {
item: json ? JSON.parse(json) : undefined,
actingUserId: game.user.id,
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)
item: JSON.parse(json),
vendeur,
acheteur,
nbLots: parseInt(chatButton.attributes['data-quantiteNbLots']?.value),
tailleLot: parseInt(chatButton.attributes['data-tailleLot']?.value ?? 1),
prixLot: Number(chatButton.attributes['data-prixLot']?.value ?? 0),
quantiteIllimite: chatButton.attributes['data-quantiteIllimite']?.value == 'true',
chatMessageIdVente: RdDUtility.findChatMessageId(chatButton),
};
}
static async onAcheter(venteData) {
static async onAcheter({ item, vendeur, acheteur, tailleLot, prixLot, nbLots, quantiteIllimite, chatMessageIdVente }) {
const venteData = {
item,
actingUserId: game.user.id,
vendeur,
acheteur,
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 dialog = new DialogItemAchat(html, venteData);
dialog.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) {
const isConsommable = venteData.item.type == 'nourritureboisson' && venteData.acheteur?.isPersonnage();
let options = { classes: ["dialogachat"], width: 400, height: 'fit-content', 'z-index': 99999 };
const actionAchat = venteData.prixLot > 0 ? "Acheter" : "Prendre";
const buttons = {};
if (isConsommable) {
if (venteData.isConsommable) {
buttons["consommer"] = { label: venteData.item.system.boisson ? "Boire" : "Manger", callback: it => this.onAchatConsommer() }
}
buttons[actionAchat] = { label: actionAchat, callback: it => { this.onAchat(); } };
buttons["decliner"] = { label: "Décliner", callback: it => { } };
const acheteur = venteData.acheteur?.name ?? 'Un acheteur';
const vendeur = venteData.vendeur?.name ?? 'Un vendeur';
let conf = {
title: venteData.acheteur ? venteData.acheteur.name + " - " + actionAchat : actionAchat,
title: `${acheteur} - ${actionAchat} à ${vendeur}`,
content: html,
default: actionAchat,
buttons: buttons
};
super(conf, options);
this.venteData = venteData;
}
async onAchat() {
await $(".nombreLots").change();
await this.html.find(".nombreLots").change();
(this.venteData.vendeur ?? this.venteData.acheteur).achatVente({
userId: game.user.id,
vendeurId: this.venteData.vendeur?.id,
@ -91,9 +107,9 @@ export class DialogItemAchat extends Dialog {
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
html.find(".nombreLots").change(event => this.setNombreLots(Number(event.currentTarget.value)));
html.find(".se-forcer").change(event => this.setSeForcer(event));
this.html = html;
this.html.find(".nombreLots").change(event => this.setNombreLots(Number(event.currentTarget.value)));
this.html.find(".se-forcer").change(event => this.setSeForcer(event));
}
setSeForcer(event) {
@ -101,13 +117,21 @@ export class DialogItemAchat extends Dialog {
}
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);
$(".nombreLots").val(this.venteData.choix.nombreLots);
$(".prixTotal").text(this.venteData.prixTotal);
DialogItemAchat.changeNombreLots(this.venteData, nombreLots);
this.html.find(".nombreLots").val(nombreLots);
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,13 +2,13 @@ import { Misc } from "./misc.js";
export class DialogConsommer extends Dialog {
static async create(actor, item, onActionItem = async ()=>{}) {
static async create(actor, item, onActionItem = async () => { }) {
const consommerData = DialogConsommer.prepareData(actor, item);
const html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-item-consommer.html', consommerData);
return new DialogConsommer(actor, item, consommerData, html, onActionItem)
}
constructor(actor, item, consommerData, html, onActionItem = async ()=>{}) {
constructor(actor, item, consommerData, html, onActionItem = async () => { }) {
const options = { classes: ["dialogconsommer"], width: 350, height: 'fit-content', 'z-index': 99999 };
let conf = {
title: consommerData.title,
@ -17,8 +17,9 @@ export class DialogConsommer extends Dialog {
buttons: {
[consommerData.buttonName]: {
label: consommerData.buttonName, callback: async it => {
await this.onConsommer(it);
await onActionItem();}
await this.onConsommer();
await onActionItem();
}
}
}
};
@ -30,17 +31,23 @@ export class DialogConsommer extends Dialog {
this.consommerData = consommerData;
}
async onConsommer(event) {
await $(".se-forcer").change();
await $(".consommer-doses").change();
activateListeners(html) {
super.activateListeners(html);
this.html = html;
this.html.find(".se-forcer").change(event => this.setSeForcer(event));
this.html.find(".consommer-doses").change(event => this.selectDoses(event));
}
async onConsommer() {
await this.html.find(".se-forcer").change();
await this.html.find(".consommer-doses").change();
await this.actor.consommer(this.item, this.consommerData.choix);
}
/* -------------------------------------------- */
static prepareData(actor, item) {
item = duplicate(item);
let consommerData = {
item: item,
item: duplicate(item),
cuisine: actor.getCompetence('cuisine'),
choix: {
doses: 1,
@ -48,33 +55,47 @@ export class DialogConsommer extends Dialog {
}
}
switch (item.type) {
case 'herbe': case 'faune':
consommerData.title = 'Manger une portion crue: ';
consommerData.buttonName = "Manger";
break;
case 'nourritureboisson':
consommerData.title = item.system.boisson ? `${item.name}: boire une dose` : `${item.name}: manger une portion`;
consommerData.title = item.system.boisson ? 'Boire une dose: ' : 'Manger une portion: ';
consommerData.buttonName = item.system.boisson ? "Boire" : "Manger";
break;
case 'potion':
consommerData.title = `${item.name}: boire la potion`;
consommerData.title = 'Boire la potion: ';
consommerData.buttonName = "Boire";
break;
}
DialogConsommer.calculDoses(consommerData, consommerData.choix.doses)
consommerData.title += item.name;
DialogConsommer.calculDoses(consommerData, item)
return consommerData;
}
static calculDoses(consommer) {
static calculDoses(consommer, item) {
const doses = consommer.choix.doses;
consommer.totalSust = Misc.keepDecimals(doses * (consommer.item.system.sust ?? 0), 2);
consommer.totalDesaltere = consommer.item.system.boisson
? Misc.keepDecimals(doses * (consommer.item.system.desaltere ?? 0), 2)
: 0;
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
html.find(".se-forcer").change(event => this.setSeForcer(event));
html.find(".consommer-doses").change(event => this.selectDoses(event));
switch (item.type) {
case 'herbe': case 'faune':
consommer.totalSust = doses;
consommer.totalDesaltere = 0;
consommer.choix.sust = 1;
consommer.choix.quantite = 0;
consommer.choix.encombrement = Misc.keepDecimals(consommer.item.system.encombrement / item.system.sust, 2);
return;
case 'nourritureboisson':
consommer.choix.sust = consommer.item.system.sust;
consommer.choix.quantite = doses;
consommer.choix.encombrement = 0
consommer.totalSust = Misc.keepDecimals(doses * (consommer.item.system.sust ?? 0), 2);
consommer.totalDesaltere = consommer.item.system.boisson
? Misc.keepDecimals(doses * (consommer.item.system.desaltere ?? 0), 2)
: 0;
break;
case 'potion':
consommer.totalSust = 0
consommer.totalDesaltere = 0
}
}
@ -84,8 +105,8 @@ export class DialogConsommer extends Dialog {
selectDoses(event) {
this.consommerData.choix.doses = Number(event.currentTarget.value);
DialogConsommer.calculDoses(this.consommerData);
$(".total-sust").text(this.consommerData.totalSust);
$(".total-desaltere").text(this.consommerData.totalDesaltere);
DialogConsommer.calculDoses(this.consommerData, this.item);
this.html.find(".total-sust").text(this.consommerData.totalSust);
this.html.find(".total-desaltere").text(this.consommerData.totalDesaltere);
}
}

View File

@ -1,23 +1,23 @@
import { HtmlUtility } from "./html-utility.js";
import { Misc } from "./misc.js";
export class DialogItemVente extends Dialog {
static async display(item, callback) {
const quantite = item.isConteneur() ? 1 : item.system.quantite;
static async display({ item, callback, quantiteMax = undefined }) {
const quantite = quantiteMax ?? item.getQuantite() ?? 1;
const isOwned = item.parent;
const venteData = {
item: item,
alias: item.actor?.name ?? game.user.name,
vendeurId: item.actor?.id,
prixOrigine: item.system.cout,
prixUnitaire: item.system.cout,
prixLot: item.system.cout,
prixOrigine: item.calculerPrixCommercant(),
prixUnitaire: item.calculerPrixCommercant(),
prixLot: item.calculerPrixCommercant(),
tailleLot: 1,
quantiteNbLots: quantite,
quantiteMaxLots: quantite,
quantiteMax: quantite ,
quantiteIllimite: !item.isOwned,
isOwned: item.isOwned,
quantiteMax: quantite,
quantiteIllimite: item.isItemCommerce() ? quantiteMax == undefined : !isOwned,
isOwned: isOwned,
};
const html = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/dialog-item-vente.html`, venteData);
return new DialogItemVente(venteData, html, callback).render(true);
@ -38,57 +38,65 @@ export class DialogItemVente extends Dialog {
this.venteData = venteData;
}
async onProposer(it) {
await $(".tailleLot").change();
await $(".quantiteNbLots").change();
await $(".quantiteIllimite").change();
await $(".prixLot").change();
this.callback(this.venteData);
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
HtmlUtility._showControlWhen($(".quantiteNbLots"), !this.venteData.quantiteIllimite)
this.html = html;
this.html.find(".tailleLot").change(event => this.setTailleLot(Number(event.currentTarget.value)));
this.html.find(".quantiteNbLots").change(event => this.setNbLots(Number(event.currentTarget.value)));
this.html.find(".quantiteIllimite").change(event => this.setQuantiteIllimite(event.currentTarget.checked));
this.html.find(".prixLot").change(event => this.setPrixLot(Number(event.currentTarget.value)));
html.find(".tailleLot").change(event => this.setTailleLot(Number(event.currentTarget.value)));
html.find(".quantiteNbLots").change(event => this.setNbLots(Number(event.currentTarget.value)));
html.find(".quantiteIllimite").change(event => this.setQuantiteIllimite(event.currentTarget.checked));
html.find(".prixLot").change(event => this.setPrixLot(Number(event.currentTarget.value)));
this.setQuantiteIllimite(this.venteData.quantiteIllimite);
}
async onProposer(it) {
this.updateVente(this.getChoixVente());
this.callback(this.venteData);
}
updateVente(update) {
mergeObject(this.venteData, update);
}
getChoixVente() {
return {
quantiteNbLots: Number(this.html.find(".quantiteNbLots").val()),
tailleLot: Number(this.html.find(".tailleLot").val()),
quantiteIllimite: this.html.find(".quantiteIllimite").is(':checked'),
prixLot: Number(this.html.find(".prixLot").val())
};
}
/* -------------------------------------------- */
setPrixLot(prixLot) {
this.venteData.prixLot = prixLot;
}
setTailleLot(tailleLot) {
// recalculer le prix du lot
if (tailleLot != this.venteData.tailleLot) {
this.venteData.prixLot = (tailleLot * this.venteData.prixOrigine).toFixed(2);
$(".prixLot").val(this.venteData.prixLot);
}
this.venteData.tailleLot = tailleLot;
if (this.venteData.isOwned) {
// recalculer le nombre de lots max
this.venteData.quantiteMaxLots = Math.floor(this.venteData.quantiteMax / tailleLot);
this.venteData.quantiteNbLots = Math.min(this.venteData.quantiteMaxLots, this.venteData.quantiteNbLots);
$(".quantiteNbLots").val(this.venteData.quantiteNbLots);
$(".quantiteNbLots").attr("max", this.venteData.quantiteMaxLots)
}
const maxLots = Math.floor(this.venteData.quantiteMax / tailleLot);
this.updateVente({
tailleLot,
quantiteNbLots: Math.min(maxLots, this.venteData.quantiteNbLots),
quantiteMaxLots: maxLots,
prixLot: (tailleLot * this.venteData.prixOrigine).toFixed(2)
});
this.html.find(".prixLot").val(this.venteData.prixLot);
this.html.find(".quantiteNbLots").val(this.venteData.quantiteNbLots);
this.html.find(".quantiteNbLots").attr("max", this.venteData.quantiteMaxLots)
}
setNbLots(nbLots) {
if (this.venteData.isOwned) {
nbLots = Math.max(0, Math.min(nbLots, this.venteData.quantiteMaxLots));
}
this.venteData.quantiteNbLots = nbLots;
$(".quantiteNbLots").val(this.venteData.quantiteNbLots);
this.updateVente({
quantiteNbLots: this.venteData.isOwned ? Math.max(0, Math.min(nbLots, this.venteData.quantiteMaxLots)) : nbLots
})
this.html.find(".quantiteNbLots").val(this.venteData.quantiteNbLots);
}
setQuantiteIllimite(checked) {
this.venteData.quantiteIllimite = checked;
$(".label-quantiteIllimite").text(this.venteData.quantiteIllimite ? "Illimités" : "disponibles");
HtmlUtility._showControlWhen($(".quantiteNbLots"), !this.venteData.quantiteIllimite)
this.updateVente({ quantiteIllimite: checked })
this.html.find(".label-quantiteIllimite").text(this.venteData.quantiteIllimite ? "Illimités" : "disponibles");
HtmlUtility.showControlWhen(this.html.find(".quantiteNbLots"), !this.venteData.quantiteIllimite)
}
}

View File

@ -1,56 +0,0 @@
import { Misc } from "./misc.js";
export class DialogRepos extends Dialog {
static async create(actor) {
const html = await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/dialog-repos.html", actor);
const dialog = new DialogRepos(html, actor);
dialog.render(true);
}
constructor(html, actor) {
let options = { classes: ["DialogCreateSigneDraconiqueActorsActors"], width: 500, height: 400, 'z-index': 99999 };
let conf = {
title: "Se reposer",
content: html,
default: "repos",
buttons: {
"repos": { label: "Se reposer", callback: async it => { this.repos(); } }
}
};
super(conf, options);
this.actor = actor;
}
async repos() {
await $("[name='nb-heures']").change();
await $("[name='nb-jours']").change();
const selection = await $("[name='repos']:checked").val();
const nbHeures = Number.parseInt(await $("[name='nb-heures']").val());
const nbJours = Number.parseInt(await $("[name='nb-jours']").val());
switch (selection) {
case "sieste": {
await this.actor.dormir(nbHeures);
return;
}
case "nuit": {
let heuresDormies = await this.actor.dormir(nbHeures);
if (heuresDormies == nbHeures){
await this.actor.dormirChateauDormant();
}
return;
}
case "chateau-dormant":
await this.actor.dormirChateauDormant();
return;
case "gris-reve": {
await this.actor.grisReve(nbJours);
return;
}
}
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
}
}

View File

@ -1,29 +1,5 @@
export class DialogSelectTarget extends Dialog {
static hasTargets() {
return (game.user.targets?.size ?? 0) > 0;
}
static async selectOneToken(onSelectTarget = target => { }) {
if (DialogSelectTarget.hasTargets()) {
const targets = game.user.targets.map(it => it);
switch (targets.size) {
case 0: return;
case 1:
onSelectTarget(targets[0]);
return;
default:
{
const tokens = targets.map(it => { return { id: it.id, name: it.document.name, img: it.document.texture.src ?? it.actor.img ?? 'icons/svg/mystery-man.svg' } })
const html = await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/dialog-select-target.html", {
tokens: tokens
});
new DialogSelectTarget(html, onSelectTarget, targets).render(true);
}
}
}
}
constructor(html, onSelectTarget, targets) {
const options = {
classes: ["rdd-dialog-select-target"],
@ -44,8 +20,9 @@ export class DialogSelectTarget extends Dialog {
activateListeners(html) {
super.activateListeners(html);
html.find("li.select-target").click((event) => {
this.targetSelected($(event.currentTarget)?.data("token-id"));
this.html = html;
this.html.find("li.select-target").click((event) => {
this.targetSelected(this.html.find(event.currentTarget)?.data("token-id"));
});
}

View File

@ -12,40 +12,34 @@ export class DialogSplitItem extends Dialog {
}
constructor(item, splitData, html, callback) {
let options = { classes: ["dialogsplit"], width: 300, height: 160, 'z-index': 99999 };
let options = { classes: ["dialogsplit"], width: 300, height: 'fit-content', 'z-index': 99999 };
let conf = {
title: "Séparer en deux",
content: html,
default: "separer",
buttons: {
"separer": {
label: "Séparer", callback: it => {
this.onSplit();
}
}
"separer": { label: "Séparer", callback: it => this.onSplit() }
}
};
super(conf, options);
this.callback = callback;
this.item = item;
this.splitData = splitData;
}
async onSplit(){
await $(".choix-quantite").change();
this.callback(this.item, this.splitData.choix.quantite);
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
html.find(".choix-quantite").change(event => {
this.html = html;
this.html.find(".choix-quantite").change(event => {
this.splitData.choix.quantite = Number(event.currentTarget.value);
});
}
/* -------------------------------------------- */
async onSplit() {
await this.html.find(".choix-quantite").change();
this.callback(this.item, this.splitData.choix.quantite);
}
}

View File

@ -7,7 +7,7 @@ import { RdDUtility } from "./rdd-utility.js";
*/
export class DialogValidationEncaissement extends Dialog {
static async validerEncaissement(actor, rollData, armure, show, onEncaisser) {
static async validerEncaissement(actor, rollData, armure, show, attackerId, 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,
@ -15,15 +15,15 @@ export class DialogValidationEncaissement extends Dialog {
encaissement: encaissement,
show: show
});
const dialog = new DialogValidationEncaissement(html, actor, rollData, armure, encaissement, show, onEncaisser);
const dialog = new DialogValidationEncaissement(html, actor, rollData, armure, encaissement, show, attackerId, onEncaisser);
dialog.render(true);
}
/* -------------------------------------------- */
constructor(html, actor, rollData, armure, encaissement, show, onEncaisser) {
constructor(html, actor, rollData, armure, encaissement, show, attackerId, onEncaisser) {
// Common conf
let buttons = {
"valider": { label: "Valider", callback: html => this.validerEncaissement() },
"valider": { label: "Valider", callback: html => this.onValider() },
"annuler": { label: "Annuler", callback: html => { } },
};
@ -35,7 +35,7 @@ export class DialogValidationEncaissement extends Dialog {
}
let dialogOptions = {
classes: ["rdddialog"],
classes: ["rdd-roll-dialog"],
width: 350,
height: 290
}
@ -48,6 +48,7 @@ export class DialogValidationEncaissement extends Dialog {
this.armure = armure;
this.encaissement = encaissement;
this.show = show;
this.attackerId = attackerId;
this.onEncaisser = onEncaisser;
this.forceDiceResult = {total: encaissement.roll.result };
}
@ -55,16 +56,17 @@ export class DialogValidationEncaissement extends Dialog {
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
html.find('input.encaissement-roll-result').keyup(async event => {
this.html = html;
this.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)
this.html.find('label.encaissement-total').text(this.encaissement.total);
this.html.find('label.encaissement-blessure').text(this.encaissement.blessures)
});
}
async validerEncaissement() {
async onValider() {
this.encaissement = await RdDUtility.jetEncaissement(this.rollData, this.armure, { showDice: SHOW_DICE, forceDiceResult: this.forceDiceResult});
this.onEncaisser(this.encaissement, this.show)
this.onEncaisser(this.encaissement, this.show, this.attackerId)
}
}

78
module/environnement.js Normal file
View File

@ -0,0 +1,78 @@
import { SYSTEM_RDD } from "./constants.js";
import { Grammar } from "./grammar.js";
import { Misc } from "./misc.js";
import { CompendiumTableHelpers, CompendiumTable, SystemCompendiums } from "./settings/system-compendiums.js";
const COMPENDIUMS_RECHERCHE = 'compendiums-recherche';
export class Environnement {
static init() {
game.settings.register(SYSTEM_RDD, COMPENDIUMS_RECHERCHE, {
name: COMPENDIUMS_RECHERCHE,
default: [
SystemCompendiums.getCompendium('faune-flore-mineraux'),
SystemCompendiums.getCompendium('meditations-et-ecrits'),
SystemCompendiums.getCompendium('equipement')
],
scope: "world",
config: false,
type: Object
});
game.system.rdd.environnement = new Environnement();
Hooks.once('ready', () => game.system.rdd.environnement.onReady());
}
constructor() {
this.compendiums = [];
this.compendiumTables = [];
this.mapMilieux = {}
}
async onReady() {
await this.$prepareCompendiums()
}
async milieux() {
return Object.values(this.mapMilieux);
}
async saveCompendiums(compendiumIds) {
game.settings.set(SYSTEM_RDD, COMPENDIUMS_RECHERCHE, compendiumIds);
await this.$prepareCompendiums();
}
async $prepareCompendiums() {
this.compendiums = game.settings.get(SYSTEM_RDD, COMPENDIUMS_RECHERCHE).filter(c => SystemCompendiums.getPack(c));
this.compendiumTables = this.compendiums.map(it => new CompendiumTable(it, 'Item'));
const compendiumItems = await this.getElements(it => 1, it => it.isInventaire());
const fromCompendiums = Misc.concat(compendiumItems.map(it => it.getMilieux().filter(m => m)));
this.mapMilieux = Misc.indexLowercase(fromCompendiums);
}
async autresMilieux(item) {
const milieuxExistants = item.getMilieux().map(it => Grammar.toLowerCaseNoAccent(it));
return Object.keys(this.mapMilieux)
.filter(it => !milieuxExistants.includes(it))
.map(it => this.mapMilieux[it]);
}
async getElements(itemFrequence, filter) {
const compendiumsElement = await Promise.all(
this.compendiumTables.map(async compTable => await compTable.getContent(itemFrequence, filter))
);
const elements = compendiumsElement.reduce((a, b) => a.concat(b));
elements.sort(Misc.ascending(it => it.name))
return elements;
}
async buildTable(itemFrequence, filter = it => true) {
if (!itemFrequence) {
itemFrequence = it => it.getFrequence()
}
const elements = await this.getElements(itemFrequence, filter);;
return CompendiumTableHelpers.buildTable(elements, itemFrequence);
}
}

View File

@ -1,10 +1,10 @@
export class HtmlUtility{
static _showControlWhen(control, condition) {
static showControlWhen(jQuerySelector, condition) {
if (condition) {
control.show();
jQuerySelector.show();
}
else {
control.hide();
jQuerySelector.hide();
}
}
}

View File

@ -1,5 +1,5 @@
import { RdDItemCompetenceCreature } from "./item-competencecreature.js"
import { Misc } from "./misc.js";
import { TYPES } from "./item.js";
import { RdDCombatManager } from "./rdd-combat.js";
const nomCategorieParade = {
@ -20,7 +20,7 @@ const nomCategorieParade = {
export class RdDItemArme extends Item {
static isArme(item) {
return (item.type == 'competencecreature' && item.system.iscombat) || item.type == 'arme';
return RdDItemCompetenceCreature.getCategorieAttaque(item) || item.type == 'arme';
}
/* -------------------------------------------- */
@ -28,7 +28,7 @@ export class RdDItemArme extends Item {
switch (arme ? arme.type : '') {
case 'arme': return arme;
case 'competencecreature':
return RdDItemCompetenceCreature.armeNaturelle(arme);
return RdDItemCompetenceCreature.armeCreature(arme);
}
return RdDItemArme.mainsNues();
}
@ -166,7 +166,7 @@ export class RdDItemArme extends Item {
let corpsACorps = competences.find(it => it.name == 'Corps à corps') ?? { system: { niveau: -6 } };
let init = RdDCombatManager.calculInitiative(corpsACorps.system.niveau, carac['melee'].value);
armes.push(RdDItemArme.mainsNues({ niveau: corpsACorps.system.niveau, initiative: init }));
//armes.push(RdDItemArme.empoignade({ niveau: corpsACorps.system.niveau, initiative: init }));
armes.push(RdDItemArme.empoignade({ niveau: corpsACorps.system.niveau, initiative: init }));
}
static corpsACorps(mainsNuesActor) {

View File

@ -8,22 +8,22 @@ const xp_par_niveau = [5, 5, 5, 10, 10, 10, 10, 15, 15, 15, 15, 20, 20, 20, 20,
const niveau_max = xp_par_niveau.length - 10;
/* -------------------------------------------- */
const limitesArchetypes = [
{ "niveau": 0, "nombreMax": 100, "nombre": 0 },
{ "niveau": 1, "nombreMax": 10, "nombre": 0 },
{ "niveau": 2, "nombreMax": 9, "nombre": 0 },
{ "niveau": 3, "nombreMax": 8, "nombre": 0 },
{ "niveau": 4, "nombreMax": 7, "nombre": 0 },
{ "niveau": 5, "nombreMax": 6, "nombre": 0 },
{ "niveau": 6, "nombreMax": 5, "nombre": 0 },
{ "niveau": 7, "nombreMax": 4, "nombre": 0 },
{ "niveau": 8, "nombreMax": 3, "nombre": 0 },
{ "niveau": 9, "nombreMax": 2, "nombre": 0 },
{ "niveau": 10, "nombreMax": 1, "nombre": 0 },
{ "niveau": 11, "nombreMax": 1, "nombre": 0 }
{ niveau: 0, nombreMax: 100 },
{ niveau: 1, nombreMax: 10 },
{ niveau: 2, nombreMax: 9 },
{ niveau: 3, nombreMax: 8 },
{ niveau: 4, nombreMax: 7 },
{ niveau: 5, nombreMax: 6 },
{ niveau: 6, nombreMax: 5 },
{ niveau: 7, nombreMax: 4 },
{ niveau: 8, nombreMax: 3 },
{ niveau: 9, nombreMax: 2 },
{ niveau: 10, nombreMax: 1 },
{ niveau: 11, nombreMax: 1 },
];
/* -------------------------------------------- */
const categorieCompetences = {
const categoriesCompetences = {
"generale": { base: -4, label: "Générales" },
"particuliere": { base: -8, label: "Particulières" },
"specialisee": { base: -11, label: "Spécialisées" },
@ -34,13 +34,6 @@ const categorieCompetences = {
"lancer": { base: -8, label: "Lancer" }
}
const compendiumCompetences = {
"personnage": "foundryvtt-reve-de-dragon.competences",
"creature": "foundryvtt-reve-de-dragon.competences-creatures",
"entite": "foundryvtt-reve-de-dragon.competences-entites"
};
function _buildCumulXP() {
let cumulXP = { "-11": 0 };
let cumul = 0;
@ -55,23 +48,17 @@ function _buildCumulXP() {
const competence_xp_cumul = _buildCumulXP();
export class RdDItemCompetence extends Item {
/* -------------------------------------------- */
static actorCompendium(actorType = undefined) {
return compendiumCompetences[actorType ?? 'personnage'];
}
/* -------------------------------------------- */
static getCategorieCompetences() {
return categorieCompetences;
}
/* -------------------------------------------- */
static getNiveauBase(category) {
return categorieCompetences[category].base;
static getCategories() {
return categoriesCompetences;
}
/* -------------------------------------------- */
static getLabelCategorie(category) {
return categorieCompetences[category].label;
return categoriesCompetences[category].label;
}
/* -------------------------------------------- */
static getNiveauBase(category, categories = categoriesCompetences) {
return categories[category]?.base ?? 0;
}
/* -------------------------------------------- */
@ -203,18 +190,9 @@ export class RdDItemCompetence extends Item {
}
}
/* -------------------------------------------- */
static isVisible(item) {
return Number(item.system.niveau) != RdDItemCompetence.getNiveauBase(item.system.categorie);
}
static nomContientTexte(item, texte) {
return Grammar.toLowerCaseNoAccent(item.name).includes(Grammar.toLowerCaseNoAccent(texte))
}
/* -------------------------------------------- */
static isNiveauBase(item) {
return Number(item.system.niveau) == RdDItemCompetence.getNiveauBase(item.system.categorie);
return Number(item.system.niveau) == RdDItemCompetence.getNiveauBase(item.system.categorie, item.getCategories());
}
/* -------------------------------------------- */
@ -280,18 +258,51 @@ export class RdDItemCompetence extends Item {
/* -------------------------------------------- */
static computeResumeArchetype(competences) {
const archetype = RdDItemCompetence.getLimitesArchetypes();
const computed = duplicate(limitesArchetypes);
computed.forEach(it => { it.nombre = 0; it.reste = it.nombreMax; });
competences.map(it => Math.max(0, it.system.niveau_archetype))
.forEach(niveau => {
archetype[niveau] = archetype[niveau] ?? { "niveau": niveau, "nombreMax": 0, "nombre": 0 };
archetype[niveau].nombre = (archetype[niveau]?.nombre ?? 0) + 1;
.filter(n => n > 0)
.forEach(n => {
computed[n] = computed[n] ?? { niveau: n, nombreMax: 0, reste: 0, nombre: 0 };
computed[n].reste--;
computed[n].nombre++;
});
return archetype;
return computed.filter(it => it.niveau > 0);
}
/* -------------------------------------------- */
static getLimitesArchetypes() {
return duplicate(limitesArchetypes);
static triVisible(competences) {
return competences.filter(it => !it.system.isHidden)
.sort((a, b) => RdDItemCompetence.compare(a, b))
}
static $positionTri(comp) {
if (comp.name.startsWith("Survie")) {
if (comp.name.includes("Cité")) return 0;
if (comp.name.includes("Extérieur")) return 1;
return 2;
}
if (comp.system.categorie.startsWith("melee")) {
if (comp.name.includes("Corps")) return 0;
if (comp.name.includes("Dague")) return 1;
if (comp.name.includes("Esquive")) return 2;
return 3;
}
if (comp.system.categorie.startsWith("draconic")) {
if (comp.name.includes("Oniros")) return 0;
if (comp.name.includes("Hypnos")) return 1;
if (comp.name.includes("Narcos")) return 2;
if (comp.name.includes("Thanatos")) return 3;
return 4;
}
return 0;
}
static compare(a, b) {
const diff = RdDItemCompetence.$positionTri(a) - RdDItemCompetence.$positionTri(b);
return diff ? diff : a.name.localeCompare(b.name);
}
}

View File

@ -1,51 +1,97 @@
import { RdDItem, TYPES } from "./item.js";
import { RdDCombatManager } from "./rdd-combat.js";
const categories = {
"generale": { base: 0, label: "Générale" },
"naturelle": { base: 0, label: "Arme naturelle" },
"melee": { base: 0, label: "Mêlée" },
"parade": { base: 0, label: "Parade" },
"tir": { base: 0, label: "Tir" },
"lancer": { base: 0, label: "Lancer" },
"possession": { base: 0, label: "Possession" },
}
/* -------------------------------------------- */
export class RdDItemCompetenceCreature extends Item {
/* -------------------------------------------- */
static setRollDataCreature(rollData) {
rollData.competence = rollData.competence
rollData.carac = { "carac_creature": { label: rollData.competence.name, value: rollData.competence.system.carac_value } }
rollData.competence.system.defaut_carac = "carac_creature"
rollData.competence.system.categorie = "creature"
rollData.selectedCarac = rollData.carac.carac_creature
if (rollData.competence.system.iscombat) {
rollData.arme = RdDItemCompetenceCreature.armeNaturelle(rollData.competence);
}
static getCategories() {
return categories;
}
/* -------------------------------------------- */
static armeNaturelle(competencecreature) {
if (RdDItemCompetenceCreature.isCompetenceAttaque(competencecreature)) {
// si c'est un Item compétence: cloner pour ne pas modifier lma compétence
let arme = (competencecreature instanceof Item) ? competencecreature.clone(): competencecreature;
mergeObject(arme.system,
static setRollDataCreature(rollData) {
rollData.carac = { "carac_creature": { label: rollData.competence.name, value: rollData.competence.system.carac_value } }
rollData.competence.system.defaut_carac = "carac_creature"
rollData.selectedCarac = rollData.carac.carac_creature
rollData.arme = RdDItemCompetenceCreature.armeCreature(rollData.competence);
}
/* -------------------------------------------- */
static armeCreature(item) {
const categorieAttaque = RdDItemCompetenceCreature.getCategorieAttaque(item)
if (categorieAttaque != undefined) {
// si c'est un Item compétence: cloner pour ne pas modifier la compétence
let arme = item.clone();
mergeObject(arme,
{
competence: arme.name,
initiative: RdDCombatManager.calculInitiative(competencecreature.system.niveau, competencecreature.system.carac_value),
niveau: competencecreature.system.niveau,
equipe: true,
resistance: 100,
dommagesReels: arme.system.dommages,
penetration: 0,
force: 0,
rapide: true,
cac: competencecreature.system.isnaturelle ? "naturelle" : "",
action: 'attaque'
action: item.isCompetencePossession() ? 'possession' : 'attaque',
system: {
competence: arme.name,
cac: categorieAttaque == "naturelle" ? "naturelle" : "",
niveau: item.system.niveau,
initiative: RdDCombatManager.calculInitiative(item.system.niveau, item.system.carac_value),
equipe: true,
resistance: 100,
dommagesReels: arme.system.dommages,
penetration: 0,
force: 0,
rapide: true,
}
});
return arme;
}
console.error("RdDItemCompetenceCreature.toActionArme(", competencecreature, ") : impossible de transformer l'Item en arme");
return undefined;
}
/* -------------------------------------------- */
static isCompetenceAttaque(item) {
return item.type == 'competencecreature' && item.system.iscombat;
static getCategorieAttaque(item) {
if (item.type == TYPES.competencecreature) {
switch (item.system.categorie) {
case "melee":
case "tir":
case "lancer":
case "naturelle":
case "possession":
return item.system.categorie
}
}
return undefined
}
static isDommages(item) {
if (item.type == TYPES.competencecreature) {
switch (item.system.categorie) {
case "melee":
case "tir":
case "lancer":
case "naturelle":
return true
}
}
return false
}
static isParade(item) {
if (item.type == TYPES.competencecreature) {
switch (item.system.categorie) {
case "melee":
case "naturelle":
case "parade":
return true
}
}
return false
}
/* -------------------------------------------- */
static isCompetenceParade(item) {
return item.type == 'competencecreature' && item.system.categorie_parade !== "";

View File

@ -2,27 +2,28 @@ import { Misc } from "./misc.js";
import { LOG_HEAD } from "./constants.js";
const MONNAIE_ETAIN = {
name: "Etain (1 denier)", type: 'monnaie',
name: "Denier (étain)", type: 'monnaie',
img: "systems/foundryvtt-reve-de-dragon/icons/objets/piece_etain_poisson.webp",
system: { quantite: 0, valeur_deniers: 1, encombrement: 0.001, description: "" }
system: { quantite: 0, cout: 0.01, encombrement: 0.001, description: "" }
};
const MONNAIE_BRONZE = {
name: "Bronze (10 deniers)", type: 'monnaie',
name: "Sou (bronze)", type: 'monnaie',
img: "systems/foundryvtt-reve-de-dragon/icons/objets/piece_bronze_epees.webp",
system: { quantite: 0, valeur_deniers: 10, encombrement: 0.002, description: "" }
system: { quantite: 0, cout: 0.10, encombrement: 0.002, description: "" }
};
const MONNAIE_ARGENT = {
name: "Argent (1 sol)", type: 'monnaie',
name: "Sol (argent)", type: 'monnaie',
img: "systems/foundryvtt-reve-de-dragon/icons/objets/piece_argent_sol.webp",
system: { quantite: 0, valeur_deniers: 100, encombrement: 0.003, description: "" }
system: { quantite: 0, cout: 1, encombrement: 0.003, description: "" }
};
const MONNAIE_OR = {
name: "Or (10 sols)", type: 'monnaie',
name: "Dragon (or)", type: 'monnaie',
img: "systems/foundryvtt-reve-de-dragon/icons/objets/piece_or_sol.webp",
system: { quantite: 0, valeur_deniers: 1000, encombrement: 0.004, description: "" }
system: { quantite: 0, cout: 10, encombrement: 0.004, description: "" }
};
const MONNAIES_STANDARD = [MONNAIE_ETAIN, MONNAIE_BRONZE, MONNAIE_ARGENT, MONNAIE_OR];
const VALEUR_DENIERS = sols => Math.max(Math.floor((sols ?? 0) * 100), 0);
export class Monnaie {
@ -32,7 +33,7 @@ export class Monnaie {
static monnaiesManquantes(actor) {
const disponibles = actor.itemTypes['monnaie'];
const manquantes = MONNAIES_STANDARD.filter(standard => !disponibles.find(disponible => Monnaie.deValeur(disponible, standard.system?.valeur_deniers)));
const manquantes = MONNAIES_STANDARD.filter(standard => !disponibles.find(disponible => Monnaie.deValeur(disponible, standard.system?.cout)));
if (manquantes.length > 0) {
console.error(`${LOG_HEAD} monnaiesManquantes pour ${actor.name}`, manquantes, ' avec monnaies', disponibles, MONNAIES_STANDARD);
}
@ -40,15 +41,11 @@ export class Monnaie {
}
static deValeur(monnaie, valeur) {
return valeur == monnaie.system.valeur_deniers
return VALEUR_DENIERS(valeur) == VALEUR_DENIERS(monnaie.system.cout)
}
static arrondiDeniers(sols) {
return Number(sols).toFixed(2);
}
static triValeurDenier() {
return Misc.ascending(item => item.system.valeur_deniers)
static triValeurEntiere() {
return Misc.ascending(item => VALEUR_DENIERS(item.system.cout))
}
static async creerMonnaiesStandard(actor) {
@ -65,29 +62,52 @@ export class Monnaie {
return deniers;
}
static async optimiser(actor, fortune) {
let reste = fortune;
static toSolsDeniers(fortune) {
return {
sols: Math.floor(fortune),
deniers: Math.round(100 * (fortune - Math.floor(fortune)))
};
}
static getFortune(monnaies) {
return (monnaies??[])
.map(m => Number(m.system.cout) * Number(m.system.quantite))
.reduce(Misc.sum(), 0);
}
static async optimiserFortune(actor, fortune) {
let resteEnDeniers = Math.round(fortune * 100);
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]) {
const itemPiece = parValeur[valeur];
Monnaie.validerMonnaies(monnaies, actor);
let parValeur = Misc.classifyFirst(monnaies, it => VALEUR_DENIERS(it.system.cout));
for (let valeurDeniers of [1000, 100, 10, 1]) {
const itemPiece = parValeur[valeurDeniers];
if (itemPiece) {
const quantite = Math.floor(reste / valeur);
const quantite = Math.floor(resteEnDeniers / valeurDeniers);
if (quantite != itemPiece.system.quantite) {
updates.push({ _id: parValeur[valeur].id, 'system.quantite': quantite });
updates.push({ _id: parValeur[valeurDeniers].id, 'system.quantite': quantite });
}
reste -= quantite*valeur;
resteEnDeniers -= quantite * valeurDeniers;
}
}
console.log('Monnaie.optimiser', actor.name, 'total', fortune, 'parValeur', parValeur, 'updates', updates, 'reste', reste);
console.log('Monnaie.optimiserFortune', actor.name, 'total', fortune, 'parValeur', parValeur, 'updates', updates, 'reste', resteEnDeniers);
if (updates.length > 0) {
await actor.updateEmbeddedDocuments('Item', updates);
}
if (reste > 0){
if (resteEnDeniers > 0) {
// créer le reste en deniers fortune en deniers
await Monnaie.creerMonnaiesDeniers(actor, reste);
await Monnaie.creerMonnaiesDeniers(actor, resteEnDeniers);
}
}
static validerMonnaies(monnaies, actor = undefined) {
monnaies.filter(it => VALEUR_DENIERS(it.system.cout) == 0)
.map(it => `La monnaie ${it.name} de l'acteur ${actor?.name ?? 'sélectionné'} a une valeur de 0!`)
.forEach(message => {
ui.notifications.warn(message);
console.warn(message);
});
}
}

View File

@ -5,43 +5,71 @@ import { RdDItemCompetence } from "./item-competence.js";
import { RdDHerbes } from "./rdd-herbes.js";
import { RdDGemme } from "./rdd-gemme.js";
import { HtmlUtility } from "./html-utility.js";
import { ReglesOptionelles } from "./settings/regles-optionelles.js";
import { ReglesOptionnelles } from "./settings/regles-optionnelles.js";
import { SYSTEM_RDD } from "./constants.js";
import { RdDSheetUtility } from "./rdd-sheet-utility.js";
import { SystemCompendiums } from "./settings/system-compendiums.js";
import { Misc } from "./misc.js";
import { RdDTimestamp } from "./time/rdd-timestamp.js";
import { RdDItemCompetenceCreature } from "./item-competencecreature.js";
import { TYPES } from "./item.js";
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
* Extend the basic ItemSheet for RdD specific items
*/
export class RdDItemSheet extends ItemSheet {
static get ITEM_TYPE() {
return undefined
}
static defaultTemplate(type) {
return type ?
`systems/foundryvtt-reve-de-dragon/templates/item-${type}-sheet.html` :
"systems/foundryvtt-reve-de-dragon/templates/item-sheet.html";
}
static register(sheetClass) {
Items.registerSheet(SYSTEM_RDD, sheetClass, {
label: Misc.typeName('Item', sheetClass.ITEM_TYPE),
types: [sheetClass.ITEM_TYPE],
makeDefault: true
})
}
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: [SYSTEM_RDD, "sheet", "item"],
template: "systems/foundryvtt-reve-de-dragon/templates/item-sheet.html",
template: RdDItemSheet.defaultTemplate(RdDItemSheet.ITEM_TYPE),
width: 550,
height: 550
});
}
/* -------------------------------------------- */
get template() {
return RdDItemSheet.defaultTemplate(this.item.type);
}
get title() {
return `${Misc.typeName('Item', this.item.type)}: ${this.item.name}`;
}
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
// Add "Post to chat" button
// We previously restricted this to GM and editable items only. If you ever find this comment because it broke something: eh, sorry!
if ("cout" in this.item.system && this.item.isVideOuNonConteneur()) {
if (this.item.isInventaire() && this.item.isVideOuNonConteneur()) {
buttons.unshift({
class: "vendre",
icon: "fas fa-comments-dollar",
onclick: ev => this.item.proposerVente()
onclick: ev => this.item.proposerVente(1)
});
}
buttons.unshift({
class: "montrer",
icon: "fas fa-comment",
onclick: ev => this.item.postItem()
onclick: ev => this.item.postItemToChat()
});
return buttons
}
@ -60,251 +88,225 @@ export class RdDItemSheet extends ItemSheet {
/* -------------------------------------------- */
async getData() {
let formData = {
id: this.item.id,
title: this.item.name,
id: this.item.id,
type: this.item.type,
img: this.item.img,
name: this.item.name,
system: this.item.system,
isGM: game.user.isGM,
actorId: this.actor?.id,
owner: this.item.isOwner,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
isSoins: false,
description: await TextEditor.enrichHTML(this.object.system.description, {async: true}),
descriptionmj: await TextEditor.enrichHTML(this.object.system.descriptionmj, {async: true})
description: await TextEditor.enrichHTML(this.item.system.description, { async: true }),
descriptionmj: await TextEditor.enrichHTML(this.item.system.descriptionmj, { async: true }),
isComestible: this.item.getUtilisationCuisine(),
options: RdDSheetUtility.mergeDocumentRights(this.options, this.item, this.isEditable)
}
if (this.actor) {
formData.isOwned = true;
if (this.item.type == 'conteneur') {
this.prepareConteneurData(formData);
}
if (this.item.type == TYPES.competencecreature) {
formData.isparade = RdDItemCompetenceCreature.isParade(this.item)
formData.isdommages = RdDItemCompetenceCreature.isDommages(this.item)
}
formData.categorieCompetences = RdDItemCompetence.getCategorieCompetences()
const competences = await SystemCompendiums.getCompetences('personnage');
formData.categories = this.item.getCategories()
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.loadItems(it => it.isCompetencePersonnage(), RdDItemCompetence.actorCompendium(this.actor?.type))
formData.competences = competences;
}
if (this.item.type == 'arme') {
formData.competences = await RdDUtility.loadItems(it => RdDItemCompetence.isCompetenceArme(it), RdDItemCompetence.actorCompendium(this.actor?.type))
formData.competences = competences.filter(it => RdDItemCompetence.isCompetenceArme(it))
}
if (['sort', 'sortreserve'].includes(this.item.type)) {
formData.competences = await RdDUtility.loadItems(it => RdDItemCompetence.isDraconic(it), RdDItemCompetence.actorCompendium(this.actor?.type))
formData.competences = competences.filter(it => RdDItemCompetence.isDraconic(it));
}
if (this.item.type == 'recettecuisine') {
formData.ingredients = await TextEditor.enrichHTML(this.object.system.ingredients, {async: true})
formData.ingredients = await TextEditor.enrichHTML(this.object.system.ingredients, { async: true })
}
if (this.item.type == 'extraitpoetique') {
formData.extrait = await TextEditor.enrichHTML(this.object.system.extrait, {async: true})
formData.texte = await TextEditor.enrichHTML(this.object.system.texte, {async: true})
formData.extrait = await TextEditor.enrichHTML(this.object.system.extrait, { async: true })
formData.texte = await TextEditor.enrichHTML(this.object.system.texte, { async: true })
}
if (this.item.type == 'recettealchimique') {
RdDAlchimie.processManipulation(this.item, this.actor && this.actor.id);
formData.manipulation_update = await TextEditor.enrichHTML(this.object.system.manipulation_update, {async: true})
formData.utilisation = await TextEditor.enrichHTML(this.object.system.utilisation, {async: true})
formData.enchantement = await TextEditor.enrichHTML(this.object.system.enchantement, {async: true})
formData.sureffet = await TextEditor.enrichHTML(this.object.system.sureffet, {async: true})
formData.manipulation_update = await TextEditor.enrichHTML(this.object.system.manipulation_update, { async: true })
formData.utilisation = await TextEditor.enrichHTML(this.object.system.utilisation, { async: true })
formData.enchantement = await TextEditor.enrichHTML(this.object.system.enchantement, { async: true })
formData.sureffet = await TextEditor.enrichHTML(this.object.system.sureffet, { async: true })
}
if (this.item.type == 'gemme') {
formData.gemmeTypeList = RdDGemme.getGemmeTypeOptionList();
RdDGemme.calculDataDerivees(this.item);
}
if (this.item.type == 'potion') {
if (this.dateUpdated) {
formData.system.prdate = this.dateUpdated;
this.dateUpdated = undefined;
}
await RdDHerbes.updatePotionData(formData);
await RdDHerbes.addPotionFormData(formData);
}
if (formData.isOwned && this.item.type == 'herbe' && (formData.system.categorie == 'Soin' || formData.system.categorie == 'Repos')) {
if (formData.options.isOwned && this.item.type == 'herbe' && (formData.system.categorie == 'Soin' || formData.system.categorie == 'Repos')) {
formData.isIngredientPotionBase = true;
}
if (this.item.type == 'sortreserve') {
const sortId = this.item.system.sortid;
formData.sort = formData.isOwned ? this.item.actor.items.get(sortId) : game.items.get(sortId);
formData.sort = formData.options.isOwned ? this.item.actor.items.get(sortId) : game.items.get(sortId);
}
formData.bonusCaseList = RdDItemSort.getBonusCaseList(formData, true);
return formData;
}
/* -------------------------------------------- */
prepareConteneurData(formData) {
RdDUtility.filterEquipementParType(formData, this.actor.itemTypes);
this.objetVersConteneur = RdDUtility.buildArbreDeConteneurs(formData.conteneurs, formData.objets);
formData.subItems = formData.conteneurs.find(it => it._id == this.item.id)?.subItems;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
this.html = html;
if (this.item.type == 'conteneur') {
this.form.ondragstart = (event) => this._onDragStart(event);
this.form.ondrop = (event) => this._onDrop(event);
}
let itemSheetDialog = this;
HtmlUtility._showControlWhen($(".item-cout"), ReglesOptionelles.isUsing('afficher-prix-joueurs') || game.user.isGM || !this.item.isOwned);
HtmlUtility._showControlWhen($(".item-magique"), this.item.isMagique());
HtmlUtility.showControlWhen(this.html.find(".item-cout"), ReglesOptionnelles.isUsing('afficher-prix-joueurs')
|| game.user.isGM
|| !this.item.isOwned);
HtmlUtility.showControlWhen(this.html.find(".item-magique"), this.item.isMagique());
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Select competence categorie
html.find(".categorie").change(event => this._onSelectCategorie(event));
this.form.ondragstart = (event) => this._onDragStart(event);
this.form.ondrop = (event) => this._onDrop(event);
html.find('.sheet-competence-xp').change((event) => {
// Select competence categorie
this.html.find(".categorie").change(event => this._onSelectCategorie(event));
this.html.find('.sheet-competence-xp').change((event) => {
if (this.item.isCompetencePersonnage()) {
RdDUtility.checkThanatosXP(this.item.name);
}
});
this.html.find(".item-cout input[name='system.cout']").change(event => {
if (this.item.isMonnaie()) {
const value = event.currentTarget.value;
if (Number(value) == 0) {
ui.notifications.error(`${this.actor?.name ?? 'Monnaie'}: La monnaie ${this.item.name} a maintenant une valeur de 0, et ne peut plus être utilisée pour payer!`)
}
}
})
html.find('.enchanteDate').change((event) => {
let jour = Number($('#jourMois').val());
let mois = $('#nomMois').val();
this.dateUpdated = game.system.rdd.calendrier.getIndexFromDate(jour, mois);
this.html.find('.date-enchantement').change((event) => {
const jour = Number(this.html.find('input.date-enchantement[name="enchantement.jour"]').val());
const mois = RdDTimestamp.definition(this.html.find('select.date-enchantement[name="enchantement.mois"]').val());
const indexDate = game.system.rdd.calendrier.getIndexFromDate(jour, mois.heure);
this.item.update({ 'system.prdate': indexDate });
console.warn(`Date d'enchantement modifiée ${jour}/${mois.heure}: ${indexDate}`)
});
html.find('.creer-tache-livre').click((event) => {
let actorId = event.currentTarget.attributes['data-actor-id'].value;
let actor = game.actors.get(actorId);
actor.creerTacheDepuisLivre(this.item);
});
html.find('.consommer-potion').click((event) => {
let actorId = event.currentTarget.attributes['data-actor-id'].value;
let actor = game.actors.get(actorId);
actor.consommerPotion(this.item);
});
html.find('.creer-potion-base').click((event) => {
let actorId = event.currentTarget.attributes['data-actor-id'].value;
let actor = game.actors.get(actorId);
actor.dialogFabriquerPotion(this.item);
});
this.html.find('.creer-tache-livre').click((event) => this._getEventActor(event).creerTacheDepuisLivre(this.item));
this.html.find('.consommer-potion').click((event) => this._getEventActor(event).consommerPotion(this.item, this.getActionRenderItem()));
this.html.find('.creer-potion-base').click((event) => this._getEventActor(event).dialogFabriquerPotion(this.item));
html.find('.alchimie-tache a').click((event) => {
let actorId = event.currentTarget.attributes['data-actor-id'].value;
let recetteId = event.currentTarget.attributes['data-recette-id'].value;
let tacheName = event.currentTarget.attributes['data-alchimie-tache'].value;
let tacheData = event.currentTarget.attributes['data-alchimie-data'].value;
let actor = game.actors.get(actorId);
this.html.find('.alchimie-tache a').click((event) => {
let actor = this._getEventActor(event);
if (actor) {
let recetteId = event.currentTarget.attributes['data-recette-id'].value;
let tacheName = event.currentTarget.attributes['data-alchimie-tache'].value;
let tacheData = event.currentTarget.attributes['data-alchimie-data'].value;
actor.effectuerTacheAlchimie(recetteId, tacheName, tacheData);
} else {
ui.notifications.info("Impossible trouver un acteur pour réaliser cette tache Alchimique.");
}
});
html.find('.item-split').click(async event => {
const item = RdDSheetUtility.getItem(event, this.actor);
await RdDSheetUtility.splitItem(item, this.actor, async () => itemSheetDialog.render(true));
});
html.find('.item-edit').click(async event => {
const item = RdDSheetUtility.getItem(event, this.actor);
item.sheet.render(true);
});
html.find('.item-delete').click(async event => {
const li = RdDSheetUtility.getEventElement(event);
const item = this.actor.getObjet(li.data("item-id"));
RdDUtility.confirmerSuppressionItem(this, item, li);
});
html.find('.item-vendre').click(async event => {
const item = RdDSheetUtility.getItem(event, this.actor);
item?.proposerVente();
});
html.find('.item-montrer').click(async event => {
const item = RdDSheetUtility.getItem(event, this.actor);
item?.postItem();
});
html.find('.item-action').click(async event => {
const item = RdDSheetUtility.getItem(event, this.actor);
this.actor.actionItem(item, async () => itemSheetDialog.render(true));
});
html.find('.conteneur-name a').click(async event => {
RdDUtility.toggleAfficheContenu(RdDSheetUtility.getItemId(event));
this.render(true);
});
if (this.actor) {
this.html.find('.item-split').click(async event => RdDSheetUtility.splitItem(RdDSheetUtility.getItem(event, this.actor), this.actor, this.getActionRenderItem()));
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.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-montrer').click(async event => RdDSheetUtility.getItem(event, this.actor)?.postItemToChat());
this.html.find('.item-action').click(async event => RdDSheetUtility.getItem(event, this.actor)?.actionPrincipale(this.actor, this.getActionRenderItem()));
this.html.find('.item-quantite-plus').click(async event => {
await this.actor.itemQuantiteIncDec(RdDSheetUtility.getItemId(event), 1)
this.render();
});
this.html.find('.item-quantite-moins').click(async event => {
await this.actor.itemQuantiteIncDec(RdDSheetUtility.getItemId(event), -1)
this.render();
});
}
const updateItemTimestamp = (path, timestamp) => this.item.update({ [path]: duplicate(timestamp) })
RdDTimestamp.handleTimestampEditor(this.html, 'system.temporel.debut', updateItemTimestamp);
RdDTimestamp.handleTimestampEditor(this.html, 'system.temporel.fin', updateItemTimestamp);
}
getActionRenderItem() {
return async () => {
let item = this.item;
while (item) {
await item.sheet?.render()
item = this.actor.getContenant(item)
}
}
}
_getEventActor(event) {
let actorId = event.currentTarget.attributes['data-actor-id'].value;
let actor = game.actors.get(actorId);
return actor;
}
/* -------------------------------------------- */
async _onSelectCategorie(event) {
event.preventDefault();
if (this.item.isCompetence()) {
let level = RdDItemCompetence.getNiveauBase(event.currentTarget.value);
const categorie = event.currentTarget.value;
const level = RdDItemCompetence.getNiveauBase(categorie, this.item.getCategories());
this.item.system.base = level;
$("#base").val(level);
this.html.find('[name="system.base"]').val(level);
}
}
/* -------------------------------------------- */
get template() {
let type = this.item.type
return `systems/foundryvtt-reve-de-dragon/templates/item-${type}-sheet.html`;
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
// Données de bonus de cases ?
formData['system.bonuscase'] = RdDItemSort.buildBonusCaseStringFromFormData(formData.bonusValue, formData.caseValue);
if (this.item.type == 'sort') {
// Données de bonus de cases ?
formData['system.bonuscase'] = RdDItemSort.buildBonuscaseFromArrays(formData.bonusValue, formData.caseValue);
}
return this.item.update(formData);
}
/* -------------------------------------------- */
async _onDragStart(event) {
console.log("_onDragStart", event);
if (event.target.classList.contains("entity-link")) return;
const itemId = event.srcElement?.attributes["data-item-id"].value;
const item = this.actor.items.get(itemId);
// Create drag data
const dragData = {
actorId: this.actor.id,
type: "Item",
data: item.system
};
event.dataTransfer.setData("text/plain", JSON.stringify(dragData));
}
async _onDrop(event) {
// Try to extract the dragData
let dragData;
try {
dragData = JSON.parse(event.dataTransfer.getData('text/plain'));
} catch (err) {
return false;
}
let dragData = RdDItemSheet.$extractDragData(event);
if (!dragData) return false;
const allowed = Hooks.call("dropActorSheetData", this.actor, this, dragData);
if (allowed === false) return;
if (allowed === false) return false;
// Handle different dragData types
switch (dragData.type) {
case "Item":
return this._onDropItem(event, dragData);
case "Actor":
return this._onDropActor(event, dragData);
}
return super._onDrop(event);
}
/* -------------------------------------------- */
async _onDropItem(event, dragData) {
if (this.actor) {
const dropParams = RdDSheetUtility.prepareItemDropParameters(this.item.id, this.actor.id, dragData, this.objetVersConteneur);
await this.actor.processDropItem(dropParams);
await this.render(true);
}
static $extractDragData(event) {
try {
const eventData = event?.dataTransfer?.getData('text/plain');
if (eventData) {
return JSON.parse(eventData);
}
} catch (err) { }
return undefined;
}
async _onDropItem(event, dragData) {
}
async _onDropActor(event, dragData) {
}
}

View File

@ -1,4 +1,3 @@
/* -------------------------------------------- */
import { Misc } from "./misc.js";
import { TMRUtility } from "./tmr-utility.js";
@ -14,9 +13,9 @@ export class RdDItemSort extends Item {
static isCoutVariable(sort) {
return sort && (sort.system.ptreve.toLowerCase() == "variable" || sort.system.ptreve.indexOf("+") >= 0);
}
/* -------------------------------------------- */
static setCoutReveReel(sort){
static setCoutReveReel(sort) {
if (sort) {
sort.system.ptreve_reel = this.isCoutVariable(sort) ? 1 : sort.system.ptreve;
}
@ -25,94 +24,91 @@ export class RdDItemSort extends Item {
/* -------------------------------------------- */
static getDifficulte(sort, variable) {
if (sort && !RdDItemSort.isDifficulteVariable(sort)) {
return Misc.toInt(sort.system.difficulte);
return Misc.toInt(sort.system.difficulte);
}
return variable;
}
/* -------------------------------------------- */
static buildBonusCaseList( caseBonusString, newCase ) {
if (caseBonusString == undefined) {
return [];
}
let bonusCaseList = [];
let bonusCaseArray = caseBonusString == undefined ? [] : caseBonusString.split(',');
for( let bonusCase of bonusCaseArray) {
let bonusSplit = bonusCase.split(':');
bonusCaseList.push( { case: bonusSplit[0], bonus: bonusSplit[1] } );
}
if ( newCase )
bonusCaseList.push( {case: "Nouvelle", bonus: 0} );
return bonusCaseList;
static buildBonusCaseList(bonuscase, newCase) {
const list = RdDItemSort._bonuscaseStringToList(bonuscase)
if (newCase) {
return list.concat({ case: "Nouvelle", bonus: 0 });
}
/* -------------------------------------------- */
return list;
}
/**
* Retourne une liste de bonus/case pour un item-sheet
* @param {} item
*/
static getBonusCaseList( item, newCase = false ) {
static getBonusCaseList(item, newCase = false) {
// Gestion spéciale case bonus
if ( item.type == 'sort') {
return this.buildBonusCaseList(item.system.bonuscase, newCase );
if (item.type == 'sort') {
return RdDItemSort.buildBonusCaseList(item.system.bonuscase, newCase);
}
return undefined;
}
/* -------------------------------------------- */
/** Met à jour les données de formulaire
* si static des bonus de cases sont présents
* */
static buildBonusCaseStringFromFormData( bonuses, cases ) {
if ( bonuses ) {
let list = [];
let caseCheck = {};
for (let i=0; i<bonuses.length; i++) {
let coord = cases[i]?.toUpperCase() || 'A1';
let bonus = bonuses[i] || 0;
if ( TMRUtility.verifyTMRCoord( coord ) && bonus > 0 && caseCheck[coord] == undefined ) {
static buildBonuscaseFromArrays(bonuses, coords) {
if (bonuses) {
const list = [];
const caseCheck = {};
for (let i = 0; i < bonuses.length && i < coords.length; i++) {
const coord = coords[i] == 'Fleuve' ? 'Fleuve' : (coords[i]?.toUpperCase() ?? 'A1');
const bonus = bonuses[i] || 0;
if (TMRUtility.verifyTMRCoord(coord) && bonus > 0 && caseCheck[coord] == undefined) {
caseCheck[coord] = bonus;
list.push( coord+":"+bonus );
list.push({ case: coord, bonus: bonus });
}
}
return list.toString();
return RdDItemSort._bonuscaseListToString(list);
}
return undefined;
}
/* -------------------------------------------- */
static incrementBonusCase( actor, sort, coord ) {
let bonusCaseList = this.buildBonusCaseList(sort.system.bonuscase, false);
//console.log("ITEMSORT", sort, bonusCaseList);
let found = false;
let StringList = [];
for( let bc of bonusCaseList) {
if (bc.case == coord) { // Case existante
found = true;
bc.bonus = Number(bc.bonus) + 1;
}
StringList.push( bc.case+':'+bc.bonus );
}
if ( !found) { //Nouvelle case, bonus de 1
StringList.push(coord+':1');
}
// Sauvegarde/update
let bonuscase = StringList.toString();
//console.log("Bonus cae :", bonuscase);
actor.updateEmbeddedDocuments('Item', [{ _id: sort._id, 'system.bonuscase': bonuscase }] );
}
/* -------------------------------------------- */
static getCaseBonus( sort, coord) {
let bonusCaseList = this.buildBonusCaseList(sort.system.bonuscase, false);
for( let bc of bonusCaseList) {
if (bc.case == coord) { // Case existante
return Number(bc.bonus);
}
static incrementBonusCase(actor, sort, coord) {
if (TMRUtility.getTMR(coord).type == "fleuve") {
coord = 'Fleuve';
}
return 0;
const list = RdDItemSort.buildBonusCaseList(sort.system.bonuscase, false);
const bonus = Number(list.find(it => it.case == coord)?.bonus ?? 0);
const modified = { case: coord, bonus: bonus + 1 };
const bonuscase = RdDItemSort._bonuscaseListToString(
list.filter(it => it.case != coord).concat(modified)
);
// Sauvegarde/update
actor.updateEmbeddedDocuments('Item', [{ _id: sort._id, 'system.bonuscase': bonuscase }]);
}
/* -------------------------------------------- */
static getCaseBonus(sort, coord) {
const isFleuve = TMRUtility.getTMR(coord).type == "fleuve";
let bc = RdDItemSort.buildBonusCaseList(sort.system.bonuscase, false)
.filter(it => it.case == coord || (isFleuve && it.case == 'Fleuve'))
.find(it => true)
return Number(bc?.bonus ?? 0);
}
static _bonuscaseListToString(list) {
return list.map(it => `${it.case}:${it.bonus}`)
.sort(Misc.ascending())
.join(',');
}
static _bonuscaseStringToList(bonuscase) {
return (bonuscase ?? '').split(',').map(it => {
const b = it.split(':');
return { case: b[0], bonus: b[1] };
});
}
}

View File

@ -1,4 +0,0 @@
export class RdDItemTache extends Item {
}

View File

@ -1,27 +1,89 @@
import { DialogItemVente } from "./dialog-item-vente.js";
import { Grammar } from "./grammar.js";
import { Misc } from "./misc.js";
import { RdDHerbes } from "./rdd-herbes.js";
import { RdDTimestamp } from "./time/rdd-timestamp.js";
import { RdDUtility } from "./rdd-utility.js";
import { SystemCompendiums } from "./settings/system-compendiums.js";
import { RdDRaretes } from "./item/raretes.js";
import { RdDItemCompetence } from "./item-competence.js";
import { RdDItemCompetenceCreature } from "./item-competencecreature.js";
const typesObjetsEquipement = [
"arme",
"armure",
"conteneur",
"gemme",
"herbe",
"ingredient",
"livre",
"monnaie",
"munition",
"nourritureboisson",
"objet",
"potion",
export const TYPES = {
competence: 'competence',
competencecreature: 'competencecreature',
empoignade: 'empoignade',
possession: 'possession',
blessure: 'blessure',
maladie: 'maladie',
poison: 'poison',
arme: 'arme',
armure: 'armure',
conteneur: 'conteneur',
objet: 'objet',
monnaie: 'monnaie',
gemme: 'gemme',
munition: 'munition',
nourritureboisson: 'nourritureboisson',
herbe: 'herbe',
plante: 'plante',
ingredient: 'ingredient',
faune: 'faune',
livre: 'livre',
potion: 'potion',
service: 'service',
musique: 'musique',
danse: 'danse',
chant: 'chant',
jeu: 'jeu',
recettecuisine: 'recettecuisine',
oeuvre: 'oeuvre',
recettealchimique: 'recettealchimique',
tache: 'tache',
sort: 'sort',
sortreserve: 'sortreserve',
rencontre: 'rencontre',
queue: 'queue',
ombre: 'ombre',
souffle: 'souffle',
tete: 'tete',
casetmr: 'casetmr',
meditation: 'meditation',
signedraconique: 'signedraconique',
tarot: 'tarot',
nombreastral: 'nombreastral',
extraitpoetique: 'extraitpoetique',
}
const typesInventaireMateriel = [
TYPES.arme,
TYPES.armure,
TYPES.conteneur,
TYPES.faune,
TYPES.gemme,
TYPES.herbe,
TYPES.plante,
TYPES.ingredient,
TYPES.livre,
TYPES.monnaie,
TYPES.munition,
TYPES.nourritureboisson,
TYPES.objet,
TYPES.potion,
]
const typesObjetsOeuvres = ["oeuvre", "recettecuisine", "musique", "chant", "danse", "jeu"]
const typesObjetsDraconiques = ["queue", "ombre", "souffle", "tete", "signedraconique", "sortreserve", "rencontre"]
const typesObjetsConnaissance = ["meditation", "recettealchimique", "sort"]
const typesObjetsEffet = ["possession", "poison", "maladie"]
const typesObjetsCompetence = ["competence", "competencecreature"]
const typesInventaire = {
materiel: typesInventaireMateriel,
all: ['service'].concat(typesInventaireMateriel),
}
const typesObjetsOeuvres = [TYPES.oeuvre, TYPES.recettecuisine, TYPES.musique, TYPES.chant, TYPES.danse, TYPES.jeu]
const typesObjetsDraconiques = [TYPES.queue, TYPES.ombre, TYPES.souffle, TYPES.tete, TYPES.signedraconique, TYPES.sortreserve, TYPES.rencontre]
const typesObjetsConnaissance = [TYPES.meditation, TYPES.recettealchimique, TYPES.sort]
const typesObjetsEffet = [TYPES.possession, TYPES.poison, TYPES.maladie, TYPES.blessure]
const typesObjetsCompetence = [TYPES.competence, TYPES.competencecreature]
const typesObjetsTemporels = [TYPES.blessure, TYPES.poison, TYPES.maladie, TYPES.queue, TYPES.ombre, TYPES.souffle, TYPES.signedraconique, TYPES.rencontre]
const typesObjetsEquipable = [TYPES.arme, TYPES.armure, TYPES.objet];
const typesEnvironnement = typesInventaireMateriel;
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/
@ -35,6 +97,7 @@ export const defaultItemImg = {
conteneur: "systems/foundryvtt-reve-de-dragon/icons/objets/sac_a_dos.webp",
sort: "systems/foundryvtt-reve-de-dragon/icons/competence_oniros.webp",
herbe: "systems/foundryvtt-reve-de-dragon/icons/botanique/Endorlotte.webp",
faune: "systems/foundryvtt-reve-de-dragon/icons/faune/rongeur.webp",
ingredient: "systems/foundryvtt-reve-de-dragon/icons/objets/sable_poudre.webp",
livre: "systems/foundryvtt-reve-de-dragon/icons/objets/livre.webp",
potion: "systems/foundryvtt-reve-de-dragon/icons/objets/liqueur_de_bagdol.webp",
@ -54,58 +117,164 @@ export const defaultItemImg = {
poison: "systems/foundryvtt-reve-de-dragon/icons/maladies_venins/venin.webp",
oeuvre: "systems/foundryvtt-reve-de-dragon/icons/competence_comedie.webp",
nourritureboisson: "systems/foundryvtt-reve-de-dragon/icons/objets/provision_crue.webp",
service: "systems/foundryvtt-reve-de-dragon/icons/services/lit.webp",
signedraconique: "systems/foundryvtt-reve-de-dragon/icons/tmr/signe_draconique.webp",
gemme: "systems/foundryvtt-reve-de-dragon/icons/gemmes/almaze.webp",
possession: "systems/foundryvtt-reve-de-dragon/icons/entites/possession2.webp",
sortreserve: "systems/foundryvtt-reve-de-dragon/icons/competence_oniros.webp",
extraitpoetique: "systems/foundryvtt-reve-de-dragon/icons/competence_ecriture.webp",
tarot: "systems/foundryvtt-reve-de-dragon/icons/tarots/dos-tarot.webp",
empoignade: "systems/foundryvtt-reve-de-dragon/icons/competence_corps_a_corps.webp"
}
/* -------------------------------------------- */
export class RdDItem extends Item {
constructor(itemData, context) {
if (!itemData.img) {
itemData.img = defaultItemImg[itemData.type];
}
super(itemData, context);
static get defaultIcon() {
return undefined;
}
static getTypesObjetsEquipement() {
return typesObjetsEquipement
static getDefaultImg(itemType) {
return game.system.rdd.itemClasses[itemType]?.defaultIcon ?? defaultItemImg[itemType];
}
static isFieldInventaireModifiable(type, field) {
switch (field) {
case 'quantite':
if ([TYPES.conteneur].includes(type)) {
return false;
}
break;
case 'cout':
if ([TYPES.monnaie].includes(type)) {
return game.user.isGM;
}
break;
}
return true;
}
static async getCorrespondingItem(itemRef) {
if (itemRef.pack) {
return await SystemCompendiums.loadDocument(itemRef)
}
return game.items.get(itemRef.id ?? itemRef._id);
}
static getItemTypesInventaire(mode = 'materiel') {
return typesInventaire[mode ?? 'materiel']
}
static getItemTypesDraconiques() {
return typesObjetsDraconiques;
}
static getItemTypesEnvironnement() {
return typesEnvironnement;
}
static getTypesOeuvres() {
return typesObjetsOeuvres
}
isCompetencePersonnage() {
return this.type == 'competence'
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);
}
isCompetence() {
return typesObjetsCompetence.includes(this.type)
getUniteQuantite() {
switch (this.type) {
case TYPES.monnaie: return "(Pièces)"
case TYPES.herbe:
switch (this.system.categorie) {
case 'Alchimie': case 'Repos': case 'Soin':
return "(Brins)"
case 'Cuisine': return '';
}
return '';
case TYPES.ingredient: return "(Pépins ou Brins)"
}
return '';
}
isEquipement() {
return typesObjetsEquipement.includes(this.type)
isEquipable() {
return typesObjetsEquipable.includes(this.type)
}
isOeuvre() {
return typesObjetsOeuvres.includes(this.type)
isCompetencePersonnage() { return this.type == TYPES.competence }
isCompetenceCreature() { return this.type == TYPES.competencecreature }
isConteneur() { return this.type == TYPES.conteneur; }
isMonnaie() { return this.type == TYPES.monnaie; }
isPotion() { return this.type == TYPES.potion; }
isNourritureBoisson() { return this.type == TYPES.nourritureboisson; }
isService() { return this.type == TYPES.service; }
isCompetence() { return typesObjetsCompetence.includes(this.type) }
isCompetencePossession() { return TYPES.competencecreature == this.type && this.system.categorie == "possession" }
isTemporel() { return typesObjetsTemporels.includes(this.type) }
isOeuvre() { return typesObjetsOeuvres.includes(this.type) }
isDraconique() { return RdDItem.getItemTypesDraconiques().includes(this.type) }
isQueueDragon() { return [TYPES.queue, TYPES.ombre].includes(this.type) }
isEffet() { return typesObjetsEffet.includes(this.type) }
isConnaissance() { return typesObjetsConnaissance.includes(this.type) }
isInventaire(mode = 'materiel') { return RdDItem.getItemTypesInventaire(mode).includes(this.type); }
isBoisson() { return this.isNourritureBoisson() && this.system.boisson; }
isAlcool() { return this.isNourritureBoisson() && this.system.boisson && this.system.alcoolise; }
isHerbeAPotion() { return this.type == TYPES.herbe && (this.system.categorie == 'Soin' || this.system.categorie == 'Repos'); }
isBlessure() { return this.type == TYPES.blessure }
isPresentDansMilieux(milieux) {
return this.getEnvironnements(milieux).length > 0
}
isDraconique() {
return typesObjetsDraconiques.includes(this.type)
getCategories() {
switch (this.type) {
case TYPES.competence: return RdDItemCompetence.getCategories()
case TYPES.competencecreature: return RdDItemCompetenceCreature.getCategories()
}
return {}
}
isEffet() {
return typesObjetsEffet.includes(this.type)
getEnvironnements(milieux = undefined) {
const environnements = this.isInventaire() ? this.system.environnement : undefined;
if (milieux == undefined || !environnements) {
return environnements ?? [];
}
return environnements.filter(env => milieux.includes(env.milieu))
}
isConnaissance() {
return typesObjetsConnaissance.includes(this.type)
getMilieux() {
return this.getEnvironnements().map(env => env.milieu);
}
isConteneur() {
return this.type == 'conteneur';
getRaretes(milieux = undefined) {
if (this.isInventaire()) {
const raretes = this.getEnvironnements(milieux).map(env => RdDRaretes.byCode(env.rarete));
if (milieux == undefined && raretes.length == 0) {
return [RdDRaretes.rareteFrequente()];
}
return raretes;
}
return [RdDRaretes.rareteEgale()];
}
getFrequence(milieux = undefined) {
const frequences = this.getEnvironnements(milieux).map(it => it.frequence);
return frequences.length == 0 ? 0 : Math.max(...frequences);
}
getItemGroup() {
if (this.isEquipement()) return "equipement";
if (this.isInventaire()) return "equipement";
if (this.isOeuvre()) return "oeuvre";
if (this.isDraconique()) return "draconique";
if (this.isConnaissance()) return "connaissance";
@ -114,70 +283,175 @@ export class RdDItem extends Item {
return "autres";
}
isConteneurNonVide() {
return this.isConteneur() && (this.system.contenu?.length ?? 0) > 0;
isConteneurNonVide() { return this.isConteneur() && (this.system.contenu?.length ?? 0) > 0; }
isConteneurVide() { return this.isConteneur() && (this.system.contenu?.length ?? 0) == 0; }
isVideOuNonConteneur() { return !this.isConteneur() || (this.system.contenu?.length ?? 0) == 0; }
isFinPeriode(oldTimestamp, newTimestamp) {
if (!this.isTemporel()) {
return false;
}
const finPeriode = new RdDTimestamp(this.system.temporel.fin);
return oldTimestamp.compare(finPeriode) < 0 && finPeriode.compare(newTimestamp) <= 0
}
isConteneurVide() {
return this.isConteneur() && (this.system.contenu?.length ?? 0) == 0;
async onCreateItemTemporel(actor) {
if (this.isTemporel()) {
const timestampDebut = game.system.rdd.calendrier.timestamp;
const timestampFin = await this.calculerFinPeriodeTemporel(timestampDebut);
await actor.updateEmbeddedDocuments('Item', [{
_id: this.id,
'system.temporel.debut': duplicate(timestampDebut),
'system.temporel.fin': duplicate(timestampFin),
}])
}
}
isVideOuNonConteneur() {
return !this.isConteneur() || (this.system.contenu?.length ?? 0) == 0;
async calculerFinPeriodeTemporel(timestampDebut) {
return timestampDebut;
}
isAlcool() {
return this.type == 'nourritureboisson' && this.system.boisson && this.system.alcoolise;
async onFinPeriodeTemporel(oldTimestamp, newTimestamp) {
if (this.isTemporel() && this.actor) {
await this.onFinPeriode(oldTimestamp, newTimestamp);
}
}
isHerbeAPotion() {
return this.type == 'herbe' && (this.system.categorie == 'Soin' || this.system.categorie == 'Repos');
async onFinPeriode(oldTimestamp, newTimestamp) {
console.log(`${this.actor.name}: l'objet ${this.name} a expiré et été supprimé`);
await this.actor?.deleteEmbeddedDocuments('Item', [this.id]);
}
isPotion() {
return this.type == 'potion';
getUtilisation() {
switch (this.type) {
case TYPES.potion:
switch (this.system.categorie) {
case 'Alchimie': case 'AlchimieEnchante': case 'AlchimieAutre': return 'alchimie'
case 'Cuisine': return 'cuisine'
case 'Remede': case 'Repos': case 'ReposEnchante': case 'Soin': case 'SoinEnchante': return 'soins'
}
return '';
case TYPES.nourritureboisson: return 'cuisine';
case TYPES.herbe: case TYPES.faune: case TYPES.ingredient: case TYPES.plante:
switch (this.system.categorie) {
case 'Cuisine': return 'cuisine';
case 'Toxique': case 'Poison': return 'poison';
case 'Alchimie': return 'alchimie'
case 'Soin': case 'Repos': return 'soins'
}
return this.system.sust > 0 ? 'cuisine' : '';
}
return '';
}
getUtilisationCuisine() {
if (this.getUtilisation() == 'cuisine') {
switch (this.type) {
case TYPES.nourritureboisson:
return 'pret';
case TYPES.herbe: case TYPES.faune: case TYPES.ingredient: case TYPES.plante:
return 'brut';
}
}
return '';
}
isCristalAlchimique() {
return this.type == 'objet' && Grammar.toLowerCaseNoAccent(this.name) == 'cristal alchimique' && this.system.quantite > 0;
return this.type == TYPES.objet && Grammar.includesLowerCaseNoAccent(this.name, 'cristal alchimique') && this.system.quantite > 0;
}
isMagique() {
return this.system.magique
}
isItemCommerce() {
return this.parent?.type == 'commerce';
}
isNomLike(texte) {
return Grammar.includesLowerCaseNoAccent(this.name, texte)
}
isNomTypeLike(texte) {
return this.isNomLike(texte) || Grammar.includesLowerCaseNoAccent(Misc.typeName(this.type, 'Item'), texte)
}
getQuantite() {
return Math.round(this.isConteneur() ? 1 : (this.system.quantite ?? 0))
return this.isService() ? undefined : Math.round(this.system.quantite ?? 0)
}
getEncTotal() {
return this.getEnc() * this.getQuantite();
}
return (this.getQuantite() ?? 0) * this.getEnc();
}
getEnc() {
switch (this.type) {
case 'herbe':
return encBrin;
case 'gemme':
case TYPES.service:
return 0;
case TYPES.herbe:
return this.getEncHerbe();
case TYPES.gemme:
return encPepin * this.system.taille;
}
return Math.max(this.system.encombrement ?? 0, 0);
}
prixTotalDeniers() {
return this.getQuantite() * this.valeurDeniers()
getEncContenu() {
return this.getContenu()
.map(it => it.getRecursiveEnc())
.reduce(Misc.sum(), 0);
}
valeurDeniers() {
return Math.max(Math.round(this.system.cout ? (this.system.cout * 100) : (this.system.valeur_deniers ?? 0)), 0)
getRecursiveEnc() {
return this.getEncTotal() + this.getEncContenu()
}
getEncHerbe() {
switch (this.system.categorie) {
case 'Repos': case 'Soin': case 'Alchimie':
return encBrin;
}
return this.system.encombrement;
}
getContenu() {
if (this.isConteneur()) {
return this.system.contenu.map(idContenu => this.actor.getItem(idContenu));
}
return []
}
isConteneurContenu(conteneur) {
return this.getContenu()
.find(it => it.id == conteneur.id || it.isConteneurContenu(conteneur))
}
valeurTotale() {
return (this.isService() ? 1 : this.getQuantite()) * this.valeur()
}
valeur() {
return this.system.cout ?? 0
}
calculerPrixCommercant() {
if (this.isItemCommerce()) {
// appliquer le pourcentage
return this.parent.calculerPrix(this);
}
return this.system.cout;
}
prepareDerivedData() {
super.prepareDerivedData();
if (this.isEquipement()) {
if (this.isInventaire()) {
this.system.encTotal = this.getEncTotal();
if (this.isPotion()) {
this.prepareDataPotion()
}
this.system.actionPrincipale = this.getActionPrincipale({ warnIfNot: false });
}
this.equipable = this.isEquipable();
}
prepareDataPotion() {
@ -192,19 +466,43 @@ export class RdDItem extends Item {
}
getActionPrincipale(options = { warnIfNot: true }) {
const warn = options.warnIfNot;
switch (this.type) {
case 'nourritureboisson': return this._actionOrWarnQuantiteZero(this.system.boisson ? 'Boire' : 'Manger', warn);
case 'potion': return this._actionOrWarnQuantiteZero('Boire', warn);
case 'livre': return this._actionOrWarnQuantiteZero('Lire', warn);
case 'conteneur': return 'Ouvrir';
case 'herbe': return this.isHerbeAPotion() ? this._actionOrWarnQuantiteZero('Décoction', warn) : undefined;
case 'queue': case 'ombre': return this.system.refoulement>0 ? 'Refouler' : undefined;
case TYPES.conteneur: return 'Ouvrir';
}
if (this.actor?.isPersonnage()) {
const warn = options.warnIfNot;
if (this.getUtilisationCuisine() == 'brut') {
return 'Utiliser';
}
switch (this.type) {
case TYPES.nourritureboisson: return this._actionOrWarnQuantiteZero(this.system.boisson ? 'Boire' : 'Manger', warn);
case TYPES.potion: return this._actionOrWarnQuantiteZero('Boire', warn);
case TYPES.livre: return this._actionOrWarnQuantiteZero('Lire', warn);
case TYPES.herbe: return this.isHerbeAPotion() ? this._actionOrWarnQuantiteZero('Décoction', warn) : undefined;
case TYPES.queue: case TYPES.ombre: return this.system.refoulement > 0 ? 'Refouler' : undefined;
}
}
return undefined;
}
_actionOrWarnQuantiteZero(actionName, warn){
/* -------------------------------------------- */
async actionPrincipale(actor, onActionItem = async () => { }) {
if (!this.getActionPrincipale()) {
return;
}
if (await actor.actionNourritureboisson(this, onActionItem)) {
return;
}
switch (this.type) {
case TYPES.potion: return await actor.consommerPotion(this, onActionItem);
case TYPES.livre: return await actor.actionLire(this);
case TYPES.conteneur: return await this.sheet.render(true);
case TYPES.herbe: return await actor.actionHerbe(this, onActionItem);
case TYPES.queue: case TYPES.ombre: return await actor.actionRefoulement(this);
}
}
_actionOrWarnQuantiteZero(actionName, warn) {
if ((this.system.quantite ?? 0) <= 0) {
if (warn) {
ui.notifications.warn(`Vous n'avez plus de ${this.name}.`);
@ -221,7 +519,43 @@ export class RdDItem extends Item {
await this.quantiteIncDec(-nombre, options);
}
async quantiteIncDec(nombre, options = { diminuerQuantite: true, supprimerSiZero: false }) {
async onCreateDecoupeComestible(actor) {
if (actor && this.getUtilisationCuisine() == 'brut' && this.system.sust != 1) {
if (this.system.sust < 1) {
await actor.updateEmbeddedDocuments('Item', [{
_id: this.id,
'system.sust': 0
}])
}
else {
const sust = Math.floor(this.system.sust);
await actor.updateEmbeddedDocuments('Item', [{
_id: this.id,
'system.quantite': this.system.quantite * sust,
'system.encombrement': Misc.keepDecimals(this.system.encombrement / sust, 2),
'system.cout': Misc.keepDecimals(this.system.cout / sust, 2),
'system.sust': 1
}])
}
}
}
async empiler(item) {
if (this.getUtilisationCuisine() == 'brut') {
const sust = this.system.sust + item.system.sust;
const encombrement = this.system.encombrement + item.system.encombrement;
await this.update({
"system.sust": sust,
"system.encombrement": encombrement
});
}
else {
await this.quantiteIncDec(item.system.quantite);
}
await item.delete();
}
async quantiteIncDec(nombre, options = { supprimerSiZero: false }) {
const quantite = Number(this.system.quantite ?? -1);
if (quantite >= 0) {
const reste = Math.max(quantite + Number(nombre), 0);
@ -244,14 +578,13 @@ export class RdDItem extends Item {
/* -------------------------------------------- */
// détermine si deux équipements sont similaires: de même type, et avec les même champs hormis la quantité
isEquipementEmpilable(other) {
if (!other || !this.isEquipement()) {
isInventaireEmpilable(other) {
if (!other || !this.isInventaire()) {
return [false, undefined];
}
if (this.system.quantite == undefined) {
return [false, `Impossible de regrouper des ${this.type}, ils ne sont pas empilables`];
}
}
else if (this.type != other.type) {
return [false, `Impossible de regrouper des ${this.type} avec des ${other.type}`];
}
@ -259,8 +592,13 @@ export class RdDItem extends Item {
return [false, `Impossible de regrouper ${this.name} avec ${other.name}`];
}
else {
const differences = Object.entries(this.system)
.filter(([key, value]) => !['quantite', 'cout', 'encTotal'].includes(key) && value != other.system[key]);
const excludedProperties = ['quantite', 'cout', 'encTotal', 'environnement', 'contenu'];
if (this.getUtilisationCuisine()) {
excludedProperties.push('sust', 'encombrement');
}
let differences = Object.entries(this.system)
.filter(([key, value]) => !excludedProperties.includes(key))
.filter(([key, value]) => value != other.system[key])
if (differences.length > 0) {
let message = `Impossible de regrouper les ${this.type} ${this.name}: `;
for (const [key, value] of differences) {
@ -272,100 +610,109 @@ export class RdDItem extends Item {
return [true, undefined];
}
async proposerVente() {
async proposerVente(quantiteMax = undefined) {
console.log(this);
if (this.isConteneurNonVide()) {
ui.notifications.warn(`Votre ${this.name} n'est pas vide, pas possible de le proposer`);
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;
await DialogItemVente.display({
item: this,
quantiteMax,
callback: 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;
}
}
vente.jsondata = JSON.stringify(vente.item);
let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-vente-item.html', vente);
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));
});
}
/* -------------------------------------------- */
getProprietes() {
return this[`_${this.type}ChatData`]();
if (this[`_${this.type}ChatData`]) {
return this[`_${this.type}ChatData`]().filter(it => it != undefined);
}
return [];
}
/* -------------------------------------------- */
async postItem(modeOverride) {
async postItemToChat(modeOverride) {
console.log(this);
let chatData = duplicate(this);
chatData["properties"] = this.getProprietes();
if (this.actor) {
chatData.actor = { id: this.actor.id };
let chatData = {
doctype: 'Item',
id: this.id,
type: this.type,
img: this.img,
pack: this.pack,
name: this.name,
actor: this.actor ? { id: this.actor.id } : undefined,
system: { description: this.system.description },
properties: this.getProprietes(),
}
// JSON object for easy creation
chatData.jsondata = JSON.stringify(
{
compendium: "postedItem",
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);
ChatMessage.create(chatOptions)
});
}
static propertyIfDefined(name, val, condition = (it) => true) {
return condition ? [`<b>${name}</b>: ${val}`] : [];
getChatItemTemplate() {
return 'systems/foundryvtt-reve-de-dragon/templates/post-item.html';
}
static propertyIfDefined(name, val, condition = true) {
return condition ? `<b>${name}</b>: ${val}` : undefined;
}
_inventaireTemplateChatData() {
return [
RdDItem.propertyIfDefined('Qualité', this.system.qualite, this.system.qualite != 0),
RdDItem.propertyIfDefined('Encombrement', this.system.encombrement)
// cout et quantité masqués
]
}
/* -------------------------------------------- */
_objetChatData() {
return [].concat(
RdDItem.propertyIfDefined('Résistance', this.system.resistance, this.system.resistance),
RdDItem.propertyIfDefined('Qualité', this.system.qualite, this.system.qualite),
RdDItem.propertyIfDefined('Encombrement', this.system.encombrement),
);
return this._inventaireTemplateChatData()
}
/* -------------------------------------------- */
_nourritureboissonChatData() {
return [].concat(
return [
RdDItem.propertyIfDefined('Sustentation', this.system.sust, this.system.sust > 0),
RdDItem.propertyIfDefined('Désaltère', this.system.desaltere, this.system.boisson),
RdDItem.propertyIfDefined('Force alcool', this.system.force, this.system.boisson && this.system.alcoolise),
RdDItem.propertyIfDefined('Exotisme', this.system.exotisme, this.system.exotisme < 0),
RdDItem.propertyIfDefined('Qualité', this.system.qualite, this.system.qualite),
RdDItem.propertyIfDefined('Encombrement', this.system.encombrement),
);
...this._inventaireTemplateChatData()
]
}
/* -------------------------------------------- */
_armeChatData() {
return [
`<b>Compétence</b>: ${this.system.competence}`,
`<b>Dommages</b>: ${this.system.dommages}`,
`<b>Dommages</b>: ${this.system.dommages} ${this.system.mortalite == 'non-mortel' ? '(Non mortel)' : ''}`,
`<b>Force minimum</b>: ${this.system.force}`,
`<b>Resistance</b>: ${this.system.resistance}`,
`<b>Encombrement</b>: ${this.system.encombrement}`
...this._inventaireTemplateChatData()
]
}
/* -------------------------------------------- */
_conteneurChatData() {
return [
`<b>Capacité</b>: ${this.system.capacite} Enc.`,
`<b>Encombrement</b>: ${this.system.encombrement}`
...this._inventaireTemplateChatData()
]
}
/* -------------------------------------------- */
_munitionChatData() {
return [
`<b>Encombrement</b>: ${this.system.encombrement}`
]
return this._inventaireTemplateChatData()
}
/* -------------------------------------------- */
_armureChatData() {
@ -373,7 +720,7 @@ export class RdDItem extends Item {
`<b>Protection</b>: ${this.system.protection}`,
`<b>Détérioration</b>: ${this.system.deterioration}`,
`<b>Malus armure</b>: ${this.system.malus}`,
`<b>Encombrement</b>: ${this.system.encombrement}`
...this._inventaireTemplateChatData()
]
}
/* -------------------------------------------- */
@ -407,16 +754,24 @@ export class RdDItem extends Item {
_herbeChatData() {
return [
`<b>Milieu</b>: ${this.system.milieu}`,
`<b>Rareté</b>: ${this.system.rarete}`,
`<b>Catégorie</b>: ${this.system.categorie}`,
...this._inventaireTemplateChatData()
]
}
/* -------------------------------------------- */
_ingredientChatData() {
return [
`<b>Milieu</b>: ${this.system.milieu}`,
`<b>Rareté</b>: ${this.system.rarete}`,
`<b>Catégorie</b>: ${this.system.categorie}`,
...this._inventaireTemplateChatData()
]
}
/* -------------------------------------------- */
_fauneChatData() {
return [
`<b>Sustentation</b>: ${this.system.sust}`,
`<b>Milieu</b>: ${this.system.milieu}`,
...this._inventaireTemplateChatData()
]
}
/* -------------------------------------------- */
@ -426,12 +781,9 @@ export class RdDItem extends Item {
`<b>Compétence</b>: ${this.system.competence}`,
`<b>Périodicité</b>: ${this.system.periodicite}`,
`<b>Fatigue</b>: ${this.system.fatigue}`,
`<b>Difficulté</b>: ${this.system.difficulte}`
].concat([
this.system.cacher_points_de_tache ? [] :`<b>Points de Tâche</b>: ${this.system.points_de_tache}`
]).concat([
`<b>Difficulté</b>: ${this.system.difficulte}`,
RdDItem.propertyIfDefined('Points de Tâche', this.system.points_de_tache, this.system.cacher_points_de_tache),
`<b>Points de Tâche atteints</b>: ${this.system.points_de_tache_courant}`]
);
}
/* -------------------------------------------- */
_livreChatData() {
@ -439,8 +791,8 @@ export class RdDItem extends Item {
`<b>Compétence</b>: ${this.system.competence}`,
`<b>Auteur</b>: ${this.system.auteur}`,
`<b>Difficulté</b>: ${this.system.difficulte}`,
`<b>Points de Tâche</b>: ${this.system.points_de_tache}`,
`<b>Encombrement</b>: ${this.system.encombrement}`
RdDItem.propertyIfDefined('Points de Tâche', this.system.points_de_tache, this.system.cacher_points_de_tache),
...this._inventaireTemplateChatData()
]
}
/* -------------------------------------------- */
@ -448,32 +800,44 @@ export class RdDItem extends Item {
return [
`<b>Rareté</b>: ${this.system.rarete}`,
`<b>Catégorie</b>: ${this.system.categorie}`,
`<b>Encombrement</b>: ${this.system.encombrement}`,
...this._inventaireTemplateChatData()
]
}
/* -------------------------------------------- */
_queueChatData() {
function label(categorie) {
switch (categorie) {
case 'ideefixe': return 'Idée fixe';
case 'lancinant': return 'Désir lancinant';
}
return ''
}
return [
`<b>Refoulement</b>: ${this.system.refoulement}`
`<b>Refoulement</b>: ${this.system.refoulement}`,
`<b>Catégorie</b>: ${label(this.system.categorie)}`,
`<b>Affecte</b>: ${this.system.hautrevant ? 'les haut-rêvants' : 'tout le monde'}`,
]
}
/* -------------------------------------------- */
_ombreChatData() {
return [
`<b>Refoulement</b>: ${this.system.refoulement}`
]
return this._queueChatData()
}
/* -------------------------------------------- */
_souffleChatData() {
return [];
return [
`<b>Affecte</b>: ${this.system.hautrevant ? 'les haut-rêvants' : 'tout le monde'}`,
];
}
/* -------------------------------------------- */
_teteChatData() {
return [];
return [
`<b>Affecte</b>: ${this.system.hautrevant ? 'les haut-rêvants' : 'tout le monde'}`,
];
}
/* -------------------------------------------- */
_tarotChatData() {
return [
RdDItem.propertyIfDefined('Carte', RdDUtility.linkCompendium(this.pack, this.id, this.name), this.pack),
`<b>Concept</b>: ${this.system.concept}`,
`<b>Aspect</b>: ${this.system.aspect}`,
]
@ -487,10 +851,7 @@ export class RdDItem extends Item {
}
/* -------------------------------------------- */
_monnaieChatData() {
return [
`<b>Valeur en Deniers</b>: ${this.system.valeur_deniers}`,
`<b>Encombrement</b>: ${this.system.encombrement}`
]
return this._inventaireTemplateChatData()
}
/* -------------------------------------------- */
_meditationChatData() {
@ -514,9 +875,9 @@ export class RdDItem extends Item {
]
}
return [
`<b>Force</b>: ${this.system.force}`,
`<b>Force</b>: ${this.system.formule}`,
`<b>Refoulement</b>: ${this.system.refoulement}`,
`<b>Présent de cités</b>: ${this.system.presentCite}`,
RdDItem.propertyIfDefined('<b>Présent de cités</b>', '', this.system.presentCite),
]
}
/* -------------------------------------------- */
@ -531,15 +892,12 @@ export class RdDItem extends Item {
if (!this.system.identifie) {
return [`<b>Inconnue</b>`]
}
let properties = [
`<b>Malignité</b>: ${this.system.malignite}`,
`<b>Périodicité</b>: ${this.system.periodicite}`,
`<b>Dommages</b>: ${this.system.dommages}`
]
if (this.system.remedesconnus) {
properties.push(`<b>Remedes</b>: ${this.system.remedes}`)
}
return properties;
return [
`<b>Malignité</b>: ${this.system.malignite}`,
`<b>Périodicité</b>: ${this.system.periodicite}`,
`<b>Dommages</b>: ${this.system.dommages}`,
RdDItem.propertyIfDefined('<b>Remedes</b>', this.system.remedes, this.system.remedesconnus),
]
}
/* -------------------------------------------- */
@ -554,9 +912,7 @@ export class RdDItem extends Item {
`<b>Taille</b>: ${this.system.taille}`,
`<b>Inertie</b>: ${this.system.inertie}`,
`<b>Enchantabilité</b>: ${this.system.enchantabilite}`,
`<b>Prix</b>: ${this.system.cout}`,
...this._inventaireTemplateChatData()
]
}
}

51
module/item/armure.js Normal file
View File

@ -0,0 +1,51 @@
import { RdDItem } from "../item.js";
import { Misc } from "../misc.js";
import { ReglesOptionnelles } from "../settings/regles-optionnelles.js";
export class RdDItemArmure extends RdDItem {
static get defaultIcon() {
return "systems/foundryvtt-reve-de-dragon/icons/armes_armures/armure_plaques.webp";
}
deteriorerArmure(dmg) {
if (!ReglesOptionnelles.isUsing('deteriorationArmure') || this.system.protection == '0') {
return;
}
let deterioration = (this.system.deterioration ?? 0) + dmg;
let protection = this.system.protection;
if (deterioration >= 10) {
deterioration -= 10;
protection = this.calculProtectionDeterioree();
ChatMessage.create({ content: `Votre armure ${this.name} s'est détériorée, elle protège maintenant de ${protection}` });
}
this.update({
system: {
deterioration: deterioration,
protection: protection
}
});
}
calculProtectionDeterioree() {
const protectionCourante = this.system.protection;
let res = /(\d+)?d(\d+)(\-\d+)?/.exec(protectionCourante);
if (res) {
let protection = Misc.toInt(res[2]);
let malus = Misc.toInt(res[3]) - 1;
if (protection + malus <= 0) {
return 0;
} else {
return `1d${protection}${malus}`;
}
}
else if (/\d+/.exec(protectionCourante)) {
return `1d${protectionCourante}`;
}
else {
ui.notifications.warn(`La valeur d'armure de votre ${this.name} est incorrecte`);
return undefined;
}
}
}

199
module/item/blessure.js Normal file
View File

@ -0,0 +1,199 @@
import { RdDItem } from "../item.js";
import { Misc } from "../misc.js";
import { RdDTimestamp } from "../time/rdd-timestamp.js";
const BASE_TACHE_SOIN_BLESSURE = {
type: "tache",
img: 'systems/foundryvtt-reve-de-dragon/icons/competence_chirurgie.webp',
system: { carac: "dexterite", competence: "Chirurgie", periodicite: "1 round", fatigue: 0, }
}
const TACHES_SOIN_BLESSURE = {
6: { name: 'Blessure critique', system: { difficulte: -6, points_de_tache: 6 } },
4: { name: 'Blessure grave', system: { difficulte: -4, points_de_tache: 4 } },
2: { name: 'Blessure légère', system: { difficulte: -2, points_de_tache: 2 } },
}
const definitionsBlessures = [
{ type: "contusion", gravite: 0, label: 'Contusion/éraflure', max: 100, icon: "systems/foundryvtt-reve-de-dragon/icons/sante/eraflure.webp" },
{ type: "legere", gravite: 2, label: 'Légère', max: 5, icon: "systems/foundryvtt-reve-de-dragon/icons/sante/blessure.webp" },
{ type: "grave", gravite: 4, label: 'Grave', max: 2, icon: "systems/foundryvtt-reve-de-dragon/icons/sante/blessure.webp" },
{ type: "critique", gravite: 6, label: 'Critique', max: 1, icon: "systems/foundryvtt-reve-de-dragon/icons/sante/blessure.webp" },
{ type: "mort", gravite: 8, label: 'Mort', max: 1, icon: "systems/foundryvtt-reve-de-dragon/icons/sante/mort.webp" }
]
export class RdDItemBlessure extends RdDItem {
static get defaultIcon() {
return "systems/foundryvtt-reve-de-dragon/icons/sante/blessure.webp";
}
prepareDerivedData() {
super.prepareDerivedData();
this.system.label = this.getLabelGravite()
}
static prepareTacheSoin(gravite) {
const tache = TACHES_SOIN_BLESSURE[gravite]
if (!tache) {
ui.notifications.warn(`Pas de tâche de soins pour une blessure ${gravite}`)
return undefined;
}
return mergeObject(duplicate(BASE_TACHE_SOIN_BLESSURE), tache)
}
static async createBlessure(actor, gravite, localisation = '', attacker) {
const definition = RdDItemBlessure.getDefinition(gravite)
const blessure = {
name: definition.label,
type: 'blessure',
img: definition.icon,
system: {
gravite: gravite,
difficulte: - gravite,
localisation: localisation,
origine: attacker?.name ?? ""
}
}
const blessures = await actor.createEmbeddedDocuments('Item', [blessure])
return blessures[0]
}
static async createTacheSoinBlessure(actor, gravite) {
const tache = RdDItemBlessure.prepareTacheSoin(gravite)
if (tache) {
const taches = await actor.createEmbeddedDocuments('Item', [tache], { renderSheet: false });
return taches[0];
}
return undefined
}
async updateTacheSoinBlessure(tache) {
if (tache) {
await tache.update({
system: {
itemId: this.id,
difficulte: Math.min(this.system.difficulte, tache.system.difficulte),
points_de_tache_courant: Math.max(0, this.system.premierssoins.tache)
}
});
}
}
async setSoinsBlessure(systemUpdate = {}) {
systemUpdate = mergeObject(systemUpdate, this.system, { overwrite: false }),
systemUpdate.soinscomplets.done = systemUpdate.premierssoins.done && systemUpdate.soinscomplets.done
await this.update({
img: this.getImgSoins(systemUpdate.gravite, systemUpdate.soinscomplets.done),
system: systemUpdate
});
}
async recuperationBlessure({ actor, timestamp, message, isMaladeEmpoisonne, blessures }) {
if (this.parent != actor || actor == undefined) {
return
}
if (new RdDTimestamp(this.system.temporel.fin).compare(timestamp) > 0) {
// attente periode
return
}
if (this.system.gravite > 0) {
const update = { system: { premierssoins: { bonus: 0 }, soinscomplets: { bonus: 0 } } }
const gravite = this.system.gravite;
const graviteMoindre = gravite - 2;
const moindres = blessures.filter(it => it.system.gravite == graviteMoindre, 'blessures').length
const label = this.getLabelGravite();
let rolled = await actor.jetRecuperationConstitution(this.system.soinscomplets.bonus, message);
if (rolled.isETotal) {
message.content += ` -- une blessure ${label} s'infecte (temps de guérison augmenté de ${gravite} jours, perte de vie)`;
await actor.santeIncDec("vie", -1);
mergeObject(update, {
system: { fin: { indexDate: timestamp.addJours(gravite).indexDate } }
});
}
else {
if (!isMaladeEmpoisonne && rolled.isSuccess && this.peutRetrograder(graviteMoindre, moindres)) {
message.content += ` -- une blessure ${label} cicatrise`;
mergeObject(update, {
system: { gravite: graviteMoindre, fin: { indexDate: timestamp.addJours(graviteMoindre).indexDate } }
});
}
else {
message.content += ` -- une blessure ${label} reste stable`;
}
}
await this.update(update);
}
}
peutRetrograder(graviteMoindre, moindres) {
return moindres < RdDItemBlessure.getDefinition(graviteMoindre).max
}
async calculerFinPeriodeTemporel(debut) {
return await debut.nouveauJour().addJours(this.system.gravite);
}
async onFinPeriode(oldTimestamp, newTimestamp) {
if (this.system.gravite <= 0) {
await super.onFinPeriode(oldTimestamp, newTimestamp)
}
}
getImgSoins(gravite, soins) {
let img = 'blessure'
if (gravite > 6) {
img = 'mort'
}
if (gravite <= 0) {
img = 'eraflure'
}
return `systems/foundryvtt-reve-de-dragon/icons/sante/${soins ? 'blessure-soins' : img}.webp`
}
getLabelGravite() {
return RdDItemBlessure.getDefinition(this.system.gravite).label
}
static getDefinition(gravite) {
return definitionsBlessures.sort(Misc.ascending(it => it.gravite))
.find(it => it.gravite >= gravite);
}
static maxBlessures(gravite) {
return RdDItemBlessure.getDefinition(gravite).max
}
isContusion() {
return this.system.gravite <= 0
}
isLegere() {
return this.system.gravite > 0 && this.system.gravite <= 2
}
isGrave() {
return this.system.gravite > 2 && this.system.gravite <= 4
}
isCritique() {
return this.system.gravite > 4 && this.system.gravite <= 6
}
isMort() {
return this.system.gravite > 6
}
getProprietes() {
return [
RdDItem.propertyIfDefined('Causée par', this.system.origine, this.system.origine),
`<b>Heure et Date</b>: ${new RdDTimestamp(this.system.temporel.debut).formatDateHeure()}`,
RdDItem.propertyIfDefined('Blessé', this.parent?.name, this.parent),
`<b>Localisation</b>: ${this.system.localisation}`,
`<b>Gravité</b>: ${RdDItemBlessure.getDefinition(this.system.gravite).label}`,
`<b>Difficulté des soins</b>: ${this.system.difficulte}`,
(this.system.soinscomplets.done ?
`<b>Bonus soins complets</b>: ${this.system.soinscomplets.bonus}` :
(this.system.premierssoins.done ?
`<b>Bonus premiers soins</b>: ${this.system.premierssoins.bonus}` :
`<b>Points de tâche</b>: ${this.system.premierssoins.tache}`
)
),
];
}
}

47
module/item/maladie.js Normal file
View File

@ -0,0 +1,47 @@
import { RdDItem } from "../item.js";
import { Misc } from "../misc.js";
import { RdDTimestamp } from "../time/rdd-timestamp.js";
export class RdDItemMaladie extends RdDItem {
static get defaultIcon() {
return "systems/foundryvtt-reve-de-dragon/icons/maladies_venins/maladie.webp";
}
async calculerFinPeriodeTemporel(debut) {
return await debut.addPeriode(this.system.periode.nombre, this.system.periode.unite);
}
async onFinPeriode(oldTimestamp, newTimestamp) {
await RdDItemMaladie.notifierMaladiePoison(this, oldTimestamp, newTimestamp)
}
static async notifierMaladiePoison(mal, oldTimestamp, newTimestamp) {
if (mal.actor) {
const souffrance = mal.system.identifie
? `de ${mal.name}`
: `d'un mal inconnu`
ChatMessage.create({ content: `${mal.actor.name} souffre ${souffrance} (${Misc.typeName('Item', mal.type)}): vérifiez que les effets ne se sont pas aggravés !` });
mal.postItemToChat('gmroll');
await RdDItemMaladie.prolongerPeriode(mal,oldTimestamp, newTimestamp);
}
}
static async prolongerPeriode(mal, oldTimestamp, newTimestamp) {
if (mal.actor) {
// TODO: déterminer le nombre de périodes écoulées
console.log(`${mal.actor.name}: le mal ${mal.name} a atteint la fin de sa période et été prolongé`);
const current = newTimestamp;
const finPeriode = new RdDTimestamp(mal.system.temporel.fin)
const periodeSuivante = (finPeriode.compare(current) > 0 ? finPeriode : current);
const timestampFin = await mal.calculerFinPeriodeTemporel(periodeSuivante);
await mal.actor.updateEmbeddedDocuments('Item', [{
_id: mal.id,
'system.temporel.fin': duplicate(timestampFin),
}])
}
}
}

11
module/item/ombre.js Normal file
View File

@ -0,0 +1,11 @@
import { RdDItem } from "../item.js";
export class RdDItemOmbre extends RdDItem {
static get defaultIcon() {
return "systems/foundryvtt-reve-de-dragon/icons/queue_dragon.webp";
}
async calculerFinPeriodeTemporel(debut) {
return await debut.appliquerDuree(this.system.duree, this.parent);
}
}

17
module/item/poison.js Normal file
View File

@ -0,0 +1,17 @@
import { RdDItem } from "../item.js";
import { RdDItemMaladie } from "./maladie.js";
export class RdDItemPoison extends RdDItem {
static get defaultIcon() {
return "systems/foundryvtt-reve-de-dragon/icons/maladies_venins/venin.webp";
}
async calculerFinPeriodeTemporel(debut) {
return await debut.addPeriode(this.system.periode.nombre, this.system.periode.unite) ;
}
async onFinPeriode(oldTimestamp, newTimestamp) {
RdDItemMaladie.notifierMaladiePoison(this, oldTimestamp, newTimestamp)
}
}

13
module/item/queue.js Normal file
View File

@ -0,0 +1,13 @@
import { RdDItem } from "../item.js";
export class RdDItemQueue extends RdDItem {
static get defaultIcon() {
return "systems/foundryvtt-reve-de-dragon/icons/queue_dragon.webp";
}
async calculerFinPeriodeTemporel(debut) {
return await debut.appliquerDuree(this.system.duree, this.parent);
}
}

31
module/item/raretes.js Normal file
View File

@ -0,0 +1,31 @@
const RARETE_COMMUNE = { code: 'Commune', label: 'Commune', frequence: 54, min: 27, max: 108 };
const RARETE_FREQUENTE = { code: 'Frequente', label: 'Fréquente', frequence: 18, min: 9, max: 36 };
const RARETE_RARE = { code: 'Rare', label: 'Rare', frequence: 6, min: 3, max: 12 };
const RARETE_RARISSIME = { code: 'Rarissime', label: 'Rarissime', frequence: 2, min: 1, max: 4 };
const RARETE_INEXISTANT = { code: 'Inexistant', label: 'Inexistant', frequence: 0, min: 0, max: 0 };
const RARETE_EGALE = { code: 'eqal', label: 'Egal', frequence: 1, min: 1, max: 1 };
const RARETES = [
RARETE_COMMUNE,
RARETE_FREQUENTE,
RARETE_RARE,
RARETE_RARISSIME,
RARETE_INEXISTANT,
]
export class RdDRaretes {
static rareteFrequente() { return RARETE_FREQUENTE; }
static rareteEgale() { return RARETE_EGALE; }
static raretes() { return RARETES; }
static byCode(code = undefined) {
return RARETES.find(it => it.code == code) ?? RARETE_FREQUENTE;
}
static getChamp(rarete, field = undefined) {
return RdDRaretes.byCode(rarete)[field ?? 'frequence'];
}
}

View File

@ -1,4 +1,5 @@
import { EffetsRencontre } from "./effets-rencontres.js";
import { EffetsRencontre } from "../tmr/effets-rencontres.js";
import { RdDItem } from "../item.js";
const tableEffets = [
{ code: "messager", resultat: "succes", description: "Envoie un message à (force) cases", method: EffetsRencontre.messager },
@ -36,7 +37,11 @@ const tableEffets = [
// { code: "epart-souffle", resultat: "echec", description: "Souffle de dragon sur échec particulier" },
];
export class RdDRencontre {
export class RdDRencontre extends RdDItem {
static get defaultIcon() {
return "systems/foundryvtt-reve-de-dragon/icons/tete_dragon.webp";
}
static getEffetsSucces() { return RdDRencontre.getEffets("succes"); }
static getEffetsEchec() { return RdDRencontre.getEffets("echec"); }
@ -68,4 +73,8 @@ export class RdDRencontre {
}
}
async calculerFinPeriodeTemporel(debut) {
return await debut.nouvelleHeure().addHeures(12);
}
}

17
module/item/service.js Normal file
View File

@ -0,0 +1,17 @@
import { RdDItem } from "../item.js";
export class RdDItemService extends RdDItem {
static get defaultIcon() {
return "systems/foundryvtt-reve-de-dragon/icons/services/lit.webp";
}
isService() { return true; }
getProprietes() {
return [
RdDItem.propertyIfDefined('Qualité', this.system.qualite, this.system.qualite != 0),
RdDItem.propertyIfDefined('Moral', 'Situation heureuse', this.system.moral),
RdDItem.propertyIfDefined('Coût', `${this.calculerPrixCommercant()} sols`),
];
}
}

View File

@ -0,0 +1,120 @@
import { HtmlUtility } from "../html-utility.js";
import { RdDItemSheet } from "../item-sheet.js";
import { Misc } from "../misc.js";
import { RdDRaretes } from "./raretes.js";
const TYPE_ITEMS_NATURELS = ["faune", "herbe", "plante", "ingredient"];
export class RdDItemInventaireSheet extends RdDItemSheet {
static get defaultOptions() {
return mergeObject(RdDItemSheet.defaultOptions, {
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "informations" }]
});
}
setPosition(options = {}) {
const position = super.setPosition(options);
const sheetHeader = this.element.find(".sheet-header");
const sheetBody = this.element.find(".sheet-body");
sheetBody.css("height", position.height - sheetHeader[0].clientHeight)
return position;
}
async getData() {
const formData = await super.getData();
return mergeObject(formData, {
milieux: await game.system.rdd.environnement.autresMilieux(this.item)
});
}
activateListeners(html) {
super.activateListeners(html);
HtmlUtility.showControlWhen(this.html.find("div.description-milieu"), TYPE_ITEMS_NATURELS.includes(this.item.type));
if (!this.options.editable) return;
this.html.find("a.preparer-nourriture").click(event => this.preparerNourriture(event));
this.html.find("a.manger-nourriture").click(event => this.mangerNourriture(event));
this.html.find("input.input-selection-milieu").keypress(event => {
if (event.keyCode == '13') {
this.onAddMilieu(event);
}
event.stopPropagation();
})
this.html.find("a.milieu-add").click(event => this.onAddMilieu(event));
this.html.find("div.environnement-milieu a.milieu-delete").click(event => this.onDeleteMilieu(event));
this.html.find("div.environnement-milieu select.environnement-rarete").change(event => this.onChange(event,
updated => this.$changeRarete(event, updated)));
this.html.find("div.environnement-milieu input[name='environnement-frequence']").change(event => this.onChange(event,
updated => this.$changeFrequence(event, updated)));
}
async preparerNourriture(event) {
if (this.actor && this.item.getUtilisationCuisine() == 'brut') {
await this.actor.preparerNourriture(this.item);
}
}
async mangerNourriture(event) {
if (this.actor && this.item.getUtilisation() == 'cuisine') {
await this.actor.mangerNourriture(this.item);
}
}
$changeFrequence(event, updated) {
updated.frequence = Number(this.html.find(event.currentTarget).val());
}
$changeRarete(event, updated) {
const code = this.html.find(event.currentTarget).val();
const rarete = RdDRaretes.byCode(code);
updated.rarete = rarete.code;
updated.frequence = rarete.frequence;
}
async onAddMilieu(event) {
const milieu = this.html.find('input.input-selection-milieu').val();
if (!milieu) {
ui.notifications.warn(`Choisissez le milieu dans lequel se trouve le/la ${this.item.name}`);
return
}
const list = this.item.getEnvironnements();
const exists = list.find(it => it.milieu == milieu);
if (exists) {
ui.notifications.warn(`${this.item.name} a déjà une rareté ${exists.rarete} en ${milieu} (fréquence: ${exists.frequence})`);
return
}
const rarete = RdDRaretes.rareteFrequente();
const added = { milieu, rarete: rarete.code, frequence: rarete.frequence };
const newList = [added, ...list].sort(Misc.ascending(it => it.milieu))
await this.item.update({ 'system.environnement': newList })
}
async onDeleteMilieu(event) {
const milieu = this.$getEventMilieu(event);
if (milieu != undefined) {
const newList = this.item.getEnvironnements().filter(it => it.milieu != milieu)
.sort(Misc.ascending(it => it.milieu));
await this.item.update({ 'system.environnement': newList });
}
}
async onChange(event, doMutation) {
const list = this.item.system.environnement;
const milieu = this.$getEventMilieu(event);
const updated = list.find(it => it.milieu == milieu);
if (updated) {
doMutation(updated);
const newList = [...list.filter(it => it.milieu != milieu), updated]
.sort(Misc.ascending(it => it.milieu));
await this.item.update({ 'system.environnement': newList });
}
}
$getEventMilieu(event) {
return this.html.find(event.currentTarget)?.parents("div.environnement-milieu").data("milieu");
}
}

View File

@ -0,0 +1,29 @@
import { RdDItemSheet } from "../item-sheet.js";
export class RdDBlessureItemSheet extends RdDItemSheet {
static get ITEM_TYPE() { return "blessure" };
async getData() {
const formData = await super.getData();
formData.disabled = formData.options.isGM || formData.options.isOwned ? '' : 'disabled';
return formData;
}
activateListeners(html) {
super.activateListeners(html);
if (!this.options.editable) return;
this.html.find('[name="premierssoins-done"]').change(async event => {
await this.item.setSoinsBlessure({ premierssoins: { done: event.currentTarget.checked } });
});
this.html.find('[name="soinscomplets-done"]').change(async event => {
await this.item.setSoinsBlessure({ soinscomplets: { done: event.currentTarget.checked } })
});
this.html.find('[name="system-gravite"]').change(async event => {
const gravite = Number(event.currentTarget.value)
await this.item.setSoinsBlessure({ gravite: gravite, difficulte: - gravite })
});
}
}

View File

@ -0,0 +1,62 @@
import { RdDBaseActorSheet } from "../actor/base-actor-sheet.js";
import { RdDSheetUtility } from "../rdd-sheet-utility.js";
import { RdDUtility } from "../rdd-utility.js";
import { RdDItemInventaireSheet } from "./sheet-base-inventaire.js";
export class RdDConteneurItemSheet extends RdDItemInventaireSheet {
static get ITEM_TYPE() { return "conteneur" };
async getData() {
const formData = await super.getData();
if (this.actor) {
this.prepareConteneurData(formData);
}
return formData;
}
activateListeners(html) {
super.activateListeners(html);
if (!this.options.editable) return;
this.html.find('.conteneur-name a').click(async event => {
RdDUtility.toggleAfficheContenu(RdDSheetUtility.getItemId(event));
this.render(true);
});
}
/* -------------------------------------------- */
prepareConteneurData(formData) {
RdDBaseActorSheet.filterItemsPerTypeForSheet(formData, this.actor.itemTypes);
this.objetVersConteneur = RdDUtility.buildArbreDeConteneurs(formData.conteneurs, formData.inventaires);
formData.subItems = formData.conteneurs.find(it => it._id == this.item.id)?.subItems;
}
async _onDragStart(event) {
console.log("_onDragStart", event);
if (event.target.classList.contains("entity-link")) return;
const itemId = event.srcElement?.attributes["data-item-id"].value;
const item = this.actor.items.get(itemId);
// Create drag data
const dragData = {
actorId: this.actor.id,
type: "Item",
data: item.system,
uuid: item.uuid
};
event.dataTransfer.setData("text/plain", JSON.stringify(dragData));
}
async _onDropItem(event, dragData) {
if (this.actor) {
const destItemId = this.html.find(event.target)?.closest('.item').attr('data-item-id') ?? this.item.id
const dropParams = await RdDSheetUtility.prepareItemDropParameters(destItemId, this.actor, dragData, this.objetVersConteneur);
await this.actor.processDropItem(dropParams);
await this.render(true);
}
}
}

View File

@ -0,0 +1,38 @@
import { RdDItemInventaireSheet } from "./sheet-base-inventaire.js";
export class RdDFauneItemSheet extends RdDItemInventaireSheet {
static get ITEM_TYPE() { return "faune" };
activateListeners(html) {
super.activateListeners(html);
if (!this.options.editable) return;
html.find("a.linked-actor-delete").click(event => this.onDeleteLinkedActor());
}
async _onDropActor(event, dragData) {
console.log('faune:dropActor', event, dragData)
const linkedActor = fromUuidSync(dragData.uuid);
if (linkedActor?.pack) {
this.item.update({
'system.actor.pack': linkedActor.pack,
'system.actor.id': linkedActor._id,
'system.actor.name': linkedActor.name
});
}
else {
ui.notifications.warn(`${linkedActor.name} ne provient pas d'un compendium.
<br>Choisissez une créature du compendium pour représenter un élément de faune générique`)
}
}
async onDeleteLinkedActor() {
this.item.update({
'system.actor.pack': '',
'system.actor.id': '',
'system.actor.name': ''
});
}
}

View File

@ -0,0 +1,6 @@
import { RdDItemInventaireSheet } from "./sheet-base-inventaire.js";
export class RdDHerbeItemSheet extends RdDItemInventaireSheet {
static get ITEM_TYPE() { return "herbe" };
}

View File

@ -0,0 +1,5 @@
import { RdDItemInventaireSheet } from "./sheet-base-inventaire.js";
export class RdDIngredientItemSheet extends RdDItemInventaireSheet {
static get ITEM_TYPE() { return "ingredient" };
}

View File

@ -0,0 +1,7 @@
import { RdDItemInventaireSheet } from "./sheet-base-inventaire.js";
export class RdDPlanteItemSheet extends RdDItemInventaireSheet {
static get ITEM_TYPE() { return "plante" };
}

View File

@ -1,29 +1,16 @@
import { RdDRencontre } from "./item-rencontre.js";
import { RdDRencontre } from "./rencontre.js";
import { RdDItemSheet } from "../item-sheet.js";
/**
* Item sheet pour configurer les rencontres
* @extends {ItemSheet}
*/
export class RdDRencontreItemSheet extends ItemSheet {
export class RdDRencontreItemSheet extends RdDItemSheet {
static get ITEM_TYPE() { return "rencontre" };
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["rdd", "sheet", "item"],
template: "systems/foundryvtt-reve-de-dragon/templates/item-rencontre-sheet.html",
width: 500,
height: 500,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "carac" }]
});
}
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
buttons.unshift({ class: "post", icon: "fas fa-comment", onclick: ev => this.item.postItem() });
return buttons;
}
/* -------------------------------------------- */
/** @override */
setPosition(options = {}) {
@ -34,18 +21,10 @@ export class RdDRencontreItemSheet extends ItemSheet {
return position;
}
/* -------------------------------------------- */
async getData() {
const formData = duplicate(this.item);
const formData = await super.getData();
mergeObject(formData, {
title: formData.name,
isGM: game.user.isGM,
owner: this.actor?.isOwner,
isOwned: this.actor ? true : false,
actorId: this.actor?.id,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
effets: {
succes: {
liste: RdDRencontre.getEffetsSucces(),
@ -65,15 +44,15 @@ export class RdDRencontreItemSheet extends ItemSheet {
activateListeners(html) {
super.activateListeners(html);
if (!this.options.editable) return;
html.find("a.effet-add").click(event => this.onAddEffet(event));
html.find("a.effet-delete").click(event => this.onDeleteEffet(event));
this.html.find("a.effet-add").click(event => this.onAddEffet(event));
this.html.find("a.effet-delete").click(event => this.onDeleteEffet(event));
}
async onAddEffet(event) {
const resultat = $(event.currentTarget)?.data("effet-resultat");
const resultat = this.html.find(event.currentTarget)?.data("effet-resultat");
const keyEffets = `system.${resultat}.effets`;
const code = $(event.currentTarget)?.data("effet-code");
const code = this.html.find(event.currentTarget)?.data("effet-code");
const liste = RdDRencontre.getListeEffets(this.item, resultat);
liste.push(code);
@ -81,10 +60,10 @@ export class RdDRencontreItemSheet extends ItemSheet {
}
async onDeleteEffet(event) {
const resultat = $(event.currentTarget)?.data("effet-resultat");
const resultat = this.html.find(event.currentTarget)?.data("effet-resultat");
const keyEffets = `system.${resultat}.effets`;
const pos = $(event.currentTarget)?.data("effet-pos");
const pos = this.html.find(event.currentTarget)?.data("effet-pos");
const liste = RdDRencontre.getListeEffets(this.item, resultat);
liste.splice(pos, 1);
@ -96,13 +75,4 @@ export class RdDRencontreItemSheet extends ItemSheet {
updates[key] = liste;
this.item.update(updates);
}
get template() {
/* -------------------------------------------- */
return `systems/foundryvtt-reve-de-dragon/templates/item-rencontre-sheet.html`;
}
get title() {
return `Rencontre: ${this.object.name}`;
}
}

View File

@ -0,0 +1,16 @@
import { RdDItemSheet } from "../item-sheet.js";
export class RdDServiceItemSheet extends RdDItemSheet {
static get ITEM_TYPE() { return "service" };
async getData() {
const formData = await super.getData();
formData.disabled = formData.options.isGM || formData.options.isOwned ? '' : 'disabled';
return formData;
}
activateListeners(html) {
super.activateListeners(html);
}
}

View File

@ -1,29 +1,14 @@
import { SYSTEM_RDD } from "./constants.js";
import { RdDItemSigneDraconique } from "./item-signedraconique.js";
import { TMRUtility } from "./tmr-utility.js";
import { RdDItemSheet } from "../item-sheet.js";
import { RdDItemSigneDraconique } from "./signedraconique.js";
import { TMRUtility } from "../tmr-utility.js";
/**
* Item sheet pour signes draconiques
* @extends {ItemSheet}
* @extends {RdDItemSheet}
*/
export class RdDSigneDraconiqueItemSheet extends ItemSheet {
export class RdDSigneDraconiqueItemSheet extends RdDItemSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: [SYSTEM_RDD, "sheet", "item"],
template: "systems/foundryvtt-reve-de-dragon/templates/item-signedraconique-sheet.html",
width: 550,
height: 550
});
}
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
buttons.unshift({ class: "post", icon: "fas fa-comment", onclick: ev => this.item.postItem() });
return buttons;
}
static get ITEM_TYPE() { return "signedraconique" }
/* -------------------------------------------- */
/** @override */
@ -38,18 +23,9 @@ export class RdDSigneDraconiqueItemSheet extends ItemSheet {
/* -------------------------------------------- */
async getData() {
const formData = duplicate(this.item);
const formData = await super.getData();
this.tmrs = TMRUtility.buildSelectionTypesTMR(this.item.system.typesTMR);
mergeObject(formData, {
tmrs: this.tmrs,
title: formData.name,
isGM: game.user.isGM,
owner: this.actor?.isOwner,
isOwned: this.actor ? true : false,
actorId: this.actor?.id,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
});
formData.tmrs = this.tmrs;
return formData;
}
@ -61,12 +37,13 @@ export class RdDSigneDraconiqueItemSheet extends ItemSheet {
if (!this.options.editable) return;
html.find(".signe-aleatoire").click(event => this.setSigneAleatoire());
html.find("input.select-tmr").change((event) => this.onSelectTmr(event));
html.find(".signe-xp-sort").change((event) => this.onValeurXpSort(event.currentTarget.attributes['data-typereussite']?.value, Number(event.currentTarget.value)));
html.find("input.select-tmr").change(event => this.onSelectTmr(event));
html.find(".signe-xp-sort").change(event => this.onValeurXpSort(event.currentTarget.attributes['data-typereussite']?.value, Number(event.currentTarget.value)));
}
async setSigneAleatoire() {
const newSigne = await RdDItemSigneDraconique.randomSigneDraconique();
newSigne.name = this.item.name;
this.item.update(newSigne);
}
@ -88,12 +65,4 @@ export class RdDSigneDraconiqueItemSheet extends ItemSheet {
await this.item.update({ 'system.valeur': newValeur });
}
/* -------------------------------------------- */
get template() {
return `systems/foundryvtt-reve-de-dragon/templates/item-signedraconique-sheet.html`;
}
get title() {
return `Signe draconique: ${this.object.name}`;
}
}

View File

@ -1,8 +1,9 @@
import { defaultItemImg } from "./item.js";
import { Misc } from "./misc.js";
import { RdDDice } from "./rdd-dice.js";
import { RdDRollTables } from "./rdd-rolltables.js";
import { TMRType, TMRUtility } from "./tmr-utility.js";
import { RdDItem, defaultItemImg } from "../item.js";
import { Misc } from "../misc.js";
import { RdDDice } from "../rdd-dice.js";
import { RdDRollTables } from "../rdd-rolltables.js";
import { RdDTimestamp } from "../time/rdd-timestamp.js";
import { TMRType, TMRUtility } from "../tmr-utility.js";
const tableSignesIndicatifs = [
{ rarete: "Très facile", difficulte: 0, xp: 6, nbCases: 14 },
@ -15,7 +16,17 @@ const tableSignesIndicatifs = [
const DIFFICULTE_LECTURE_SIGNE_MANQUE = +11;
export class RdDItemSigneDraconique {
export class RdDItemSigneDraconique extends RdDItem {
static get defaultIcon() {
return "systems/foundryvtt-reve-de-dragon/icons/tmr/signe_draconique.webp";
}
async calculerFinPeriodeTemporel(debut) {
// TODO
return RdDTimestamp.formulesDuree().find(it => it.code == "").calcul(debut, this.actor);
}
static prepareSigneDraconiqueMeditation(meditation, rolled) {
return {
@ -96,6 +107,6 @@ export class RdDItemSigneDraconique {
static async randomSigneDescription() {
return await RdDRollTables.drawTextFromRollTable("Signes draconiques", false);
}
}
}

13
module/item/souffle.js Normal file
View File

@ -0,0 +1,13 @@
import { RdDItem } from "../item.js";
export class RdDItemSouffle extends RdDItem {
static get defaultIcon() {
return "systems/foundryvtt-reve-de-dragon/icons/souffle_dragon.webp";
}
async calculerFinPeriodeTemporel(debut) {
return await debut.appliquerDuree(this.system.duree, this.parent);
}
}

View File

@ -1,6 +1,10 @@
import { RdDBaseActor } from "./actor/base-actor.js";
import { LOG_HEAD, SYSTEM_RDD } from "./constants.js";
import { Grammar } from "./grammar.js";
import { Misc } from "./misc.js";
import { Monnaie } from "./item-monnaie.js";
import { RdDItem, TYPES } from "./item.js";
import { RdDTimestamp } from "./time/rdd-timestamp.js";
import { RdDRaretes } from "./item/raretes.js";
class Migration {
get code() { return "sample"; }
@ -9,7 +13,7 @@ class Migration {
async applyItemsUpdates(computeUpdates) {
await game.actors.forEach(async (actor) => {
const actorItemUpdates = computeUpdates(actor.items);
const actorItemUpdates = computeUpdates(actor.items).filter(it => it != undefined);
if (actorItemUpdates.length > 0) {
console.log(
this.code,
@ -20,7 +24,7 @@ class Migration {
}
});
const itemUpdates = computeUpdates(game.items);
const itemUpdates = computeUpdates(game.items).filter(it => it != undefined);
if (itemUpdates.length > 0) {
console.log(this.code, "Applying updates on items", itemUpdates);
await Item.updateDocuments(itemUpdates);
@ -29,6 +33,38 @@ 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 {
get code() { return "creation-item-sort-reserve"; }
get version() { return "10.0.16"; }
@ -180,14 +216,322 @@ class _10_2_5_ArmesTirLancer extends Migration {
}
}
class _10_2_10_DesirLancinant_IdeeFixe extends Migration {
get code() { return "desir-lancinat-idee-fixe"; }
get version() { return "10.2.10"; }
migrateQueue(it) {
let categorie = undefined
let name = it.name
if (Grammar.toLowerCaseNoAccent(name).includes('desir')) {
categorie = 'lancinant';
name = it.name.replace('Désir lancinant : ', '');
}
if (Grammar.toLowerCaseNoAccent(name).includes('idee fixe')) {
categorie = 'ideefixe';
name = it.name.replace('Idée fixe : ', '')
}
return {
_id: it.id, name: name,
'system.ideefixe': undefined,
'system.lancinant': undefined,
'system.categorie': categorie
}
}
async migrate() {
await this.applyItemsUpdates(items => items
.filter(it => ['queue', 'ombre'].includes(it.type))
.map(it => this.migrateQueue(it))
);
}
}
class _10_3_0_Inventaire extends Migration {
get code() { return "migration-equipement-inventaire"; }
get version() { return "10.3.0"; }
async migrate() {
await this.applyItemsUpdates(items => {
return this._updatesMonnaies(items)
.concat(this._updatesNonEquipe(items))
.concat(this._updatesObjets(items))
});
}
_updatesNonEquipe(items) {
return items
.filter(it => ['munition'].includes(it.type))
.map(it => { return { _id: it.id, 'system.equipe': undefined } });
}
_updatesObjets(items) {
return items
.filter(it => ['objet'].includes(it.type))
.map(it => { return { _id: it.id, 'system.resistance': undefined, 'system.equipe': undefined } });
}
_updatesMonnaies(items) {
return items
.filter(it => ['monnaie'].includes(it.type) && it.system.valeur_deniers != undefined)
.map(it => { return { _id: it.id, 'system.cout': it.system.valeur_deniers / 100, 'system.valeur_deniers': undefined } });
}
}
class _10_3_0_FrequenceEnvironnement extends Migration {
get code() { return "migration-frequence-resources"; }
get version() { return "10.3.0"; }
async migrate() {
await this.applyItemsUpdates(items => items.filter(it => ['herbe', 'ingredient'].includes(it.type))
.map(it => this._updatesFrequences(it)));
}
_updatesFrequences(it) {
const rarete = RdDRaretes.byCode(it.system.rarete);
return {
_id: it.id,
'system.rarete': undefined,
'system.environnement': [{ milieu: it.system.milieu, rarete: rarete.code, frequence: rarete.frequence }]
}
}
}
class _10_3_17_Monnaies extends Migration {
constructor() {
super();
this.mapValeur = {
"Etain (1 denier)": { name: 'Denier (étain)', system: { cout: 0.01 } },
"Bronze (10 deniers)": { name: "Sou (bronze)", system: { cout: 0.1 } },
"Argent (1 sol)": { name: "Sol (argent)", system: { cout: 1 } },
"Or (10 sols)": { name: "Dragon (or)", system: { cout: 10 } }
};
}
get code() { return "migration-monnaies"; }
get version() { return "10.3.17"; }
async migrate() {
await this.applyItemsUpdates(items => this._updatesMonnaies(items));
}
_updatesMonnaies(items) {
return items
.filter(it => 'monnaie' == it.type)
.filter(it => this.mapValeur[it.name] != undefined)
.map(it => {
const correction = this.mapValeur[it.name];
return {
_id: it.id,
'name': correction.name,
'system.cout': correction.system.cout,
'system.valeur_deniers': undefined
}
});
}
}
class _10_4_6_ServicesEnCommerces extends Migration {
get code() { return "migration-service-acteurs"; }
get version() { return "10.4.6"; }
async migrate() {
const servicesToMigrate = game.items.filter(it => it.type == 'service');
servicesToMigrate.forEach(async service => {
const commerce = await this.convertServiceToCommerce(service);
await RdDBaseActor.create(commerce, { renderSheet: false });
await service.delete();
});
}
async convertServiceToCommerce(service) {
return {
name: service.name, img: service.img, type: 'commerce',
system: {
description: service.system.description,
notesmj: service.system.descriptionmj,
illimite: service.system.illimite
},
items: await this.transformInventaireCommerce(service)
}
}
async transformInventaireCommerce(service) {
const serviceItems = (service.system.items ?? []);
const commerceItems = await Promise.all(serviceItems.map(async (it) => { return await this.transformToItemBoutique(it); }));
return commerceItems.concat(Monnaie.monnaiesStandard());
}
async transformToItemBoutique(serviceRefItem) {
const item = await RdDItem.getCorrespondingItem(serviceRefItem);
const itemToCreate = {
name: item.name, img: item.img, type: item.type,
system: mergeObject({ cout: serviceRefItem.system.cout, quantite: serviceRefItem.system.quantite }, item.system, { overwrite: false })
};
return itemToCreate;
}
}
class _10_5_0_UpdatePeriodicite extends Migration {
get code() { return "migration-periodicite-poisons-maladies"; }
get version() { return "10.5.0"; }
async migrate() {
await this.applyItemsUpdates(items => this._updatePeriodicite(items));
}
_updatePeriodicite(items) {
return items.filter(it => ['poison', 'maladie'].includes(it.type))
.filter(it => it.system.periodicite != "")
.map(it => {
let [incubation, periodicite] = this.getPeriodicite(it);
const periode = periodicite.split(' ');
let unite = periode.length == 2
? RdDTimestamp.formulesPeriode().find(it => Grammar.includesLowerCaseNoAccent(periode[1], it.code))?.code
: undefined
if (unite && Number(periode[0])) {
return {
_id: it.id,
'system.periodicite': undefined,
'system.incubation': incubation,
'system.periode.nombre': Number.parseInt(periode[0]),
'system.periode.unite': unite
};
}
else {
return {
_id: it.id,
'system.periodicite': undefined,
'system.incubation': it.system.periodicite
};
}
}).filter(it => it != undefined);
}
getPeriodicite(it) {
let p = it.system.periodicite.split(/[\/\\]/);
switch (p.length) {
case 2: return [p[0].trim(), p[1].trim()];
case 1: return ["", it.system.periodicite.trim()];
default: return [it.system.periodicite.trim(), ""];
}
}
}
class _10_7_0_MigrationBlessures extends Migration {
get code() { return "migration-blessures"; }
get version() { return "10.7.0"; }
async migrate() {
const timestamp = game.system.rdd.calendrier.getTimestamp()
await Promise.all(game.actors.filter(it => it.isPersonnage() || it.isCreature())
.map(async (actor) => {
const legeres = actor.system.blessures?.legeres.liste.filter(it => it.active).map(it => this.creerBlessure(2, 'légère', it, timestamp)) ?? [];
const graves = actor.system.blessures?.graves.liste.filter(it => it.active).map(it => this.creerBlessure(4, 'grave', it, timestamp)) ?? [];
const critiques = actor.system.blessures?.critiques.liste.filter(it => it.active).map(it => this.creerBlessure(6, 'critique', it, timestamp));
const blessures = legeres.concat(graves).concat(critiques);
if (blessures.length > 0) {
await actor.createEmbeddedDocuments("Item", blessures);
}
await actor.update({
'system.blessures.legeres.liste': [],
'system.blessures.graves.liste': [],
'system.blessures.critiques.liste': []
})
}));
}
creerBlessure(gravite, graviteTexte, blessure, timestamp) {
const dateBlessure = timestamp.addJours(-blessure.jours);
const datePremiereRecup = dateBlessure.addJours(gravite);
return {
name: `Blessure ${graviteTexte}`,
type: 'blessure',
img: `systems/foundryvtt-reve-de-dragon/icons/sante/blessure${blessure.psdone ? '-soins' : ''}.webp`,
system: {
gravite: gravite,
difficulte: -gravite,
debut: { indexDate: dateBlessure.indexDate, indexMinute: 0 },
fin: { indexDate: datePremiereRecup.indexDate, indexMinute: 0 },
premierssoins: { done: blessure.psdone, bonus: blessure.premiers_soins },
soinscomplets: { done: blessure.scdone, bonus: blessure.soins_complets },
localisation: blessure.localisation
}
}
}
}
class _10_7_19_CategorieCompetenceCreature extends Migration {
get code() { return "categorie-competence-creature"; }
get version() { return "10.7.19"; }
async migrate() {
await this.applyItemsUpdates(items => items
.filter(it => TYPES.competencecreature == it.type)
.map(it => this.migrateCompetenceCreature(it))
);
}
migrateCompetenceCreature(it) {
const categorie = this.getCategorie(it)
if (categorie == it.system.categorie) {
return undefined
}
return { _id: it.id, 'system.categorie': categorie }
}
getCategorie(it) {
if (it.system.ispossession) {
return 'possession'
}
switch (it.system.categorie) {
case "melee":
if (it.system.isnaturelle) {
return 'naturelle'
}
return 'melee'
case "particuliere": case "specialisee": case "connaissance":
return "generale"
default:
return it.system.categorie
}
}
}
class _10_7_19_PossessionsEntiteVictime extends Migration {
get code() { return "possessions-entite-victime"; }
get version() { return "10.7.19"; }
async migrate() {
await this.applyItemsUpdates(items => items
.filter(it => TYPES.possession == it.type)
.map(it => this.migratePossession(it))
);
}
migratePossession(it) {
return { _id: it.id,
'system.entite.actorid': it.system.possesseurid,
'system.victime.actorid': it.system.possedeid
}
}
}
export class Migrations {
static getMigrations() {
return [
new _1_5_34_migrationPngWebp(),
new _10_0_16_MigrationSortsReserve(),
new _10_0_17_MigrationCompetenceCreature(),
new _10_0_21_VehiculeStructureResistanceMax(),
new _10_0_33_MigrationNomsDraconic(),
new _10_2_5_ArmesTirLancer(),
new _10_2_10_DesirLancinant_IdeeFixe(),
new _10_3_0_Inventaire(),
new _10_3_0_FrequenceEnvironnement(),
new _10_3_17_Monnaies(),
new _10_4_6_ServicesEnCommerces(),
new _10_5_0_UpdatePeriodicite(),
new _10_7_0_MigrationBlessures(),
new _10_7_19_CategorieCompetenceCreature(),
new _10_7_19_PossessionsEntiteVictime(),
];
}
@ -202,20 +546,12 @@ export class Migrations {
}
migrate() {
const currentVersion = game.settings.get(
SYSTEM_RDD,
"systemMigrationVersion"
);
const currentVersion = game.settings.get(SYSTEM_RDD, "systemMigrationVersion");
if (isNewerVersion(game.system.version, currentVersion)) {
// if (true) { /* comment previous and uncomment here to test before upgrade */
const migrations = Migrations.getMigrations().filter(m => isNewerVersion(m.version, currentVersion));
if (migrations.length > 0) {
migrations.sort((a, b) =>
isNewerVersion(a.version, b.version)
? 1
: isNewerVersion(b.version, a.version)
? -1
: 0
);
migrations.sort((a, b) => this.compareVersions(a, b));
migrations.forEach(async (m) => {
ui.notifications.info(
`Executing migration ${m.code}: version ${currentVersion} is lower than ${m.version}`
@ -241,4 +577,8 @@ export class Migrations {
console.log(LOG_HEAD + `No system version changed`);
}
}
compareVersions(a, b) {
return isNewerVersion(a.version, b.version) ? 1 : isNewerVersion(b.version, a.version) ? -1 : 0;
}
}

View File

@ -24,7 +24,7 @@ export class Misc {
}
static sum() {
return (a, b) => a + b;
return (a, b) => Number(a) + Number(b);
}
static ascending(orderFunction = x => x) {
@ -41,6 +41,14 @@ export class Misc {
return 0;
}
static typeName(type, subType) {
return subType ? game.i18n.localize(`TYPES.${type}.${Misc.upperFirst(subType)}`)
: '';
}
static arrayOrEmpty(items) {
return items?.length ? items : [];
}
/**
* Converts the value to an integer, or to 0 if undefined/null/not representing integer
* @param {*} value value to convert to an integer using parseInt
@ -68,6 +76,20 @@ export class Misc {
}
}
static indexLowercase(list) {
const obj = {};
const addToObj = (map, val) => {
const key = Grammar.toLowerCaseNoAccent(val);
if (key && !map[key]) map[key] = val
}
list.forEach(it => addToObj(obj, it))
return obj;
}
static concat(lists) {
return lists.reduce((a, b) => a.concat(b), []);
}
static classify(items, classifier = it => it.type) {
let itemsBy = {}
Misc.classifyInto(itemsBy, items, classifier)
@ -97,12 +119,27 @@ export class Misc {
}
}
/**
* @returns an array of incremental integers (including from / excluding to).
* if max<min, the array is decrementing integers
*/
static intArray(from, to) {
if (from > to) {
return Array.from(Array(from - to).keys()).map(i => from - i)
}
return Array.from(Array(to - from).keys()).map(i => from + i)
}
static distinct(array) {
return [...new Set(array)];
}
static join(params, separator = '') {
return params?.reduce((a, b) => a + separator + b) ?? '';
return params?.reduce(Misc.joining(separator)) ?? '';
}
static joining(separator = '') {
return (a, b) => a + separator + b;
}
static connectedGMOrUser(ownerId = undefined) {
@ -112,20 +149,27 @@ export class Misc {
return Misc.firstConnectedGM()?.id ?? game.user.id;
}
static isRollModeHiddenToPlayer() {
switch (game.settings.get("core", "rollMode")) {
case CONST.DICE_ROLL_MODES.BLIND:
case CONST.DICE_ROLL_MODES.SELF: return true;
}
return false
}
static getActiveUser(id) {
return game.users.find(u => u.id == id && u.active);
}
}
static firstConnectedGM() {
return game.users.filter(u => u.isGM && u.active).sort(Misc.ascending(u => u.id)).find(u => u.isGM && u.active);
}
static isOwnerPlayer(actor, user=undefined) {
static isOwnerPlayer(actor, user = undefined) {
return actor.testUserPermission(user ?? game.user, CONST.DOCUMENT_PERMISSION_LEVELS.OWNER)
}
static isOwnerPlayerOrUniqueConnectedGM(actor, user =undefined){
static isOwnerPlayerOrUniqueConnectedGM(actor, user = undefined) {
return Misc.isOwnerPlayer(actor, user) ?? Misc.isUniqueConnectedGM();
}
@ -195,4 +239,15 @@ export class Misc {
}
return subset;
}
static cssRotation(angle) {
const rotation = `rotate(${angle}deg)`;
return {
'transform': rotation,
'-ms-transform': rotation,
'-moz-transform': rotation,
'-webkit-transform': rotation,
'-o-transform': rotation
};
}
}

Some files were not shown because too many files have changed in this diff Show More