Compare commits

...

100 Commits

Author SHA1 Message Date
39ce66a26c Setup auto-publish action
Some checks failed
Release Creation / build (release) Failing after 1m18s
2024-12-03 14:30:18 +01:00
5265c013c1 Setup auto-publish action
Some checks failed
Release Creation / build (release) Failing after 15s
2024-12-03 14:15:43 +01:00
a03cee5d60 Setup auto-publish action 2024-12-03 14:12:19 +01:00
296c4babbb Setup auto-publish action
Some checks failed
Release Creation / build (release) Has been cancelled
2024-12-03 14:09:32 +01:00
d9c1804a08 Merge pull request 'Imports créatures et entités de cauchemar' (#727) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #727
2024-12-03 07:31:22 +01:00
fa375f8092 Améliorations parser
- armure équipée
- armes équipées
- ajout des bonus de cases
- dommages des créatures
- ajout des esquives spéciales
- fix édition feuilles de compétences créatures

- déplacement des valeurs de test en fin de fichier
2024-12-03 00:50:39 +01:00
a27e3894a0 Fix Import entités de cauchemar&créatures
- utiliser les carac liées au type d'entités
- fix: +dom&protection 0 ou négatifs
- fix compétences (qui peuvent ne pas avoir d'init)
2024-12-02 22:21:43 +01:00
ce8616c34e Merge compétences créatures/entités
Les deux compendiums n'avaient pas de raison d'être séparés
2024-12-02 22:21:43 +01:00
bbfac286a6 Amelioration parser : heures, cheveux, yeux et sorts 2024-12-01 14:22:40 +01:00
c0563efcfa Merge pull request 'Amélioration du parser' (#725) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #725
2024-12-01 12:38:50 +01:00
c481bad81a Amélioration du parser:
- correction de la taille (ne prennait que avant le 'm'
- ajout du sexe masculin/féminin
- correction heure de naissance (support heure composée, articles)
- support Unicode (nom, HN)
- support groupes de compétences (saut, danse +1)
- ignorer parenthèses dans ccompétences
- armes avec compétences accentuées
2024-11-30 22:32:00 +01:00
37c281b300 Merge pull request 'Version 12.0.24' (#724) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
All checks were successful
Release Creation / build (release) Successful in 2m6s
Reviewed-on: #724
2024-11-30 12:48:20 +01:00
9b1a179a71 Corrections voies draconic
- Utilisation du nom de voie (Oniros au lieu de Voie d'Oniros)
- compatible anciens sorts
- migration des objets existants
- amélioration de l'affichage de la liste des sorts
2024-11-30 00:08:20 +01:00
8be4c3343f Fix: les tas de conteneurs 2024-11-30 00:07:52 +01:00
6af5a85dc1 Corrections des tirages tmr/rencontres
- on n'affiche plus le compendium d'origine des rencontres dans le tchat
- utilisation de ChatMessageData#rolls (compat future v14)
- le résultat d'un d100 n'a pas à afficher de signe '%'
- utilistaion des types de TMR plutôt que les noms en minuscules
2024-11-30 00:07:52 +01:00
c6d64e09d5 Fix: saisie des bonus de cases 2024-11-30 00:07:48 +01:00
56c99c5044 Change zip file path
All checks were successful
Release Creation / build (release) Successful in 2m21s
2024-11-27 10:13:54 +01:00
a7862a25e5 Change zip file path 2024-11-27 10:13:16 +01:00
f4b5a4a4d2 Merge pull request 'Version 12.0.23 - La bibliothèque d'Astrobazzarh' (#723) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #723
2024-11-27 10:12:25 +01:00
6a17586a6c Changelog 12.0.23 2024-11-26 23:55:52 +01:00
40f7ef48c9 Lettre accentuées
Utilisation des majuscules Écriture, Épée
pour exports scriptariums
2024-11-26 23:42:38 +01:00
62dfa8a580 fix export Corps à Corps 2024-11-26 23:28:59 +01:00
e71088fe2f Fix sorts r1+
Ne pas mettre d'espace entre le r et une valeur commençant comme un
nombre
2024-11-26 22:48:23 +01:00
b7cb0f0752 Fix recherche compétence sans accents
La recherche était case-sensitive, du coup corriger le nom de la
compétence 'Epées' dans le compendium des compétences rend
les Épées inutilisable (et échec au chargement de la fiche)
2024-11-26 22:35:05 +01:00
9b31f25640 Fix doublon isActionPhysique 2024-11-25 22:17:05 +01:00
2291eaf135 Support des sorts voies multiples
Si un sort (autre que détection/lecture/annulation) a plusieurs voies
(ie: Oniros/Hypnos), les voies draconiques sont proposées pour le sort
2024-11-24 23:12:03 +01:00
a91264cd82 Correction erreur onMsgEncaisser
Pas d'actor passé pour déterminer si on doit gérer le message
2024-11-24 23:12:03 +01:00
864194e3b4 Correction "lacks permission to update"
Correction de message d'erreur

'User ... lacks permission to update Item ... in parent Actor'

Causé par le traitement d'un hook onUpdateActor qui semble
autorisé à modifier l'actor, mais ne l'est pas
- lors de modifications/ajouts de blessures
- lors de l'ajout d'effets
2024-11-24 23:12:03 +01:00
2c836f17e7 Correction sorts
- typos
- voies de draconic multiples
  (Détection/Lecture/Annulation)
- case TMR variable Invocation entité de cauchemar
2024-11-24 23:12:02 +01:00
444e52cb49 Armes naturelles
Corrections mineures sur l'affichage des armes
naturelles
2024-11-24 16:28:17 +01:00
82b0537d43 cleanup compendiums
- suppression flags inutiles
- mise à jour des versions dans les documents
- suppression des anciennes listes de blessures
- suppression des anciennes listes de queues/souffles/...
- suppression des textes 'Histoire personnelle/Notes du MJ
2024-11-24 16:28:16 +01:00
1eea07c8a8 Suppression notes obsoletes 2024-11-24 16:28:15 +01:00
de70ba8a3c Mise à jour résistance 2024-11-24 16:28:15 +01:00
f0e02e2205 Sync release scheme
All checks were successful
Release Creation / build (release) Successful in 1m18s
2024-11-20 10:27:59 +01:00
7d9ccfaf28 Sync release scheme 2024-11-20 10:25:33 +01:00
830cd1c200 Merge pull request 'Version 12.0.21' (#722) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
All checks were successful
Release Creation / build (release) Successful in 2m8s
Reviewed-on: #722
2024-11-20 10:19:30 +01:00
b9458c717e Version 12.0.21 2024-11-20 00:10:15 +01:00
22f25b45ce Fix typo "ambidextre" 2024-11-20 00:10:15 +01:00
dca9505925 Utilisation du token.name au lieu d'actor.name
Pour permettre de masquer les informations sur les
PNJs secondaires, et ne pas dévoiler le nom de l'acteur
2024-11-20 00:10:08 +01:00
45e9e94646 Fix selection des status effects 2024-11-20 00:09:22 +01:00
a0c7b87996 Merge pull request 'build-test' (#721) from build-test into v11
Reviewed-on: #721
2024-11-15 11:26:25 +01:00
a214256d69 Change compendium path and banner 2024-11-14 23:39:15 +01:00
d2de8aa972 Preparation de build avec compendiums séparés
All checks were successful
Release Creation / build (release) Successful in 1m9s
2024-11-10 19:01:26 +01:00
00ec86632f Preparation de build avec compendiums séparés 2024-11-10 18:57:53 +01:00
010234e001 Preparation de build avec compendiums séparés
All checks were successful
Release Creation / build (release) Successful in 1m39s
2024-11-10 18:51:42 +01:00
39604afa32 Preparation de build avec compendiums séparés 2024-11-10 18:46:58 +01:00
aad256bfee Ajout des fichiers sources des compendiums 2024-11-10 18:39:40 +01:00
6ebd2ee576 Add test build release
All checks were successful
Release Creation / build (release) Successful in 41s
2024-11-10 18:09:20 +01:00
ac6e8b4689 Add release build 2024-11-10 17:47:02 +01:00
387713e6f8 Add test build release
All checks were successful
Release Creation / build (release) Successful in 49s
2024-11-09 10:10:26 +01:00
c69cb3c13a Add test build release 2024-11-09 09:45:44 +01:00
bda2e39e35 Add test build release
All checks were successful
Release Creation / build (release) Successful in 42s
2024-11-09 00:25:56 +01:00
cb2d462cfe Add test build release
Some checks failed
Release Creation / build (release) Failing after 7m11s
2024-11-08 22:08:59 +01:00
5f8b5b104d Add default scene 2024-11-08 19:57:57 +01:00
9fe265165b Add default scene 2024-11-08 19:57:36 +01:00
b92003ac1f Merge pull request '12.0.19 - La témérité d'Astrobazzarh' (#720) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #720
2024-11-08 07:10:13 +01:00
a1e4bfb529 Fix: erreur droits sur scène
L'ajustement automatique de l'obscurité se déclenchait
pour les joueurs et échouaitn faute de droits sur la scènequand un joueur
2024-11-08 01:52:19 +01:00
b4f0be5d64 Version 12.0.19 2024-11-07 00:26:59 +01:00
5ff31d462f Fix initiative de masse
L'initiative de masse utilise la première compétence de combat trouvée
2024-11-07 00:16:12 +01:00
be1d109def Corrections actions combat créatures
Les actions de combat des créatures et leurs défenses sont
correctement filtrées
2024-11-06 23:24:53 +01:00
d91bee0f42 Merge pull request 'Version 12.0.18 - A la barbe d'Astrobazzarh' (#719) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #719
2024-11-05 20:27:55 +01:00
ed222c7b6d Version 12.0.18 2024-11-04 00:20:12 +01:00
70198a5727 Correction des armes de défense 2024-11-04 00:19:21 +01:00
bfe70c6634 Fix difficulté défense distance 2024-11-03 23:37:21 +01:00
09365eb744 Fix: reset des utilisations d'items 2024-11-03 23:31:56 +01:00
894d4f3941 Fix: jet d'endurance feuille simplifiée 2024-11-03 22:01:04 +01:00
6e234411ca Fix: Commande /astro 2024-11-03 22:01:04 +01:00
85acb5a255 Fix: message de tchat des commerces 2024-11-03 22:01:04 +01:00
6106e2a19e Fix changelog feuille PNJ 2024-11-03 22:01:04 +01:00
f896f1da6e Fix rename attackerToken
correction oubli sans gravité
2024-11-03 22:01:03 +01:00
9bb45c2349 PNJ: boutons des compteurs
Pas de bouton + au max de vie/endurance.
Pas de bouton - au min, de fatigue
2024-11-03 22:01:03 +01:00
5cab219e0e PNJ: affichage niveau d'esquive 2024-11-03 22:01:03 +01:00
2de9ea49c8 PNJ: ajout mains nues et empoignade 2024-11-03 22:01:03 +01:00
1760d26014 PNJ: support initiative 2024-11-03 22:01:03 +01:00
92185d4a5b Portrait dans les feuilles simplifiées 2024-11-03 22:01:03 +01:00
210b129934 Fix: niveau de compétence non défini
- quand le niveau de compétence n'était pas défini, la feuille de
personnage simplifiée et l'export scriptarium ne marchaient pas
- on pouvait saisitr une compétence avec un niveau non défini
2024-11-03 22:01:02 +01:00
b892068b38 Utilisation du nom du Nom du token
suite (pour les encaissements)
2024-11-03 22:01:02 +01:00
3906cb0a7b Localisation des blessures en regle optionnelle 2024-11-03 22:01:02 +01:00
5c58932a0d Amélioration message encaissement 2024-11-03 22:01:02 +01:00
3b06bd382b Afficher le nom du token au lieu du nom d'Acteur
Dans les messages d'automatisation de combat, le nom des
tokens est utilisé au lieu d'utiliser le nom de l'acteur.

Ceci permet de ne pas dévoiler un nom générique (Villageois)
si le token a un nom personnalisé.
2024-11-03 22:01:02 +01:00
b4a725ff12 Fix: Message uniquement MJ
L'utilisation de firstConnectedGM renvoie le GM, pas true si
un GM est connecté.
2024-11-03 22:01:01 +01:00
05df6a68cc Amelioration parser 2024-10-31 23:38:02 +01:00
025c3483a9 Amelioration parser 2024-10-31 23:36:16 +01:00
97138b25c7 Parser de personnage/creature, WIP 2024-10-30 22:45:47 +01:00
c0066f79c1 Move to v12.0.17 2024-10-26 10:56:52 +02:00
4db8bf95f9 Move to v12.0.16 2024-10-25 19:02:23 +02:00
a7e1ca0b07 Merge pull request 'Version 12.0.15' (#715) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #715
2024-10-17 07:53:59 +02:00
e2c4d93413 Version 12.0.15 2024-10-17 02:08:03 +02:00
3888efc6aa Correction dés dice-so-nice 2024-10-17 02:07:24 +02:00
5004774a15 Rendu des tables de compendiums 2024-10-16 23:32:19 +02:00
226afc1680 Fix: tooltip augmentation compétences 2024-10-16 23:32:19 +02:00
582df7e290 Simplify ChatMessage whispers
Les messages dans les TMRs sont envoyés au GM

Simplification des messages de tchat liés à un actor: on peut
utiliser les Owners (car les GMs sont owner).

Au lieu de passer le name de l'Actor (qui peut être incorrect si deux
actors ont le même, mais pas les mêmes propriétaires), on passe
directement l'actor pour déterminer mles destinataires de messages
2024-10-16 23:32:18 +02:00
501f1f2e4f Fix: Messages de maladies non publics
Les messages de maladies sont uniquement pour les "owners"
(ce qui inclut les MJs)
2024-10-16 23:32:18 +02:00
90d46c6a78 Init settings before sheets
Eviter que le chargement d'acteurs ait lieu avant le chargement
des settings
2024-10-14 03:47:13 +02:00
174ef4256a Merge pull request 'v11' (#714) from VincentVk/foundryvtt-reve-de-dragon:v11 into v11
Reviewed-on: #714
2024-10-11 17:42:24 +02:00
be132a9ff1 Gestion esquive feuille simplifiée 2024-10-11 01:30:43 +02:00
b2037a852c Pas de lien sur la ligne titre 2024-10-11 01:25:08 +02:00
2137a6c403 Version 12.0.14 2024-10-11 01:20:01 +02:00
99f5578c4f Armes sur la fiche de PNJ 2024-10-11 01:19:28 +02:00
a6ae7babbe Feuille PNJ suite 2024-10-11 01:19:27 +02:00
1449 changed files with 227897 additions and 1508 deletions

View File

@ -0,0 +1,74 @@
name: Release Creation
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "💡 The ${{ gitea.repository }} repository will cloned to the runner."
#- uses: actions/checkout@v3
- uses: RouxAntoine/checkout@v3.5.4
# get part of the tag after the `v`
- name: Extract tag version number
id: get_version
uses: battila7/get-version-action@v2
# Substitute the Manifest and Download URLs in the module.json
- name: Substitute Manifest and Download Links For Versioned Ones
id: sub_manifest_link_version
uses: microsoft/variable-substitution@v1
with:
files: 'system.json'
env:
version: ${{steps.get_version.outputs.version-without-v}}
url: https://www.uberwald.me/gitea/${{gitea.repository}}
manifest: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/system.json
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/rddsystem.zip
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '18' # Use the node version your project requires
- name: Install dependencies
run: npm install
- name: Build the compendiums
run: node ./tools/packCompendiumsToDist.mjs
# Create a zip file with all files required by the module to add to the release
- run: |
apt update -y
apt install -y zip
- run: zip -r ./rddsystem.zip system.json template.json README.md LICENSE.txt assets/ fonts/ icons lang/ module/ packs/ pic/ sounds/ styles/ templates/
- name: setup go
uses: actions/setup-go@v3
with:
go-version: '>=1.20.1'
- name: Use Go Action
id: use-go-action
uses: https://gitea.com/actions/release-action@main
with:
files: |-
./rddsystem.zip
system.json
api_key: '${{secrets.ALLOW_PUSH_RELEASE}}'
- name: Publish to Foundry server
uses: djlechuck/foundryvtt-publish-package-action@v1
with:
token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
id: 'foundryvtt-reve-de-dragon'
version: ${{github.event.release.tag_name}}
manifest: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/system.json'
notes: 'https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/raw/branch/v11/changelog.md'
compatibility-minimum: '12'
compatibility-verified: '12'

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -1,4 +1,80 @@
# 12.0
## 12.0.24 - Les ajustements d'Astrobazzarh
- amélioration
- meilleure gestion des noms des voies de draconic
- affichage du détail des sorts avec le nom de voie, 'court', la difficulté, le coût
- corrections
- les tas dans les conteneurs peuvent être désempilés sans rendre le conteneur inutilisable
- les conteneurs ne peuvent plus être empilés (pour éviter que le contenu de Schroedinger quand on les sépare)
- on peut maintenant saisir et supprimer les bonus de cases de manière intuitive
## 12.0.23 - La bibliothèque d'Astrobazzarh
- corrections mineures
- meilleure gestion de la parade des armes naturelles
- cas de "User lacks permission to update" pour les blessures et les StatusEffects
- risque de message d'encaissement non affiché
- support de sorts à voies multiples
- correction de compendiums
- résistance des armes mise à jour
- voies multiples pour las sorts de Lecture d'aura, Détection d'aura et Annulation de magie
- améliorations "Scriptarium"
- recherche des compétences sans accents pour permettre les noms accentués (standard Scriptarium)
- affichage r1+ des sorts à coût variable dans la feuille simplifiée
- affichage de Corps à corps pour le combat à mains nues dans la feuille simplifiée
- dans les compendiums, les compétences Écriture et Épée ont une majuscule accentuée. Les Épée dans le compendium d'équipements référence le nom de compétence accentué.
## 12.0.21 - La nomination d'Astrobazzarh
- Les noms pour les messages dans le tchat sont maintenant ceux des tokens plutôt que ceux des acteurs
- Fix: le choix des effets dans les options s'affiche correctement
## 12.0.20 - Le tableau d'Astrobazzarh
- Ecran d'accueil officiel Scriptarium
## 12.0.19 - La témérité d'Astrobazzarh
- Fix
- les défenses des créatures sont correctement filtrées
- le lancer d'initiative pour tous les personnages/PNJs fonctionne correctement
- les lieux et commerces n'ont pas d'initiative
## 12.0.18 - A la barbe d'Astrobazzarh
- Améliorations sur la feuille de PNJ simplifiée
- Ajout du portrait
- Ajout du corps à corps
- Affichage du niveau d'esquive
- Un clic sur l'initiative permet de lancer l'initiative
- les boutons +/- pour la vie, l'endurance et la fatigue changent si on est à la valeur normale
- un clic sur l'endurance effectue un jet d'endurance
- Fix
- les achats des commerces sont de nouveau possibles
- la commande /astro fonctionne de nouveau
- le nombre d'utilisations d'items est réinitialisé à chaque round et fin de combat
- la difficulté de parade pour les armes à distances n'est plus indiquée
- les propositions d'armes de parade sont corrigées
- Ajout d'un indicateur pour les armes de parade nécessitant une significative
## 12.0.16 - Le secret d'Astrobazzarh
- Fix: les jets envoyés messages uniquement au MJ ne sont plus envoyés à tous les autres joueurs (et dupliqués)
- Les noms affichés dans les automatisations de combat sont maintenant ceux des tokens plutôt que ceux des acteurs
- Ajout d'une option pour la localisation des blessures
## 12.0.15 - Le messager d'Astrobazzarh
- Correction des faces de dés personalisés dice-so-nice
- Les messages de maladies ne sont plus publics
- Les messages privés dans les TMR sont aussi envoyés au GM
- Les informations de compétences pouvant augmenter s'affichent comme tooltips
- Amélioration du rendu des tables de compendiums (commande /table)
## 12.0.14 - Les légions d'Astrobazzarh
- Feuille de PNJ:
- boutons standard (encaissement, ...)
- boutons pour ajuster les compteurs
- visualisation des blessures
- click sur blessure pour ajouter/enlever
- gestion des armes
## 12.0.13 - La Chance d'Astrobazzarh
- Fix: jets de caractéristiques
## 12.0.12 - L'étalage d'Astrobazzarh
- Fix: On peut de nouveau vendre des items sans propriétaire, depuis les compendiums ou depuis l'onglet des Objets
- Début de Feuille PNJ au format des encarts Scriptarium

View File

@ -1,61 +0,0 @@
# Actor notes
> The Actor#getData default implementation gives you the following for use in sheet rendering:
```
actor -> the Actor instance
data -> a cloned copy of Actor#data
items -> a cloned copy of Actor#data#items
effects -> a cloned copy of Actor#data#effects
```
> if all you need is a safe copy of `Actor#data`, you'll be much better off by simply defining your own function and avoiding all the wasted work that the parent class does which will slow down your sheet
```js
getData(options) {
return {
data: foundry.utils.deepClone(this.object.data)
}
}
```
who knows, maybe you don't even need to copy your actor data, skip the copy and it's even faster:
```js
getData(options) {
return {
data: this.object.data
}
}
```
Atropos19/02/2021
There are two recommended ways to create owned items in 0.8.0:
```js
await Item.create(itemData, {parent: actor});
await actor.createEmbeddedDocuments("Item", itemDataArray);
```
You can update an embedded item in one of two ways:
```js
//Method 1:
const item = actor.items.get(itemId);
item.update(data);
//Method 2:
actor.updateEmbeddedDocuments("Item", [{_id: itemId, ...}]);
```
I noticed adding an ActiveEffect to an actor in code using
```js
this.createEmbeddedDocuments('ActiveEffect', [effet], options);
this.applyActiveEffects();
```
Atropos — Aujourdhui à 14:42
Two notes on this:
1. You don't actually need to call this.applyActiveEffects() because this will happen automatically whenever an effect is created/updated/deleted
2. If you want to suppress the automatic display of the sheet for the newly created document, you can pass options.renderSheet = false as part of your options object

View File

@ -80,7 +80,7 @@ export class RdDActorSheet extends RdDBaseActorSangSheet {
const actor = this.actor;
formData.combat = foundry.utils.duplicate(formData.armes);
RdDItemArme.computeNiveauArmes(formData.combat, formData.competences);
formData.combat.push(RdDItemArme.mainsNues(actor));
formData.combat.push(RdDItemArme.corpsACorps(actor));
formData.combat.push(RdDItemArme.empoignade(actor));
formData.esquives = this.actor.getCompetences("Esquive");
@ -128,7 +128,7 @@ export class RdDActorSheet extends RdDBaseActorSangSheet {
this.render(true);
});
this.html.find('.visu-tmr').click(async event => this.actor.displayTMR("visu"))
this.html.find('.button-tmr-visu').click(async event => this.actor.displayTMR("visu"))
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
@ -186,7 +186,7 @@ export class RdDActorSheet extends RdDBaseActorSangSheet {
this.html.find('.item-equip').click(async event => this.actor.equiperObjet(RdDSheetUtility.getItemId(event)))
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('.button-appel-chance').click(async event => this.actor.rollAppelChance())
this.html.find('[name="jet-astrologie"]').click(async event => this.actor.astrologieNombresAstraux())
this.html.find('.tache-label a').click(async event => this.actor.rollTache(RdDSheetUtility.getItemId(event)))
@ -220,24 +220,24 @@ export class RdDActorSheet extends RdDBaseActorSangSheet {
// Points de reve actuel
this.html.find('.roll-reve-actuel').click(async event => this.actor.rollCarac('reve-actuel', true))
this.html.find('.empoignade-label a').click(async event => RdDEmpoignade.onAttaqueEmpoignadeFromItem(RdDSheetUtility.getItem(event, this.actor)))
this.html.find('.arme-label a').click(async event => this.actor.rollArme(foundry.utils.duplicate(this._getEventArmeCombat(event))))
this.html.find('.roll-arme').click(async event => this.actor.rollArme(foundry.utils.duplicate(this._getEventArmeCombat(event)), 'competence'))
// 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);
this.html.find('.roll-init-arme').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);
RdDCombatManager.rollInitiativeAction(combatant._id, this._getEventArmeCombat(event));
} else {
ui.notifications.info("Impossible de lancer l'initiative sans être dans un combat.");
}
});
})
// Display TMR
this.html.find('.monte-tmr').click(async event => this.actor.displayTMR("normal"))
this.html.find('.monte-tmr-rapide').click(async event => this.actor.displayTMR("rapide"))
this.html.find('.button-tmr').click(async event => this.actor.displayTMR("normal"))
this.html.find('.button-tmr-rapide').click(async event => this.actor.displayTMR("rapide"))
this.html.find('.repos').click(async event => await this.actor.repos())
this.html.find('.button-repos').click(async event => await this.actor.repos())
this.html.find('.carac-xp-augmenter').click(async event => this.actor.updateCaracXPAuto(event.currentTarget.name.replace("augmenter.", "")))
this.html.find('.competence-xp-augmenter').click(async event => this.actor.updateCompetenceXPAuto(RdDSheetUtility.getItemId(event)))
@ -279,7 +279,7 @@ export class RdDActorSheet extends RdDBaseActorSangSheet {
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('.button-ethylisme').click(async event => this.actor.jetEthylisme())
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))

View File

@ -36,6 +36,8 @@ import { ITEM_TYPES } from "./item.js";
import { RdDBaseActorSang } from "./actor/base-actor-sang.js";
import { RdDCoeur } from "./coeur/rdd-coeur.js";
import { DialogChoixXpCarac } from "./dialog-choix-xp-carac.js";
import { RdDItemArme } from "./item-arme.js";
import { RdDCombatManager } from "./rdd-combat.js";
export const MAINS_DIRECTRICES = ['Droitier', 'Gaucher', 'Ambidextre']
@ -130,6 +132,23 @@ export class RdDActor extends RdDBaseActorSang {
.reduce(Misc.sum(), 0);
}
listActionsCombat() {
// Recupération des armes
const actions = RdDCombatManager.listActionsArmes(
this.itemTypes[ITEM_TYPES.arme]
.filter(it => RdDItemArme.isAttaque(it))
.concat(RdDItemArme.corpsACorps(this))
.concat(RdDItemArme.empoignade(this))
,
this.itemTypes[ITEM_TYPES.competence],
this.system.carac)
if (this.system.attributs.hautrevant.value) {
actions.push({ name: "Draconic", action: 'haut-reve', system: { initOnly: true, competence: "Draconic" } });
}
return actions
}
/* -------------------------------------------- */
getTache(id) { return this.findItemLike(id, 'tache') }
getMeditation(id) { return this.findItemLike(id, 'meditation') }
@ -159,10 +178,10 @@ export class RdDActor extends RdDBaseActorSang {
const potionUpdates = await Promise.all(potions.map(async it => {
const nouveauReve = Math.max(it.system.pr - 1, 0)
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-potionenchantee-chateaudormant.html`, {
pr: nouveauReve,
alias: this.name,
alias: this.getAlias(),
potionName: it.name,
potionImg: it.img
})
@ -210,7 +229,7 @@ export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */
async grisReve(nbJours) {
let message = {
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: `${nbJours} jours de gris rêve sont passés. `
};
for (let i = 0; i < nbJours; i++) {
@ -264,7 +283,7 @@ export class RdDActor extends RdDBaseActorSang {
async dormirChateauDormant() {
if (!ReglesOptionnelles.isUsing("chateau-dormant-gardien") || !this.system.sommeil || this.system.sommeil.nouveaujour) {
const message = {
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: ""
};
@ -400,7 +419,7 @@ export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */
async remiseANeuf() {
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: 'Remise à neuf de ' + this.name
});
await this.supprimerBlessures(it => true);
@ -417,7 +436,7 @@ export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */
async dormir(heures, options = { grisReve: false, chateauDormant: false }) {
const message = {
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: this.name + ': '
};
const insomnie = this.system.sommeil?.insomnie || heures == 0;
@ -488,7 +507,7 @@ export class RdDActor extends RdDBaseActorSang {
else {
if (!ReglesOptionnelles.isUsing("recuperation-reve")) {
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: `Pas de récupération de rêve (${reve} points ignorés)`
});
jetsReve.push(0);
@ -919,7 +938,7 @@ export class RdDActor extends RdDBaseActorSang {
await this.createEmbeddedDocuments('Item', [souffle]);
if (options.chat) {
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: this.name + " subit un Souffle de Dragon : " + souffle.name
});
}
@ -939,7 +958,7 @@ export class RdDActor extends RdDBaseActorSang {
await this.createEmbeddedDocuments('Item', [queue]);
if (options.chat) {
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: this.name + " subit une Queue de Dragon : " + queue.name
});
}
@ -977,7 +996,7 @@ export class RdDActor extends RdDBaseActorSang {
let tmr = await TMRUtility.getTMRAleatoire(tmr => accessible(tmr) && !innaccessible.includes(tmr.coord));
ChatMessage.create({
content: `${raison} : ré-insertion aléatoire.`,
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name)
whisper: ChatUtility.getOwners(this)
});
await this.forcerPositionTMRInconnue(tmr);
return tmr;
@ -1082,7 +1101,7 @@ export class RdDActor extends RdDBaseActorSang {
const jetMoral = await this._jetDeMoral(situation);
const finMessage = (jetMoral.succes ? messageReussi : messageManque) ?? (jetMoral.ajustement == 0 ? "Vous gardez votre moral" : jetMoral.ajustement > 0 ? "Vous gagnez du moral" : "Vous perdez du moral");
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: `${finMessage} - jet ${jetMoral.succes ? "réussi" : "manqué"} en situation ${situation} (${jetMoral.jet}/${jetMoral.difficulte}).`
});
return jetMoral.ajustement;
@ -1322,7 +1341,7 @@ export class RdDActor extends RdDBaseActorSang {
const etat = this.getEtatGeneral({ ethylisme: true });
const nbDoses = Number(this.system.compteurs.ethylisme.nb_doses || 0);
const ethylismeData = {
alias: this.name,
alias: this.getAlias(),
actor: this,
vie: this.system.sante.vie.max,
alcool: alcool,
@ -1407,7 +1426,7 @@ export class RdDActor extends RdDBaseActorSang {
const perteDissolution = Math.max(0, Math.min(dissolution, conversion));
let stressRollData = {
alias: this.name,
alias: this.getAlias(),
selectedCarac: this.system.carac.reve,
rolled: stressRoll,
stress: fromStress,
@ -1419,7 +1438,7 @@ export class RdDActor extends RdDBaseActorSang {
};
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-resultat-transformer-stress.html`, stressRollData)
});
@ -1498,14 +1517,14 @@ export class RdDActor extends RdDBaseActorSang {
carac.value = niveauSuivant;
let checkXp = {
alias: this.name,
alias: this.getAlias(),
carac: caracName,
value: niveauSuivant,
xp: carac.xp
}
if (display) {
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-carac-xp.html`, checkXp)
});
}
@ -1528,7 +1547,7 @@ export class RdDActor extends RdDBaseActorSang {
newCompData.system.niveau += 1;
newCompData.system.xp = newXP;
let checkXp = {
alias: this.name,
alias: this.getAlias(),
competence: newCompData.name,
niveau: newCompData.system.niveau,
xp: newCompData.system.xp,
@ -1537,7 +1556,7 @@ export class RdDActor extends RdDBaseActorSang {
}
if (display) {
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-competence-xp.html`, checkXp)
});
}
@ -1548,7 +1567,7 @@ export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */
async appliquerAjoutExperience(rollData, hideChatMessage = 'show') {
if (!Misc.firstConnectedGM()){
if (!Misc.isFirstConnectedGM()) {
return
}
hideChatMessage = hideChatMessage == 'hide' || (Misc.isRollModeHiddenToPlayer() && !game.user.isGM)
@ -1563,7 +1582,7 @@ export class RdDActor extends RdDBaseActorSang {
}
else {
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: content
});
}
@ -1666,7 +1685,7 @@ export class RdDActor extends RdDBaseActorSang {
}
ChatMessage.create({
content: "Vous êtes sous le coup d'une Mauvaise Rencontre en Persective." + addMsg,
whisper: ChatMessage.getWhisperRecipients(this.name)
whisper: ChatUtility.getOwners(this)
});
}
return rencSpecial;
@ -1678,7 +1697,7 @@ export class RdDActor extends RdDBaseActorSang {
if (countInertieDraconique > 0) {
ChatMessage.create({
content: `Vous êtes sous le coup d'Inertie Draconique : vous perdrez ${countInertieDraconique + 1} cases de Fatigue par déplacement au lieu d'une.`,
whisper: ChatMessage.getWhisperRecipients(this.name)
whisper: ChatUtility.getOwners(this)
});
}
return countInertieDraconique + 1;
@ -1690,7 +1709,7 @@ export class RdDActor extends RdDBaseActorSang {
await this.reveActuelIncDec(-1);
ChatMessage.create({
content: "Vous êtes sous le coup d'un Péage : l'entrée sur cette case vous a coûté 1 Point de Rêve (déduit automatiquement).",
whisper: ChatMessage.getWhisperRecipients(this.name)
whisper: ChatUtility.getOwners(this)
});
}
}
@ -1765,7 +1784,7 @@ export class RdDActor extends RdDBaseActorSang {
}
const competence = this.getCompetence(compName);
let rollData = {
alias: this.name,
alias: this.getAlias(),
caracValue: Number(carac.value),
selectedCarac: carac,
competence: competence,
@ -1838,7 +1857,7 @@ export class RdDActor extends RdDBaseActorSang {
label: 'Jet ' + Grammar.apostrophe('de', competence.name),
template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html',
rollData: {
alias: this.name,
alias: this.getAlias(),
carac: this.system.carac,
selectedCarac: this.getCaracByName(caracName),
selectedCaracName: caracName,
@ -2270,7 +2289,7 @@ export class RdDActor extends RdDBaseActorSang {
// Cas de désir lancinant, pas d'expérience sur particulière
ChatMessage.create({
content: `Vous souffrez au moins d'un Désir Lancinant, vous ne pouvez pas gagner d'expérience sur une Particulière tant que le désir n'est pas assouvi`,
whisper: ChatMessage.getWhisperRecipients(this.name)
whisper: ChatUtility.getOwners(this)
});
return []
}
@ -2405,7 +2424,7 @@ export class RdDActor extends RdDBaseActorSang {
if (countMonteeLaborieuse > 0) {
ChatMessage.create({
content: `Vous êtes sous le coup d'une Montée Laborieuse : vos montées en TMR coûtent ${countMonteeLaborieuse} Point de Rêve de plus.`,
whisper: ChatMessage.getWhisperRecipients(this.name)
whisper: ChatUtility.getOwners(this)
});
}
return countMonteeLaborieuse;
@ -2453,7 +2472,7 @@ export class RdDActor extends RdDBaseActorSang {
if (this.getReveActuel() < minReveValue) {
ChatMessage.create({
content: `Vous n'avez les ${minReveValue} Points de Reve nécessaires pour monter dans les Terres Médianes`,
whisper: ChatMessage.getWhisperRecipients(this.name)
whisper: ChatUtility.getOwners(this)
});
return;
}
@ -2615,7 +2634,7 @@ export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */
async resetItemUse() {
await this.setFlag(SYSTEM_RDD, 'itemUse', {});
await this.unsetFlag(SYSTEM_RDD, 'itemUse');
}
/* -------------------------------------------- */
@ -2850,7 +2869,7 @@ export class RdDActor extends RdDBaseActorSang {
await this.setBonusPotionSoin(potionData.system.herbebonus);
}
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-consommer-potion-soin.html`, potionData)
});
}
@ -2887,7 +2906,7 @@ export class RdDActor extends RdDBaseActorSang {
this.bonusRepos = potionData.system.herbebonus;
}
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-consommer-potion-repos.html`, potionData)
});
}
@ -2911,7 +2930,7 @@ export class RdDActor extends RdDBaseActorSang {
let newQuantite = herbeData.system.quantite - herbeData.nbBrins;
let messageData = {
alias: this.name,
alias: this.getAlias(),
nbBrinsReste: newQuantite,
potion: newPotion,
herbe: herbeData
@ -2919,7 +2938,7 @@ export class RdDActor extends RdDBaseActorSang {
this.diminuerQuantiteObjet(herbeData._id, herbeData.nbBrins);
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-fabriquer-potion-base.html`, messageData)
});
}
@ -2944,7 +2963,7 @@ export class RdDActor extends RdDBaseActorSang {
}
}
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-consommer-potion-generique.html`, potionData)
});
}
@ -2998,20 +3017,21 @@ export class RdDActor extends RdDBaseActorSang {
case 'queue':
case 'ombre':
case 'souffle':
await this.onDeleteOwnedDraconique(item, options, id);
break;
await this.onDeleteOwnedDraconique(item, options, id)
break
case 'casetmr':
await this.onDeleteOwnedCaseTmr(item, options, id);
break;
await this.onDeleteOwnedCaseTmr(item, options, id)
break
case 'empoignade':
await RdDEmpoignade.deleteLinkedEmpoignade(this.id, item)
break;
break
}
super.onDeleteItem(item, options, id)
}
/* -------------------------------------------- */
async onCreateOwnedDraconique(item, options, id) {
if (Misc.isUniqueConnectedGM()) {
if (Misc.isFirstConnectedGM()) {
let draconique = Draconique.all().find(it => it.match(item));
if (draconique) {
await draconique.onActorCreateOwned(this, item)
@ -3023,7 +3043,7 @@ export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */
async onDeleteOwnedDraconique(item, options, id) {
if (Misc.isUniqueConnectedGM()) {
if (Misc.isFirstConnectedGM()) {
let draconique = Draconique.all().find(it => it.match(item));
if (draconique) {
await draconique.onActorDeleteOwned(this, item)
@ -3033,7 +3053,7 @@ export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */
async onDeleteOwnedCaseTmr(item, options, id) {
if (Misc.isUniqueConnectedGM()) {
if (Misc.isFirstConnectedGM()) {
let draconique = Draconique.all().find(it => it.isCase(item));
if (draconique) {
await draconique.onActorDeleteCaseTmr(this, item)
@ -3044,7 +3064,7 @@ export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */
notifyGestionTeteSouffleQueue(item, manualMessage = true) {
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: `${this.name} a reçu un/une ${item.type}: ${item.name}, qui ${manualMessage ? "n'est pas" : "est"} géré(e) automatiquement. ${manualMessage ? manualMessage : ''}`
});
}
@ -3067,7 +3087,7 @@ export class RdDActor extends RdDBaseActorSang {
incarnation.name = 'Réincarnation de ' + incarnation.name
incarnation.system = {
carac: foundry.utils.duplicate(this.system.carac),
heure: RdDTimestamp.defHeure(await RdDDice.rollHeure( { rollMode: "selfroll", showDice: SHOW_DICE })).key,
heure: RdDTimestamp.defHeure(await RdDDice.rollHeure({ rollMode: "selfroll", showDice: SHOW_DICE })).key,
age: 18,
biographie: '',
notes: '',

View File

@ -24,14 +24,15 @@ export class RdDBaseActorReveSheet extends RdDBaseActorSheet {
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
this.html.find('.encaisser-direct').click(async event => this.actor.encaisser())
this.html.find('.roll-carac a').click(async event => this.actor.rollCarac(Grammar.toLowerCaseNoAccent(event.currentTarget.attributes.name.value)));
this.html.find('.button-encaissement').click(async event => this.actor.encaisser())
this.html.find('.roll-carac').click(async event => {
this.actor.rollCarac(Grammar.toLowerCaseNoAccent(event.currentTarget.attributes['data-carac-name'].value))});
this.html.find('.roll-competence').click(async event => this.actor.rollCompetence(RdDSheetUtility.getItemId(event)));
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));
if (game.user.isGM) {
this.html.find('.remise-a-neuf').click(async event => this.actor.remiseANeuf())
this.html.find('.button-remise-a-neuf').click(async event => this.actor.remiseANeuf())
this.html.find('.delete-active-effect').click(async event => this.actor.removeEffect(this.html.find(event.currentTarget).parents(".active-effect").data('effect')));
this.html.find('.enlever-tous-effets').click(async event => await this.actor.removeEffects());
}

View File

@ -15,7 +15,7 @@ import { StatusEffects } from "../settings/status-effects.js";
import { ITEM_TYPES } from "../item.js";
import { Targets } from "../targets.js";
import { RdDPossession } from "../rdd-possession.js";
import { RdDCombat } from "../rdd-combat.js";
import { RdDCombat, RdDCombatManager } from "../rdd-combat.js";
import { RdDConfirm } from "../rdd-confirm.js";
import { ENTITE_INCARNE, SHOW_DICE, SYSTEM_RDD } from "../constants.js";
import { RdDItemArme } from "../item-arme.js";
@ -78,8 +78,27 @@ export class RdDBaseActorReve extends RdDBaseActor {
}
async jetEndurance(resteEndurance = undefined) { return { jetEndurance: 0, sonne: false } }
isDead() { return false }
isSonne() { return false }
blessuresASoigner() { return [] }
getEtatGeneral(options = { ethylisme: false }) { return 0 }
isActorCombat() { return true }
getCaracInit(competence) {
if (!competence){
return 0
}
if (competence.type == ITEM_TYPES.competencecreature) {
return competence.system.carac_value
}
return this.system.carac[competence.system.defaut_carac].value;
}
listActionsCombat() {
return this.itemTypes[ITEM_TYPES.competencecreature]
.filter(it => RdDItemCompetenceCreature.isAttaque(it))
.map(it => RdDItemCompetenceCreature.armeCreature(it))
.filter(it => it != undefined);
}
async computeArmure(attackerRoll) { return this.getProtectionNaturelle() }
async remiseANeuf() { }
@ -98,7 +117,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
for (let effect of this.getEffects()) {
if (effect.duration.type !== 'none' && (effect.duration.remaining <= 0 || options.terminer)) {
await effect.delete();
ChatMessage.create({ content: `${this.name} n'est plus ${Misc.lowerFirst(game.i18n.localize(effect.system.label))} !` });
ChatMessage.create({ content: `${this.getAlias()} n'est plus ${Misc.lowerFirst(game.i18n.localize(effect.system.label))} !` });
}
}
}
@ -153,9 +172,6 @@ export class RdDBaseActorReve extends RdDBaseActor {
getPossession(possessionId) {
return this.itemTypes[ITEM_TYPES.possession].find(it => it.system.possessionid == possessionId);
}
getPossessions() {
return this.itemTypes[ITEM_TYPES.possession];
}
getEmpoignades() {
return this.itemTypes[ITEM_TYPES.empoignade];
}
@ -189,7 +205,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
getEffect(effectId) {
return this.getEmbeddedCollection("ActiveEffect").find(it => it.statuses?.has(effectId));
}
async setEffect(effectId, status) {
if (this.isEffectAllowed(effectId)) {
const effect = this.getEffect(effectId);
@ -201,7 +217,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
}
}
}
async removeEffect(id) {
const effect = this.getEmbeddedCollection("ActiveEffect").find(it => it.id == id);
if (effect) {
@ -272,7 +288,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
await this.openRollDialog({
name: `jet-${this.id}`,
label: `Jet de ${this.name}`,
label: `Jet de ${this.getAlias()}`,
template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll.html',
rollData: {
carac: carac,
@ -296,7 +312,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
/* -------------------------------------------- */
async rollCarac(caracName, jetResistance = undefined) {
if (Grammar.equalsInsensitive(caracName, 'taille')){
if (Grammar.equalsInsensitive(caracName, 'taille')) {
return
}
RdDEmpoignade.checkEmpoignadeEnCours(this)
@ -327,14 +343,15 @@ export class RdDBaseActorReve extends RdDBaseActor {
const competence = this.getCompetence(idOrName);
let rollData = { carac: this.system.carac, competence: competence, arme: options.arme }
if (competence.type == ITEM_TYPES.competencecreature) {
const token = RdDUtility.getSelectedToken(this)
const arme = RdDItemCompetenceCreature.armeCreature(competence)
if (arme && options.tryTarget && Targets.hasTargets()) {
Targets.selectOneToken(target => {
Targets.selectOneTargetToken(target => {
if (arme.action == "possession") {
RdDPossession.onAttaquePossession(target, this, competence)
}
else {
RdDCombat.rddCombatTarget(target, this).attaque(competence, arme)
RdDCombat.rddCombatTarget(target, this, token).attaque(competence, arme)
}
});
return;
@ -364,9 +381,10 @@ export class RdDBaseActorReve extends RdDBaseActor {
* @param {*} categorieArme catégorie d'attaque à utiliser: competence (== melee), lancer, tir; naturelle, possession
* @returns
*/
rollArme(arme, categorieArme = "competence") {
rollArme(arme, categorieArme, token) {
token = token ?? RdDUtility.getSelectedToken(this)
const compToUse = this.$getCompetenceArme(arme, categorieArme)
if (!RdDItemArme.isArmeUtilisable(arme)) {
if (!RdDItemArme.isUtilisable(arme)) {
ui.notifications.warn(`Arme inutilisable: ${arme.name} a une résistance de 0 ou moins`)
return
}
@ -385,7 +403,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
return
}
Targets.selectOneToken(target => {
Targets.selectOneTargetToken(target => {
if (Targets.isTargetEntite(target)) {
ui.notifications.warn(`Vous ne pouvez pas attaquer une entité non incarnée avec votre ${arme.name}!!!!`);
return
@ -395,7 +413,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
if (competence.isCompetencePossession()) {
return RdDPossession.onAttaquePossession(target, this, competence);
}
RdDCombat.rddCombatTarget(target, this).attaque(competence, arme);
RdDCombat.rddCombatTarget(target, this, token).attaque(competence, arme);
})
}
@ -403,69 +421,66 @@ export class RdDBaseActorReve extends RdDBaseActor {
return RdDItemArme.getCompetenceArme(arme, competenceName)
}
verifierForceMin(item) {
}
/* -------------------------------------------- */
async resetItemUse() { }
async incDecItemUse(itemId, inc = 1) { }
getItemUse(itemId) { return 0; }
verifierForceMin(item) { }
/* -------------------------------------------- */
async encaisser() { await RdDEncaisser.encaisser(this) }
async encaisserDommages(rollData, attacker = undefined, show = undefined) {
async encaisserDommages(rollData, attacker = undefined, show = undefined, attackerToken = undefined, defenderToken = undefined) {
if (attacker && !await attacker.accorder(this, 'avant-encaissement')) {
return;
}
const armure = await this.computeArmure(rollData);
if (ReglesOptionnelles.isUsing('validation-encaissement-gr')){
await this.encaisserDommagesValidationGR(rollData, armure, attacker?.id, show);
}
if (ReglesOptionnelles.isUsing('validation-encaissement-gr')) {
await this.encaisserDommagesValidationGR(rollData, armure, show, attackerToken, defenderToken);
}
else {
const jet = await RdDUtility.jetEncaissement(rollData, armure, { showDice: SHOW_DICE });
await this.$onEncaissement(jet, show, attacker);
const jet = await RdDUtility.jetEncaissement(this, rollData, armure, { showDice: SHOW_DICE });
await this.$onEncaissement(jet, show, attackerToken, defenderToken)
}
}
async encaisserDommagesValidationGR(rollData, armure, attackerId, show) {
async encaisserDommagesValidationGR(rollData, armure, show, attackerToken, defenderToken) {
if (!game.user.isGM) {
RdDBaseActor.remoteActorCall({
tokenId: this.token?.id,
actorId: this.id,
method: 'encaisserDommagesValidationGR',
args: [rollData, armure, attackerId, show]
});
args: [rollData, armure, show, attackerToken, defenderToken]
})
} else {
const attacker = game.actors.get(attackerId);
DialogValidationEncaissement.validerEncaissement(this, rollData, armure,
jet => this.$onEncaissement(jet, show, attacker));
jet => this.$onEncaissement(jet, show, attackerToken, defenderToken));
}
}
async $onEncaissement(jet, show, attacker) {
await this.onAppliquerJetEncaissement(jet, attacker);
await this.$afficherEncaissement(jet, show);
async $onEncaissement(jet, show, attackerToken, defenderToken) {
await this.onAppliquerJetEncaissement(jet, attackerToken);
await this.$afficherEncaissement(jet, show, defenderToken);
}
async onAppliquerJetEncaissement(encaissement, attacker) { }
async onAppliquerJetEncaissement(encaissement, attackerToken) { }
async $afficherEncaissement(encaissement, show) {
async $afficherEncaissement(encaissement, show, defenderToken) {
foundry.utils.mergeObject(encaissement, {
alias: this.name,
alias: defenderToken?.name ?? this.getAlias(),
hasPlayerOwner: this.hasPlayerOwner,
show: show ?? {}
});
}, { overwrite: false });
await ChatUtility.createChatWithRollMode(this.name, {
roll: encaissement.roll,
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement)
});
await ChatUtility.createChatWithRollMode(
{
roll: encaissement.roll,
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement)
},
this
)
if (!encaissement.hasPlayerOwner && encaissement.endurance != 0) {
encaissement = foundry.utils.duplicate(encaissement);
encaissement.isGM = true;
encaissement = foundry.utils.duplicate(encaissement)
encaissement.isGM = true
ChatMessage.create({
whisper: ChatMessage.getWhisperRecipients("GM"),
whisper: ChatUtility.getGMs(),
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement)
});
}
@ -481,7 +496,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
}
const rolled = await RdDResolutionTable.roll(this.getReveActuel(), - Number(entite.system.carac.niveau.value));
const rollData = {
alias: this.name,
alias: this.getAlias(),
rolled: rolled,
entite: entite.name,
selectedCarac: this.system.carac.reve
@ -501,7 +516,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
isEntiteAccordee(attacker) { return true }
async setEntiteReveAccordee(actor) {
ui.notifications.error("Impossible de s'accorder à " + this.name + ": ce n'est pas une entité incarnée");
ui.notifications.error("Impossible de s'accorder à " + this.getAlias() + ": ce n'est pas une entité incarnée");
}
}

View File

@ -39,8 +39,8 @@ export class RdDBaseActorSangSheet extends RdDBaseActorReveSheet {
ChatMessage.create({
content: `Jet d'Endurance : ${result.jetEndurance} / ${endurance}
<br>${this.actor.name} a ${result.sonne ? 'échoué' : 'réussi'} son Jet d'Endurance ${result.sonne ? 'et devient Sonné' : ''}`,
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.actor.name)
});
whisper: ChatUtility.getOwners(this.actor)
})
}
}

View File

@ -5,6 +5,7 @@ import { ITEM_TYPES } from "../item.js";
import { RdDBaseActorReve } from "./base-actor-reve.js";
import { RdDDice } from "../rdd-dice.js";
import { RdDItemBlessure } from "../item/blessure.js";
import { ChatUtility } from "../chat-utility.js";
/**
* Classe de base pour les acteurs qui peuvent subir des blessures
@ -88,9 +89,9 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
/* -------------------------------------------- */
async onAppliquerJetEncaissement(encaissement, attacker) {
async onAppliquerJetEncaissement(encaissement, attackerToken) {
const santeOrig = foundry.utils.duplicate(this.system.sante);
const blessure = await this.ajouterBlessure(encaissement, attacker); // Will update the result table
const blessure = await this.ajouterBlessure(encaissement, attackerToken); // Will update the result table
const perteVie = await this.santeIncDec("vie", -encaissement.vie);
const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, blessure?.isCritique());
@ -168,7 +169,7 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
/* -------------------------------------------- */
async ajouterBlessure(encaissement, attacker = undefined) {
async ajouterBlessure(encaissement, attackerToken = undefined) {
if (encaissement.gravite < 0) return;
if (encaissement.gravite > 0) {
while (this.countBlessures(it => it.system.gravite == encaissement.gravite) >= RdDItemBlessure.maxBlessures(encaissement.gravite) && encaissement.gravite <= 6) {
@ -180,7 +181,7 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
}
}
const endActuelle = this.getEnduranceActuelle();
const blessure = await RdDItemBlessure.createBlessure(this, encaissement.gravite, encaissement.dmg.loc.label, attacker);
const blessure = await RdDItemBlessure.createBlessure(this, encaissement.gravite, encaissement.dmg?.loc.label ?? '', attackerToken);
if (blessure.isCritique()) {
encaissement.endurance = endActuelle;
}
@ -190,12 +191,19 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
encaissement.mort = true;
ChatMessage.create({
content: `<img class="chat-icon" src="icons/svg/skull.svg" data-tooltip="charge" />
<strong>${this.name} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !</strong>`
<strong>${this.getAlias()} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !</strong>`
});
}
return blessure;
}
async supprimerBlessure({ gravite }) {
const toDelete = this.itemTypes[ITEM_TYPES.blessure].find(it => it.system.gravite == gravite)?.id
if (toDelete) {
await this.deleteEmbeddedDocuments('Item', [toDelete]);
}
}
async supprimerBlessures(filterToDelete) {
const toDelete = this.filterItems(filterToDelete, ITEM_TYPES.blessure)
.map(it => it.id);
@ -203,13 +211,16 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
}
countBlessures(filter = it => !it.isContusion()) {
return this.filterItems(filter, 'blessure').length
return this.filterItems(filter, ITEM_TYPES.blessure).length
}
/* -------------------------------------------- */
async jetDeVie() {
if (this.isDead()) {
ChatMessage.create({ content: `Jet de Vie: ${this.name} est déjà mort, ce n'est pas la peine d'en rajouter !!!!!`, whisper: ChatMessage.getWhisperRecipients(this.name) });
ChatMessage.create({
content: `Jet de Vie: ${this.getAlias()} est déjà mort, ce n'est pas la peine d'en rajouter !!!!!`,
whisper: ChatUtility.getOwners(this)
})
return
}
const jetDeVie = await RdDDice.roll("1d20");
@ -231,12 +242,15 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
await this.santeIncDec("vie", -perte);
}
if (this.isDead()) {
msgText += `<br><strong>${this.name} est mort !!!!</strong>`;
msgText += `<br><strong>${this.getAlias()} est mort !!!!</strong>`;
}
else if (prochainJet > 0) {
msgText += `<br>Prochain jet de vie dans ${prochainJet} ${isCritique ? 'round' : 'minute'}${prochainJet > 1 ? 's' : ''} ${isCritique ? '(état critique)' : '(état grave)'}`
}
ChatMessage.create({ content: msgText, whisper: ChatMessage.getWhisperRecipients(this.name) });
ChatMessage.create({
content: msgText,
whisper: ChatUtility.getOwners(this)
});
}
/* -------------------------------------------- */
@ -259,14 +273,14 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
async setSonne(sonne = true) {
if (!game.combat && sonne) {
ui.notifications.info(`${this.name} est hors combat, il ne reste donc pas sonné`);
ui.notifications.info(`${this.getAlias()} est hors combat, il ne reste donc pas sonné`);
return;
}
await this.setEffect(STATUSES.StatusStunned, sonne);
await this.setEffect(STATUSES.StatusStunned, sonne)
}
getSonne() {
return this.getEffect(STATUSES.StatusStunned);
isSonne() {
return this.getEffect(STATUSES.StatusStunned)
}
isEffectAllowed(effectId) { return true }

View File

@ -78,6 +78,13 @@ export class RdDBaseActor extends Actor {
return game.actors.get(actorId)
}
getAlias() {
if (this.token?.name != null && this.token != this.prototypeToken) {
return this.token.name
}
return this.name
}
isPersonnageJoueur() { return false }
static extractActorMin = (actor) => { return { id: actor?.id, type: actor?.type, name: actor?.name, img: actor?.img }; };
@ -205,12 +212,21 @@ export class RdDBaseActor extends Actor {
/* -------------------------------------------- */
async onPreUpdateItem(item, change, options, id) { }
async onCreateItem(item, options, id) { }
async onDeleteItem(item, options, id) { }
async onUpdateActor(update, options, actorId) { }
async onDeleteItem(item, options, id) {
if (item.isInventaire()) {
this._removeItemFromConteneur(item)
}
}
_removeItemFromConteneur(item) {
this.items.filter(it => it.isConteneur() && it.system.contenu.includes(item.id))
.forEach(conteneur => {
const nouveauContenu = conteneur.system.contenu.filter(id => id != item.id);
conteneur.update({ 'system.contenu': nouveauContenu });
});
}
async onTimeChanging(oldTimestamp, newTimestamp) {
this.items.filter(it => it.isFinPeriode(oldTimestamp, newTimestamp))
@ -218,7 +234,7 @@ export class RdDBaseActor extends Actor {
}
async creerObjetParMJ(object) {
if (!Misc.isUniqueConnectedGM()) {
if (!Misc.isFirstConnectedGM()) {
RdDBaseActor.remoteActorCall({
tokenId: this.token?.id,
actorId: this.id,
@ -270,6 +286,7 @@ export class RdDBaseActor extends Actor {
}
let fortune = this.getFortune();
console.log("payer", game.user.character, depense, fortune);
// TODO: passer en handlebars
let msg = "";
if (fortune >= depense) {
await Monnaie.optimiserFortune(this, fortune - depense);
@ -279,11 +296,10 @@ export class RdDBaseActor extends Actor {
msg = "Vous n'avez pas assez d'argent pour payer cette somme !";
}
let message = {
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
ChatMessage.create({
whisper: ChatUtility.getOwners(this),
content: msg
};
ChatMessage.create(message);
})
}
async depenserSols(sols) {
@ -317,7 +333,7 @@ export class RdDBaseActor extends Actor {
RdDAudio.PlayContextAudio("argent"); // Petit son
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(this),
content: `Vous avez reçu <strong>${sols} Sols</strong> ${fromActor ? " de " + fromActor.name : ''}, qui ont été ajoutés à votre argent.`
});
}
@ -335,7 +351,7 @@ export class RdDBaseActor extends Actor {
ui.notifications.info("Inutile de se vendre à soi-même");
return;
}
if (!Misc.isUniqueConnectedGM()) {
if (!Misc.isFirstConnectedGM()) {
RdDBaseActor.remoteActorCall({
actorId: achat.vendeurId ?? achat.acheteurId,
method: 'achatVente',
@ -370,8 +386,8 @@ export class RdDBaseActor extends Actor {
chatAchatItem.quantiteTotal = quantite;
ChatMessage.create({
user: achat.userId,
speaker: { alias: (acheteur ?? vendeur).name },
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
speaker: { alias: (acheteur ?? vendeur).getAlias() },
whisper: ChatUtility.getOwners(this),
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-achat-item.html', chatAchatItem)
});
@ -686,7 +702,7 @@ export class RdDBaseActor extends Actor {
type: this.type,
img: this.img,
pack: this.pack,
name: this.name,
name: this.getAlias(),
system: { description: this.system.description }
}
renderTemplate('systems/foundryvtt-reve-de-dragon/templates/post-actor.html', chatData)
@ -694,10 +710,10 @@ export class RdDBaseActor extends Actor {
}
actionImpossible(action) {
ui.notifications.info(`${this.name} ne peut pas faire cette action: ${action}`)
ui.notifications.info(`${this.getAlias()} ne peut pas faire cette action: ${action}`)
}
async roll() { this.actionImpossible("jet de caractéristiques") }
async jetEthylisme() { this.actionImpossible("jet d'éthylisme") }
async rollAppelChance() { this.actionImpossible("appel à la chance") }
async jetDeMoral() { this.actionImpossible("jet de moral") }
@ -708,5 +724,24 @@ export class RdDBaseActor extends Actor {
}
return undefined
}
async resetItemUse() { }
async incDecItemUse(itemId, inc = 1) { }
getItemUse(itemId) { return 0; }
async finDeRound(options = { terminer: false }) { }
isActorCombat() { return false }
getCaracInit(competence) { return 0 }
listActionsCombat() { return [] }
listActionsPossessions() {
return this.itemTypes[ITEM_TYPES.possession]
.map(p => {
return {
name: p.name,
action: 'possession',
system: {
competence: p.name,
possessionid: p.system.possessionid,
}
}
})
}
}

View File

@ -73,7 +73,7 @@ export class RdDCommerceSheet extends RdDBaseActorSheet {
}
const disponible = this.actor.getQuantiteDisponible(item)
if (disponible == 0) {
ui.notifications.warn(`${this.name} n'a plus de ${item.name} en vente`);
ui.notifications.warn(`${this.getAlias()} n'a plus de ${item.name} en vente`);
return;
}

View File

@ -74,7 +74,7 @@ export class RdDEntite extends RdDBaseActorReve {
return [STATUSES.StatusComma].includes(effectId);
}
async onAppliquerJetEncaissement(encaissement, attacker) {
async onAppliquerJetEncaissement(encaissement, attackerToken) {
const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance);
foundry.utils.mergeObject(encaissement, {
resteEndurance: perteEndurance.newValue,

View File

@ -9,16 +9,19 @@ export class RdDActorExportSheet extends RdDActorSheet {
static async init() {
await loadTemplates([
"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/arme.hbs",
"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/blessure.hbs",
"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/blessures.hbs",
"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/carac.hbs",
"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/carac-compteur.hbs",
"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/carac-derivee.hbs",
"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/carac-derivee-compteur.hbs",
"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/competences.hbs",
"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/esquive.hbs",
"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/fatigue.hbs",
"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/protection.hbs",
"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/sort.hbs",
])
Actors.registerSheet(SYSTEM_RDD, RdDActorExportSheet, { types: ["personnage"], makeDefault: false, label: "Feuille d'encart" })
Actors.registerSheet(SYSTEM_RDD, RdDActorExportSheet, { types: ["personnage"], makeDefault: false, label: "Feuille simplifiée" })
}
static get defaultOptions() {
return foundry.utils.mergeObject(RdDActorSheet.defaultOptions, {
@ -36,23 +39,36 @@ export class RdDActorExportSheet extends RdDActorSheet {
async getData() {
const formData = await super.getData()
// Add any structured, precomputed list of data
formData.export = this.getMappingValues();
formData.context = Mapping.prepareContext(this.actor)
formData.export = this.getMappingValues(formData.context, this.actor)
formData.competences = this.getCompetences(CATEGORIES_COMPETENCES)
formData.draconic = this.getCompetences(CATEGORIES_DRACONIC)
const legeres = this.actor.nbBlessuresLegeres()
const graves = this.actor.nbBlessuresGraves()
const critiques = this.actor.nbBlessuresCritiques()
formData.etat = {
surenc: this.actor.computeMalusSurEncombrement(),
fatigue: {
value: this.actor.getFatigueActuelle(),
max: this.actor.getFatigueMax(),
malus: this.actor.malusFatigue()
},
blessures: legeres + graves + critiques,
blessure: [legeres > 0, legeres > 1, legeres > 2, legeres > 3, legeres > 4, graves > 0, graves > 1, critiques > 0],
}
formData.options.exportScriptarium = OptionsAvancees.isUsing(EXPORT_CSV_SCRIPTARIUM)
return formData
}
getMappingValues() {
const context = Mapping.prepareContext(this.actor)
getMappingValues(context, actor) {
return Object.fromEntries(Mapping.getMapping().map(it => [it.column, {
colName: it.colName ?? it.column,
column: it.column,
rollClass: it.rollClass,
value: it.getter(this.actor, context)
value: String(it.getter(actor, context))
}]))
}
getCompetences(categories) {
const competences = Mapping.getCompetencesCategorie(this.actor, categories)
if (competences.length == 0) {
@ -68,7 +84,7 @@ export class RdDActorExportSheet extends RdDActorSheet {
}
const listCategorieByNiveau = niveaux.map(niveau => {
const list = byNiveau[niveau].sort(Misc.ascending(it => it.name))
return {niveau, list}
return { niveau, list }
})
return Misc.concat(listCategorieByNiveau)
}).filter(it => it != undefined)
@ -78,9 +94,20 @@ export class RdDActorExportSheet extends RdDActorSheet {
activateListeners(html) {
super.activateListeners(html);
this.html.find('.click-blessure-remove').click(async event =>
await this.actor.supprimerBlessure({
gravite: this.html.find(event.currentTarget).data('gravite')
})
)
this.html.find('.click-blessure-add').click(async event =>
await this.actor.ajouterBlessure({
gravite: this.html.find(event.currentTarget).data('gravite')
// event.currentTarget.attributes['data-gravite'].value
})
)
this.html.find('.button-export').click(async event => {
ExportScriptarium.INSTANCE.exportActors([this.actor],
ExportScriptarium.INSTANCE.exportActors([this.actor],
`${this.actor.uuid}-${this.actor.name}`
)
})

View File

@ -84,7 +84,7 @@ const MAPPING_BASE = [
{ column: "malus_armure", getter: (actor, context) => Mapping.getMalusArmure(actor, context) },
{ column: "reve_actuel", rollClass: 'roll-reve-actuel', colName: 'Rêve actuel', getter: (actor, context) => actor.system.reve.reve.value },
{ column: "vie_actuel", rollClass: 'jet-vie', getter: (actor, context) => actor.system.sante.vie.value },
{ column: "endurance_actuel", rollClass: 'jet-vie', getter: (actor, context) => actor.system.sante.endurance.value },
{ column: "endurance_actuel", rollClass: 'jet-endurance', getter: (actor, context) => actor.system.sante.endurance.value },
{ column: "esquive", getter: (actor, context) => Mapping.getEsquive(context) },
{ column: "esquive_armure", getter: (actor, context) => Mapping.getEsquiveArmure(context) },
{ column: "competences", getter: (actor, context) => Mapping.getCompetences(actor, CATEGORIES_COMPETENCES) },
@ -140,33 +140,54 @@ export class Mapping {
static prepareArmes(actor) {
const armes = actor.items.filter(it => it.type == ITEM_TYPES.arme)
return armes.map(arme =>
[
arme.system.tir != "" ? Mapping.prepareArme(actor, arme, 'tir') : undefined,
arme.system.lancer = "" ? Mapping.prepareArme(actor, arme, 'lancer') : undefined,
arme.system.unemain ? Mapping.prepareArme(actor, arme, 'unemain') : undefined,
arme.system.deuxmains ? Mapping.prepareArme(actor, arme, 'deuxmains') : undefined,
!(arme.system.unemain || arme.system.deuxmains) ? Mapping.prepareArme(actor, arme, 'competence') : undefined
]
.filter(it => it != undefined)
).reduce((a, b) => a.concat(b), [])
armes.push(RdDItemArme.corpsACorps(actor));
armes.push(RdDItemArme.empoignade(actor));
return armes.map(arme => [
arme.system.unemain ? Mapping.prepareArme(actor, arme, 'unemain') : undefined,
arme.system.deuxmains ? Mapping.prepareArme(actor, arme, 'deuxmains') : undefined,
!(arme.system.unemain || arme.system.deuxmains) ? Mapping.prepareArme(actor, arme, 'competence') : undefined,
arme.system.lancer != "" ? Mapping.prepareArme(actor, arme, 'lancer') : undefined,
arme.system.tir != "" ? Mapping.prepareArme(actor, arme, 'tir') : undefined]
.filter(it => it != undefined))
.reduce((a, b) => a.concat(b), [])
}
static prepareArme(actor, arme, maniement) {
const nameCompArme = RdDItemArme.getCompetenceArme(arme, maniement)
const competence = actor.getCompetence(nameCompArme)
const nameCompetenceArme = RdDItemArme.getCompetenceArme(arme, maniement)
const competence = actor.getCompetence(nameCompetenceArme)
if (RdDItemCompetence.isNiveauBase(competence)) {
return undefined
}
const dmgArme = RdDItemArme.dommagesReels(arme, maniement)
const dommages = dmgArme + RdDBonus.bonusDmg(actor, maniement, dmgArme)
const categorie = Mapping.complementCategorie(arme, maniement)
const dommages = Mapping.dommagesArme(actor, arme, maniement)
return {
name: arme.name,
name: arme.name + categorie,
niveau: Misc.toSignedString(competence.system.niveau),
init: Mapping.calculBaseInit(actor, competence.system.categorie) + competence.system.niveau,
dommages: Misc.toSignedString(dommages)
dommages: dommages,
competence: competence,
arme: arme
}
}
static dommagesArme(actor, arme, maniement){
const dmgArme = RdDItemArme.dommagesReels(arme, maniement)
const dommages = Misc.toSignedString(dmgArme + RdDBonus.bonusDmg(actor, maniement, dmgArme))
switch(arme.system.mortalite) {
case 'non-mortel': return `(${dommages})`
case 'empoignade': return '-'
}
return dommages
}
static complementCategorie(arme, maniement) {
switch (maniement) {
case 'unemain': return (arme.system.deuxmains) ? ' 1 main' : (arme.system.lancer || arme.system.tir) ? ' mêlée' : ''
case 'deuxmains': return (arme.system.unemain) ? ' 2 mains' : (arme.system.lancer || arme.system.tir) ? ' mêlée' : ''
case 'lancer': return (arme.system.unemain || arme.system.deuxmains || arme.system.tir) ? ' jet' : ''
case 'tir': return (arme.system.unemain || arme.system.deuxmains || arme.system.lancer) ? ' tir' : ''
}
return ''
}
static calculBaseInit(actor, categorie) {
const mapping = MAPPING_BASE.find(it => it.column == categorie)
@ -208,7 +229,8 @@ export class Mapping {
const esquive = esquives[0]
return {
name: esquive.name,
niveau: esquive.system.niveau
niveau: esquive.system.niveau,
competence: esquive
}
}
return undefined
@ -216,7 +238,7 @@ export class Mapping {
static prepareSorts(actor) {
const codeVoies = Mapping.getCompetencesCategorie(actor, CATEGORIES_DRACONIC)
.map(it => RdDItemSort.getVoieCode(it))
.map(it => RdDItemSort.getCodeVoie(it.name))
return actor.itemTypes[ITEM_TYPES.sort].map(it => Mapping.prepareSort(it, codeVoies))
.sort(Misc.ascending(it => `${it.voie} : ${it.description}`))
@ -224,7 +246,7 @@ export class Mapping {
static prepareSort(sort, voies) {
return {
voie: RdDItemSort.getCodeDraconic(sort, voies),
voie: RdDItemSort.getCode(sort, voies),
description: Mapping.descriptionSort(sort),
bonus: Mapping.bonusCase(sort)
}
@ -233,21 +255,13 @@ export class Mapping {
static descriptionSort(sort) {
const ptSeuil = Array(sort.system.coutseuil).map(it => '*')
const caseTMR = sort.system.caseTMRspeciale.length > 0 ? Mapping.toVar(sort.system.caseTMRspeciale) : Misc.upperFirst(TMRType[sort.system.caseTMR].name)
const ptreve = Mapping.addSpaceToNonNumeric(sort.system.ptreve)
const diff = Mapping.addSpaceToNonNumeric(sort.system.difficulte)
return `${sort.name}${ptSeuil} (${caseTMR}) R${diff} r${ptreve}`
}
static addSpaceToNonNumeric(value) {
return Number.isNumeric(value) ? value : ' ' + Mapping.toVar(value)
}
static toVar(value) {
return value.replace('variable', 'var')
const coutReve = 'r' + RdDItemSort.addSpaceToNonNumeric(sort.system.ptreve)
const diff = 'R' + RdDItemSort.addSpaceToNonNumeric(sort.system.difficulte)
return `${sort.name}${ptSeuil} (${caseTMR}) ${diff} ${coutReve}`
}
static bonusCase(sort) {
const list = RdDItemSort.bonuscaseStringToList(sort.system.bonuscase).sort(Misc.descending(it => it.bonus))
const list = RdDItemSort.stringToBonuscases(sort.system.bonuscase).sort(Misc.descending(it => it.bonus))
if (list.length > 0) {
const bonus = list[0]
return `+${bonus.bonus}% en ${bonus.case}`
@ -313,7 +327,8 @@ export class Mapping {
const txtByCategories = Object.values(byCategories)
.map(it => it.competencesParNiveau)
.map(byNiveau => {
const niveaux = Object.keys(byNiveau).map(it => Number(it)).sort(Misc.ascending())
const niveaux = Object.keys(byNiveau)
.map(it => Number(it)).sort(Misc.ascending())
if (niveaux.length == 0) {
return ''
}

View File

@ -19,7 +19,7 @@ const PATHS = [
const RANDOM_VALUES = {
'system.sexe': { 'masculin': 1, 'féminin': 1 },
'system.main': { 'droitier': 51, 'gaucher': 15, 'ambidectre': 6 },
'system.main': { 'droitier': 51, 'gaucher': 15, 'ambidextre': 6 },
'system.cheveux': { 'noirs': 2, 'bruns': 5, 'châtains clair': 5, 'blonds': 4, 'blonds très clair': 1, 'roux carotte': 1, 'roux cuivré': 3 },
'system.yeux': { 'noirs': 2, 'noisettes': 3, 'bruns vert': 4, 'verts': 3, 'bleus clair': 3, 'bleus gris': 2, 'gris': 1, 'mauves': 1, 'indigos': 1 },
}

View File

@ -0,0 +1,564 @@
/************************************************************************************/
import "./xregexp-all.js";
import { SystemCompendiums } from "../settings/system-compendiums.js";
import { RdDBaseActorReve } from "../actor/base-actor-reve.js";
import { Grammar } from "../grammar.js";
import { Misc } from "../misc.js";
import { ENTITE_INCARNE, ENTITE_NONINCARNE } from "../constants.js";
const WHITESPACES = "\\s+"
const NUMERIC = "[\\+\\-]?\\d+"
const NUMERIC_VALUE = "(?<value>" + NUMERIC + ")"
const XREGEXP_NAME = "(?<name>[A-Za-zÀ-ÖØ-öø-ÿ\\s\\-]+)"
const XREGEXP_COMP_CREATURE = WHITESPACES + "(?<carac>\\d+)"
+ WHITESPACES + NUMERIC_VALUE
+ "(" + WHITESPACES + "(?<init>\\d+)?\\s+?(?<dommages>[\\+\\-]?\\d+)?" + ")?"
// Skill parser depending on the type of actor
const compParser = {
personnage: "(\\D+)*" + WHITESPACES + NUMERIC_VALUE,
creature: XREGEXP_COMP_CREATURE,
entite: XREGEXP_COMP_CREATURE
}
const XREGEXP_SORT_VOIE = "[OHNT\\/]+"
const XREGEXP_SORT_CASE = "\\((?<case>[A-Za-zÀ-ÖØ-öø-ÿ\\s\\-]+)\\)";
const XREGEXP_SORT = "(" + XREGEXP_SORT_VOIE
+ WHITESPACES + XREGEXP_NAME
+ WHITESPACES + XREGEXP_SORT_CASE
+ WHITESPACES + "R(?<diff>([\\-\\d]+|(\\w|\\s)+))"
+ WHITESPACES + "r(?<reve>(\\d+(\\+)?|\\s\\w+))"
+ "(" + WHITESPACES + "\\+(?<bonus>\\d+)\\s?%" + WHITESPACES + "en" + WHITESPACES + "(?<bonuscase>[A-M]\\d{1,2})" + ")?"
+ ")"
// Main class for parsing a stat block
export class RdDStatBlockParser {
static openInputDialog() {
let dialog = new Dialog({
title: "Import de stats de PNJ/Créatures",
content: `
<div>
<p>Coller le texte de la stat ici</p>
<textarea id="statBlock" style="width: 100%; height: 200px;"></textarea>
</div>
`,
buttons: {
ok: {
label: "OK",
callback: async (html) => {
let statBlock = html.find("#statBlock")[0].value;
await RdDStatBlockParser.parseStatBlock(statBlock);
dialog.close();
}
},
cancel: {
label: "Cancel"
}
}
});
dialog.render(true);
}
static fixWeirdPDF(statString) {
// Split the statString into lines
let lines = statString.split("\n");
let newLines = [];
let index = 0;
let nextType = "string";
// Loop through each line
for (let i = 0; i < lines.length; i++) {
// remove trailing spaces
lines[i] = lines[i].trim();
// Is it text ?
if (lines[i].match(/^[a-zA-Zéêè\s]+/)) {
if (nextType == "string") {
newLines[index] = lines[i];
nextType = "number";
} else {
console.log("Wrong sequence string detected...", lines[i], nextType);
}
}
// Is it a number ?
if (lines[i].match(/^[\d\s]+/)) {
if (nextType == "number") {
newLines[index] = newLines[index] + lines[i];
nextType = "string";
index++;
} else {
console.log("Wrong sequence number detected...", lines[i], nextType);
}
}
}
}
static getHeureKey(heure) {
for (let h of game.system.rdd.config.heuresRdD) {
if (h.label.toLowerCase() == heure.toLowerCase()) {
return h.value;
}
}
return "vaisseau";
}
static async parseStatBlock(statString, type = "npc") {
//statString = statBlock03;
if (!statString) {
return;
}
// Special function to fix strange/weird copy/paste from PDF readers
// Unused up to now : this.fixWeirdPDF(statString);
// Replace all endline by space in the statString
statString = statString.replace(/\n/g, " ");
// Remove all multiple spaces
statString = statString.replace(/\s{2,}/g, " ");
// Remove all leading and trailing spaces
statString = statString.trim();
// TODO: check for entite
let actorType = RdDStatBlockParser.parseActorType(statString);
// Now start carac
let actorData = foundry.utils.deepClone(game.model.Actor[actorType]);
for (let key in actorData.carac) {
let caracDef = actorData.carac[key];
// Parse the stat string for each caracteristic
let carac = XRegExp.exec(statString, XRegExp(caracDef.label + "\\s+(?<value>\\d+)", 'giu'));
if (carac?.value) {
actorData.carac[key].value = Number(carac.value);
}
}
// If creature we need to setup additionnal fields
switch (actorType) {
case "creature":
RdDStatBlockParser.parseCreature(statString, actorData)
break
case "entite":
RdDStatBlockParser.parseEntite(statString, actorData)
break
}
let items = [];
// Get skills from compendium
const competences = await SystemCompendiums.getCompetences(actorType);
//console.log("Competences : ", competences);
for (let comp of competences) {
let compMatch = XRegExp.exec(statString, XRegExp(comp.name + compParser[actorType], 'giu'));
if (compMatch) {
comp = comp.toObject()
comp.system.niveau = Number(compMatch.value);
if (actorType == "creature" || actorType == "entite") {
comp.system.carac_value = Number(compMatch.carac);
if (compMatch.dommages != undefined) {
comp.system.dommages = Number(compMatch.dommages);
comp.system.iscombat = true;
}
}
items.push(comp)
}
else if (actorType == "personnage") {
comp = comp.toObject()
items.push(comp)
}
}
// Now process weapons
const weapons = await SystemCompendiums.getWorldOrCompendiumItems("arme", "equipement")
//console.log("Equipement : ", equipment);
// TODO: les noms d'armes peuvent avoir un suffixe (à une main, lancée) qui détermine la compétence correspondante
// TODO: une arme peut être spécifique ("fourche"), ajouter une compétence dans ces cas là?
for (let weapon of weapons) {
let weapMatch = XRegExp.exec(statString, XRegExp(weapon.name + "\\s+(?<value>\\+\\d+)", 'giu'));
if (weapMatch) {
weapon = weapon.toObject()
weapon.system.equipe = 'true'
items.push(weapon)
// now process the skill
if (weapon.system?.competence != "") {
let wComp = items.find(i => Grammar.equalsInsensitive(i.name, weapon.system.competence))
if (wComp) {
wComp.system.niveau = Number(weapMatch.value);
}
}
if (weapon.system?.tir != "") {
let wComp = items.find(i => Grammar.equalsInsensitive(i.name, weapon.system.tir))
if (wComp) {
wComp.system.niveau = Number(weapMatch.value);
}
}
if (weapon.system?.lancer != "") {
let wComp = items.find(i => Grammar.equalsInsensitive(i.name, weapon.system.lancer))
if (wComp) {
wComp.system.niveau = Number(weapMatch.value);
}
}
}
}
// Now process armors
const armors = await SystemCompendiums.getWorldOrCompendiumItems("armure", "equipement")
for (let armor of armors) {
let matchArmor = XRegExp.exec(statString, XRegExp(armor.name, 'giu'));
if (matchArmor) {
armor = armor.toObject()
armor.system.equipe = true
items.push(armor);
}
}
// Attemp to detect spell
let hautRevant = false
let sorts = await SystemCompendiums.getWorldOrCompendiumItems("sort", "sorts-oniros")
sorts = sorts.concat(await SystemCompendiums.getWorldOrCompendiumItems("sort", "sorts-hypnos"))
sorts = sorts.concat(await SystemCompendiums.getWorldOrCompendiumItems("sort", "sorts-narcos"))
sorts = sorts.concat(await SystemCompendiums.getWorldOrCompendiumItems("sort", "sorts-thanatos"))
XRegExp.forEach(statString, XRegExp(XREGEXP_SORT, 'giu'),
function (matchSort, i) {
let sort = sorts.find(s => Grammar.equalsInsensitive(s.name, matchSort.name))
if (sort) {
hautRevant = true
sort = sort.toObject();
if (matchSort.bonus && matchSort.bonuscase) {
sort.system.bonuscase = `${matchSort.bonuscase}:${matchSort.bonus}`
}
items.push(sort);
}
});
if (hautRevant) {
let tetes = await SystemCompendiums.getWorldOrCompendiumItems("tete", "tetes-de-dragon-pour-tous-personnages")
let donHR = tetes.find(t => Grammar.equalsInsensitive(t.name, "Don de Haut-Rêve"))
if (donHR) {
items.push(donHR.toObject());
}
}
if (actorType == "personnage") {
let feminin = XRegExp.exec(statString, XRegExp("né(?<value>e?) à", 'giu'));
actorData.sexe = (feminin?.value == 'e') ? 'féminin' : 'masculin'
// Get hour name : heure du XXXXX
let heure = XRegExp.exec(statString, XRegExp("heure (du|de la|des|de l\')\\s*(?<value>[A-Za-zÀ-ÖØ-öø-ÿ\\s]+),", 'giu'));
actorData.heure = this.getHeureKey(heure?.value || "Vaisseau");
// Get age
let age = XRegExp.exec(statString, XRegExp("(?<value>\\d+) ans", 'giu'));
if (age?.value) {
actorData.age = Number(age.value);
}
// Get height
let taille = XRegExp.exec(statString, XRegExp("(?<value>\\d+m\\d+)", 'giu'));
if (taille?.value) {
actorData.taille = taille.value;
}
// Get weight
let poids = XRegExp.exec(statString, XRegExp("(?<value>\\d+) kg", 'giu'));
if (poids?.value) {
actorData.poids = poids.value;
}
// Get cheveux
let cheveux = XRegExp.exec(statString, XRegExp("kg,\\s+(?<value>[A-Za-zÀ-ÖØ-öø-ÿ\\s\\-]+),\\s+yeux", 'giu'));
if (cheveux?.value) {
actorData.cheveux = cheveux.value;
}
// Get yeux
let yeux = XRegExp.exec(statString, XRegExp("yeux\\s+(?<value>[A-Za-zÀ-ÖØ-öø-ÿ\\s\\-]+), Beau", 'giu'));
if (yeux?.value) {
actorData.yeux = yeux.value;
}
// Get beauty
let beaute = XRegExp.exec(statString, XRegExp("beauté\\s+(?<value>\\d+)", 'giu'));
if (beaute?.value) {
actorData.beaute = Number(beaute.value);
}
}
// Name is all string before ', né'
let name = RdDStatBlockParser.extractName(actorType, statString);
let newActor = RdDBaseActorReve.create({ name: name || "Importé", type: actorType, system: actorData, items: items });
// DUmp....
console.log(actorData);
}
static parseCreature(statString, actorData) {
let plusDom = XRegExp.exec(statString, XRegExp("\\+dom\\s+(?<value>[\\+\\-]?\\d+)", 'giu'));
if (plusDom?.values) {
actorData.attributs.plusdom.value = Number(plusDom.value);
}
let protection = XRegExp.exec(statString, XRegExp("protection\\s+(?<value>[\\-]?\\d+)", 'giu'));
if (protection?.value) {
actorData.attributs.protection.value = Number(protection.value);
}
let endurance = XRegExp.exec(statString, XRegExp("endurance\\s+(?<value>\\d+)", 'giu'));
if (endurance?.value) {
actorData.sante.endurance.value = Number(endurance.value);
actorData.sante.endurance.max = Number(endurance.value);
}
let vie = XRegExp.exec(statString, XRegExp("vie\\s+(?<value>\\d+)", 'giu'));
if (vie.value) {
actorData.sante.vie.value = Number(vie.value);
actorData.sante.vie.max = Number(vie.value);
}
let vitesse = XRegExp.exec(statString, XRegExp("vitesse\\s+(?<value>[\\d\\/]+)", 'giu'));
if (vitesse?.value) {
actorData.attributs.vitesse.value = vitesse.value;
}
}
static parseEntite(statString, actorData) {
let plusDom = XRegExp.exec(statString, XRegExp("\\+dom\\s+(?<value>[\\+\\-]?\\d+)", 'giu'));
if (plusDom?.values) {
actorData.attributs.plusdom.value = Number(plusDom.value);
}
actorData.definition.categorieentite = 'cauchemar'
actorData.definition.typeentite = ENTITE_NONINCARNE
let endurance = XRegExp.exec(statString, XRegExp("endurance\\s+(?<value>\\d+)", 'giu'));
if (endurance?.value) {
actorData.sante.endurance.value = Number(endurance.value);
actorData.sante.endurance.max = Number(endurance.value);
actorData.definition.typeentite = ENTITE_INCARNE
}
let vitesse = XRegExp.exec(statString, XRegExp("vitesse\\s+(?<value>[\\d\\/]+)", 'giu'));
if (vitesse?.value) {
actorData.attributs.vitesse.value = vitesse.value;
}
}
static parseActorType(statString) {
let niveau = XRegExp.exec(statString, XRegExp("Niveau\\s+(?<value>[\\+\\-]?\\d+)", 'giu'))
let perception = XRegExp.exec(statString, XRegExp("perception\\s+(?<value>\\d+)", 'giu'))
if (perception?.value) {
return "creature"
}
if (niveau?.value) {
return "entite"
}
return "personnage"
}
static extractName(actorType, statString) {
switch (actorType) {
case "personnage": return RdDStatBlockParser.extractNamePersonnage(statString);
case "creature": return RdDStatBlockParser.extractNameCreature(statString);
}
return RdDStatBlockParser.extractNameCreature(statString);
}
static extractNamePersonnage(statString) {
let name = XRegExp.exec(statString, XRegExp("(?<value>[\\p{Letter}\\s\\d]+),", 'giu'));
if (!name?.value) {
name = XRegExp.exec(statString, XRegExp("(?<value>.+)\\s+taille", 'giu'));
}
return Misc.upperFirst(name?.value || "Importé");
}
static extractNameCreature(statString) {
const name = XRegExp.exec(statString, XRegExp("(?<value>.+)\\s+taille", 'giu'));
return Misc.upperFirst(name?.value || "Importé");
}
}
/************************************************************************************/
// Some internal test strings
let statBlock01 = `+$16(/, baron de Sylvedire, né à lheure du
Roseau, 40 ans, 1m78, 65 kg, Beauté 13.
TAILLE
10
Mêlée
14
APPARENCE
13
Tir
11
CONSTITUTION
12
Lancer
11
FORCE
12
Dérobée
13
AGILITÉ
16
Vie
11
DEXTÉRITÉ
13
Endurance
25
VUE
10
+dom
0
OUÏE
11
Protection
2 ou 4
ODO-GOÛT
9
cuir souple
VOLONTÉ
14
ou cuir / métal
INTELLECT
9
EMPATHIE
11
RÊVE
13
CHANCE
10
niv
init
+dom
Épée dragonne
+5
12
+3
Hache de bataille
+6
13
+3
Bouclier moyen
+5
Dague mêlée
+4
11
+1
Corps à corps
+4
11
(0)
Esquive
+8
Escalade, Saut +4 / Commerce +3 / Équitation
+6 / Chirurgie 0 / Survie en extérieur +4 / Survie fo-
rêt +6 / Acrobatie -2 / Métallurgie +2 / Natation +3 /
Légendes -1 / Écriture -4
`;
let statBlock02 = `/HVJDUGHV
TAILLE
11
Mêlée
12
CONSTITUTION
11
Tir
11
FORCE
12
Lancer
11
AGILITÉ
12
Dérobée
11
DEXTERITÉ
11
Vie
11
VUE
11
Endurance
22
OUÏE
11
Vitesse
12
VOLONTÉ
10
+dom
0
Protection
4
cuir / métal
niv
init
+dom
Hache de bataille
+4
10
+3
Bouclier moyen
+4
Dague mêlée
+3
9
+1
Arc
+5
10
+2
Corps à corps
+3
9
(0)
Esquive avec armure
+2
Course +1/ Vigilance +4
`;
let statBlock03 = `rencontres sont laissées à /HVFKLHQVORXSVGXEDURQ
chaque gardien des rêves.
TAILLE
8
Vie
10
CONSTITUTION FORCE
12
11
Endurance
Vitesse
12/38
21
/HVFKLHQV]RPELV
PERCEPTION 13
+dom
0
VOLONTÉ
10
Protection
0
Les « monstres » apparaîtront un soir, durant
RÊVE
10
lheure du Serpent, et attaqueront les voya-
niv
init
+dom
geurs à leur campement. Si ces derniers ne
Morsure
13
+4
10
+1
campent pas, ils apparaîtront tout de même à
Esquive
11
+3
lheure du Serpent. Le feu ne les effraie pas. Ils
Course, Saut
12
+3
ne sont pas très rapides, mais en revanche, très
Discrétion
12
+3
silencieux : ils naboient pas. Les voyageurs
Vigilance
13
+3
`

8225
module/apps/xregexp-all.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,15 +8,20 @@ import { RdDTimestamp } from "./time/rdd-timestamp.js";
*/
export class ChatUtility {
static async init() {
Hooks.on("renderChatMessage", async (app, html, msg) => await ChatUtility.onRenderChatMessage(app, html, msg))
Hooks.on("createChatMessage", async (chatMessage, options, id) => await ChatUtility.onCreateChatMessage(chatMessage, options, id))
}
/* -------------------------------------------- */
static onSocketMessage(sockmsg) {
switch (sockmsg.msg) {
case "msg_delete_chat_message": return ChatUtility.onRemoveMessages(sockmsg.data);
case "msg_user_ui_notifications": return ChatUtility.onNotifyUser(sockmsg.data);
case "msg_gm_chat_message": return ChatUtility.handleGMChatMessage(sockmsg.data)
case "msg_delete_chat_message": return ChatUtility.onRemoveMessages(sockmsg.data)
case "msg_user_ui_notifications": return ChatUtility.onNotifyUser(sockmsg.data)
}
}
/* -------------------------------------------- */
static notifyUser(userId, level = 'info', message) {
const socketData = {
@ -44,7 +49,7 @@ export class ChatUtility {
/* -------------------------------------------- */
static onRemoveMessages(socketData) {
if (Misc.isUniqueConnectedGM()) {
if (Misc.isFirstConnectedGM()) {
if (socketData.part) {
const toDelete = game.messages.filter(it => it.content.includes(socketData.part));
toDelete.forEach(it => it.delete());
@ -58,7 +63,7 @@ export class ChatUtility {
/* -------------------------------------------- */
static removeMessages(socketData) {
if (Misc.isUniqueConnectedGM()) {
if (Misc.isFirstConnectedGM()) {
ChatUtility.onRemoveMessages(socketData);
}
else {
@ -78,73 +83,90 @@ export class ChatUtility {
}
/* -------------------------------------------- */
static async createChatWithRollMode(name, chatOptions) {
let rollMode = game.settings.get("core", "rollMode")
switch (rollMode) {
static async createChatWithRollMode(messageData, actor = undefined) {
switch (game.settings.get("core", "rollMode")) {
case "blindroll": // GM only
if (!game.user.isGM) {
ChatUtility.blindMessageToGM(chatOptions);
chatOptions.whisper = [game.user.id];
chatOptions.content = "Message envoyé en aveugle au Gardien";
ChatUtility.blindMessageToGM(messageData)
messageData.whisper = [game.user];
messageData.content = "Message envoyé en aveugle au Gardien"
}
else {
chatOptions.whisper = ChatUtility.getUsers(user => user.isGM);
messageData.whisper = ChatUtility.getGMs()
}
break;
default:
chatOptions.whisper = ChatUtility.getWhisperRecipients(rollMode, name);
break;
break
case "gmroll":
messageData.whisper = ChatUtility.getOwners(actor)
break
case "selfroll":
messageData.whisper = [game.user]
break
}
chatOptions.alias = chatOptions.alias || name;
return await ChatMessage.create(chatOptions);
messageData.alias = messageData.alias ?? actor?.name ?? game.user.name
return await ChatMessage.create(messageData)
}
static getOwners(document) {
return game.users.filter(it => document.getUserLevel(it) == CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)
}
static getUserAndGMs() {
return [game.user, ...ChatUtility.getGMs()]
}
/* -------------------------------------------- */
static prepareChatMessage(rollMode, name) {
return {
user: game.user.id,
whisper: ChatUtility.getWhisperRecipients(rollMode, name)
}
}
/* -------------------------------------------- */
static getWhisperRecipients(rollMode, name) {
switch (rollMode) {
case "blindroll": return ChatUtility.getUsers(user => user.isGM);
case "gmroll": return ChatUtility.getWhisperRecipientsAndGMs(name);
case "selfroll": return [game.user.id];
}
return undefined;
}
/* -------------------------------------------- */
static getWhisperRecipientsAndGMs(...names) {
let recipients = [...ChatMessage.getWhisperRecipients('GM')]
names.forEach(name => recipients.push(...ChatMessage.getWhisperRecipients(name)))
return recipients
static getMultipleActorsOwners(...actors) {
return Misc.concat(actors.map(it => it == undefined ? [] : ChatUtility.getOwners(it)))
}
/* -------------------------------------------- */
static getUsers(filter) {
return game.users.filter(filter).map(user => user.id);
return game.users.filter(filter)
}
static getGMs() {
return game.users.filter(user => user.isGM)
}
static applyRollMode(chatMessageData = {}, rollMode = game.settings.get("core", "rollMode")) {
switch (rollMode) {
case "blindroll":
chatMessageData.blind = true
chatMessageData.whisper = ChatUtility.getGMs()
break
case "gmroll":
chatMessageData.whisper = ChatUtility.getGMs()
chatMessageData.blind = false
break
case "roll":
chatMessageData.whisper = ChatUtility.getUsers(user => user.active)
chatMessageData.blind = false
break
case "selfroll":
chatMessageData.whisper = [game.user]
chatMessageData.blind = false
break
}
return chatMessageData
}
/* -------------------------------------------- */
static blindMessageToGM(chatOptions) {
let chatGM = foundry.utils.duplicate(chatOptions);
chatGM.whisper = ChatUtility.getUsers(user => user.isGM);
chatGM.content = "Message aveugle de " + game.user.name + "<br>" + chatOptions.content;
console.log("blindMessageToGM", chatGM);
game.socket.emit(SYSTEM_SOCKET_ID, { msg: "msg_gm_chat_message", data: chatGM });
const chatGM = foundry.utils.duplicate(chatOptions)
chatGM.content = "Message aveugle de " + game.user.name + "<br>" + chatOptions.content
console.log("blindMessageToGM", chatGM)
game.socket.emit(SYSTEM_SOCKET_ID, { msg: "msg_gm_chat_message", data: chatGM })
}
/* -------------------------------------------- */
static handleGMChatMessage(socketData) {
console.log("blindMessageToGM", socketData);
if (game.user.isGM) { // message privé pour GM only
socketData.user = game.user.id;
ChatMessage.create(socketData);
if (Misc.isFirstConnectedGM()) {
ChatMessage.create({
user: game.user.id,
whisper: ChatUtility.getGMs(),
content: socketData.content
})
}
}

View File

@ -30,7 +30,8 @@ export class RdDCoeur {
}
static extractInfoCoeur(event) {
return ChatUtility.getMessageData(ChatUtility.getChatMessage(event), INFO_COEUR)
const chatMesage = ChatUtility.getChatMessage(event);
return ChatUtility.getMessageData(chatMesage, INFO_COEUR)
}
static getInfoCoeur(sourceActorId, targetActorId) {
@ -98,12 +99,11 @@ export class RdDCoeur {
static async startSubActeurTendreMoment(actorId, subActeurId) {
const infoCoeur = RdDCoeur.getInfoCoeur(actorId, subActeurId)
if (infoCoeur.target?.actor.id) {
if (infoCoeur.target?.actor?.id) {
// TODO: passer par une fenêtre pour saisir sa proposition (lieu, heure, ...)
const chatHtml = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/coeur/chat-proposer-tendre-moment.hbs`, infoCoeur)
const chatMessage = await ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(infoCoeur.target?.actor.name),
content: chatHtml
whisper: ChatUtility.getOwners(infoCoeur.target.actor),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/coeur/chat-proposer-tendre-moment.hbs`, infoCoeur)
})
RdDCoeur.addTagsInfoCoeur(infoCoeur, chatMessage)
}
@ -127,7 +127,7 @@ export class RdDCoeur {
}
const chatHtml = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/coeur/chat-accepter-tendre-moment.hbs`, infoCoeur)
const chatMessage = await ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(infoCoeur.source?.actor.name, infoCoeur.target?.actor.name),
whisper: ChatUtility.getMultipleActorsOwners(infoCoeur.source?.actor, infoCoeur.target?.actor),
content: chatHtml
})
RdDCoeur.addTagsInfoCoeur(infoCoeur, chatMessage)
@ -142,7 +142,7 @@ export class RdDCoeur {
ChatUtility.removeChatMessageId(infoCoeur.chatMessageId)
const chatHtml = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/coeur/chat-refuser-tendre-moment.hbs`, infoCoeur)
await ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(infoCoeur.source?.actor.name, infoCoeur.target?.actor.name),
whisper: ChatUtility.getMultipleActorsOwners(infoCoeur.source?.actor, infoCoeur.target?.actor),
content: chatHtml
});
}

View File

@ -8,7 +8,7 @@ const LATEST_USED_JOURNAL_ID = "chronologie-dernier-journal";
export class DialogChronologie extends Dialog {
static init() {
static initSettings() {
game.settings.register(SYSTEM_RDD, LATEST_USED_JOURNAL_ID, {
name: "Dernier article de journal utilisé pour enregistrer la chronologie",
scope: "client",

View File

@ -48,10 +48,10 @@ export class DialogCreateSigneDraconique extends Dialog {
async _createSigneForActor(actor, signe) {
actor.createEmbeddedDocuments("Item", [signe]);
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(actor.name),
whisper: ChatUtility.getOwners(actor),
content: await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/chat-signe-draconique-actor.html", {
signe: signe,
alias: actor.name
alias: actor.getAlias()
})
});
}

View File

@ -8,14 +8,13 @@ import { RdDUtility } from "./rdd-utility.js";
export class DialogValidationEncaissement extends Dialog {
static async validerEncaissement(actor, rollData, armure, onEncaisser) {
let encaissement = await RdDUtility.jetEncaissement(rollData, armure, { showDice: HIDE_DICE });
const encaissement = await RdDUtility.jetEncaissement(actor, rollData, armure, { showDice: HIDE_DICE });
const html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-validation-encaissement.html', {
actor: actor,
rollData: rollData,
encaissement: encaissement
});
const dialog = new DialogValidationEncaissement(html, actor, rollData, armure, encaissement, onEncaisser);
dialog.render(true);
new DialogValidationEncaissement(html, actor, rollData, armure, encaissement, onEncaisser).render(true);
}
/* -------------------------------------------- */
@ -56,14 +55,14 @@ export class DialogValidationEncaissement extends Dialog {
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});
this.encaissement = await RdDUtility.jetEncaissement(this.actor, this.rollData, this.armure, { showDice: HIDE_DICE, forceDiceResult: this.forceDiceResult});
this.html.find('label.encaissement-total').text(this.encaissement.total);
this.html.find('label.encaissement-blessure').text(this.encaissement.blessures)
});
}
async onValider() {
this.encaissement = await RdDUtility.jetEncaissement(this.rollData, this.armure, { showDice: SHOW_DICE, forceDiceResult: this.forceDiceResult});
this.encaissement = await RdDUtility.jetEncaissement(this.actor, this.rollData, this.armure, { showDice: SHOW_DICE, forceDiceResult: this.forceDiceResult});
this.onEncaisser(this.encaissement)
}
}

View File

@ -7,7 +7,7 @@ import { CompendiumTableHelpers, CompendiumTable, SystemCompendiums } from "./se
const COMPENDIUMS_RECHERCHE = 'compendiums-recherche';
export class Environnement {
static init() {
static initSettings() {
game.settings.register(SYSTEM_RDD, COMPENDIUMS_RECHERCHE, {
name: COMPENDIUMS_RECHERCHE,
default: [

View File

@ -24,7 +24,7 @@ export class Grammar {
}
static includesLowerCaseNoAccent(value, content) {
return Grammar.toLowerCaseNoAccent(value).includes(Grammar.toLowerCaseNoAccent(content));
return Grammar.toLowerCaseNoAccent(value)?.includes(Grammar.toLowerCaseNoAccent(content));
}
/* -------------------------------------------- */

View File

@ -1,10 +1,11 @@
import { Grammar } from "./grammar.js";
import { RdDItemCompetenceCreature } from "./item-competencecreature.js"
import { ITEM_TYPES } from "./item.js";
import { RdDCombatManager } from "./rdd-combat.js";
const nomCategorieParade = {
"sans-armes": "Sans arme",
"armes-naturelles": "Sans arme",
"armes-naturelles": "Armes naturelles",
"hast": "Armes d'hast",
"batons": "Bâtons",
"boucliers": "Boucliers",
@ -30,7 +31,7 @@ export class RdDItemArme extends Item {
case ITEM_TYPES.competencecreature:
return RdDItemCompetenceCreature.armeCreature(arme);
}
return RdDItemArme.mainsNues();
return RdDItemArme.corpsACorps();
}
static getCompetenceArme(arme, maniement) {
@ -56,8 +57,8 @@ export class RdDItemArme extends Item {
}
static niveauCompetenceArme(arme, competences) {
const compArme = competences.find(it => it.name == arme.system.competence);
return compArme?.system.niveau ?? -8;
const compArme = competences.find(it => Grammar.equalsInsensitive(it.name, arme.system.competence))
return compArme?.system.niveau ?? -8
}
/* -------------------------------------------- */
@ -81,37 +82,59 @@ export class RdDItemArme extends Item {
/* -------------------------------------------- */
static getCategorieParade(armeData) {
if (armeData.system.categorie_parade) {
return armeData.system.categorie_parade;
return armeData.system.categorie_parade
}
// pour compatibilité avec des personnages existants
if (armeData.type == ITEM_TYPES.competencecreature || armeData.system.categorie == 'creature') {
return armeData.system.categorie_parade || (armeData.system.isparade ? 'armes-naturelles' : '');
return armeData.system.categorie_parade || (armeData.system.isparade ? 'armes-naturelles' : '')
}
if (!armeData.type.match(/arme|competencecreature/)) {
return '';
return ''
}
if (armeData.system.competence == undefined) {
return ITEM_TYPES.competencecreature;
}
let compname = armeData.system.competence.toLowerCase();
if (compname.match(/^(dague de jet|javelot|fouet|arc|arbalête|fronde|hache de jet|fléau)$/)) return '';
if (compname.match(/^(dague de jet|javelot|fouet|arc|arbalête|fronde|hache de jet|fléau)$/)) {
return ''
}
if (compname.match('hache')) return 'haches';
if (compname.match('hast')) return 'hast';
if (compname.match('lance')) return 'lances';
if (compname.match('bouclier')) return 'boucliers';
if (compname.match('masse')) return 'masses';
if (compname.match('hache')) return 'haches'
if (compname.match('hast')) return 'hast'
if (compname.match('lance')) return 'lances'
if (compname.match('bouclier')) return 'boucliers'
if (compname.match('masse')) return 'masses'
if (compname.match('epée') || compname.match('épée')) {
if (armeData.name.toLowerCase().match(/(gnome)/))
return 'epees-courtes';
return 'epees-courtes'
if (armeData.name.toLowerCase().match(/((e|é)pée dragone|esparlongue|demi-dragonne)/))
return 'epees-longues';
return 'epees-lourdes';
return 'epees-longues'
return 'epees-lourdes'
}
if (compname.match('dague')) {
return 'dagues';
return 'dagues'
}
return 'sans-armes'
}
static defenseArmeParade(armeAttaque, armeParade) {
const defCategory = RdDItemArme.getCategorieParade(armeParade)
if (defCategory == 'bouclier') {
return 'norm'
}
if (armeAttaque.system.competence.toLowerCase().match(/(fléau)/)) {
return ''
}
if (armeParade.system.tir) {
return ''
}
const attCategory = RdDItemArme.getCategorieParade(armeAttaque)
switch (attCategory) {
case 'armes-naturelles': case 'sans-armes':
return defCategory == 'sans-armes' ? 'norm' : ''
default:
return RdDItemArme.needParadeSignificative(armeAttaque, armeParade) ? 'sign' : 'norm'
}
return 'sans-armes';
}
/* -------------------------------------------- */
@ -120,8 +143,8 @@ export class RdDItemArme extends Item {
return false;
}
// categories d'armes à la parade (cf. page 115 )
let attCategory = RdDItemArme.getCategorieParade(armeAttaque);
let defCategory = RdDItemArme.getCategorieParade(armeParade);
const attCategory = RdDItemArme.getCategorieParade(armeAttaque)
const defCategory = RdDItemArme.getCategorieParade(armeParade)
// bouclier et mêmes catégorie: peuvent se parer sans difficulté
if (defCategory == 'boucliers') {
return false;
@ -184,7 +207,7 @@ export class RdDItemArme extends Item {
return arme.system.competence.replace(" 1 main", " 2 mains");
}
static isArmeUtilisable(arme) {
static isUtilisable(arme) {
switch (arme.type) {
case ITEM_TYPES.arme: return arme.system.equipe && (arme.system.resistance > 0 || arme.system.portee_courte > 0)
case ITEM_TYPES.competencecreature: return true
@ -192,9 +215,24 @@ export class RdDItemArme extends Item {
return false
}
static ajoutCorpsACorps(armes, actor) {
armes.push(RdDItemArme.mainsNues(actor));
armes.push(RdDItemArme.empoignade(actor));
static isAttaque(arme) {
switch (arme.type) {
case ITEM_TYPES.arme:
return arme.system.equipe && (arme.system.resistance > 0 || arme.system.portee_courte > 0)
case ITEM_TYPES.competencecreature:
return arme.system.iscombat && RdDItemCompetenceCreature.isAttaque(item)
}
return false
}
static isParade(arme) {
switch (arme.type) {
case ITEM_TYPES.arme:
return arme.system.equipe && arme.system.resistance > 0 && true/* TODO: regarder la categorie d'arme?*/
case ITEM_TYPES.competencecreature:
return arme.system.iscombat && RdDItemCompetenceCreature.isParade(arme)
}
return false
}
static corpsACorps(actor) {
@ -215,6 +253,8 @@ export class RdDItemArme extends Item {
mortalite: 'non-mortel',
competence: 'Corps à corps',
resistance: 1,
baseInit: 4,
cac: 'pugilat',
deuxmains: true,
categorie_parade: 'sans-armes'
}
@ -224,8 +264,6 @@ export class RdDItemArme extends Item {
static mainsNues(actor) {
const mainsNues = RdDItemArme.corpsACorps(actor)
mainsNues.name = 'Mains nues'
mainsNues.system.cac = 'pugilat'
mainsNues.system.baseInit = 4
return mainsNues;
}

View File

@ -190,7 +190,7 @@ export class RdDItemCompetence extends Item {
/* -------------------------------------------- */
static isNiveauBase(item) {
return Number(item.system.niveau) == RdDItemCompetence.getNiveauBase(item.system.categorie, item.type);
return item.system.niveau == undefined || Number(item.system.niveau) == RdDItemCompetence.getNiveauBase(item.system.categorie, item.type);
}
/* -------------------------------------------- */

View File

@ -27,40 +27,29 @@ export class RdDItemCompetenceCreature extends Item {
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();
return foundry.utils.mergeObject(arme, {
// cloner pour ne pas modifier la compétence
return foundry.utils.mergeObject(item, {
action: item.isCompetencePossession() ? 'possession' : 'attaque',
system: {
competence: arme.name,
competence: item.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,
dommagesReels: item.system.dommages,
penetration: 0,
force: 0,
rapide: true,
}
}, { inplace: false });
}, { inplace: false, });
}
return undefined;
}
/* -------------------------------------------- */
static isCompetenceAttaque(item) {
if (item.type == ITEM_TYPES.competencecreature) {
switch (item.system.categorie) {
case "melee":
case "tir":
case "lancer":
case "naturelle":
case "possession":
return true
}
}
return undefined
static isAttaque(item) {
return RdDItemCompetenceCreature.getCategorieAttaque(item) != undefined
}
static getCategorieAttaque(item) {
@ -71,12 +60,12 @@ export class RdDItemCompetenceCreature extends Item {
case "lancer":
case "naturelle":
case "possession":
case "parade":
return item.system.categorie
}
}
return undefined
}
static isDommages(item) {
if (item.type == ITEM_TYPES.competencecreature) {
switch (item.system.categorie) {
@ -89,20 +78,12 @@ export class RdDItemCompetenceCreature extends Item {
}
return false
}
static isParade(item) {
if (item.type == ITEM_TYPES.competencecreature) {
switch (item.system.categorie) {
case "melee":
case "naturelle":
case "parade":
return true
}
return item.system.categorie_parade || item.system.isparade
}
return false
}
/* -------------------------------------------- */
static isCompetenceParade(item) {
return item.type == 'competencecreature' && item.system.categorie_parade !== "";
}
}
}

View File

@ -13,6 +13,7 @@ import { Misc } from "./misc.js";
import { RdDTimestamp } from "./time/rdd-timestamp.js";
import { RdDItemCompetenceCreature } from "./item-competencecreature.js";
import { ITEM_TYPES, RdDItem } from "./item.js";
import { FLEUVE_COORD, TMRUtility } from "./tmr-utility.js";
/**
* Extend the basic ItemSheet for RdD specific items
@ -100,55 +101,59 @@ export class RdDItemSheet extends ItemSheet {
descriptionmj: await TextEditor.enrichHTML(this.item.system.descriptionmj, { async: true }),
isComestible: this.item.getUtilisationCuisine(),
options: RdDSheetUtility.mergeDocumentRights(this.options, this.item, this.isEditable),
competences: await SystemCompendiums.getCompetences('personnage'),
categories: RdDItem.getCategories(this.item.type),
}
if (this.item.type == ITEM_TYPES.competencecreature) {
formData.isparade = RdDItemCompetenceCreature.isParade(this.item)
formData.isdommages = RdDItemCompetenceCreature.isDommages(this.item)
}
const competences = await SystemCompendiums.getCompetences('personnage');
formData.categories = RdDItem.getCategories(this.item.type)
if (this.item.type == 'tache' || this.item.type == 'livre' || this.item.type == 'meditation' || this.item.type == 'oeuvre') {
if (this.item.type == ITEM_TYPES.tache ||
this.item.type == ITEM_TYPES.livre ||
this.item.type == ITEM_TYPES.meditation ||
this.item.type == ITEM_TYPES.oeuvre) {
formData.caracList = foundry.utils.duplicate(game.model.Actor.personnage.carac)
formData.caracList["reve-actuel"] = foundry.utils.duplicate(game.model.Actor.personnage.reve.reve)
formData.competences = competences;
}
if (this.item.type == 'arme') {
formData.competences = competences.filter(it => it.isCompetenceArme())
if (this.item.type == ITEM_TYPES.arme) {
formData.competences = formData.competences.filter(it => it.isCompetenceArme())
}
if (['sort', 'sortreserve'].includes(this.item.type)) {
formData.competences = competences.filter(it => RdDItemCompetence.isDraconic(it));
}
if (this.item.type == 'recettecuisine') {
if (this.item.type == ITEM_TYPES.recettecuisine) {
formData.ingredients = await TextEditor.enrichHTML(this.object.system.ingredients, { async: true })
}
if (this.item.type == 'extraitpoetique') {
if (this.item.type == ITEM_TYPES.extraitpoetique) {
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);
if (this.item.type == ITEM_TYPES.recettealchimique) {
RdDAlchimie.processManipulation(this.item, 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 })
}
if (this.item.type == 'gemme') {
if (this.item.type == ITEM_TYPES.gemme) {
formData.gemmeTypeList = RdDGemme.getGemmeTypeOptionList();
RdDGemme.calculDataDerivees(this.item);
RdDGemme.calculDataDerivees(this.item)
}
if (this.item.type == 'potion') {
await RdDHerbes.addPotionFormData(formData);
if (this.item.type == ITEM_TYPES.potion) {
RdDHerbes.calculFormData(formData, this.item)
}
if (formData.options.isOwned && this.item.type == 'herbe' && (formData.system.categorie == 'Soin' || formData.system.categorie == 'Repos')) {
formData.isIngredientPotionBase = true;
if (this.item.type == ITEM_TYPES.herbe) {
if (formData.options.isOwned && ['Soin', 'Repos'].includes(formData.system.categorie)) {
formData.isIngredientPotionBase = true;
}
}
if (this.item.type == 'sortreserve') {
if (this.item.type == ITEM_TYPES.sortreserve) {
const sortId = this.item.system.sortid;
formData.competences = formData.competences.filter(it => RdDItemCompetence.isDraconic(it));
formData.sort = formData.options.isOwned ? this.item.actor.items.get(sortId) : game.items.get(sortId);
}
formData.bonusCaseList = RdDItemSort.getBonusCaseList(formData, true);
if (this.item.type == ITEM_TYPES.sort) {
formData.competences = formData.competences.filter(it => RdDItemCompetence.isDraconic(it));
formData.bonusCaseList = RdDItemSort.getBonusCaseList(this.item);
}
return formData;
}
@ -185,6 +190,9 @@ export class RdDItemSheet extends ItemSheet {
}
}
})
this.html.find('.delete-bonus-case').click((event) => {
this.supprimerBonusCase(event.currentTarget.attributes['data-deleteCoord'].value)
})
this.html.find('.date-enchantement').change((event) => {
const jour = Number(this.html.find('input.date-enchantement[name="enchantement.jour"]').val());
@ -264,16 +272,66 @@ export class RdDItemSheet extends ItemSheet {
}
}
async supprimerBonusCase(deleteCoord){
if (this.item.type == ITEM_TYPES.sort) {
const oldList = RdDItemSort.getBonusCaseList(this.item)
const newList = oldList.filter(it => it.case != deleteCoord);
if (newList.length != oldList.length) {
await this.item.update({
'system.bonuscase': RdDItemSort.bonuscasesToString(newList)
})
}
}
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
if (this.item.type == 'sort') {
// Données de bonus de cases ?
formData['system.bonuscase'] = RdDItemSort.buildBonuscaseFromArrays(formData.bonusValue, formData.caseValue);
switch (this.item.type) {
case ITEM_TYPES.sort:
formData['system.bonuscase'] = RdDItemSort.bonuscasesToString(RdDItemSheet._listCaseTmr(
formData.caseTmrCoord,
formData.caseTmrBonus,
formData.caseTmrAdd
))
break
case ITEM_TYPES.competence:
formData['system.niveau'] = formData['system.niveau'] ?? formData['system.base']
break
}
return this.item.update(formData);
}
/* -------------------------------------------- */
/**
* reconstruit les bonus de cases
* @param {*} caseTmrCoord tableau des coordonées
* @param {*} caseTmrBonus tableau des bonus
* @param {*} caseTmrAdd case à ajouter
* @returns list d'objets {coord, bonus}
*/
static _listCaseTmr(caseTmrCoord, caseTmrBonus, caseTmrAdd) {
const listCaseTmrCoord = caseTmrCoord == undefined ? [] : Array.isArray(caseTmrCoord) ? caseTmrCoord : [caseTmrCoord]
const listCaseTmrBonus = caseTmrBonus == undefined ? [] : Array.isArray(caseTmrBonus) ? caseTmrBonus : [caseTmrBonus]
if (caseTmrAdd != undefined && caseTmrAdd != '' && TMRUtility.verifyTMRCoord(caseTmrAdd) && !listCaseTmrCoord.includes(caseTmrAdd)) {
listCaseTmrCoord.push(TMRUtility.getTMR(caseTmrAdd).coord)
listCaseTmrBonus.push(1)
}
const list = [];
const caseChecked = {};
for (let i = 0; i < listCaseTmrBonus.length && i < listCaseTmrCoord.length; i++) {
const coord = listCaseTmrCoord[i] == FLEUVE_COORD ? FLEUVE_COORD : (listCaseTmrCoord[i]?.toUpperCase() ?? 'A1')
const bonus = listCaseTmrBonus[i] ?? 0
if (TMRUtility.verifyTMRCoord(coord) && bonus >= 0 && !caseChecked[coord]) {
caseChecked[coord] = coord
list.push({ case: coord, bonus: bonus })
}
}
return list
}
/* -------------------------------------------- */
async _onDragStart(event) {
}

View File

@ -2,52 +2,69 @@ import { Grammar } from "./grammar.js";
import { RdDItemCompetence } from "./item-competence.js";
import { ITEM_TYPES } from "./item.js";
import { Misc } from "./misc.js";
import { TMRUtility } from "./tmr-utility.js";
import { FLEUVE_COORD, TMRUtility } from "./tmr-utility.js";
const VOIES_DRACONIC = [
export const VOIES_DRACONIC = [
{ code: 'O', label: "Voie d'Oniros", short: 'Oniros', ordre: 'a' },
{ code: 'H', label: "Voie d'Hypnos", short: 'Hypnos', ordre: 'b' },
{ code: 'N', label: "Voie de Narcos", short: 'Narcos', ordre: 'c' },
{ code: 'T', label: "Voie de Thanatos", short: 'Thanatos', ordre: 'd' },
{ code: 'O/H/N/T', label: "Oniros/Hypnos/Narcos/Thanatos", short: 'Oniros/Hypnos/Narcos/Thanatos', ordre: 'e' },
{ code: 'O/H/N', label: "Oniros/Hypnos/Narcos", short: "Oniros/Hypnos/Narcos", ordre: 'f' }
{ code: 'O/H/N/T', label: "Oniros/Hypnos/Narcos/Thanatos", short: 'O/H/N/T', ordre: 'e' },
{ code: 'O/H/N', label: "Oniros/Hypnos/Narcos", short: "O/H/N", ordre: 'f' }
]
/* -------------------------------------------- */
export class RdDItemSort extends Item {
static preloadHandlebars() {
Handlebars.registerHelper('itemSort-spaceIfText', val => RdDItemSort.addSpaceToNonNumeric(val))
Handlebars.registerHelper('itemSort-codeDraconic', voie => RdDItemSort.getCode(voie))
Handlebars.registerHelper('itemSort-shortDraconic', voie => RdDItemSort.getShortVoie(voie))
}
static getDraconicsSort(draconicList, sort) {
static addSpaceToNonNumeric(value) {
return Number.isNumeric(value) || ['-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].includes( String(value).charAt[0]) ? value : ' ' + RdDItemSort.toVar(value)
}
static toVar(value) {
return value ? value.replace('variable', 'var') : ''
}
static getDraconicsSort(competencesDraconic, sort) {
// se baser sur la voie du sort?
switch (Grammar.toLowerCaseNoAccent(sort.name)) {
case "lecture d'aura":
case "detection d'aura":
return draconicList;
return competencesDraconic;
case "annulation de magie":
return draconicList.filter(it => !RdDItemCompetence.isThanatos(it));
return competencesDraconic.filter(it => !RdDItemCompetence.isThanatos(it));
}
return [RdDItemCompetence.getVoieDraconic(draconicList, sort.system.draconic)];
const voies = sort.system.draconic.split('/')
return voies.map(voie => RdDItemCompetence.getVoieDraconic(competencesDraconic, voie))
}
static getOrdreCode(code) {
return (VOIES_DRACONIC.find(it => it.code == code)?.ordre ?? '?')
}
static getVoieCode(voie) {
return VOIES_DRACONIC.find(it => voie.name.includes(it.short))?.code ?? '?'
static getCodeVoie(voie) {
return VOIES_DRACONIC.find(it => [it.code, it.short, it.label].includes(voie))?.code ?? '?'
}
static getCodeDraconic(sort, voies = ['O', 'H', 'N', 'T']) {
static getShortVoie(voie) {
return VOIES_DRACONIC.find(it => [it.code, it.short, it.label].includes(voie))?.short ?? voie
}
static getCode(sort, codeVoies = ['O', 'H', 'N', 'T']) {
switch (Grammar.toLowerCaseNoAccent(sort.name)) {
case "lecture d'aura":
case "detection d'aura":
return RdDItemSort.$voiesConnues('O/H/N/T', voies)
return RdDItemSort.$voiesConnues('O/H/N/T', codeVoies)
case "annulation de magie":
return RdDItemSort.$voiesConnues('O/H/N', voies)
return RdDItemSort.$voiesConnues('O/H/N', codeVoies)
}
const voie = VOIES_DRACONIC.find(it => it.label.includes(sort.system.draconic))
return voie?.code ?? sort.system.draconic
return RdDItemSort.getCodeVoie(sort.system.draconic)
}
static $voiesConnues(voiesSort, voies) {
const codes = voies.filter(it => voiesSort.includes(it))
.sort(Misc.ascending(it => RdDItemSort.getOrdreCode(it)))
@ -79,89 +96,60 @@ export class RdDItemSort extends Item {
return variable;
}
/* -------------------------------------------- */
static buildBonusCaseList(bonuscase, newCase) {
const list = RdDItemSort.bonuscaseStringToList(bonuscase)
if (newCase) {
list.push({ 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) {
// Gestion spéciale case bonus
if (item.type == ITEM_TYPES.sort) {
return RdDItemSort.buildBonusCaseList(item.system.bonuscase, newCase);
return RdDItemSort.stringToBonuscases(item.system.bonuscase)
}
return undefined;
}
/* -------------------------------------------- */
/** Met à jour les données de formulaire
* si static des bonus de cases sont présents
* */
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({ case: coord, bonus: bonus });
}
}
return RdDItemSort._bonuscaseListToString(list);
}
return undefined;
return [];
}
/* -------------------------------------------- */
static incrementBonusCase(actor, sort, coord) {
if (TMRUtility.getTMR(coord).type == "fleuve") {
coord = 'Fleuve';
if (TMRUtility.isFleuve(coord)) {
coord = FLEUVE_COORD;
}
let list = RdDItemSort.stringToBonuscases(sort.system.bonuscase);
const existing = list.find(it => it.case == coord)
const bonus = Number(existing?.bonus ?? 0) + 1
if (existing) {
existing.bonus = bonus
}
else {
list.push({ case: coord, bonus: 1 })
}
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 }]);
actor.updateEmbeddedDocuments('Item', [{ _id: sort._id, 'system.bonuscase': RdDItemSort.bonuscasesToString(list) }]);
}
/* -------------------------------------------- */
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)
const search = TMRUtility.isFleuve(coord)
? it => it.case == 'Fleuve'
: it => it.case == coord;
const bc = RdDItemSort.stringToBonuscases(sort.system.bonuscase)
.find(search)
return Number(bc?.bonus ?? 0);
}
static _bonuscaseListToString(list) {
static bonuscasesToString(list) {
return list.map(it => `${it.case}:${it.bonus}`)
.sort(Misc.ascending())
.join(',');
}
static bonuscaseStringToList(bonuscase) {
static stringToBonuscases(bonuscase) {
if (bonuscase == undefined || bonuscase == '') {
return []
}
return bonuscase.split(',').map(it => {
const b = it.split(':');
return { case: b[0], bonus: b[1] };
});
return bonuscase.split(',')
.map(it => it.split(':'))
.map(it => { return { case: it[0], bonus: it[1] } });
}
}

View File

@ -571,6 +571,7 @@ export class RdDItem extends Item {
else {
await this.quantiteIncDec(item.system.quantite);
}
// TODO: suppression dans les conteneurs!
await item.delete();
}
@ -601,6 +602,9 @@ export class RdDItem extends Item {
if (!other || !this.isInventaire()) {
return [false, undefined];
}
if (this.isConteneur()){
return [false, `Impossible de regrouper des conteneurs, ils ne sont pas empilables`];
}
if (this.system.quantite == undefined) {
return [false, `Impossible de regrouper des ${this.type}, ils ne sont pas empilables`];
}

View File

@ -62,12 +62,12 @@ export class RdDItemBlessure extends RdDItem {
content: `Blessure ${definition.label} appliquée à ${actor.name}`+
`<br>Perte d'endurance : ${lostEndurance}`+
`<br>Perte de Vie : ${lostVie}`,
whisper: ChatUtility.getWhisperRecipientsAndGMs(actor.name)
whisper: ChatUtility.getOwners(actor)
});
}
static async createBlessure(actor, gravite, localisation = '', attacker) {
static async createBlessure(actor, gravite, localisation = '', attackerToken) {
const definition = RdDItemBlessure.getDefinition(gravite)
const blessure = {
name: definition.label,
@ -77,7 +77,7 @@ export class RdDItemBlessure extends RdDItem {
gravite: gravite,
difficulte: - gravite,
localisation: localisation,
origine: attacker?.name ?? ""
origine: attackerToken?.name ?? ""
}
}
const blessures = await actor.createEmbeddedDocuments('Item', [blessure])

View File

@ -1,3 +1,4 @@
import { ChatUtility } from "../chat-utility.js";
import { RdDItem } from "../item.js";
import { Misc } from "../misc.js";
import { RdDTimestamp } from "../time/rdd-timestamp.js";
@ -21,9 +22,12 @@ export class RdDItemMaladie extends RdDItem {
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);
ChatMessage.create({
whisper: ChatUtility.getOwners(mal.actor),
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)
}
}

View File

@ -5,7 +5,7 @@ import { Monnaie } from "./item-monnaie.js";
import { RdDItem, ITEM_TYPES } from "./item.js";
import { RdDTimestamp } from "./time/rdd-timestamp.js";
import { RdDRaretes } from "./item/raretes.js";
import { RdDCalendrier } from "./time/rdd-calendrier.js";
import { VOIES_DRACONIC } from "./item-sort.js";
class Migration {
get code() { return "sample"; }
@ -530,6 +530,32 @@ class _11_2_20_MigrationAstrologie extends Migration {
}
}
class _12_0_24_MigrationVoieSorts extends Migration {
get code() { return "migration-voies-sorts" }
get version() { return "12.0.24" }
async migrate() {
await this.applyItemsUpdates(items => items
.filter(it => ITEM_TYPES.sort == it.type)
.map(it => this.migrateSort(it))
)
}
migrateSort(it) {
return {
_id: it.id,
'system.draconic': this.convertDraconic(it.system.draconic),
}
}
convertDraconic(draconic) {
for (let v of VOIES_DRACONIC) {
if ([v.label, v.short, v.code].includes(draconic)) {
return v.short
}
}
return draconic
}
}
export class Migrations {
static getMigrations() {
return [

View File

@ -139,7 +139,7 @@ export class Misc {
}
static join(params, separator = '') {
return params?.reduce(Misc.joining(separator)) ?? '';
return (!params || params.length == 0) ? '' : params.reduce(Misc.joining(separator))
}
static joining(separator = '') {
@ -189,13 +189,7 @@ export class Misc {
* and there is no connected GM
*/
static documentIfResponsible(document) {
if (foundry.utils.isNewerVersion(game.release.version, '12.0')) {
if (game.users.activeGM || (Misc.connectedGMs().length == 0 && Misc.isOwnerPlayer(document)))
{
return document
}
}
else if (Misc.isUniqueConnectedGM() || (Misc.connectedGMs().length == 0 && Misc.isOwnerPlayer(document))) {
if (Misc.isFirstConnectedGM() || (Misc.connectedGMs().length == 0 && Misc.isFirstOwnerPlayer(document))) {
return document
}
return undefined
@ -205,15 +199,22 @@ export class Misc {
return document.testUserPermission && document.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)
}
static isFirstOwnerPlayer(document) {
if (!document.testUserPermission){
return false
}
return game.users.filter(u => document.testUserPermission(u, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)) == game.user
}
static isOwnerPlayerOrUniqueConnectedGM(actor) {
return Misc.isOwnerPlayer(actor) ?? Misc.isUniqueConnectedGM();
return Misc.isFirstOwnerPlayer(actor) ?? Misc.isFirstConnectedGM();
}
/**
* @returns true pour un seul utilisateur: le premier GM connecté par ordre d'id
*/
static isUniqueConnectedGM() {
return game.user.id == Misc.firstConnectedGMId();
static isFirstConnectedGM() {
return game.user == Misc.firstConnectedGM();
}
static firstConnectedGMId() {

View File

@ -53,9 +53,14 @@ export class RdDCarac {
return selectedCarac?.label?.toLowerCase()?.match(/r(e|ê)ve(( |-)actuel)?/);
}
/**
* Lappel à la chance nest possible que pour recommencer les jets dactions physiques :
* tous les jets de combat, de FORCE, dAGILITÉ, de DEXTÉRITÉ, de Dérobée, dAPPARENCE,
* ainsi que de Perception active et volontaire.
*/
static isActionPhysique(selectedCarac) {
return !selectedCarac ||
selectedCarac?.label.match(/(Apparence|Force|Agilité|Dextérité|Vue|Ouïe|Odorat-Goût|Empathie|Dérobée|Mêlée|Tir|Lancer)/);
return Grammar.toLowerCaseNoAccent(selectedCarac?.label)
?.match(/(apparence|force|agilite|dexterite|vue|ouie|gout|odorat|empathie|melee|tir|lancer|derobee)/) != null
}
static getCaracDerivee(value) {
@ -90,15 +95,4 @@ export class RdDCarac {
static getCaracXp(targetValue) {
return RdDCarac.getCaracDerivee(targetValue)?.xp ?? 200;
}
/**
* Lappel à la chance nest possible que pour recommencer les jets dactions physiques :
* tous les jets de combat, de FORCE, dAGILITÉ, de DEXTÉRITÉ, de Dérobée, dAPPARENCE,
* ainsi que de Perception active et volontaire.
*/
static isActionPhysique(selectedCarac) {
return Grammar.toLowerCaseNoAccent(selectedCarac?.label)?.match(/(apparence|force|agilite|dexterite|vue|ouie|odorat|empathie|melee|tir|lancer|derobee)/);
}
}

View File

@ -42,7 +42,8 @@ export class RdDCombatManager extends Combat {
/* -------------------------------------------- */
Hooks.on("getCombatTrackerEntryContext", (html, options) => { RdDCombatManager.pushInitiativeOptions(html, options); });
Hooks.on("updateCombat", (combat, change, options, userId) => { RdDCombat.onUpdateCombat(combat, change, options, userId) });
Hooks.on("preDeleteCombat", (combat, html, id) => { combat.onPreDeleteCombat() });
Hooks.on("preDeleteCombat", (combat, html, id) => { combat.onPreDeleteCombat() })
Hooks.on("deleteCombat", (combat, html, id) => { combat.onDeleteCombat() })
}
/* -------------------------------------------- */
@ -53,28 +54,48 @@ export class RdDCombatManager extends Combat {
/* -------------------------------------------- */
async onPreDeleteCombat() {
if (Misc.isUniqueConnectedGM()) {
await this.finDeRound({ terminer: true });
if (Misc.isFirstConnectedGM()) {
await this.finDeRound({ terminer: true })
ChatUtility.removeChatMessageContaining(`<div data-combatid="${this.id}" data-combatmessage="actor-turn-summary">`)
game.messages.filter(m => ChatUtility.getMessageData(m, 'attacker-roll') != undefined && ChatUtility.getMessageData(m, 'defender-roll') != undefined)
.forEach(it => it.delete());
.forEach(it => it.delete())
RdDEmpoignade.deleteAllEmpoignades()
}
}
async onDeleteCombat() {
if (Misc.isFirstConnectedGM()) {
if (game.combats.size <= 1) {
game.actors.forEach(actor => actor.resetItemUse())
}
}
}
/* -------------------------------------------- */
async finDeRound(options = { terminer: false }) {
this.turns.forEach(turn => turn.actor.resetItemUse());
this.combatants.map(it => RdDCombatManager.getActorCombatant(it, { warning: false }))
.filter(it => it != undefined)
.forEach(async actor => {
await actor.finDeRound(options)
await actor.resetItemUse()
})
}
for (let combatant of this.combatants) {
if (combatant.actor) {
await combatant.actor.finDeRound(options);
}
else {
static getActorCombatant(combatant, options = { warning: true }) {
if (!combatant.actor) {
if (options.warning) {
ui.notifications.warn(`Le combatant ${combatant.name} n'est pas associé à un acteur!`)
}
return undefined
}
else if (!combatant.actor.isActorCombat()) {
if (options.warning) {
ui.notifications.warn(`${combatant.name} ne peut pas combattre!`)
}
return undefined
}
return combatant.actor
}
static calculAjustementInit(actor, arme) {
const efficacite = (arme?.system.magique) ? arme.system.ecaille_efficacite : 0
const etatGeneral = actor.getEtatGeneral() ?? 0
@ -83,57 +104,27 @@ export class RdDCombatManager extends Combat {
}
/************************************************************************************/
async rollInitiative(ids, formula = undefined, messageOptions = {}) {
console.log(`${game.system.title} | Combat.rollInitiative()`, ids, formula, messageOptions);
async rollInitiative(ids, messageOptions = {}) {
console.log(`${game.system.title} | Combat.rollInitiative()`, ids, messageOptions)
ids = typeof ids === "string" ? [ids] : ids
ids.forEach(async id =>
await this.rollInitRdD(id, undefined, messageOptions)
)
return this
}
ids = typeof ids === "string" ? [ids] : ids;
// calculate initiative
for (let cId = 0; cId < ids.length; cId++) {
const combatant = this.combatants.get(ids[cId]);
const ajustement = RdDCombatManager.calculAjustementInit(combatant.actor, undefined);
let rollFormula = formula ?? RdDCombatManager.formuleInitiative(2, 10, 0, ajustement);
if (!formula) {
if (combatant.actor.type == 'creature' || combatant.actor.type == 'entite') {
const competence = combatant.actor.items.find(it => RdDItemCompetenceCreature.isCompetenceAttaque(it))
if (competence) {
rollFormula = RdDCombatManager.formuleInitiative(2, competence.system.carac_value, competence.system.niveau, etatGeneral);
}
} else {
const armeCombat = combatant.actor.itemTypes['arme'].find(it => it.system.equipe)
let compName = "Corps à corps"
if (armeCombat) {
if (armeCombat.system.competence != "") {
compName = armeCombat.system.competence
}
if (armeCombat.system.lancer != "") {
compName = armeCombat.system.lancer
}
if (armeCombat.system.tir != "") {
compName = armeCombat.system.tir
}
}
const competence = RdDItemCompetence.findCompetence(combatant.actor.items, compName);
if (competence && competence.system.defaut_carac) {
const carac = combatant.actor.system.carac[competence.system.defaut_carac].value;
const niveau = competence.system.niveau;
const ajustement = RdDCombatManager.calculAjustementInit(combatant.actor, armeCombat)
rollFormula = RdDCombatManager.formuleInitiative(2, carac, niveau, ajustement);
} else {
ui.notifications.warn(`Votre arme ${armeCombat.name} n'a pas de compétence renseignée`);
}
}
}
//console.log("Combatat", c);
async rollInitRdD(id, formula, messageOptions = {}) {
const combatant = this.combatants.get(id);
const actor = RdDCombatManager.getActorCombatant(combatant)
if (actor) {
const rollFormula = formula ?? RdDCombatManager.getFirstInitRollFormula(actor)
const roll = combatant.getInitiativeRoll(rollFormula);
if (!roll.total) {
await roll.evaluate();
}
const total = Math.max(roll.total, 0.00);
console.log("Compute init for", rollFormula, roll, total, combatant);
let id = combatant._id || combatant.id;
await this.updateEmbeddedDocuments("Combatant", [{ _id: id, initiative: total }]);
await this.updateEmbeddedDocuments("Combatant", [{ _id: combatant._id || combatant.id, initiative: total }]);
// Send a chat message
let rollMode = messageOptions.rollMode || game.settings.get("core", "rollMode");
@ -142,10 +133,10 @@ export class RdDCombatManager extends Combat {
scene: canvas.scene._id,
actor: combatant.actor?._id,
token: combatant.token._id,
alias: combatant.token.name,
alias: combatant.token?.name,
sound: CONFIG.sounds.dice,
},
flavor: `${combatant.token.name} a fait son jet d'Initiative (${messageOptions.initInfo})<br>`,
flavor: `${combatant.token?.name} a fait son jet d'Initiative (${messageOptions.info})<br>`
},
messageOptions);
roll.toMessage(messageData, { rollMode, create: true });
@ -153,7 +144,20 @@ export class RdDCombatManager extends Combat {
RdDCombatManager.processPremierRoundInit();
}
return this;
};
}
static getFirstInitRollFormula(actor) {
const actions = actor.listActionsCombat()
if (actions.length > 0) {
const action = actions[0]
const init = RdDCombatManager.getInitData(actor, action)
const ajustement = RdDCombatManager.calculAjustementInit(actor, action)
return RdDCombatManager.formuleInitiative(init.offset, init.carac, init.niveau, ajustement);
}
let ajustement = RdDCombatManager.calculAjustementInit(actor, undefined);
return RdDCombatManager.formuleInitiative(2, 10, 0, ajustement);
}
static formuleInitiative(rang, carac, niveau, bonusMalus) {
return `${rang} +( (${RdDCombatManager.calculInitiative(niveau, carac, bonusMalus)} )/100)`;
@ -222,7 +226,7 @@ export class RdDCombatManager extends Combat {
}
static $prepareAttaqueArme(infoAttaque) {
const comp = infoAttaque.competences.find(c => c.name == infoAttaque.competence);
const comp = infoAttaque.competences.find(it => Grammar.equalsInsensitive(it.name, infoAttaque.competence))
const arme = infoAttaque.arme;
const attaque = foundry.utils.duplicate(arme);
attaque.action = 'attaque';
@ -236,62 +240,10 @@ export class RdDCombatManager extends Combat {
return attaque;
}
static listActionsCreature(competences) {
return competences
.filter(it => RdDItemCompetenceCreature.isCompetenceAttaque(it))
.map(it => RdDItemCompetenceCreature.armeCreature(it))
.filter(it => it != undefined);
}
static listActionsPossessions(actor) {
return RdDCombatManager._indexActions(actor.getPossessions().map(p => {
return {
name: p.name,
action: 'possession',
system: {
competence: p.name,
possessionid: p.system.possessionid,
}
}
}));
}
/* -------------------------------------------- */
static listActionsCombat(combatant) {
const actor = combatant.actor;
let actions = RdDCombatManager.listActionsPossessions(actor);
if (actions.length > 0) {
return actions;
}
if (actor.isCreatureEntite()) {
actions = RdDCombatManager.listActionsCreature(actor.itemTypes['competencecreature']);
} else if (actor.isPersonnage()) {
// Recupération des items 'arme'
const competences = actor.itemTypes['competence'];
const armes = actor.itemTypes['arme'].filter(it => RdDItemArme.isArmeUtilisable(it))
.concat(RdDItemArme.empoignade(actor))
.concat(RdDItemArme.mainsNues(actor));
actions = RdDCombatManager.listActionsArmes(armes, competences, actor.system.carac);
if (actor.system.attributs.hautrevant.value) {
actions.push({ name: "Draconic", action: 'haut-reve', system: { initOnly: true, competence: "Draconic" } });
}
}
return RdDCombatManager._indexActions(actions);
}
static _indexActions(actions) {
for (let index = 0; index < actions.length; index++) {
actions[index].index = index;
}
return actions;
}
/* -------------------------------------------- */
static processPremierRoundInit() {
// Check if we have the whole init !
if (Misc.isUniqueConnectedGM() && game.combat.current.round == 1) {
if (Misc.isFirstConnectedGM() && game.combat.current.round == 1) {
let initMissing = game.combat.combatants.find(it => !it.initiative);
if (!initMissing) { // Premier round !
for (let combatant of game.combat.combatants) {
@ -300,7 +252,7 @@ export class RdDCombatManager extends Combat {
if (action && action.type == "arme") {
for (let initData of premierRoundInit) {
if (Grammar.toLowerCaseNoAccentNoSpace(action.system.initpremierround).includes(initData.pattern)) {
let msg = `<h4>L'initiative de ${combatant.actor.name} a été modifiée !</h4>
let msg = `<h4>L'initiative de ${combatant.actor.getAlias()} a été modifiée !</h4>
<hr>
<div>
Etant donné son ${action.name}, son initative pour ce premier round est désormais de ${initData.init}.
@ -325,13 +277,13 @@ export class RdDCombatManager extends Combat {
/* -------------------------------------------- */
static pushInitiativeOptions(html, options) {
for (let i = 0; i < options.length; i++) {
let option = options[i];
let option = options[i]
if (option.name == 'COMBAT.CombatantReroll') { // Replace !
option.name = "Sélectionner l'initiative...";
option.condition = true;
option.icon = '<i class="far fa-question-circle"></i>';
option.name = "Sélectionner l'initiative..."
option.condition = true
option.icon = '<i class="far fa-question-circle"></i>'
option.callback = target => {
RdDCombatManager.displayInitiativeMenu(html, target.data('combatant-id'));
RdDCombatManager.displayInitiativeMenu(html, target.data('combatant-id'))
}
}
}
@ -342,96 +294,83 @@ export class RdDCombatManager extends Combat {
}
/* -------------------------------------------- */
static rollInitiativeAction(combatantId, action) {
const combatant = game.combat.combatants.get(combatantId);
if (combatant.actor == undefined) {
ui.notifications.warn(`Le combatant ${combatant.name} n'est pas associé à un acteur, impossible de déterminer ses actions de combat!`)
return [];
}
let initInfo = "";
let initOffset = 0;
let caracForInit = 0;
let compNiveau = 0;
let compData = { name: "Aucune" };
if (combatant.actor.getSurprise() == "totale") {
initOffset = -1; // To force 0
initInfo = "Surprise Totale"
} else if (combatant.actor.getSurprise() == "demi") {
initOffset = 0;
initInfo = "Demi Surprise"
} else if (action.action == 'possession') {
initOffset = 10;
caracForInit = combatant.actor.getReveActuel();
initInfo = "Possession"
} else if (action.action == 'autre') {
initOffset = 2;
initInfo = "Autre Action"
} else if (action.action == 'haut-reve') {
initOffset = 9;
initInfo = "Draconic"
} else {
compData = RdDItemCompetence.findCompetence(combatant.actor.items, action.system.competence);
compNiveau = compData.system.niveau;
initInfo = action.name + " / " + action.system.competence;
const combatant = game.combat.combatants.get(combatantId)
const actor = RdDCombatManager.getActorCombatant(combatant)
if (actor == undefined) { return [] }
if (combatant.actor.type == 'creature' || combatant.actor.type == 'entite') {
caracForInit = compData.system.carac_value;
} else {
caracForInit = combatant.actor.system.carac[compData.system.defaut_carac].value;
}
initOffset = RdDCombatManager._baseInitOffset(compData.system.categorie, action);
}
// Cas des créatures et entités vs personnages
const ajustement = RdDCombatManager.calculAjustementInit(combatant.actor, action)
let rollFormula = RdDCombatManager.formuleInitiative(initOffset, caracForInit, compNiveau, ajustement);
// Garder la trace de l'arme/compétence utilisée pour l'iniative
combatant.initiativeData = { arme: action } // pour reclasser l'init au round 0
game.combat.rollInitiative(combatantId, rollFormula, { initInfo: initInfo });
const init = RdDCombatManager.getInitData(actor, action)
const ajustement = RdDCombatManager.calculAjustementInit(actor, action)
const rollFormula = RdDCombatManager.formuleInitiative(init.offset, init.carac, init.niveau, ajustement);
game.combat.rollInitRdD(combatantId, rollFormula, init);
}
/* -------------------------------------------- */
static _baseInitOffset(categorie, arme) {
if (categorie == "tir") { // Offset de principe pour les armes de jet
return 8;
static getInitData(actor, action) {
if (actor.getSurprise() == "totale") { return { offset: -1, info: "Surprise Totale", carac: 0, niveau: 0 } }
if (actor.getSurprise() == "demi") { return { offset: 0, info: "Demi Surprise", carac: 0, niveau: 0 } }
if (action.action == 'autre') { return { offset: 2, info: "Autre Action", carac: 0, niveau: 0 } }
if (action.action == 'possession') { return { offset: 10, info: "Possession", carac: actor.getReveActuel(), niveau: 0 } }
if (action.action == 'haut-reve') { return { offset: 9, info: "Draconic", carac: actor.getReveActuel(), niveau: 0 } }
const comp = RdDItemCompetence.findCompetence(actor.items, action.system.competence);
return {
offset: RdDCombatManager.initOffset(comp?.system.categorie, action),
info: action.name + " / " + action.system.competence,
carac: actor.getCaracInit(comp),
niveau: comp?.system.niveau ?? -8
}
if (categorie == "lancer") { // Offset de principe pour les armes de jet
return 7;
}
static initOffset(categorie, arme) {
switch (categorie) {
case "tir": return 8
case "lancer": return 7
default:
switch (arme.system.cac) {
case "empoignade": return 3
case "pugilat": return 4
case "naturelle": return 4
default: return 5
}
}
switch (arme.system.cac) {
case "empoignade":
return 3;
case "pugilat":
case "naturelle":
return 4;
}
return 5;
}
/* -------------------------------------------- */
static displayInitiativeMenu(html, combatantId) {
console.log("Combatant ; ", combatantId);
const combatant = game.combat.combatants.get(combatantId);
if (!(combatant?.actor)) {
ui.notifications.warn(`Le combatant ${combatant.name ?? combatantId} n'est pas associé à un acteur, impossible de déterminer ses actions de combat!`)
return;
}
let actions = RdDCombatManager.listActionsCombat(combatant);
// Build the relevant submenu
if (actions) {
let menuItems = [];
for (let action of actions) {
menuItems.push({
const combatant = game.combat.combatants.get(combatantId)
const actor = RdDCombatManager.getActorCombatant(combatant, { warning: false })
if (actor) {
const actions = RdDCombatManager.listActionsActorCombatant(actor)
// Build the relevant submenu
const menuItems = actions.map(action => {
return {
name: action.system.competence,
icon: "<i class='fas fa-dice-d6'></i>",
callback: target => { RdDCombatManager.rollInitiativeAction(combatantId, action) }
});
}
})
if (menuItems.length > 0) {
new ContextMenu(html, ".directory-list", menuItems).render();
}
new ContextMenu(html, ".directory-list", menuItems).render();
}
}
/* -------------------------------------------- */
static listActionsActorCombatant( actor) {
const possessions = actor.listActionsPossessions()
const actions = possessions.length > 0
? possessions
: actor.listActionsCombat()
for (let index = 0; index < actions.length; index++) {
actions[index].index = index
}
return actions
}
}
/* -------------------------------------------- */
@ -454,11 +393,11 @@ export class RdDCombat {
/* -------------------------------------------- */
static combatNouveauTour(combat) {
if (Misc.isUniqueConnectedGM()) {
if (Misc.isFirstConnectedGM()) {
let turn = combat.turns.find(t => t.token?.id == combat.current.tokenId);
if (turn?.actor) {
RdDCombat.displayActorCombatStatus(combat, turn.actor, turn.token.id);
// TODO Playaudio for player??
RdDCombat.displayActorCombatStatus(combat, turn.actor, turn.token);
}
}
}
@ -469,52 +408,50 @@ export class RdDCombat {
}
/* -------------------------------------------- */
static rddCombatTarget(target, attacker) {
const defender = target?.actor;
const defenderTokenId = target?.id;
return new RdDCombat(attacker, defender, defenderTokenId, target)
static rddCombatTarget(target, attacker, attackerToken) {
return new RdDCombat(attacker, attackerToken?.id, target?.actor, target?.id, target)
}
/* -------------------------------------------- */
static rddCombatForAttackerAndDefender(attackerId, defenderTokenId) {
const attacker = game.actors.get(attackerId);
let defender = defenderTokenId ? canvas.tokens.get(defenderTokenId)?.actor : undefined;
static rddCombatForAttackerAndDefender(attackerId, attackerTokenId, defenderTokenId) {
const attacker = game.actors.get(attackerId)
const defenderToken = defenderTokenId ? canvas.tokens.get(defenderTokenId) : undefined
let defender = defenderToken?.actor;
let target = undefined
if (!defenderTokenId || !defender) {
console.warn(`RdDCombat.rddCombatForAttackerAndDefender: appel avec defenderTokenId ${defenderTokenId} incorrect, ou pas de defender correspondant`);
target = Targets.getTarget()
if (!target) {
return;
return
}
defenderTokenId = target.id;
defender = target.actor;
if (!defenderTokenId || !defender) {
return;
return
}
}
return new RdDCombat(attacker, defender, defenderTokenId, target)
return new RdDCombat(attacker, attackerTokenId, defender, defenderTokenId, target)
}
/* -------------------------------------------- */
static onMsgEncaisser(msg) {
let defender = canvas.tokens.get(msg.defenderTokenId).actor;
if (Misc.isOwnerPlayerOrUniqueConnectedGM()) {
let defender = canvas.tokens.get(msg.defenderToken.id).actor;
if (Misc.isOwnerPlayerOrUniqueConnectedGM(defender)) {
let attackerRoll = msg.attackerRoll;
let attacker = msg.attackerId ? game.actors.get(msg.attackerId) : undefined;
defender.encaisserDommages(attackerRoll, attacker);
const rddCombat = RdDCombat.rddCombatForAttackerAndDefender(msg.attackerId, msg.defenderTokenId);
defender.encaisserDommages(attackerRoll, attacker, msg.attackerToken);
const rddCombat = RdDCombat.rddCombatForAttackerAndDefender(msg.attackerId, msg.attackerToken.id, msg.defenderToken.id);
rddCombat?.removeChatMessageActionsPasseArme(attackerRoll.passeArme);
}
}
/* -------------------------------------------- */
static onMsgDefense(msg) {
let defenderToken = canvas.tokens.get(msg.defenderTokenId);
if (defenderToken && Misc.isUniqueConnectedGM()) {
const rddCombat = RdDCombat.rddCombatForAttackerAndDefender(msg.attackerId, msg.defenderTokenId);
rddCombat?.removeChatMessageActionsPasseArme(msg.defenderRoll.passeArme);
rddCombat?._chatMessageDefense(msg.paramChatDefense, msg.defenderRoll);
let defenderToken = canvas.tokens.get(msg.defenderToken.id)
if (defenderToken && Misc.isFirstConnectedGM()) {
const rddCombat = RdDCombat.rddCombatForAttackerAndDefender(msg.attackerId, msg.attackerToken.id, msg.defenderToken.id)
rddCombat?.removeChatMessageActionsPasseArme(msg.defenderRoll.passeArme)
rddCombat?._chatMessageDefense(msg.paramChatDefense, msg.defenderRoll)
}
}
@ -545,6 +482,7 @@ export class RdDCombat {
html.on("click", button, event => {
const rddCombat = RdDCombat.rddCombatForAttackerAndDefender(
event.currentTarget.attributes['data-attackerId']?.value,
event.currentTarget.attributes['data-attackerTokenId']?.value,
event.currentTarget.attributes['data-defenderTokenId']?.value);
if (rddCombat) {
rddCombat.onEvent(button, event);
@ -560,22 +498,38 @@ export class RdDCombat {
}
/* -------------------------------------------- */
constructor(attacker, defender, defenderTokenId, target) {
constructor(attacker, attackerTokenId, defender, defenderTokenId, target) {
this.attacker = attacker
this.defender = defender
this.target = target
this.attackerId = this.attacker.id
this.defenderId = this.defender.id
this.attackerTokenId = attackerTokenId
this.defenderTokenId = defenderTokenId
this.attackerToken = RdDCombat.$extractAttackerTokenData(attacker, attackerTokenId)
this.defenderToken = RdDCombat.$extractDefenderTokenData(defender, defenderTokenId, target)
}
static $extractAttackerTokenData(attacker, attackerTokenId) {
const token = canvas.tokens.get(attackerTokenId);
return token ? Targets.extractTokenData(token) : Targets.buildActorTokenData(attackerTokenId, attacker)
}
static $extractDefenderTokenData(defender, defenderTokenId, target) {
if (target) {
return Targets.extractTokenData(target)
}
const token = canvas.tokens.get(defenderTokenId);
return token ? Targets.extractTokenData(token) : Targets.buildActorTokenData(defenderTokenId, defender)
}
/* -------------------------------------------- */
async onEvent(button, event) {
const chatMessage = ChatUtility.getChatMessage(event);
const defenderRoll = ChatUtility.getMessageData(chatMessage, 'defender-roll');
const attackerRoll = defenderRoll?.attackerRoll ?? ChatUtility.getMessageData(chatMessage, 'attacker-roll');
console.log('RdDCombat', attackerRoll, defenderRoll);
const defenderTokenId = event.currentTarget.attributes['data-defenderTokenId']?.value;
const armeParadeId = event.currentTarget.attributes['data-armeid']?.value;
const competence = event.currentTarget.attributes['data-competence']?.value;
@ -585,7 +539,7 @@ export class RdDCombat {
case '#particuliere-attaque': return await this.choixParticuliere(attackerRoll, event.currentTarget.attributes['data-mode'].value);
case '#parer-button': return this.parade(attackerRoll, armeParadeId);
case '#esquiver-button': return this.esquive(attackerRoll, compId, competence);
case '#encaisser-button': return this.encaisser(attackerRoll, defenderRoll, defenderTokenId);
case '#encaisser-button': return this.encaisser(attackerRoll, defenderRoll);
case '#echec-total-attaque': return this._onEchecTotal(attackerRoll);
case '#appel-chance-attaque': return this.attacker.rollAppelChance(
@ -698,11 +652,11 @@ export class RdDCombat {
if (this.defender.isEntite([ENTITE_BLURETTE])) {
ChatMessage.create({
content: `<strong>La cible est une blurette, l'arme à distance sera perdue dans le blurêve`,
whisper: ChatMessage.getWhisperRecipients("GM")
whisper: ChatUtility.getGMs()
})
}
else {
const defenderToken = canvas.tokens.get(this.defenderTokenId);
const defenderToken = canvas.tokens.get(this.defenderTokenId)
const dist = this.distance(_token, defenderToken)
const isVisible = this.isVisible(_token, defenderToken)
const portee = this._ajustementPortee(dist, rollData.arme)
@ -721,7 +675,7 @@ export class RdDCombat {
activite: activite,
total: total
}),
whisper: ChatMessage.getWhisperRecipients("GM")
whisper: ChatUtility.getGMs()
})
}
}
@ -769,7 +723,7 @@ export class RdDCombat {
}
RdDEmpoignade.checkEmpoignadeEnCours(this.attacker)
let rollData = this._prepareAttaque(competence, arme);
let rollData = this._prepareAttaque(competence, arme)
console.log("RdDCombat.attaque >>>", rollData);
if (arme) {
this.attacker.verifierForceMin(arme);
@ -795,15 +749,18 @@ export class RdDCombat {
dialog.render(true);
}
/* -------------------------------------------- */
_prepareAttaque(competence, arme) {
let rollData = {
alias: this.attacker?.getAlias(),
passeArme: foundry.utils.randomID(16),
mortalite: arme?.system.mortalite,
competence: competence,
surprise: this.attacker.getSurprise(true),
surpriseDefenseur: this.defender.getSurprise(true),
targetToken: Targets.extractTokenData(this.target),
sourceToken: this.attackerToken,
targetToken: this.defenderToken,
essais: {}
};
@ -816,9 +773,9 @@ export class RdDCombat {
}
else {
// sans armes: à mains nues
const niveau = competence.system.niveau;
const init = RdDCombatManager.calculInitiative(niveau, this.attacker.system.carac['melee'].value);
rollData.arme = RdDItemArme.mainsNues({ niveau: niveau, initiative: init });
rollData.arme = RdDItemArme.corpsACorps(this.attacker)
rollData.arme.system.niveau = competence.system.niveau
rollData.arme.system.initiative = RdDCombatManager.calculInitiative(competence.system.niveau, this.attacker.system.carac['melee'].value);
}
return rollData;
}
@ -845,12 +802,13 @@ export class RdDCombat {
}
const choixParticuliere = await ChatMessage.create({
alias: this.attacker.name,
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.attacker.name),
alias: this.attacker.getAlias(),
whisper: ChatUtility.getOwners(this.attacker),
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-demande-attaque-particuliere.html', {
alias: this.attacker.name,
alias: this.attacker.getAlias(),
attackerId: this.attackerId,
defenderTokenId: this.defenderTokenId,
attackerToken: this.attackerToken,
defenderToken: this.defenderToken,
isForce: isForce,
isFinesse: isFinesse,
isRapide: isRapide,
@ -867,7 +825,7 @@ export class RdDCombat {
attackerRoll.dmg = RdDBonus.dmg(attackerRoll, this.attacker, this.defender.isEntite());
let defenderRoll = { attackerRoll: attackerRoll, passeArme: attackerRoll.passeArme, show: {} }
attackerRoll.show = {
cible: this.target ? this.defender.name : 'la cible',
cible: this.defender?.getAlias() ?? 'la cible',
isRecul: (attackerRoll.particuliere == 'force' || attackerRoll.tactique == 'charge')
}
await RdDResolutionTable.displayRollData(attackerRoll, this.attacker, 'chat-resultat-attaque.html');
@ -898,7 +856,7 @@ export class RdDCombat {
// # utilisation esquive
const corpsACorps = this.defender.getCompetenceCorpsACorps({ onMessage: it => console.info(it, this.defender) });
const esquives = foundry.utils.duplicate(this.defender.getCompetencesEsquive())
esquives.forEach(e => e.system.nbUsage = e?._id ? this.defender.getItemUse(e._id) : 0);
esquives.forEach(e => e.nbUsage = e?._id ? this.defender.getItemUse(e._id) : 0);
const paramChatDefense = {
passeArme: attackerRoll.passeArme,
@ -908,7 +866,8 @@ export class RdDCombat {
attacker: this.attacker,
attackerId: this.attackerId,
esquives: esquives,
defenderTokenId: this.defenderTokenId,
attackerToken: this.attackerToken,
defenderToken: this.defenderToken,
mainsNues: attackerRoll.dmg.mortalite != 'mortel' && corpsACorps,
armes: this._filterArmesParade(this.defender, attackerRoll.competence, attackerRoll.arme),
diffLibre: attackerRoll.ajustements?.diffLibre?.value ?? 0,
@ -919,7 +878,7 @@ export class RdDCombat {
dmg: attackerRoll.dmg,
};
if (Misc.isUniqueConnectedGM()) {
if (Misc.isFirstConnectedGM()) {
await this._chatMessageDefense(paramChatDefense, defenderRoll);
}
else {
@ -932,8 +891,8 @@ export class RdDCombat {
const choixDefense = await ChatMessage.create({
// message privé: du défenseur à lui même (et aux GMs)
speaker: ChatMessage.getSpeaker(this.defender, canvas.tokens.get(this.defenderTokenId)),
alias: this.attacker.name,
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.defender.name),
alias: this.attacker?.getAlias(),
whisper: ChatUtility.getOwners(this.defender),
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-demande-defense.html', paramDemandeDefense),
});
// flag pour garder les jets d'attaque/defense
@ -946,8 +905,9 @@ export class RdDCombat {
game.socket.emit(SYSTEM_SOCKET_ID, {
msg: "msg_defense", data: {
attackerId: this.attacker?.id,
attackerToken: this.attackerToken,
defenderId: this.defender?.id,
defenderTokenId: this.defenderTokenId,
defenderToken: this.defenderToken,
defenderRoll: defenderRoll,
paramChatDefense: paramChatDefense,
rollMode: true
@ -956,31 +916,33 @@ export class RdDCombat {
}
/* -------------------------------------------- */
_filterArmesParade(defender, competence) {
let items = defender.items.filter(it => RdDItemArme.isArmeUtilisable(it) || RdDItemCompetenceCreature.isCompetenceParade(it))
items.forEach(item => item.system.nbUsage = defender.getItemUse(item.id)); // Ajout du # d'utilisation ce round
_filterArmesParade(defender, competence, armeAttaque) {
let defenses = defender.items.filter(it => RdDItemArme.isParade(it))
defenses = foundry.utils.duplicate(defenses)
defenses.forEach(armeDefense => {
// Ajout du # d'utilisation ce round
armeDefense.nbUsage = defender.getItemUse(armeDefense.id)
armeDefense.typeParade = RdDItemArme.defenseArmeParade(armeAttaque, armeDefense)
})
switch (competence.system.categorie) {
case 'tir':
case 'lancer':
return items.filter(item => RdDItemArme.getCategorieParade(item) == 'boucliers')
return defenses.filter(armeDefense => RdDItemArme.getCategorieParade(armeDefense) == 'boucliers')
default:
// Le fléau ne peut être paré quau bouclier p115
if (competence.name == "Fléau") {
return items.filter(item => RdDItemArme.getCategorieParade(item) == 'boucliers')
}
return items.filter(item => RdDItemArme.getCategorieParade(item));
return defenses.filter(armeDefense => armeDefense.typeParade != '')
}
}
/* -------------------------------------------- */
async _onAttaqueEchecTotal(attackerRoll) {
const choixEchecTotal = await ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.attacker.name),
whisper: ChatUtility.getOwners(this.attacker),
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-demande-attaque-etotal.html', {
attackerId: this.attackerId,
attacker: this.attacker,
defenderTokenId: this.defenderTokenId,
attackerToken: this.attackerToken,
defenderToken: this.defenderToken,
essais: attackerRoll.essais
})
});
@ -994,9 +956,9 @@ export class RdDCombat {
const arme = rollData.arme;
const avecArme = !['', 'sans-armes', 'armes-naturelles'].includes(arme?.system.categorie_parade ?? '');
const action = (rollData.attackerRoll ? (arme ? "la parade" : "l'esquive") : "l'attaque");
ChatUtility.createChatWithRollMode(this.defender.name, {
content: `<strong>Maladresse à ${action}!</strong> ` + await RdDRollTables.getMaladresse({ arme: avecArme })
});
ChatUtility.createChatWithRollMode(
{ content: `<strong>Maladresse à ${action}!</strong> ` + await RdDRollTables.getMaladresse({ arme: avecArme }) },
this.defender)
}
/* -------------------------------------------- */
@ -1052,8 +1014,11 @@ export class RdDCombat {
/* -------------------------------------------- */
_prepareParade(attackerRoll, armeParade, competenceParade) {
let defenderRoll = {
alias: this.defender?.getAlias(),
passeArme: attackerRoll.passeArme,
diffLibre: attackerRoll.diffLibre,
attackerToken: this.attackerToken,
defenderToken: this.defenderToken,
attackerRoll: attackerRoll,
competence: this.defender.getCompetence(competenceParade),
arme: armeParade,
@ -1076,9 +1041,9 @@ export class RdDCombat {
console.log("RdDCombat._onParadeParticuliere >>>", defenderRoll);
if (!defenderRoll.attackerRoll.isPart) {
// TODO: attaquant doit jouer résistance et peut être désarmé p132
ChatUtility.createChatWithRollMode(this.defender.name, {
content: `(à gérer) L'attaquant doit jouer résistance et peut être désarmé (p132)`
});
ChatUtility.createChatWithRollMode(
{ content: `(à gérer) L'attaquant doit jouer résistance et peut être désarmé (p132)` },
this.defender)
}
}
@ -1106,7 +1071,7 @@ export class RdDCombat {
async esquive(attackerRoll, compId, compName) {
const esquive = this.defender.getCompetence(compId) ?? this.defender.getCompetence(compName)
if (esquive == undefined) {
ui.notifications.error(this.defender.name + " n'a pas de compétence " + compName);
ui.notifications.error(this.defender.getAlias() + " n'a pas de compétence " + compName);
return;
}
console.log("RdDCombat.esquive >>>", attackerRoll, esquive);
@ -1133,8 +1098,11 @@ export class RdDCombat {
/* -------------------------------------------- */
_prepareEsquive(attackerRoll, competence) {
let rollData = {
alias: this.defender.getAlias(),
passeArme: attackerRoll.passeArme,
diffLibre: attackerRoll.diffLibre,
attackerToken: this.attackerToken,
defenderToken: this.defenderToken,
attackerRoll: attackerRoll,
competence: competence,
surprise: this.defender.getSurprise(true),
@ -1152,9 +1120,9 @@ export class RdDCombat {
/* -------------------------------------------- */
_onEsquiveParticuliere(rollData) {
console.log("RdDCombat._onEsquiveParticuliere >>>", rollData);
ChatUtility.createChatWithRollMode(this.defender.name, {
content: "<strong>Vous pouvez esquiver une deuxième fois!</strong>"
});
ChatUtility.createChatWithRollMode(
{ content: "<strong>Vous pouvez esquiver une deuxième fois!</strong>" },
this.defender);
}
/* -------------------------------------------- */
@ -1276,9 +1244,8 @@ export class RdDCombat {
}
/* -------------------------------------------- */
async encaisser(attackerRoll, defenderRoll, defenderTokenId) {
defenderTokenId = defenderTokenId || this.defenderTokenId;
console.log("RdDCombat.encaisser >>>", attackerRoll, defenderTokenId);
async encaisser(attackerRoll, defenderRoll) {
console.log("RdDCombat.encaisser >>>", attackerRoll, defenderRoll);
if (defenderRoll?.rolled && RdDCombat.isEchecTotal(defenderRoll)) {
this._onEchecTotal(defenderRoll);
@ -1286,18 +1253,19 @@ export class RdDCombat {
if (Misc.isOwnerPlayerOrUniqueConnectedGM(this.defender)) {
attackerRoll.attackerId = this.attackerId;
attackerRoll.defenderTokenId = defenderTokenId;
attackerRoll.defenderTokenId = this.defenderToken.id;
await this.computeRecul(defenderRoll);
await this.defender.encaisserDommages(attackerRoll, this.attacker, defenderRoll?.show);
await this.defender.encaisserDommages(attackerRoll, this.attacker, defenderRoll?.show, this.attackerToken, this.defenderToken);
}
else { // envoi à un GM: les joueurs n'ont pas le droit de modifier les personnages qu'ils ne possèdent pas
game.socket.emit(SYSTEM_SOCKET_ID, {
msg: "msg_encaisser",
data: {
attackerId: this.attackerId,
defenderTokenId: defenderTokenId,
attackerRoll: attackerRoll
attackerRoll: attackerRoll,
attackerToken: this.attackerToken,
defenderToken: this.defenderToken
}
});
}
@ -1305,28 +1273,32 @@ export class RdDCombat {
}
/* -------------------------------------------- */
static async displayActorCombatStatus(combat, actor, tokenId) {
let formData = {
static async displayActorCombatStatus(combat, actor, token) {
if (!actor?.isActorCombat()) {
return
}
const alias = token?.name ?? actor.getAlias();
const formData = {
combatId: combat._id,
alias: actor.name,
alias: alias,
etatGeneral: actor.getEtatGeneral(),
isSonne: actor.getSonne(),
isSonne: actor.isSonne(),
blessuresStatus: actor.computeResumeBlessure(),
SConst: actor.getSConst(),
actorId: actor.id,
actor: actor,
tokenId: tokenId,
tokenId: token.id,
isGrave: actor.countBlessures(it => it.isGrave()) > 0,
isCritique: actor.countBlessures(it => it.isCritique()) > 0
}
await ChatMessage.create({
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-turn-acteur.hbs`, formData),
alias: actor.name
});
alias: alias
})
await ChatMessage.create({
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-turn-sante.hbs`, formData),
whisper: ChatUtility.getWhisperRecipientsAndGMs(actor.name),
alias: actor.name
});
whisper: ChatUtility.getOwners(actor),
alias: alias
})
}
}

View File

@ -17,6 +17,7 @@ import { RdDUtility } from "./rdd-utility.js";
import { FenetreRechercheTirage } from "./tirage/fenetre-recherche-tirage.js";
import { TMRUtility } from "./tmr-utility.js";
import { DialogFatigueVoyage } from "./voyage/dialog-fatigue-voyage.js";
import { ChatUtility } from "./chat-utility.js";
const rddRollNumeric = /^(\d+)\s*([\+\-]?\d+)?\s*(s)?/;
@ -206,26 +207,20 @@ export class RdDCommands {
/* Manage chat commands */
processChatCommand(commandLine, content = '', msg = {}) {
// Setup new message's visibility
let rollMode = game.settings.get("core", "rollMode");
if (["gmroll", "blindroll"].includes(rollMode)) {
msg["whisper"] = ChatMessage.getWhisperRecipients("GM");
}
if (rollMode === "blindroll") {
msg["blind"] = true;
}
msg["type"] = 0;
ChatUtility.applyRollMode(msg)
msg.type = 0;
if (!this.commandsTable) {
this._registerCommands();
this._registerCommands()
}
let command = commandLine[0].toLowerCase();
if (this._isCommandHandled(command)) {
let params = commandLine.slice(1);
this._processCommand(this.commandsTable, command, params, content, msg);
return true;
this._processCommand(this.commandsTable, command, params, content, msg)
return true
}
return false;
return false
}
_isCommandHandled(command) {
@ -300,7 +295,7 @@ export class RdDCommands {
async getRencontreTMR(params) {
if (params.length == 1 || params.length == 2) {
return game.system.rdd.rencontresTMR.rollRencontre(params[0], params[1])
}
}
return false;
}
@ -365,13 +360,14 @@ export class RdDCommands {
async getTMRAleatoire(msg, params) {
if (params.length < 2) {
let type = params[0];
const tmr = await TMRUtility.getTMRAleatoire(type ? (it => it.type == type) : (it => true));
return RdDCommands._chatAnswer(msg, `Case aléatoire: ${tmr.coord} - ${tmr.label}`);
}
else {
return false;
let type = params[0]
const solvedTerrain = TMRUtility.findTMRLike(type)?.type
if (solvedTerrain){
const tmr = await TMRUtility.getTMRAleatoire(type ? (it => it.type == solvedTerrain) : (it => true))
return RdDCommands._chatAnswer(msg, `Case aléatoire: ${tmr.coord} - ${tmr.label}`)
}
}
return false;
}
async findTMR(msg, params) {
@ -389,11 +385,11 @@ export class RdDCommands {
async tableRencontres(msg, params) {
if (params && params.length > 0) {
const search = Misc.join(params, ' ');
const solvedTerrain = TMRUtility.findTMRLike(search);
const solvedTerrain = TMRUtility.findTMRLike(search)
if (solvedTerrain == undefined) {
return RdDCommands._chatAnswer(msg, 'Aucune TMR correspondant à ' + search);
}
return await game.system.rdd.rencontresTMR.chatTable(solvedTerrain);
return await game.system.rdd.rencontresTMR.chatTable(solvedTMRType.name)
}
return false;
}

View File

@ -2,19 +2,14 @@ import { ChatUtility } from "./chat-utility.js";
import { HIDE_DICE, SHOW_DICE } from "./constants.js";
import { Misc } from "./misc.js";
function img(src) {
return `<img src="${src}" class="dice-img" />`
}
function iconHeure(heure) {
const imgHeures = [1, 2, 3, 4, 5, 6, 7, 9, 9, 10, 11, 12].map(heure => {
if (heure < 10) {
heure = '0' + heure;
}
return `systems/foundryvtt-reve-de-dragon/icons/heures/hd${heure}.webp`
}
const imagesHeures = [1, 2, 3, 4, 5, 6, 7, 9, 9, 10, 11, 12].map(it => iconHeure(it));
return `<img src="systems/foundryvtt-reve-de-dragon/icons/heures/hd${heure}.webp" class="dice-img" />`
})
const imgSigneDragon = img(imagesHeures[4]);
const imgSigneDragon = imgHeures[4]
/** De pour les jets de rencontre */
export class DeTMR extends Die {
@ -25,7 +20,7 @@ export class DeTMR extends Die {
return {
type: "dt",
font: "HeuresDraconiques",
fontScale: 0.7,
fontScale: 0.8,
labels: ['1', '2', '3', '4', '5', '6', 'd', '0'],
system: system
}
@ -37,13 +32,13 @@ export class DeTMR extends Die {
}
async evaluate(options) {
await super.evaluate(options);
this.explode("x=8");
await super.evaluate(options)
await this.reroll('r=8', { recursive: true })
return this;
}
get total() {
return this.values.filter(it => it != 8).reduce(Misc.sum(), 0);
return this.values.map(it => Misc.modulo(it, 8)).reduce(Misc.sum(), 0);
}
getResultLabel(diceTerm) {
@ -56,13 +51,14 @@ export class DeTMR extends Die {
/** DeDraconique pour le D8 sans limite avec 8=>0 */
export class DeDraconique extends Die {
/** @override */
static DENOMINATION = "r";
static diceSoNiceData(system) {
return {
type: "dr",
font: "HeuresDraconiques",
fontScale: 0.7,
fontScale: 0.8,
labels: ['1', '2', '3', '4', '5', '6', 'd', '0'],
system: system
}
@ -75,7 +71,7 @@ export class DeDraconique extends Die {
async evaluate(options) {
await super.evaluate(options);
this.explode("x=7");
await this.explode("x=7");
return this;
}
@ -85,7 +81,7 @@ export class DeDraconique extends Die {
getResultLabel(diceTerm) {
switch (diceTerm.result) {
case 7: return imgSigneDragon;
case 7: return imgSigneDragon
case 8: return '0';
}
return diceTerm.result.toString();
@ -102,6 +98,7 @@ export class DeHeure extends Die {
return {
type: "dh",
font: "HeuresDraconiques",
fontScale: 1.2,
labels: ['v', 'i', 'f', 'o', 'd', 'e', 'l', 's', 'p', 'a', 'r', 'c'],
system: system
}
@ -113,15 +110,15 @@ export class DeHeure extends Die {
}
getResultLabel(diceTerm) {
return img(imagesHeures[diceTerm.result - 1]);
return imgHeures[diceTerm.result - 1]
}
}
export class RdDDice {
static init() {
CONFIG.Dice.terms[DeTMR.DENOMINATION] = DeTMR;
CONFIG.Dice.terms[DeDraconique.DENOMINATION] = DeDraconique;
CONFIG.Dice.terms[DeHeure.DENOMINATION] = DeHeure;
CONFIG.Dice.terms[DeTMR.DENOMINATION] = DeTMR
CONFIG.Dice.terms[DeDraconique.DENOMINATION] = DeDraconique
CONFIG.Dice.terms[DeHeure.DENOMINATION] = DeHeure
}
static onReady() {
@ -132,6 +129,14 @@ export class RdDDice {
}
}
static diceSoNiceReady(dice3d) {
dice3d.DiceFactory.systems.keys().forEach(system => {
dice3d.addDicePreset(DeTMR.diceSoNiceData(system));
dice3d.addDicePreset(DeDraconique.diceSoNiceData(system));
dice3d.addDicePreset(DeHeure.diceSoNiceData(system));
})
}
static async rollHeure(options = { showDice: HIDE_DICE }) {
return await RdDDice.rollTotal("1dh", options) - 1
}
@ -155,21 +160,13 @@ export class RdDDice {
return array[roll - 1];
}
static diceSoNiceReady(dice3d) {
for (const system of Object.keys(dice3d.DiceFactory.systems)) {
dice3d.addDicePreset(DeTMR.diceSoNiceData(system));
dice3d.addDicePreset(DeDraconique.diceSoNiceData(system));
dice3d.addDicePreset(DeHeure.diceSoNiceData(system));
}
}
/* -------------------------------------------- */
static async showDiceSoNice(roll, options) {
if (options.showDice == HIDE_DICE || !game.modules.get("dice-so-nice")?.active || !game.dice3d) {
return;
}
let { whisper, blind } = RdDDice._getWhisperBlind(options);
let { whisper, blind } = ChatUtility.applyRollMode({}, options?.rollMode);
if (options.forceDiceResult?.total) {
let terms = await RdDDice._getForcedTerms(options);
if (terms) {
@ -223,24 +220,4 @@ export class RdDDice {
await roll.evaluate();
return roll.total;
}
static _getWhisperBlind(options) {
let whisper = undefined;
let blind = false;
let rollMode = options.rollMode ?? game.settings.get("core", "rollMode");
switch (rollMode) {
case "blindroll": //GM only
blind = true;
case "gmroll": //GM + rolling player
whisper = ChatUtility.getUsers(user => user.isGM);
break;
case "roll": //everybody
whisper = ChatUtility.getUsers(user => user.active);
break;
case "selfroll":
whisper = [game.user.id];
break;
}
return { whisper, blind };
}
}

View File

@ -158,9 +158,12 @@ export class RdDEmpoignade {
empoignade = empoignade ?? (await RdDEmpoignade.createEmpoignade(attacker, defender))
//console.log("W.", empoignade, defender.hasArmeeMeleeEquipee())
if ((isNouvelle || empoignade.system.pointsemp == 0) && defender.hasArmeeMeleeEquipee()) {
ChatUtility.createChatWithRollMode(attacker.name, {
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-empoignade-valider.html`, { attacker: attacker, defender: defender })
})
ChatUtility.createChatWithRollMode(
{
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-empoignade-valider.html`, { attacker: attacker, defender: defender })
},
attacker
)
} else {
await this.onAttaqueEmpoignadeValidee(attacker, defender)
}
@ -213,7 +216,7 @@ export class RdDEmpoignade {
competence: attacker.getCompetenceCorpsACorps()
}
const msg = await ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(attacker.name),
whisper: ChatUtility.getOwners(attacker),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-empoignade-immobilise.html`, rollData)
})
RdDEmpoignade.$storeRollEmpoignade(msg, rollData);
@ -300,7 +303,7 @@ export class RdDEmpoignade {
/* -------------------------------------------- */
static async $onRollContrerLiberer(rollData) {
let empoignade = rollData.empoignade
if (rollData.mode == "contrer-empoigner" && !rollData.rolled.isSuccess) {
empoignade.system.pointsemp++
RdDEmpoignade.$updateEtatEmpoignade(empoignade)
@ -309,7 +312,7 @@ export class RdDEmpoignade {
empoignade.system.pointsemp--
RdDEmpoignade.$updateEtatEmpoignade(empoignade)
}
await RdDResolutionTable.displayRollData(rollData, rollData.defender, 'chat-empoignade-resultat.html')
if (empoignade.system.pointsemp >= 2) {
let msg = await RdDResolutionTable.displayRollData(rollData, rollData.attacker, 'chat-empoignade-entrainer.html');

View File

@ -28,9 +28,9 @@ export class RdDHerbes extends Item {
}
/* -------------------------------------------- */
static async addPotionFormData(formData) {
formData.isSoins = formData.system.categorie.includes('Soin');
formData.isRepos = formData.system.categorie.includes('Repos');
static calculFormData(formData, item) {
formData.isSoins = item.system.categorie.includes('Soin');
formData.isRepos = item.system.categorie.includes('Repos');
if (formData.isSoins) {
RdDHerbes.calculBonusHerbe(formData, this.herbesSoins, 12);
}
@ -40,7 +40,7 @@ export class RdDHerbes extends Item {
formData.herbesSoins = RdDHerbes.buildHerbesList(this.herbesSoins, 12);
formData.herbesRepos = RdDHerbes.buildHerbesList(this.herbesRepos, 7);
formData.dateActuelle = game.system.rdd.calendrier.dateCourante();
formData.enchantement = RdDTimestamp.splitIndexDate(formData.system.prdate);
formData.enchantement = RdDTimestamp.splitIndexDate(item.system.prdate);
}
/* -------------------------------------------- */

View File

@ -79,8 +79,7 @@ export class RdDHotbar {
* Actor - open actor sheet
* Journal - open journal sheet
*/
static init() {
static initHooks() {
Hooks.on('hotbarDrop', (bar, documentData, slot) => {
// Create item macro if rollable item - weapon, spell, prayer, trait, or skill
@ -122,7 +121,7 @@ export class RdDHotbar {
if (item.isCorpsACorps()) {
switch (categorieArme) {
case 'pugilat':
return actor.rollArme(RdDItemArme.mainsNues(actor), 'competence');
return actor.rollArme(RdDItemArme.corpsACorps(actor), 'competence');
case 'empoignade':
return actor.rollArme(RdDItemArme.empoignade(actor), 'competence');
}

View File

@ -61,13 +61,15 @@ import { RdDSigneDraconiqueItemSheet } from "./item/sheet-signedraconique.js"
import { RdDItemInventaireSheet } from "./item/sheet-base-inventaire.js"
import { AppAstrologie } from "./sommeil/app-astrologie.js"
import { RdDItemArmure } from "./item/armure.js"
import { AutoAdjustDarkness as AutoAdjustDarkness } from "./time/auto-adjust-darkness.js"
import { AutoAdjustDarkness } from "./time/auto-adjust-darkness.js"
import { RdDCreature } from "./actor/creature.js"
import { RdDTMRDialog } from "./rdd-tmr-dialog.js"
import { OptionsAvancees } from "./settings/options-avancees.js"
import { ExportScriptarium } from "./actor/export-scriptarium/export-scriptarium.js"
import { AppPersonnageAleatoire } from "./actor/random/app-personnage-aleatoire.js"
import { RdDActorExportSheet } from "./actor/export-scriptarium/actor-encart-sheet.js"
import { RdDStatBlockParser } from "./apps/rdd-import-stats.js"
import { RdDItemSort } from "./item-sort.js"
/**
* RdD system
@ -80,13 +82,14 @@ export class SystemReveDeDragon {
const system = new SystemReveDeDragon()
Hooks.once('init', async () => await system.onInit())
Hooks.once('diceSoNiceReady', (dice3d) => RdDDice.diceSoNiceReady(dice3d))
Hooks.once('ready', () => system.onReady())
Hooks.once('ready', async () => await system.onReady())
}
constructor() {
this.config = RDD_CONFIG
this.RdDUtility = RdDUtility
this.RdDHotbar = RdDHotbar
this.RdDStatBlockParser = RdDStatBlockParser
this.itemClasses = {
armure: RdDItemArmure,
blessure: RdDItemBlessure,
@ -115,24 +118,32 @@ export class SystemReveDeDragon {
game.system.rdd = this
this.AppAstrologie = AppAstrologie
console.log(`Initializing Reve de Dragon System`)
console.log(`Initializing Reve de Dragon System Settings`)
// preload handlebars templates
RdDUtility.preloadHandlebarsTemplates()
AppPersonnageAleatoire.preloadHandlebars()
RdDItemSort.preloadHandlebars()
/* -------------------------------------------- */
this.initSystemSettings()
ReglesOptionnelles.initSettings()
OptionsAvancees.initSettings()
AutoAdjustDarkness.initSettings()
RdDTimestamp.initSettings()
RdDCalendrier.initSettings()
SystemCompendiums.initSettings()
DialogChronologie.initSettings()
RdDTMRDialog.initSettings()
Environnement.initSettings()
this.initSettings()
/* -------------------------------------------- */
// Set an initiative formula for the system
CONFIG.Combat.initiative = {
formula: "1+(1d6/10)",
decimals: 2
}
CONFIG.Combat.initiative = { formula: "1+(1d6/10)", decimals: 2 }
/* -------------------------------------------- */
console.log(`Initializing Reve de Dragon Socket handlers`)
game.socket.on(SYSTEM_SOCKET_ID, async (sockmsg) => {
console.log(">>>>> MSG RECV", sockmsg)
try {
@ -147,6 +158,7 @@ export class SystemReveDeDragon {
/* -------------------------------------------- */
// Define custom Entity classes
console.log(`Initializing Reve de Dragon Documents`)
CONFIG.Actor.documentClass = RdDBaseActor
CONFIG.Item.documentClass = RdDItem
CONFIG.RDD = {
@ -192,17 +204,12 @@ export class SystemReveDeDragon {
"tarot", "extraitpoetique", "empoignade"
], makeDefault: true
})
CONFIG.Combat.documentClass = RdDCombatManager
// préparation des différents modules
AutoAdjustDarkness.init()
RdDTimestamp.init()
RdDCalendrier.init()
SystemCompendiums.init()
DialogChronologie.init()
ReglesOptionnelles.init()
OptionsAvancees.init()
RdDUtility.init()
console.log(`Initializing Reve de Dragon Hooks and handlers`)
CONFIG.Combat.documentClass = RdDCombatManager
ChatUtility.init()
RdDUtility.initHooks()
RdDDice.init()
RdDCommands.init()
RdDCombatManager.init()
@ -211,15 +218,14 @@ export class SystemReveDeDragon {
RdDCompendiumOrganiser.init()
EffetsDraconiques.init()
TMRUtility.init()
await RdDTMRDialog.init()
RdDHotbar.init()
RdDHotbar.initHooks()
RdDPossession.init()
TMRRencontres.init()
Environnement.init()
ExportScriptarium.init()
}
initSystemSettings() {
initSettings() {
// TODO: déplacer vers les modules correspondants
game.settings.register(SYSTEM_RDD, "accorder-entite-cauchemar", {
name: "Accorder le rêve aux entités",
hint: "A quel moment les personnages doivent accorder leur rêve aux entités de cauchemar",
@ -270,13 +276,23 @@ export class SystemReveDeDragon {
})
}
static async setupAccueil() {
let exists = game.scenes.find(j => j.name == "Accueil RdD");
if (!exists) {
const scenes = await SystemCompendiums.loadCompendium("foundryvtt-reve-de-dragon.scenes-rdd")
let newDocuments = scenes.filter(i => i.name == "Accueil RdD");
await game.scenes.documentClass.create(newDocuments);
game.scenes.find(i => i.name == "Accueil RdD").activate();
}
}
async onReady() {
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
game.system.rdd.calendrier = new RdDCalendrier()
if (Misc.isUniqueConnectedGM()) {
if (Misc.isFirstConnectedGM()) {
new Migrations().migrate()
this.messageDeBienvenue()
import("https://www.uberwald.me/fvtt_appcount/count-class-ready.js").then(moduleCounter => {
@ -290,6 +306,8 @@ export class SystemReveDeDragon {
StatusEffects.onReady()
RdDHerbes.onReady()
RdDDice.onReady()
RdDStatBlockParser.parseStatBlock()
/* -------------------------------------------- */
/* Affiche/Init le calendrier */
game.system.rdd.calendrier.display()
@ -301,6 +319,8 @@ export class SystemReveDeDragon {
user: game.user.id
})
}
SystemReveDeDragon.setupAccueil()
}
/* -------------------------------------------- */
@ -318,4 +338,3 @@ export class SystemReveDeDragon {
}
SystemReveDeDragon.start()

View File

@ -1,3 +1,4 @@
import { ChatUtility } from "./chat-utility.js"
const vents = [
{ min: 0, max: 0, valeur: 'Calme' },
@ -117,7 +118,7 @@ export class RdDMeteo {
ChatMessage.create({
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-meteo.html', meteo),
whisper: ChatMessage.getWhisperRecipients('GM')
whisper: ChatUtility.getGMs()
});
}

View File

@ -1,4 +1,5 @@
import { RdDBaseActor } from "./actor/base-actor.js";
import { ChatUtility } from "./chat-utility.js";
import { Misc } from "./misc.js";
import { RdDDice } from "./rdd-dice.js";
@ -15,7 +16,7 @@ export class RdDNameGen {
const html = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-command-nom.html`, {
nom: await RdDNameGen.generate()
});
ChatMessage.create({ content: html, whisper: ChatMessage.getWhisperRecipients("GM") });
ChatMessage.create({ content: html, whisper: ChatUtility.getGMs() });
}
static async generate() {

View File

@ -91,13 +91,14 @@ export class RdDResolutionTable {
/* -------------------------------------------- */
static async displayRollData(rollData, actor = undefined, template = 'chat-resultat-general.html') {
return await ChatUtility.createChatWithRollMode(RdDResolutionTable.actorChatName(actor), {
content: await RdDResolutionTable.buildRollDataHtml(rollData, template)
});
return await ChatUtility.createChatWithRollMode(
{ content: await RdDResolutionTable.buildRollDataHtml(rollData, template) },
actor
)
}
static actorChatName(actor) {
return actor?.name ?? game.user.name;
return actor ?? game.user.name;
}
/* -------------------------------------------- */
@ -138,14 +139,14 @@ export class RdDResolutionTable {
if (carac == 0) {
return NaN;
}
if (rolled >= carac){
const upper = Math.ceil(rolled/carac);
return 2*upper -10
if (rolled >= carac) {
const upper = Math.ceil(rolled / carac);
return 2 * upper - 10
}
if (rolled > Math.floor(carac/2)) {
if (rolled > Math.floor(carac / 2)) {
return -8
}
if (rolled > Math.floor(carac/4)) {
if (rolled > Math.floor(carac / 4)) {
return -9
}
if (rolled > 1) {
@ -265,7 +266,7 @@ export class RdDResolutionTable {
}
/* -------------------------------------------- */
static subTable(carac, level, delta = { carac: 2, level: 5}) {
static subTable(carac, level, delta = { carac: 2, level: 5 }) {
return {
carac,
level,
@ -287,8 +288,8 @@ export class RdDResolutionTable {
carac: carac,
difficulte: level,
min: minLevel,
rows: Misc.intArray(minCarac, maxCarac+1),
cols: Misc.intArray(minLevel, maxLevel+1)
rows: Misc.intArray(minCarac, maxCarac + 1),
cols: Misc.intArray(minLevel, maxLevel + 1)
});
}

View File

@ -72,6 +72,6 @@ export class RdDEncaisser extends Dialog {
encaisserSpecial: this.encaisserSpecial,
mortalite: mortalite
}
});
})
}
}

View File

@ -7,6 +7,7 @@ import { RdDBonus } from "./rdd-bonus.js";
import { RdDCarac } from "./rdd-carac.js";
import { RdDResolutionTable } from "./rdd-resolution-table.js";
import { ReglesOptionnelles } from "./settings/regles-optionnelles.js";
import { Grammar } from "./grammar.js";
/**
* Extend the base Dialog entity to select roll parameters
@ -32,7 +33,7 @@ export class RdDRoll extends Dialog {
/* -------------------------------------------- */
static _setDefaultOptions(actor, rollData) {
let defaultRollData = {
alias: actor.name,
alias: actor.getAlias(),
ajustementsConditions: CONFIG.RDD.ajustementsConditions,
difficultesLibres: CONFIG.RDD.difficultesLibres,
etat: actor.getEtatGeneral(),
@ -173,8 +174,8 @@ export class RdDRoll extends Dialog {
this.html.find("[name='diffLibre']").val(this.rollData.diffLibre);
});
this.html.find('.roll-carac-competence').change((event) => {
const competence = event.currentTarget.value;
this.rollData.competence = this.rollData.competences.find(it => it.name == competence);
const competence = event.currentTarget.value
this.rollData.competence = this.rollData.competences.find(it => Grammar.equalsInsensitive(it.name, competence))
this.updateRollResult(html);
});
this.html.find('.select-suivant-coeur').change((event) => {
@ -345,24 +346,25 @@ export class RdDRoll extends Dialog {
/* -------------------------------------------- */
_getTitle(rollData) {
const alias = rollData.alias
const carac = rollData.selectedCarac.label;
if (!rollData.competence) {
return carac;
return `${alias}: ${carac}`
}
const compName = rollData.competence.name;
const niveau = Misc.toSignedString(rollData.competence.system.niveau)
if (compName == carac) {
// cas des créatures
return `${carac} Niveau ${niveau}`
return `${alias}: ${carac} Niveau ${niveau}`
}
if (rollData.draconicList && rollData.selectedSort) {
// cas de lancer de sort
return `${rollData.competence.name} Niveau ${niveau} ${rollData.selectedSort.name}`
return `${alias}: ${rollData.competence.name} Niveau ${niveau} ${rollData.selectedSort.name}`
}
if (rollData.arme && rollData.arme.name != compName) {
// ajouter l'arme au titre si son nom n'est pas la compétence
return `${carac} / ${compName} (${rollData.arme.name}) Niveau ${niveau}`
return `${alias}: ${carac} / ${compName} (${rollData.arme.name}) Niveau ${niveau}`
}
return `${carac} / ${compName} Niveau ${niveau}`
return `${alias}: ${carac} / ${compName} Niveau ${niveau}`
}
}

View File

@ -1,4 +1,3 @@
import { Grammar } from "./grammar.js";
import { CompendiumTable, CompendiumTableHelpers, SystemCompendiums } from "./settings/system-compendiums.js";
export class RdDRollTables {

View File

@ -34,7 +34,7 @@ const TMR_DISPLAY_SIZE = {
/* -------------------------------------------- */
export class RdDTMRDialog extends Dialog {
static async init() {
static initSettings() {
game.settings.register(SYSTEM_RDD, TMR_DISPLAY_SIZE.code, {
name: 'Taille des cases des TMR',
hint: "Taille en pixel des cases des TMR (réglable directement dans la fenêtre des TMR)",
@ -50,7 +50,7 @@ export class RdDTMRDialog extends Dialog {
await PixiTMR.init()
let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-tmr.html', tmrData);
if (tmrData.mode != 'visu' && !game.user.isGM) {
ChatMessage.create({ content: actor.name + " est monté dans les TMR en mode : " + tmrData.mode, whisper: ChatMessage.getWhisperRecipients("GM") });
ChatMessage.create({ content: actor.name + " est monté dans les TMR en mode : " + tmrData.mode, whisper: ChatUtility.getGMs() });
}
return new RdDTMRDialog(html, actor, tmrData)
}
@ -82,7 +82,7 @@ export class RdDTMRDialog extends Dialog {
this.rencontreState = 'aucune';
this.subdialog = undefined
this.displaySize = undefined
if (!this.viewOnly) {
if (!this.viewOnly && !game.user.isGM) {
this._tellToGM(this.actor.name + " monte dans les terres médianes (" + tmrData.mode + ")");
}
this.callbacksOnAnimate = [];
@ -460,7 +460,7 @@ export class RdDTMRDialog extends Dialog {
let rencontreData = {
actor: this.actor,
alias: this.actor.name,
alias: this.actor.getAlias(),
reveDepart: this.actor.getReveActuel(),
competence: this.actor.getBestDraconic(),
rencontre: this.currentRencontre,
@ -496,7 +496,7 @@ export class RdDTMRDialog extends Dialog {
rencData.message = this.formatMessageRencontre(rencData, result.message);
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
whisper: ChatUtility.getOwners(this.actor),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-rencontre-tmr.html`, rencData)
});
@ -571,12 +571,20 @@ export class RdDTMRDialog extends Dialog {
/* -------------------------------------------- */
_tellToGM(message) {
ChatMessage.create({ content: message, user: game.user.id, whisper: ChatMessage.getWhisperRecipients("GM") });
ChatMessage.create({
user: game.user.id,
content: message,
whisper: ChatUtility.getGMs()
});
}
/* -------------------------------------------- */
_tellToUserAndGM(message) {
ChatMessage.create({ content: message, user: game.user.id, whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients("GM")) });
ChatMessage.create({
user: game.user.id,
content: message,
whisper: ChatUtility.getUserAndGMs()
})
}
/* -------------------------------------------- */
@ -633,7 +641,7 @@ export class RdDTMRDialog extends Dialog {
// simuler une rencontre
let rencontreData = {
actor: this.actor,
alias: this.actor.name,
alias: this.actor.getAlias(),
reveDepart: this.actor.getReveActuel(),
competence: this.actor.getBestDraconic(),
rencontre: this.currentRencontre,
@ -715,7 +723,7 @@ export class RdDTMRDialog extends Dialog {
}
rollData.poesie = await Poetique.getExtrait();
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
whisper: ChatUtility.getOwners(this.actor),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-resultat-maitrise-tmr.html`, rollData)
});
if (rollData.rolled.isEchec) {
@ -739,7 +747,7 @@ export class RdDTMRDialog extends Dialog {
if (this.isCaseMaitrisee(tmr.coord)) {
ChatMessage.create({
content: tmr.label + ": cette case humide est déja maitrisée grâce à votre Tête <strong>Quête des Eaux</strong>",
whisper: ChatMessage.getWhisperRecipients(game.user.name)
whisper: ChatUtility.getOwners(this.actor)
});
return false;
}
@ -751,14 +759,14 @@ export class RdDTMRDialog extends Dialog {
if (tmr.type == 'pont' && EffetsDraconiques.isPontImpraticable(this.actor)) {
ChatMessage.create({
content: tmr.label + ": Vous êtes sous le coup d'une Impraticabilité des Ponts : ce pont doit être maîtrisé comme une case humide.",
whisper: ChatMessage.getWhisperRecipients(game.user.name)
whisper: ChatUtility.getOwners(this.actor)
});
return true;
}
if (this.isCaseInondee(tmr.coord)) {
ChatMessage.create({
content: tmr.label + ": cette case est inondée, elle doit être maîtrisée comme une case humide.",
whisper: ChatMessage.getWhisperRecipients(game.user.name)
whisper: ChatUtility.getOwners(this.actor)
});
return true;
}
@ -832,7 +840,7 @@ export class RdDTMRDialog extends Dialog {
}
rollData.poesie = await Poetique.getExtrait();
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
whisper: ChatUtility.getOwners(this.actor),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-resultat-maitrise-tmr.html`, rollData)
});
if (rollData.rolled.isEchec) {
@ -882,17 +890,16 @@ export class RdDTMRDialog extends Dialog {
const reserveSecurite = EffetsDraconiques.isReserveEnSecurite(this.actor);
const reserveExtensible = this.isReserveExtensible(coord);
if (!EffetsDraconiques.isUrgenceDraconique(this.actor) && (reserveSecurite || reserveExtensible)) {
const msg = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-demande-declencher-sort.hbs`, {
actor: this.actor,
sorts: sorts,
coord: coord,
tete: { reserveSecurite: reserveSecurite, reserveExtensible: reserveExtensible }
})
ChatMessage.create({
content: msg,
whisper: ChatMessage.getWhisperRecipients(game.user.name)
});
return;
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-demande-declencher-sort.hbs`, {
actor: this.actor,
sorts: sorts,
coord: coord,
tete: { reserveSecurite: reserveSecurite, reserveExtensible: reserveExtensible }
}),
whisper: ChatUtility.getOwners(this.actor)
})
return
}
await this.processSortReserve(sorts[0]);
}
@ -906,9 +913,8 @@ export class RdDTMRDialog extends Dialog {
this.processSortReserve(sort);
} else {
ChatMessage.create({
content:
"Une erreur est survenue : impossible de récupérer le sort en réserve demandé.",
whisper: ChatMessage.getWhisperRecipients(game.user.name),
content: "Une erreur est survenue : impossible de récupérer le sort en réserve demandé.",
whisper: ChatUtility.getOwners(this.actor)
});
}
}
@ -1091,8 +1097,11 @@ export class RdDTMRDialog extends Dialog {
async notifierResonanceSigneDraconique(coord) {
if (!this.viewOnly && this.actor.isResonanceSigneDraconique(coord)) {
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-signe-draconique-resonance.html`, { alias: this.actor.name, typeTMR: TMRUtility.getTMRType(coord) })
whisper: ChatUtility.getOwners(this.actor),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-signe-draconique-resonance.html`, {
alias: this.actor.getAlias(),
typeTMR: TMRUtility.getTMRType(coord)
})
});
}
}

View File

@ -28,16 +28,15 @@ export class RdDTokenHud {
await RdDTokenHud.addExtensionHudSoins(html, actor);
if (isCombat) {
let combatant = game.combat.combatants.find(c => c.tokenId == tokenId);
if (!(combatant?.actor)) {
ui.notifications.warn(`Le combatant ${token.name} n'est pas associé à un acteur, impossible de déterminer ses actions de combat!`)
return;
const combatant = game.combat.combatants.find(c => c.tokenId == tokenId)
const actor = RdDCombatManager.getActorCombatant(combatant, { warning: false })
if (actor) {
let actions = RdDCombatManager.listActionsActorCombatant(actor)
// initiative
await RdDTokenHud.addExtensionHudInit(html, combatant, actions)
// combat
await RdDTokenHud.addExtensionHudCombat(html, combatant, token, actions)
}
let actions = RdDCombatManager.listActionsCombat(combatant);
// initiative
await RdDTokenHud.addExtensionHudInit(html, combatant, actions);
// combat
await RdDTokenHud.addExtensionHudCombat(html, combatant, actions);
}
@ -68,8 +67,8 @@ export class RdDTokenHud {
});
}
static async addExtensionHudCombat(html, combatant, actions) {
const hudData = { combatant, actions, commandes: [] };
static async addExtensionHudCombat(html, combatant, token, actions) {
const hudData = { combatant, token, actions, commandes: [] };
const controlIconTarget = html.find('.control-icon[data-action=target]');
await RdDTokenHud._configureSubMenu(controlIconTarget, 'systems/foundryvtt-reve-de-dragon/templates/hud-actor-attaque.html', hudData,
(event) => {
@ -80,7 +79,7 @@ export class RdDTokenHud {
combatant.actor.conjurerPossession(possession);
}
else {
combatant.actor.rollArme(action);
combatant.actor.rollArme(action, 'competence', token)
}
});
}

View File

@ -99,9 +99,7 @@ export class RdDUtility {
// persistent handling of conteneur show/hide
static afficheContenu = {}
/* -------------------------------------------- */
static async init() {
Hooks.on("renderChatMessage", async (app, html, msg) => await ChatUtility.onRenderChatMessage(app, html, msg))
Hooks.on("createChatMessage", async (chatMessage, options, id) => await ChatUtility.onCreateChatMessage(chatMessage, options, id))
static async initHooks() {
Hooks.on('renderChatLog', (log, html, chatLog) => RdDUtility.chatListeners(html))
}
@ -292,7 +290,7 @@ export class RdDUtility {
Handlebars.registerHelper('array-includes', (array, value) => array.includes(value));
Handlebars.registerHelper('min', (...args) => Math.min(...args.slice(0, -1)));
Handlebars.registerHelper('isLastIndex', (index, list) => index+1 >= list.length);
Handlebars.registerHelper('isLastIndex', (index, list) => index + 1 >= list.length);
Handlebars.registerHelper('regle-optionnelle', (option) => ReglesOptionnelles.isUsing(option));
Handlebars.registerHelper('trier', list => list.sort((a, b) => a.name.localeCompare(b.name)));
Handlebars.registerHelper('filtreTriCompetences', competences => RdDItemCompetence.triVisible(competences));
@ -565,14 +563,14 @@ export class RdDUtility {
}
/* -------------------------------------------- */
static async jetEncaissement(rollData, armure, options = { showDice: HIDE_DICE }) {
static async jetEncaissement(actor, rollData, armure, options = { showDice: HIDE_DICE }) {
const diff = Math.abs(rollData.diffLibre);
let formula = RdDUtility.formuleEncaissement(diff, options)
const roll = await RdDDice.roll(formula, options);
RdDUtility.remplaceDeMinParDifficulte(roll, diff, options);
return await RdDUtility.prepareEncaissement(rollData, roll, armure);
return await RdDUtility.prepareEncaissement(actor, rollData, roll, armure);
}
static remplaceDeMinParDifficulte(roll, diff, options) {
@ -604,15 +602,20 @@ export class RdDUtility {
}
/* -------------------------------------------- */
static async prepareEncaissement(rollData, roll, armure) {
static async prepareEncaissement(actor, rollData, roll, armure) {
// La difficulté d'ataque s'ajoute aux dégâts
const bonusDegatsDiffLibre = ReglesOptionnelles.isUsing('degat-ajout-malus-libre') ? Math.abs(rollData.diffLibre ?? 0) : 0
const jetTotal = roll.total + rollData.dmg.total - armure + bonusDegatsDiffLibre
const encaissement = RdDUtility._selectEncaissement(jetTotal, rollData.dmg.mortalite);
const over20 = Math.max(jetTotal - 20, 0);
encaissement.dmg = rollData.dmg;
encaissement.dmg.loc = rollData.dmg.loc ?? await RdDUtility.getLocalisation(this.type);
encaissement.dmg.loc.label = encaissement.dmg.loc.label ?? 'Corps;';
encaissement.dmg = rollData.dmg
if (ReglesOptionnelles.isUsing('localisation-aleatoire')) {
encaissement.dmg.loc = rollData.dmg.loc ?? await RdDUtility.getLocalisation(actor.type)
encaissement.dmg.loc.label = encaissement.dmg.loc.label ?? 'Corps;'
}
else {
encaissement.dmg.loc = { label: '' }
}
encaissement.dmg.bonusDegatsDiffLibre = bonusDegatsDiffLibre
encaissement.roll = roll;
encaissement.armure = armure;
@ -645,18 +648,16 @@ export class RdDUtility {
/* -------------------------------------------- */
static onSocketMessage(sockmsg) {
switch (sockmsg.msg) {
case "msg_gm_chat_message":
return ChatUtility.handleGMChatMessage(sockmsg.data);
case "msg_app_astrologie_refresh":
return Hooks.callAll(APP_ASTROLOGIE_REFRESH);
return Hooks.callAll(APP_ASTROLOGIE_REFRESH)
case "msg_request_nombre_astral":
return game.system.rdd.calendrier.requestNombreAstral(sockmsg.data);
return game.system.rdd.calendrier.requestNombreAstral(sockmsg.data)
case "msg_tmr_move":
let actor = game.actors.get(sockmsg.data.actorId);
if (actor.isOwner || game.user.isGM) {
actor.refreshTMRView();
actor.refreshTMRView()
}
break;
break
}
}
@ -745,6 +746,15 @@ export class RdDUtility {
return undefined;
}
static getSelectedToken(actor) {
if (canvas.tokens.controlled.length > 0) {
const tokens = canvas.tokens.controlled
.filter(it => it.actor.id == actor.id)
return tokens[0]
}
return undefined
}
static getSelectedActor(msgPlayer = undefined) {
if (canvas.tokens.controlled.length == 1) {
let token = canvas.tokens.controlled[0];
@ -808,17 +818,13 @@ export class RdDUtility {
user: game.user.id,
rollMode: modeOverride || game.settings.get("core", "rollMode"),
content: content
};
if (["gmroll", "blindroll"].includes(chatData.rollMode)) chatData["whisper"] = ChatMessage.getWhisperRecipients("GM").map(u => u.id);
if (chatData.rollMode === "blindroll") chatData["blind"] = true;
else if (chatData.rollMode === "selfroll") chatData["whisper"] = [game.user];
}
ChatUtility.applyRollMode(chatData)
if (forceWhisper) { // Final force !
chatData["speaker"] = ChatMessage.getSpeaker();
chatData["whisper"] = ChatMessage.getWhisperRecipients(forceWhisper);
chatData.speaker = ChatMessage.getSpeaker();
chatData.whisper = ChatMessage.getWhisperRecipients(forceWhisper);
}
return chatData;
}
@ -875,13 +881,13 @@ export class RdDUtility {
/* -------------------------------------------- */
static afficherHeuresChanceMalchance(heureNaissance) {
if (game.user.isGM) {
const heure = RdDTimestamp.findHeure(heureNaissance - 1);
const heure = RdDTimestamp.findHeure(heureNaissance)
if (heureNaissance && heure) {
let ajustement = game.system.rdd.calendrier.getAjustementAstrologique(heureNaissance);
const current = game.system.rdd.calendrier.heureCourante();
const ajustement = game.system.rdd.calendrier.getAjustementAstrologique(heureNaissance)
const current = game.system.rdd.calendrier.heureCourante()
ChatMessage.create({
content: `A l'heure de <strong>${current.label}</strong>, le modificateur de Chance/Malchance est de <strong>${Misc.toSignedString(ajustement)}</strong> pour l'heure de naissance <strong>${heure.label}</strong>.`,
whisper: ChatMessage.getWhisperRecipients("GM")
whisper: ChatUtility.getGMs()
});
}
else if (heureNaissance) {
@ -900,7 +906,7 @@ export class RdDUtility {
if (compName.includes('Thanatos')) {
let message = "Vous avez mis des points d'Expérience dans la Voie de Thanatos !<br>Vous devez réduire manuellement d'un même montant d'XP une autre compétence Draconique.";
ChatMessage.create({
whisper: ChatMessage.getWhisperRecipients(game.user.name),
whisper: ChatUtility.getUserAndGMs(),
content: message
});
}

View File

@ -8,7 +8,7 @@ const OPTIONS_AVANCEES = [
]
export class OptionsAvancees extends FormApplication {
static init() {
static initSettings() {
for (const regle of OPTIONS_AVANCEES) {
const name = regle.name
const id = OptionsAvancees._getId(name)

View File

@ -11,6 +11,7 @@ const listeReglesOptionnelles = [
{ group: 'Récupération', name: 'recuperation-moral', descr: "Le moral revient vers 0 durant Château Dormant"},
{ group: 'Règles de combat', name: 'localisation-aleatoire', descr: "Proposer une localisation aléatoire des blessures" },
{ group: 'Règles de combat', name: 'recul', descr: "Appliquer le recul en cas de particulière en force ou de charge" },
{ group: 'Règles de combat', name: 'resistanceArmeParade', descr: "Faire le jet de résistance des armes lors de parades pouvant les endommager" },
{ group: 'Règles de combat', name: 'deteriorationArmure', descr: "Tenir compte de la détérioration des armures" },
@ -46,7 +47,7 @@ const listeReglesOptionnelles = [
const uniquementJoueur = listeReglesOptionnelles.filter(it => it.uniquementJoueur).map(it=>it.name);
export class ReglesOptionnelles extends FormApplication {
static init() {
static initSettings() {
for (const regle of listeReglesOptionnelles) {
const name = regle.name;
const id = ReglesOptionnelles._getIdRegle(name);

View File

@ -15,17 +15,17 @@ export const STATUSES = {
}
const rddStatusEffects = [
{ rdd: true, id: STATUSES.StatusStunned, label: 'EFFECT.StatusStunned', icon: 'icons/svg/stoned.svg', "duration.rounds": 1 },
{ rdd: true, id: STATUSES.StatusBleeding, label: 'EFFECT.StatusBleeding', icon: 'icons/svg/blood.svg' },
{ rdd: true, id: STATUSES.StatusProne, label: 'EFFECT.StatusProne', icon: 'icons/svg/falling.svg' },
{ rdd: true, id: STATUSES.StatusGrappling, tint: '#33cc33', label: 'EFFECT.StatusGrappling', icon: 'systems/foundryvtt-reve-de-dragon/icons/empoignade.webp' },
{ rdd: true, id: STATUSES.StatusGrappled, tint: '#ff9900', label: 'EFFECT.StatusGrappled', icon: 'systems/foundryvtt-reve-de-dragon/icons/empoignade.webp' },
{ rdd: true, id: STATUSES.StatusRestrained, label: 'EFFECT.StatusRestrained', icon: 'icons/svg/net.svg' },
{ rdd: true, id: STATUSES.StatusUnconscious, label: 'EFFECT.StatusUnconscious', icon: 'icons/svg/unconscious.svg' },
{ rdd: true, id: STATUSES.StatusBlind, label: 'EFFECT.StatusBlind', icon: 'icons/svg/blind.svg' },
{ rdd: true, id: STATUSES.StatusComma, label: 'EFFECT.StatusComma', icon: 'icons/svg/skull.svg' },
{ rdd: true, id: STATUSES.StatusDead, label: 'EFFECT.StatusDead', icon: 'icons/svg/skull.svg' },
{ rdd: true, id: STATUSES.StatusDemiReve, label: 'EFFECT.StatusDemiReve', icon: 'systems/foundryvtt-reve-de-dragon/icons/heures/hd12.svg' }
{ rdd: true, id: STATUSES.StatusStunned, name: 'EFFECT.StatusStunned', img: 'icons/svg/stoned.svg', "duration.rounds": 1 },
{ rdd: true, id: STATUSES.StatusBleeding, name: 'EFFECT.StatusBleeding', img: 'icons/svg/blood.svg' },
{ rdd: true, id: STATUSES.StatusProne, name: 'EFFECT.StatusProne', img: 'icons/svg/falling.svg' },
{ rdd: true, id: STATUSES.StatusGrappling, tint: '#33cc33', name: 'EFFECT.StatusGrappling', img: 'systems/foundryvtt-reve-de-dragon/icons/empoignade.webp' },
{ rdd: true, id: STATUSES.StatusGrappled, tint: '#ff9900', name: 'EFFECT.StatusGrappled', img: 'systems/foundryvtt-reve-de-dragon/icons/empoignade.webp' },
{ rdd: true, id: STATUSES.StatusRestrained, name: 'EFFECT.StatusRestrained', img: 'icons/svg/net.svg' },
{ rdd: true, id: STATUSES.StatusUnconscious, name: 'EFFECT.StatusUnconscious', img: 'icons/svg/unconscious.svg' },
{ rdd: true, id: STATUSES.StatusBlind, name: 'EFFECT.StatusBlind', img: 'icons/svg/blind.svg' },
{ rdd: true, id: STATUSES.StatusComma, name: 'EFFECT.StatusComma', img: 'icons/svg/skull.svg' },
{ rdd: true, id: STATUSES.StatusDead, name: 'EFFECT.StatusDead', img: 'icons/svg/skull.svg' },
{ rdd: true, id: STATUSES.StatusDemiReve, name: 'EFFECT.StatusDemiReve', img: 'systems/foundryvtt-reve-de-dragon/icons/heures/hd12.svg' }
];
const demiReveStatusEffect = rddStatusEffects.find(it => it.id == STATUSES.StatusDemiReve);
@ -57,7 +57,7 @@ export class StatusEffects extends FormApplication {
restricted: true
});
CONFIG.RDD.allEffects = rddStatusEffects.concat(CONFIG.statusEffects.filter(it => !rddEffectIds.includes(it.id)));
CONFIG.RDD.allEffects = rddStatusEffects.concat(CONFIG.statusEffects.filter(it => !rddEffectIds.includes(it.id)))
StatusEffects._setUseStatusEffects(StatusEffects._getUseStatusEffects());
console.log('statusEffects', CONFIG.statusEffects);

View File

@ -25,14 +25,14 @@ const CONFIGURABLE_COMPENDIUMS = {
* ======= Gestion des accès aux compendiums systèmes (ou surchargés) =======
*/
export class SystemCompendiums extends FormApplication {
static init() {
static initSettings() {
Object.keys(CONFIGURABLE_COMPENDIUMS).forEach(compendium => {
const definition = CONFIGURABLE_COMPENDIUMS[compendium];
foundry.utils.mergeObject(definition, {
compendium: compendium,
default: SystemCompendiums._getDefaultCompendium(compendium),
setting: SystemCompendiums._getSettingCompendium(compendium)
});
})
game.settings.register(SYSTEM_RDD, definition.setting, {
name: definition.label,
@ -40,8 +40,8 @@ export class SystemCompendiums extends FormApplication {
scope: "world",
config: false,
type: String
});
});
})
})
game.settings.registerMenu(SYSTEM_RDD, "compendium-settings", {
name: "Choisir les compendiums système",
@ -71,16 +71,18 @@ export class SystemCompendiums extends FormApplication {
static async getCompetences(actorType) {
switch (actorType ?? 'personnage') {
case 'personnage': return await SystemCompendiums.getWorldOrCompendiumItems('competence', 'competences');
case 'creature': return await SystemCompendiums.getWorldOrCompendiumItems('competencecreature', 'competences-creatures');
case 'entite': return await SystemCompendiums.getWorldOrCompendiumItems('competencecreature', 'competences-entites');
case 'personnage':
return await SystemCompendiums.getWorldOrCompendiumItems('competence', 'competences')
case 'entite':
case 'creature':
return await SystemCompendiums.getWorldOrCompendiumItems('competencecreature', 'competences-creatures')
case 'vehicule': return [];
}
}
/* -------------------------------------------- */
static async getWorldOrCompendiumItems(itemType, compendium) {
let items = game.items.filter(it => it.type == itemType);
let items = game.items.filter(it => it.type == itemType)
if (compendium) {
const ids = items.map(it => it.id);
const names = items.map(it => it.name.toLowerCase());
@ -112,6 +114,19 @@ export class SystemCompendiums extends FormApplication {
return elements;
}
/* -------------------------------------------- */
static async loadCompendiumData(compendium) {
const pack = game.packs.get(compendium);
return await pack?.getDocuments() ?? [];
}
/* -------------------------------------------- */
static async loadCompendium(compendium, filter = item => true) {
let compendiumData = await SystemCompendiums.loadCompendiumData(compendium);
return compendiumData.filter(filter);
}
static async getDefaultItems(compendium) {
const pack = game.packs.get(SystemCompendiums._getDefaultCompendium(compendium));
if (pack.metadata.type == 'Item') {
@ -236,12 +251,12 @@ export class CompendiumTableHelpers {
let max = 0;
const total = rows.map(it => it.frequence).reduce(Misc.sum(), 0);
return rows.map(row => {
const frequence = row.frequence;
row.min = max + 1;
row.max = max + frequence;
const frequence = row.frequence
row.min = max + 1
row.max = max + frequence
row.total = total
max += frequence;
return row;
max += frequence
return row
})
}
static async getRandom(table, type, subTypes = ['objet'], forcedRoll = undefined, localisation = undefined) {
@ -260,8 +275,8 @@ export class CompendiumTableHelpers {
}
const total = table[0].total;
const formula = `1d${total}`;
if (forcedRoll == undefined && (forcedRoll > total || forcedRoll <= 0)) {
ui.notifications.warn(`Jet de rencontre ${forcedRoll} en dehors de la table [1..${total}], le jet est relancé`);
if (forcedRoll != undefined && (forcedRoll > total || forcedRoll <= 0)) {
ui.notifications.warn(`Jet forcé ${forcedRoll} en dehors de la table [1..${total}], le jet est relancé`);
forcedRoll = undefined;
}
const roll = forcedRoll ? { total: forcedRoll, formula } : await RdDDice.roll(formula, { showDice: HIDE_DICE });
@ -271,32 +286,29 @@ export class CompendiumTableHelpers {
}
/* -------------------------------------------- */
static async tableRowToChatMessage(row, type = 'Item') {
static async tableRowToChatMessage(row, type, options = { showSource: true }) {
if (!row) {
return;
}
const percentages = (row.total == 100) ? '%' : ''
const flavorContent = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-compendium-table-roll.html', {
const flavorContent = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-compendium-table-roll.hbs', {
roll: row.roll,
document: row.document,
percentages,
typeName: Misc.typeName(type, row.document?.type ?? 'objet'),
isGM: game.user.isGM,
options
});
const messageData = {
// flavor: flavorContent,
user: game.user.id,
type: CONST.CHAT_MESSAGE_TYPES.ROLL,
roll: row.roll,
rolls: [row.roll],
sound: CONFIG.sounds.dice,
content: flavorContent
};
await ChatUtility.createChatWithRollMode(game.user.id, messageData)
await ChatUtility.createChatWithRollMode(messageData)
}
/* -------------------------------------------- */
static async tableToChatMessage(table, type, subTypes, typeName = undefined) {
const flavorContent = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-compendium-table.html', {
const flavorContent = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-compendium-table.hbs', {
img: RdDItem.getDefaultImg(subTypes[0]),
typeName: typeName ?? Misc.typeName(type, subTypes[0]),
table,
@ -304,10 +316,10 @@ export class CompendiumTableHelpers {
});
const messageData = {
user: game.user.id,
whisper: game.user.id,
whisper: [game.user],
content: flavorContent
};
await ChatUtility.createChatWithRollMode(game.user.id, messageData)
await ChatUtility.createChatWithRollMode(messageData)
}
}

View File

@ -186,7 +186,7 @@ export class AppAstrologie extends Application {
date: this.html.find('[name="joursAstrologie"]').val(),
userId: game.user.id
}
if (Misc.isUniqueConnectedGM()) {
if (Misc.isFirstConnectedGM()) {
game.system.rdd.calendrier.requestNombreAstral(socketData);
} else {
game.socket.emit(SYSTEM_SOCKET_ID, {

View File

@ -13,18 +13,21 @@ export class Targets {
static extractTokenData(target) {
return { id: target?.id, name: target?.document.name, img: target?.document.texture.src ?? target?.actor.img ?? 'icons/svg/mystery-man.svg' };
}
static buildActorTokenData(tokenId, actor) {
return { id: tokenId, name: actor.name, img: actor.img ?? 'icons/svg/mystery-man.svg' };
}
static isTargetEntite(target) {
return target?.actor.type == 'entite' && target?.actor.system.definition.typeentite == ENTITE_NONINCARNE;
}
static async selectOneToken(onSelectTarget = target => { }) {
const targets = Targets.listTargets();
static async selectOneTargetToken(onSelectTarget = target => { }) {
const targets = Targets.listTargets()
switch (targets.length) {
case 0: return;
case 0:
return
case 1:
onSelectTarget(targets[0]);
return;
onSelectTarget(targets[0])
return
default:
{
const selectData = {
@ -32,7 +35,7 @@ export class Targets {
label: "Choisir une seule des cibles",
list: targets.map(it => Targets.extractTokenData(it))
};
DialogSelect.select(selectData, onSelectTarget);
DialogSelect.select(selectData, onSelectTarget)
}
}
}

View File

@ -4,7 +4,7 @@ export const AUTO_ADJUST_DARKNESS = "auto-adjust-darkness";
export class AutoAdjustDarkness {
static init() {
static initSettings() {
game.settings.register(SYSTEM_RDD, AUTO_ADJUST_DARKNESS, {
name: AUTO_ADJUST_DARKNESS,
scope: "world",
@ -15,7 +15,7 @@ export class AutoAdjustDarkness {
}
static async adjust(darkness) {
if (AutoAdjustDarkness.isAuto()) {
if (game.user.isGM && AutoAdjustDarkness.isAuto()) {
const scene = game.scenes.viewed;
if (scene?.environment?.globalLight?.enabled && scene?.tokenVision) {
await scene.update({ darkness });

View File

@ -15,7 +15,7 @@ const TEMPLATE_CALENDRIER = "systems/foundryvtt-reve-de-dragon/templates/time/ca
const INITIAL_CALENDAR_POS = { top: 200, left: 200, horlogeAnalogique: true };
/* -------------------------------------------- */
export class RdDCalendrier extends Application {
static init() {
static initSettings() {
game.settings.register(SYSTEM_RDD, "liste-nombre-astral", {
name: "liste-nombre-astral",
scope: "world",
@ -48,7 +48,7 @@ export class RdDCalendrier extends Application {
constructor() {
super();
this.timestamp = RdDTimestamp.getWorldTime();
if (Misc.isUniqueConnectedGM()) { // Uniquement si GM
if (Misc.isFirstConnectedGM()) { // Uniquement si GM
RdDTimestamp.setWorldTime(this.timestamp);
this.rebuildNombresAstraux(); // Ensure always up-to-date
}
@ -258,7 +258,7 @@ export class RdDCalendrier extends Application {
/* -------------------------------------------- */
async rebuildNombresAstraux() {
if (Misc.isUniqueConnectedGM()) {
if (Misc.isFirstConnectedGM()) {
const nombresAstraux = this.getNombresAstraux()
let newNombresAstraux = [];
for (let i = 0; i < MAX_NOMBRE_ASTRAL; i++) {
@ -337,7 +337,7 @@ export class RdDCalendrier extends Application {
/* -------------------------------------------- */
async requestNombreAstral(request) {
const actor = game.actors.get(request.id);
if (Misc.isUniqueConnectedGM()) { // Only once
if (Misc.isFirstConnectedGM()) { // Only once
console.log(request);
let jourDiff = this.getLectureAstrologieDifficulte(request.date);
let niveau = Number(request.astrologie.system.niveau) + Number(request.conditions) + Number(jourDiff) + Number(request.etat);

View File

@ -49,7 +49,7 @@ const FORMULES_PERIODE = [
export class RdDTimestamp {
static init() {
static initSettings() {
game.settings.register(SYSTEM_RDD, WORLD_TIMESTAMP_SETTING, {
name: WORLD_TIMESTAMP_SETTING,
scope: "world",
@ -160,15 +160,21 @@ export class RdDTimestamp {
}
static findHeure(heure) {
heure = Grammar.toLowerCaseNoAccentNoSpace(heure);
let parHeureOuLabel = DEFINITION_HEURES.filter(it => Grammar.toLowerCaseNoAccentNoSpace(it.label) == heure || it.heure == Misc.modulo(parseInt(heure), RDD_HEURES_PAR_JOUR));
if (parHeureOuLabel.length == 1) {
return parHeureOuLabel[0];
let filtered
if (Number.isInteger(Number(heure))) {
filtered = DEFINITION_HEURES.filter(it => it.heure == Misc.modulo(Number(heure) - 1, RDD_HEURES_PAR_JOUR))
}
let parLabelPartiel = DEFINITION_HEURES.filter(it => Grammar.toLowerCaseNoAccentNoSpace(it.label).includes(heure));
if (parLabelPartiel.length > 0) {
parLabelPartiel.sort(Misc.ascending(h => h.label.length));
return parLabelPartiel[0];
else {
heure = Grammar.toLowerCaseNoAccentNoSpace(heure);
filtered = DEFINITION_HEURES.filter(it => Grammar.toLowerCaseNoAccentNoSpace(it.label) == heure || it.heure == Misc.modulo(parseInt(heure), RDD_HEURES_PAR_JOUR));
}
if (filtered.length == 1) {
return filtered[0]
}
filtered = DEFINITION_HEURES.filter(it => Grammar.toLowerCaseNoAccentNoSpace(it.label).includes(heure));
if (filtered.length > 0) {
filtered.sort(Misc.ascending(h => h.label.length));
return filtered[0]
}
return undefined;
}

View File

@ -26,22 +26,19 @@ export class TMRRencontres {
* @param {*} forcedRoll
*/
async rollRencontre(terrain, forcedRoll) {
terrain = TMRUtility.findTMRLike(terrain);
if (terrain == undefined) {
const tmrType = TMRUtility.findTMRLike(terrain)?.type
if (tmrType == undefined) {
return undefined;
}
if (forcedRoll && (forcedRoll <= 0 || forcedRoll > 100)) {
forcedRoll = undefined;
}
const codeTerrain = Grammar.toLowerCaseNoAccent(terrain)
const filtreMauvaise = codeTerrain == 'mauvaise' ? it => it.system.mauvaiseRencontre : it => !it.system.mauvaiseRencontre;
const frequence = it => it.system.frequence[codeTerrain];
const filtreMauvaise = tmrType == 'mauvaise' ? it => it.system.mauvaiseRencontre : it => !it.system.mauvaiseRencontre;
const frequence = it => it.system.frequence[tmrType];
const row = await this.table.getRandom(frequence, filtreMauvaise, forcedRoll);
if (row) {
console.log("DORM", row);
//row.document.system.computedForce = new Roll(row.document.system.formula).roll({async: false}).total;
await CompendiumTableHelpers.tableRowToChatMessage(row);
await CompendiumTableHelpers.tableRowToChatMessage(row, 'Item', {showSource: false});
}
return row?.document;
@ -105,7 +102,7 @@ export class TMRRencontres {
/* -------------------------------------------- */
async $chatRolledRencontre(row, rencontre, tmr) {
const flavorContent = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-compendium-table-roll-rencontre.html',
const flavorContent = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-compendium-table-roll-rencontre.hbs',
{
roll: row.roll,
rencontre,

View File

@ -2,228 +2,216 @@ import { Misc } from "./misc.js";
import { Grammar } from "./grammar.js";
import { RdDDice } from "./rdd-dice.js";
/* -------------------------------------------- */
const TMRMapping = {
A1: { type: "cite", label: "Cité Vide" },
B1: { type: "plaines", label: "Plaines dAssorh" },
C1: { type: "necropole", label: "Nécropole de Kroak" },
D1: { type: "fleuve", label: "Fleuve de l'Oubli" },
E1: { type: "monts", label: "Monts de Kanaï" },
F1: { type: "cite", label: "Cité Glauque" },
G1: { type: "desolation", label: "Désolation de Jamais" },
H1: { type: "lac", label: "Lac dAnticalme" },
I1: { type: "plaines", label: "Plaines Grises" },
J1: { type: "monts", label: "Monts Fainéants" },
K1: { type: "cite", label: "Cité dOnkause" },
L1: { type: "fleuve", label: "Fleuve de l'Oubli" },
M1: { type: "cite", label: "Cité Jalouse" },
A2: { type: "desert", label: "Désert de Mieux" },
B2: { type: "collines", label: "Collines de Dawell" },
C2: { type: "marais", label: "Marais Glignants" },
D2: { type: "cite", label: "Cité de Frost" },
E2: { type: "plaines", label: "Plaines de Fiask" },
F2: { type: "lac", label: "Lac de Misère" },
G2: { type: "marais", label: "Marais Nuisants" },
H2: { type: "collines", label: "Collines de Parta" },
I2: { type: "foret", label: "Forêt Fade" },
J2: { type: "desert", label: "Désert de Poly" },
K2: { type: "foret", label: "Forêt Tamée" },
L2: { type: "fleuve", label: "Fleuve de l'Oubli" },
M2: { type: "necropole", label: "Nécropole de Logos" },
A3: { type: "desolation", label: "Désolation de Demain" },
B3: { type: "plaines", label: "Plaines de Rubéga" },
C3: { type: "fleuve", label: "Fleuve de l'Oubli" },
D3: { type: "gouffre", label: "Gouffre dOki" },
E3: { type: "foret", label: "Forêt dEstoubh" },
F3: { type: "fleuve", label: "Fleuve de l'Oubli" },
G3: { type: "gouffre", label: "Gouffre de Sun" },
H3: { type: "foret", label: "Forêt de Ganna" },
I3: { type: "monts", label: "Monts Grinçants" },
J3: { type: "cite", label: "Cité Venin" },
K3: { type: "plaines", label: "Plaines de Dois" },
L3: { type: "lac", label: "Lac Laineux" },
M3: { type: "monts", label: "Monts de Vdah" },
A4: { type: "foret", label: "Forêt de Falconax" },
B4: { type: "monts", label: "Monts Crâneurs" },
C4: { type: "pont", label: "Pont de Giolii" },
D4: { type: "lac", label: "Lac de Foam" },
E4: { type: "plaines", label: "Plaines dOrti" },
F4: { type: "fleuve", label: "Fleuve de l'Oubli" },
G4: { type: "sanctuaire", label: "Sanctuaire Blanc" },
H4: { type: "plaines", label: "Plaines de Psark" },
I4: { type: "plaines", label: "Plaines de Xiax" },
J4: { type: "collines", label: "Collines dEncre" },
K4: { type: "pont", label: "Pont de Fah" },
L4: { type: "sanctuaire", label: "Sanctuaire Mauve" },
M4: { type: "gouffre", label: "Gouffre Grisant" },
A5: { type: "plaines", label: "Plaines de Trilkh" },
B5: { type: "collines", label: "Collines de Tanegy" },
C5: { type: "marais", label: "Marais Flouants" },
D5: { type: "fleuve", label: "Fleuve de l'Oubli" },
E5: { type: "monts", label: "Monts Brûlants" },
F5: { type: "cite", label: "Cité de Panople" },
G5: { type: "pont", label: "Pont dIk" },
H5: { type: "desert", label: "Désert de Krane" },
I5: { type: "desolation", label: "Désolation de Toujours" },
J5: { type: "marais", label: "Marais de Jab" },
K5: { type: "fleuve", label: "Fleuve de l'Oubli" },
L5: { type: "collines", label: "Collines Suaves" },
M5: { type: "cite", label: "Cité Rimarde" },
A6: { type: "necropole", label: "Nécropole de Zniak" },
B6: { type: "foret", label: "Forêt de Bust" },
C6: { type: "cite", label: "Cité Pavois" },
D6: { type: "fleuve", label: "Fleuve de l'Oubli" },
E6: { type: "sanctuaire", label: "Sanctuaire de Plaine" },
F6: { type: "fleuve", label: "Fleuve de l'Oubli" },
G6: { type: "marais", label: "Marais Glutants" },
H6: { type: "monts", label: "Monts Gurdes" },
I6: { type: "necropole", label: "Nécropole de Xotar" },
J6: { type: "lac", label: "Lac dIaupe" },
K6: { type: "desolation", label: "Désolation de Poor" },
L6: { type: "foret", label: "Forêt Gueuse" },
M6: { type: "desolation", label: "Désolation de Presque" },
A7: { type: "plaines", label: "Plaines de lArc" },
B7: { type: "marais", label: "Marais Bluants" },
C7: { type: "fleuve", label: "Fleuve de l'Oubli" },
D7: { type: "plaines", label: "Plaines dAffa" },
E7: { type: "foret", label: "Forêt de Glusks" },
F7: { type: "fleuve", label: "Fleuve de l'Oubli" },
G7: { type: "cite", label: "Cité de Terwa" },
H7: { type: "gouffre", label: "Gouffre de Kapfa" },
I7: { type: "plaines", label: "Plaines de Troo" },
J7: { type: "fleuve", label: "Fleuve de l'Oubli" },
K7: { type: "cite", label: "Cité de Kolix" },
L7: { type: "gouffre", label: "Gouffre dEpisophe" },
M7: { type: "desert", label: "Désert de Lave" },
A8: { type: "gouffre", label: "Gouffre de Shok" },
B8: { type: "fleuve", label: "Fleuve de l'Oubli" },
C8: { type: "foret", label: "Forêt Turmide" },
D8: { type: "cite", label: "Cité dOlak" },
E8: { type: "plaines", label: "Plaines dIolise" },
F8: { type: "lac", label: "Lac des Chats" },
G8: { type: "plaines", label: "Plaines Sans Joie" },
H8: { type: "foret", label: "Forêt dOurf" },
I8: { type: "fleuve", label: "Fleuve de l'Oubli" },
J8: { type: "monts", label: "Monts Barask" },
K8: { type: "desert", label: "Désert de Fumée" },
L8: { type: "monts", label: "Monts Tavelés" },
M8: { type: "plaines", label: "Plaines Lavées" },
A9: { type: "collines", label: "Collines de Korrex" },
B9: { type: "lac", label: "Lac de Lucre" },
C9: { type: "monts", label: "Monts Tuméfiés" },
D9: { type: "pont", label: "Pont dOrx" },
E9: { type: "fleuve", label: "Fleuve de l'Oubli" },
F9: { type: "plaines", label: "Plaines de Foe" },
G9: { type: "desolation", label: "Désolation de Sel" },
H9: { type: "collines", label: "Collines de Noirseul" },
I9: { type: "fleuve", label: "Fleuve de l'Oubli" },
J9: { type: "marais", label: "Marais Gronchants" },
K9: { type: "sanctuaire", label: "Sanctuaire Noir" },
L9: { type: "collines", label: "Collines Cornues" },
M9: { type: "necropole", label: "Nécropole de Zonar" },
A10: { type: "sanctuaire", label: "Sanctuaire dOlis" },
B10: { type: "monts", label: "Monts Salés" },
C10: { type: "marais", label: "Marais de Dom" },
D10: { type: "fleuve", label: "Fleuve de l'Oubli" },
E10: { type: "gouffre", label: "Gouffre de Junk" },
F10: { type: "marais", label: "Marais Zultants" },
G10: { type: "cite", label: "Cité de Sergal" },
H10: { type: "plaines", label: "Plaines Noires" },
I10: { type: "lac", label: "Lac Wanito" },
J10: { type: "fleuve", label: "Fleuve de l'Oubli" },
K10: { type: "plaines", label: "Plaines Jaunes" },
L10: { type: "desert", label: "Désert de Nicrop" },
M10: { type: "foret", label: "Forêt de Jajou" },
A11: { type: "desolation", label: "Désolation dHier" },
B11: { type: "cite", label: "Cité de Brilz" },
C11: { type: "pont", label: "Pont de Roï" },
D11: { type: "desolation", label: "Désolation de Partout" },
E11: { type: "lac", label: "Lac de Glinster" },
F11: { type: "cite", label: "Cité de Noape" },
G11: { type: "fleuve", label: "Fleuve de l'Oubli" },
H11: { type: "fleuve", label: "Fleuve de l'Oubli" },
I11: { type: "pont", label: "Pont de Yalm" },
J11: { type: "plaines", label: "Plaines de Miltiar" },
K11: { type: "cite", label: "Cité Tonnerre" },
L11: { type: "collines", label: "Collines de Kol" },
M11: { type: "cite", label: "Cité Crapaud" },
A12: { type: "plaines", label: "Plaines Sages" },
B12: { type: "fleuve", label: "Fleuve de l'Oubli" },
C12: { type: "lac", label: "Lac de Fricassa" },
D12: { type: "collines", label: "Collines dHuaï" },
E12: { type: "monts", label: "Monts Ajourés" },
F12: { type: "necropole", label: "Nécropole de Throat" },
G12: { type: "plaines", label: "Plaines de Lufmil" },
H12: { type: "collines", label: "Collines de Tooth" },
I12: { type: "gouffre", label: "Gouffre Abimeux" },
J12: { type: "cite", label: "Cité Folle" },
K12: { type: "desolation", label: "Désolation dAmour" },
L12: { type: "plaines", label: "Plaines Venteuses" },
M12: { type: "collines", label: "Collines Révulsantes" },
A13: { type: "fleuve", label: "Fleuve de l'Oubli" },
B13: { type: "gouffre", label: "Gouffre des Litiges" },
C13: { type: "desert", label: "Désert de Neige" },
D13: { type: "cite", label: "Cité Sordide" },
E13: { type: "plaines", label: "Plaines de Xnez" },
F13: { type: "foret", label: "Forêt des Cris" },
G13: { type: "plaines", label: "Plaines Calcaires" },
H13: { type: "desolation", label: "Désolation de Rien" },
I13: { type: "monts", label: "Monts Bigleux" },
J13: { type: "gouffre", label: "Gouffre de Gromph" },
K13: { type: "foret", label: "Forêt de Kluth" },
L13: { type: "monts", label: "Monts Dormants" },
M13: { type: "plaines", label: "Plaines dAnjou" },
A14: { type: "collines", label: "Collines de Stolis" },
B14: { type: "necropole", label: "Nécropole de Gorlo" },
C14: { type: "foret", label: "Forêt de Bissam" },
D14: { type: "sanctuaire", label: "Sanctuaire Plat" },
E14: { type: "monts", label: "Monts de Quath" },
F14: { type: "plaines", label: "Plaines Brisées" },
G14: { type: "desert", label: "Désert de Sek" },
H14: { type: "plaines", label: "Plaines Blanches" },
I14: { type: "cite", label: "Cité Destituée" },
J14: { type: "desert", label: "Désert de Sank" },
K14: { type: "necropole", label: "Nécropole dAntinéar" },
L14: { type: "plaines", label: "Plaines de Jislith" },
M14: { type: "desolation", label: "Désolation dAprès" },
A15: { type: "cite", label: "Cité de Mielh" },
C15: { type: "plaines", label: "Plaines de Toué" },
E15: { type: "foret", label: "Forêt des Furies" },
G15: { type: "plaines", label: "Plaines des Soupirs" },
I15: { type: "monts", label: "Monts des Dragées" },
K15: { type: "collines", label: "Collines Pourpres" },
M15: { type: "cite", label: "Cité de Klana" }
export const TMRType = {
cite: { type: 'cite', name: "cité", genre: "f" },
sanctuaire: { type: 'sanctuaire', name: "sanctuaire", genre: 'm' },
plaines: { type: 'plaines', name: "plaines", genre: "fp" },
pont: { type: 'pont', name: "pont", genre: "m" },
collines: { type: 'collines', name: "collines", genre: "p" },
foret: { type: 'foret', name: "forêt", genre: "f" },
monts: { type: 'monts', name: "monts", genre: "p" },
desert: { type: 'desert', name: "désert", genre: "m" },
fleuve: { type: 'fleuve', name: "fleuve", genre: "m" },
lac: { type: 'lac', name: "lac", genre: "m" },
marais: { type: 'marais', name: "marais", genre: "m" },
gouffre: { type: 'gouffre', name: "gouffre", genre: "m" },
necropole: { type: 'necropole', name: "nécropole", genre: "f" },
desolation: { type: 'desolation', name: "désolation", genre: "f" }
}
export const TMRType = {
cite: { name: "cité", genre: "f" },
sanctuaire: { name: "sanctuaire", genre: 'm' },
plaines: { name: "plaines", genre: "fp" },
pont: { name: "pont", genre: "m" },
collines: { name: "collines", genre: "p" },
foret: { name: "forêt", genre: "f" },
monts: { name: "monts", genre: "p" },
desert: { name: "désert", genre: "m" },
fleuve: { name: "fleuve", genre: "m" },
lac: { name: "lac", genre: "m" },
marais: { name: "marais", genre: "m" },
gouffre: { name: "gouffre", genre: "m" },
necropole: { name: "nécropole", genre: "f" },
desolation: { name: "désolation", genre: "f" }
export const FLEUVE_COORD = 'Fleuve'
const TMRMapping = {
Fleuve: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
A1: { type: TMRType.cite.type, label: "Cité Vide" },
B1: { type: TMRType.plaines.type, label: "Plaines dAssorh" },
C1: { type: TMRType.necropole.type, label: "Nécropole de Kroak" },
D1: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
E1: { type: TMRType.monts.type, label: "Monts de Kanaï" },
F1: { type: TMRType.cite.type, label: "Cité Glauque" },
G1: { type: TMRType.desolation.type, label: "Désolation de Jamais" },
H1: { type: TMRType.lac.type, label: "Lac dAnticalme" },
I1: { type: TMRType.plaines.type, label: "Plaines Grises" },
J1: { type: TMRType.monts.type, label: "Monts Fainéants" },
K1: { type: TMRType.cite.type, label: "Cité dOnkause" },
L1: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
M1: { type: TMRType.cite.type, label: "Cité Jalouse" },
A2: { type: TMRType.desert.type, label: "Désert de Mieux" },
B2: { type: TMRType.collines.type, label: "Collines de Dawell" },
C2: { type: TMRType.marais.type, label: "Marais Glignants" },
D2: { type: TMRType.cite.type, label: "Cité de Frost" },
E2: { type: TMRType.plaines.type, label: "Plaines de Fiask" },
F2: { type: TMRType.lac.type, label: "Lac de Misère" },
G2: { type: TMRType.marais.type, label: "Marais Nuisants" },
H2: { type: TMRType.collines.type, label: "Collines de Parta" },
I2: { type: TMRType.foret.type, label: "Forêt Fade" },
J2: { type: TMRType.desert.type, label: "Désert de Poly" },
K2: { type: TMRType.foret.type, label: "Forêt Tamée" },
L2: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
M2: { type: TMRType.necropole.type, label: "Nécropole de Logos" },
A3: { type: TMRType.desolation.type, label: "Désolation de Demain" },
B3: { type: TMRType.plaines.type, label: "Plaines de Rubéga" },
C3: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
D3: { type: TMRType.gouffre.type, label: "Gouffre dOki" },
E3: { type: TMRType.foret.type, label: "Forêt dEstoubh" },
F3: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
G3: { type: TMRType.gouffre.type, label: "Gouffre de Sun" },
H3: { type: TMRType.foret.type, label: "Forêt de Ganna" },
I3: { type: TMRType.monts.type, label: "Monts Grinçants" },
J3: { type: TMRType.cite.type, label: "Cité Venin" },
K3: { type: TMRType.plaines.type, label: "Plaines de Dois" },
L3: { type: TMRType.lac.type, label: "Lac Laineux" },
M3: { type: TMRType.monts.type, label: "Monts de Vdah" },
A4: { type: TMRType.foret.type, label: "Forêt de Falconax" },
B4: { type: TMRType.monts.type, label: "Monts Crâneurs" },
C4: { type: TMRType.pont.type, label: "Pont de Giolii" },
D4: { type: TMRType.lac.type, label: "Lac de Foam" },
E4: { type: TMRType.plaines.type, label: "Plaines dOrti" },
F4: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
G4: { type: TMRType.sanctuaire.type, label: "Sanctuaire Blanc" },
H4: { type: TMRType.plaines.type, label: "Plaines de Psark" },
I4: { type: TMRType.plaines.type, label: "Plaines de Xiax" },
J4: { type: TMRType.collines.type, label: "Collines dEncre" },
K4: { type: TMRType.pont.type, label: "Pont de Fah" },
L4: { type: TMRType.sanctuaire.type, label: "Sanctuaire Mauve" },
M4: { type: TMRType.gouffre.type, label: "Gouffre Grisant" },
A5: { type: TMRType.plaines.type, label: "Plaines de Trilkh" },
B5: { type: TMRType.collines.type, label: "Collines de Tanegy" },
C5: { type: TMRType.marais.type, label: "Marais Flouants" },
D5: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
E5: { type: TMRType.monts.type, label: "Monts Brûlants" },
F5: { type: TMRType.cite.type, label: "Cité de Panople" },
G5: { type: TMRType.pont.type, label: "Pont dIk" },
H5: { type: TMRType.desert.type, label: "Désert de Krane" },
I5: { type: TMRType.desolation.type, label: "Désolation de Toujours" },
J5: { type: TMRType.marais.type, label: "Marais de Jab" },
K5: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
L5: { type: TMRType.collines.type, label: "Collines Suaves" },
M5: { type: TMRType.cite.type, label: "Cité Rimarde" },
A6: { type: TMRType.necropole.type, label: "Nécropole de Zniak" },
B6: { type: TMRType.foret.type, label: "Forêt de Bust" },
C6: { type: TMRType.cite.type, label: "Cité Pavois" },
D6: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
E6: { type: TMRType.sanctuaire.type, label: "Sanctuaire de Plaine" },
F6: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
G6: { type: TMRType.marais.type, label: "Marais Glutants" },
H6: { type: TMRType.monts.type, label: "Monts Gurdes" },
I6: { type: TMRType.necropole.type, label: "Nécropole de Xotar" },
J6: { type: TMRType.lac.type, label: "Lac dIaupe" },
K6: { type: TMRType.desolation.type, label: "Désolation de Poor" },
L6: { type: TMRType.foret.type, label: "Forêt Gueuse" },
M6: { type: TMRType.desolation.type, label: "Désolation de Presque" },
A7: { type: TMRType.plaines.type, label: "Plaines de lArc" },
B7: { type: TMRType.marais.type, label: "Marais Bluants" },
C7: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
D7: { type: TMRType.plaines.type, label: "Plaines dAffa" },
E7: { type: TMRType.foret.type, label: "Forêt de Glusks" },
F7: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
G7: { type: TMRType.cite.type, label: "Cité de Terwa" },
H7: { type: TMRType.gouffre.type, label: "Gouffre de Kapfa" },
I7: { type: TMRType.plaines.type, label: "Plaines de Troo" },
J7: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
K7: { type: TMRType.cite.type, label: "Cité de Kolix" },
L7: { type: TMRType.gouffre.type, label: "Gouffre dEpisophe" },
M7: { type: TMRType.desert.type, label: "Désert de Lave" },
A8: { type: TMRType.gouffre.type, label: "Gouffre de Shok" },
B8: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
C8: { type: TMRType.foret.type, label: "Forêt Turmide" },
D8: { type: TMRType.cite.type, label: "Cité dOlak" },
E8: { type: TMRType.plaines.type, label: "Plaines dIolise" },
F8: { type: TMRType.lac.type, label: "Lac des Chats" },
G8: { type: TMRType.plaines.type, label: "Plaines Sans Joie" },
H8: { type: TMRType.foret.type, label: "Forêt dOurf" },
I8: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
J8: { type: TMRType.monts.type, label: "Monts Barask" },
K8: { type: TMRType.desert.type, label: "Désert de Fumée" },
L8: { type: TMRType.monts.type, label: "Monts Tavelés" },
M8: { type: TMRType.plaines.type, label: "Plaines Lavées" },
A9: { type: TMRType.collines.type, label: "Collines de Korrex" },
B9: { type: TMRType.lac.type, label: "Lac de Lucre" },
C9: { type: TMRType.monts.type, label: "Monts Tuméfiés" },
D9: { type: TMRType.pont.type, label: "Pont dOrx" },
E9: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
F9: { type: TMRType.plaines.type, label: "Plaines de Foe" },
G9: { type: TMRType.desolation.type, label: "Désolation de Sel" },
H9: { type: TMRType.collines.type, label: "Collines de Noirseul" },
I9: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
J9: { type: TMRType.marais.type, label: "Marais Gronchants" },
K9: { type: TMRType.sanctuaire.type, label: "Sanctuaire Noir" },
L9: { type: TMRType.collines.type, label: "Collines Cornues" },
M9: { type: TMRType.necropole.type, label: "Nécropole de Zonar" },
A10: { type: TMRType.sanctuaire.type, label: "Sanctuaire dOlis" },
B10: { type: TMRType.monts.type, label: "Monts Salés" },
C10: { type: TMRType.marais.type, label: "Marais de Dom" },
D10: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
E10: { type: TMRType.gouffre.type, label: "Gouffre de Junk" },
F10: { type: TMRType.marais.type, label: "Marais Zultants" },
G10: { type: TMRType.cite.type, label: "Cité de Sergal" },
H10: { type: TMRType.plaines.type, label: "Plaines Noires" },
I10: { type: TMRType.lac.type, label: "Lac Wanito" },
J10: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
K10: { type: TMRType.plaines.type, label: "Plaines Jaunes" },
L10: { type: TMRType.desert.type, label: "Désert de Nicrop" },
M10: { type: TMRType.foret.type, label: "Forêt de Jajou" },
A11: { type: TMRType.desolation.type, label: "Désolation dHier" },
B11: { type: TMRType.cite.type, label: "Cité de Brilz" },
C11: { type: TMRType.pont.type, label: "Pont de Roï" },
D11: { type: TMRType.desolation.type, label: "Désolation de Partout" },
E11: { type: TMRType.lac.type, label: "Lac de Glinster" },
F11: { type: TMRType.cite.type, label: "Cité de Noape" },
G11: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
H11: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
I11: { type: TMRType.pont.type, label: "Pont de Yalm" },
J11: { type: TMRType.plaines.type, label: "Plaines de Miltiar" },
K11: { type: TMRType.cite.type, label: "Cité Tonnerre" },
L11: { type: TMRType.collines.type, label: "Collines de Kol" },
M11: { type: TMRType.cite.type, label: "Cité Crapaud" },
A12: { type: TMRType.plaines.type, label: "Plaines Sages" },
B12: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
C12: { type: TMRType.lac.type, label: "Lac de Fricassa" },
D12: { type: TMRType.collines.type, label: "Collines dHuaï" },
E12: { type: TMRType.monts.type, label: "Monts Ajourés" },
F12: { type: TMRType.necropole.type, label: "Nécropole de Throat" },
G12: { type: TMRType.plaines.type, label: "Plaines de Lufmil" },
H12: { type: TMRType.collines.type, label: "Collines de Tooth" },
I12: { type: TMRType.gouffre.type, label: "Gouffre Abimeux" },
J12: { type: TMRType.cite.type, label: "Cité Folle" },
K12: { type: TMRType.desolation.type, label: "Désolation dAmour" },
L12: { type: TMRType.plaines.type, label: "Plaines Venteuses" },
M12: { type: TMRType.collines.type, label: "Collines Révulsantes" },
A13: { type: TMRType.fleuve.type, label: "Fleuve de l'Oubli" },
B13: { type: TMRType.gouffre.type, label: "Gouffre des Litiges" },
C13: { type: TMRType.desert.type, label: "Désert de Neige" },
D13: { type: TMRType.cite.type, label: "Cité Sordide" },
E13: { type: TMRType.plaines.type, label: "Plaines de Xnez" },
F13: { type: TMRType.foret.type, label: "Forêt des Cris" },
G13: { type: TMRType.plaines.type, label: "Plaines Calcaires" },
H13: { type: TMRType.desolation.type, label: "Désolation de Rien" },
I13: { type: TMRType.monts.type, label: "Monts Bigleux" },
J13: { type: TMRType.gouffre.type, label: "Gouffre de Gromph" },
K13: { type: TMRType.foret.type, label: "Forêt de Kluth" },
L13: { type: TMRType.monts.type, label: "Monts Dormants" },
M13: { type: TMRType.plaines.type, label: "Plaines dAnjou" },
A14: { type: TMRType.collines.type, label: "Collines de Stolis" },
B14: { type: TMRType.necropole.type, label: "Nécropole de Gorlo" },
C14: { type: TMRType.foret.type, label: "Forêt de Bissam" },
D14: { type: TMRType.sanctuaire.type, label: "Sanctuaire Plat" },
E14: { type: TMRType.monts.type, label: "Monts de Quath" },
F14: { type: TMRType.plaines.type, label: "Plaines Brisées" },
G14: { type: TMRType.desert.type, label: "Désert de Sek" },
H14: { type: TMRType.plaines.type, label: "Plaines Blanches" },
I14: { type: TMRType.cite.type, label: "Cité Destituée" },
J14: { type: TMRType.desert.type, label: "Désert de Sank" },
K14: { type: TMRType.necropole.type, label: "Nécropole dAntinéar" },
L14: { type: TMRType.plaines.type, label: "Plaines de Jislith" },
M14: { type: TMRType.desolation.type, label: "Désolation dAprès" },
A15: { type: TMRType.cite.type, label: "Cité de Mielh" },
C15: { type: TMRType.plaines.type, label: "Plaines de Toué" },
E15: { type: TMRType.foret.type, label: "Forêt des Furies" },
G15: { type: TMRType.plaines.type, label: "Plaines des Soupirs" },
I15: { type: TMRType.monts.type, label: "Monts des Dragées" },
K15: { type: TMRType.collines.type, label: "Collines Pourpres" },
M15: { type: TMRType.cite.type, label: "Cité de Klana" }
}
/* -------------------------------------------- */
@ -251,13 +239,16 @@ const TMR_MOVE = {
*/
export class TMRUtility {
static init() {
for (let coord in TMRMapping) {
const tmr = TMRMapping[coord];
tmr.coord = coord;
tmr.oddq = TMRUtility.coordTMRToOddq(coord);
tmr.genre = TMRType[tmr.type].genre;
const tmr = TMRMapping[coord]
tmr.coord = coord
tmr.genre = TMRType[tmr.type].genre
if (coord != FLEUVE_COORD) {
tmr.oddq = TMRUtility.coordTMRToOddq(coord)
}
}
let tmrByType = Misc.classify(Object.values(TMRMapping));
let tmrByType = Misc.classify(Object.values(TMRMapping).filter(it => it.coord != FLEUVE_COORD))
for (const [type, list] of Object.entries(tmrByType)) {
TMRType[type].list = list;
}
@ -265,14 +256,17 @@ export class TMRUtility {
/* -------------------------------------------- */
static verifyTMRCoord(coord) {
return Grammar.equalsInsensitive(coord, 'Fleuve') || TMRUtility.getTMR(coord);
return Grammar.equalsInsensitive(coord, FLEUVE_COORD) || TMRUtility.getTMR(coord);
}
/* -------------------------------------------- */
static getTMR(coord) {
return coord == 'Fleuve' ? TMRMapping['D1'] : TMRMapping[coord];
return coord == FLEUVE_COORD ? TMRMapping['D1'] : TMRMapping[coord];
}
static isFleuve(coord) {
return TMRMapping[coord]?.type == TMRType.fleuve.type
}
static getTMRLabel(coord) {
return TMRUtility.getTMR(coord)?.label ?? (coord + ": case inconnue");
}
@ -290,18 +284,18 @@ export class TMRUtility {
static findTMRLike(type, options = { inclusMauvaise: true }) {
const choix = [...Object.values(TMRType)]
if (options.inclusMauvaise) {
choix.push({ name: 'Mauvaise' });
choix.push({ name: 'Mauvaise', type: 'mauvaise'});
}
const selection = Misc.findAllLike(type, choix).map(it => it.name);
const selection = Misc.findAllLike(type, choix)
if (selection.length == 0) {
ui.notifications.warn(`Un type de TMR doit être indiqué, '${type}' n'est pas trouvé dans ${choix}`);
return undefined;
return undefined
}
if (selection.length > 1) {
ui.notifications.warn(`Plusieurs types de TMR pourraient correspondre à '${type}': ${selection}`);
ui.notifications.warn(`Plusieurs types de TMR pourraient correspondre à '${type}': ${selection.map(it => it.name)}`);
return undefined;
}
return selection[0];
return selection[0]
}
static typeTmrName(type) {
@ -326,7 +320,7 @@ export class TMRUtility {
/* -------------------------------------------- */
static deplacement(coordOrig, moveName) {
const tmrMove = TMR_MOVE[moveName];
if (! tmrMove) {
if (!tmrMove) {
ui.notifications.error(`Le déplacement dans les TMR '${moveName}' est inconnu`)
return coordOrig
}

View File

@ -30,7 +30,7 @@ export class EffetsRencontre {
static $reve_plus = async (actor, reve) => {
if (!ReglesOptionnelles.isUsing("recuperation-reve") && reve < 0) {
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(actor.name),
whisper: ChatUtility.getOwners(actor),
content: `Pas de récupération de rêve (${reve} points ignorés)`
});
return
@ -112,7 +112,7 @@ export class EffetsRencontre {
poesie: await Poetique.getExtrait()
})
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(context.actor.name),
whisper: ChatUtility.getOwners(context.actor),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-resultat-reve-de-dragon.html`, context)
});
}
@ -127,7 +127,7 @@ export class EffetsRencontre {
}
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
whisper: ChatUtility.getOwners(context.actor),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-resultat-reve-de-dragon.html`, context)
});
}

View File

@ -28,7 +28,7 @@ export class PresentCites extends Draconique {
let existants = actor.items.filter(it => this.isCase(it)).map(it => it.system.coord);
if (existants.length > 0) {
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
whisper: ChatUtility.getOwners(actor),
content: "Vous avez encore des présents dans des cités, vous devrez tirer une autre tête pour remplacer celle ci!"
})
}

View File

@ -19,7 +19,7 @@ export class UrgenceDraconique extends Draconique {
// La queue se transforme en idée fixe
const ideeFixe = await RdDRollTables.getIdeeFixe();
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
whisper: ChatUtility.getOwners(actor),
content: `En l'absence de sorts en réserve, l'urgence draconique de ${actor.name} se transforme en ${ideeFixe.name}`
});
await actor.createEmbeddedDocuments('Item', [ideeFixe]);

View File

@ -175,7 +175,7 @@ export class DialogFatigueVoyage extends Dialog {
.forEach(async it => {
const perteFatigue = fatigueBase + it.ajustement
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(it.actor.name),
whisper: ChatUtility.getOwners(it.actor),
content: await renderTemplate(
'systems/foundryvtt-reve-de-dragon/templates/voyage/chat-fatigue_voyage.hbs',
foundry.utils.mergeObject(it,

BIN
pack_ref/animaux/000005.ldb Normal file

Binary file not shown.

View File

1
pack_ref/animaux/CURRENT Normal file
View File

@ -0,0 +1 @@
MANIFEST-000022

0
pack_ref/animaux/LOCK Normal file
View File

3
pack_ref/animaux/LOG Normal file
View File

@ -0,0 +1,3 @@
2024/11/10-18:39:00.183093 7fed63e006c0 Recovering log #21
2024/11/10-18:39:00.195436 7fed63e006c0 Delete type=0 #21
2024/11/10-18:39:00.195482 7fed63e006c0 Delete type=3 #20

3
pack_ref/animaux/LOG.old Normal file
View File

@ -0,0 +1,3 @@
2024/11/10-18:38:41.533242 7f4c0b8006c0 Recovering log #19
2024/11/10-18:38:41.547823 7f4c0b8006c0 Delete type=0 #19
2024/11/10-18:38:41.547917 7f4c0b8006c0 Delete type=3 #18

Binary file not shown.

Binary file not shown.

View File

View File

@ -0,0 +1 @@
MANIFEST-000010

0
pack_ref/archetypes/LOCK Normal file
View File

3
pack_ref/archetypes/LOG Normal file
View File

@ -0,0 +1,3 @@
2024/11/10-18:39:00.253481 7fed62a006c0 Recovering log #8
2024/11/10-18:39:00.263751 7fed62a006c0 Delete type=3 #6
2024/11/10-18:39:00.263866 7fed62a006c0 Delete type=0 #8

View File

@ -0,0 +1,8 @@
2024/11/08-19:40:34.139501 7f9d7f4006c0 Recovering log #4
2024/11/08-19:40:34.182207 7f9d7f4006c0 Delete type=3 #2
2024/11/08-19:40:34.182311 7f9d7f4006c0 Delete type=0 #4
2024/11/08-19:56:45.129455 7f9d7d6006c0 Level-0 table #9: started
2024/11/08-19:56:45.129487 7f9d7d6006c0 Level-0 table #9: 0 bytes OK
2024/11/08-19:56:45.135738 7f9d7d6006c0 Delete type=0 #7
2024/11/08-19:56:45.142639 7f9d7d6006c0 Manual compaction at level-0 from '!actors!1Nng9d8r6lrPHCaJ' @ 72057594037927935 : 1 .. '!actors.items!ryUZTa17LzNv25UY.zyNYa3hYtrOcF2jA' @ 0 : 0; will stop at (end)
2024/11/08-19:56:45.142697 7f9d7d6006c0 Manual compaction at level-1 from '!actors!1Nng9d8r6lrPHCaJ' @ 72057594037927935 : 1 .. '!actors.items!ryUZTa17LzNv25UY.zyNYa3hYtrOcF2jA' @ 0 : 0; will stop at (end)

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
MANIFEST-000010

View File

View File

@ -0,0 +1,3 @@
2024/11/10-18:39:00.448675 7fed63e006c0 Recovering log #8
2024/11/10-18:39:00.460061 7fed63e006c0 Delete type=3 #6
2024/11/10-18:39:00.460115 7fed63e006c0 Delete type=0 #8

View File

@ -0,0 +1,8 @@
2024/11/08-19:40:32.605216 7f9d7fe006c0 Recovering log #4
2024/11/08-19:40:32.664872 7f9d7fe006c0 Delete type=3 #2
2024/11/08-19:40:32.665020 7f9d7fe006c0 Delete type=0 #4
2024/11/08-19:56:44.948429 7f9d7d6006c0 Level-0 table #9: started
2024/11/08-19:56:44.948495 7f9d7d6006c0 Level-0 table #9: 0 bytes OK
2024/11/08-19:56:44.954943 7f9d7d6006c0 Delete type=0 #7
2024/11/08-19:56:44.975424 7f9d7d6006c0 Manual compaction at level-0 from '!items!240HPtZsgZQERFMF' @ 72057594037927935 : 1 .. '!items!zRQ5WnPI483CKm9Q' @ 0 : 0; will stop at (end)
2024/11/08-19:56:44.975473 7f9d7d6006c0 Manual compaction at level-1 from '!items!240HPtZsgZQERFMF' @ 72057594037927935 : 1 .. '!items!zRQ5WnPI483CKm9Q' @ 0 : 0; will stop at (end)

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
MANIFEST-000010

View File

View File

@ -0,0 +1,3 @@
2024/11/10-18:39:00.493027 7fed63e006c0 Recovering log #8
2024/11/10-18:39:00.503119 7fed63e006c0 Delete type=3 #6
2024/11/10-18:39:00.503242 7fed63e006c0 Delete type=0 #8

View File

@ -0,0 +1,8 @@
2024/11/08-19:40:32.670029 7f9d7ea006c0 Recovering log #4
2024/11/08-19:40:32.734322 7f9d7ea006c0 Delete type=3 #2
2024/11/08-19:40:32.734474 7f9d7ea006c0 Delete type=0 #4
2024/11/08-19:56:44.968549 7f9d7d6006c0 Level-0 table #9: started
2024/11/08-19:56:44.968601 7f9d7d6006c0 Level-0 table #9: 0 bytes OK
2024/11/08-19:56:44.975299 7f9d7d6006c0 Delete type=0 #7
2024/11/08-19:56:44.975466 7f9d7d6006c0 Manual compaction at level-0 from '!items!0zRL8bOpCXNQnIR4' @ 72057594037927935 : 1 .. '!items!yDHZfK4RmwQW4YaW' @ 0 : 0; will stop at (end)
2024/11/08-19:56:44.975488 7f9d7d6006c0 Manual compaction at level-1 from '!items!0zRL8bOpCXNQnIR4' @ 72057594037927935 : 1 .. '!items!yDHZfK4RmwQW4YaW' @ 0 : 0; will stop at (end)

Binary file not shown.

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