Compare commits

..
55 Commits
Author SHA1 Message Date
fabres fd3c228b32 Update changelog.md 2026-08-07 16:03:59 +02:00
fabres 9b3eceb96b fix(commerce): description pleine largeur sur la fiche
Release Creation / build (release) Successful in 2m13s
Le champ Description était dans un wrapper form-group (flex-direction: row)
qui le réduisait à la largeur de son contenu (~90px) au lieu de toute la
fiche (592px). Retrait de la classe form-group : le fieldset s'étire à
nouveau sur toute la largeur, comme sur les autres feuilles.

Valide par TC-06 (acteur commerce, 14/14) lors de la passe de
non-régression.

Ajoute aussi les permissions d'exécution manquantes sur les scripts
TC-02 et TC-07 (l'orchestrateur les sautait silencieusement).
2026-08-07 15:54:09 +02:00
fabres 73b91285e3 fix(sommeil): insomnie, château dormant automatique et reprise de nuit
Nouveau mode de repos (nouveau-mode-repos) :
- Insomnie (Queue de Dragon) : bannière d'avertissement dans le dialogue,
  rafraîchissement du drapeau à l'ouverture, vérification en direct dans la
  boucle de sommeil (coupe la récupération fatigue/rêve si elle surgit en
  cours de nuit), drapeau bidirectionnel (setInfoSommeilInsomnie) et mention
  dans le dialogue Château Dormant.
- Château Dormant : déclenchement automatique quand l'heure absolue de fin
  de nuit (11) est atteinte, au lieu de seulement après 12 h de sommeil.
- Rêve de Dragon : reprise de nuit possible — system.sommeil.heures
  initialisé à la durée, heures restantes conservées au réveil, dialogue
  pré-rempli avec bannière 'Reprise de nuit' et remise à zéro après une
  nuit complète.
- Don 'double rêve' : second jet de récupération de rêve par heure.
- Garde anti-boucle infinie si l'insomnie apparaît pendant le sommeil.
- Info-bulle sur le réveil forcé.

Perte de rêve des enchantements au Château Dormant :
- Suppression du message en double (whisper dédié) : texte intégré au
  résumé du CD ; la gemme renvoie désormais nouveaupr.
2026-08-07 15:54:09 +02:00
VincentVk 1238460cc8 Fix: quantité d'achat depuis commerce illimité
Lors d'un achat depuis un commerce illimité, la quantité du commerce
(non visible) était utilisée au lieux de la quantité réellement achetée
2026-08-07 15:25:35 +02:00
VincentVk d590dfce7f Fix: ne pas créer d'Item "Sans compétence" 2026-08-07 14:53:58 +02:00
fabres 332da735f8 Re-org CSS title a bit
Release Creation / build (release) Successful in 2m57s
2026-08-06 20:53:40 +02:00
fabres 4a6913a2ba fix: XP compétences négatives (base de catégorie)
Le champ base (INTEGER, min 0) rejetait les bases négatives
(-4/-6/-8/-11) : toute compétence voyait sa base forcée à 0, d'où un
total d'XP compétences fortement négatif (ex. -5790 sur un personnage
neuf).
- competence.mjs : base en INTEGER_SIGNED
- item-competence.js : _preCreate affecte la base de la catégorie
- migrations.js : migration 14.0.3 corrige les compétences existantes
- compendiums repackés (bases de catégorie présentes dans les packs)
2026-08-06 14:27:39 +02:00
fabres 3a55d8e912 Update changelog.md
Release Creation / build (release) Successful in 1m2s
2026-08-06 09:07:33 +02:00
fabres 7caeb0c918 Update changelog.md 2026-08-06 09:06:49 +02:00
fabres 188e7676fa docs: changelog des updates depuis 14.0.0 (migration V14, sommeil, tests) 2026-08-06 09:04:17 +02:00
fabres bb56a3a809 docs: documentation des tests (orchestrateur, liste, API) 2026-08-04 21:37:37 +02:00
fabres ac2e2027a0 test: orchestrateur run-all-tests.sh (demande démarrage monde, rapport final) 2026-08-04 21:36:12 +02:00
fabres dca201c5e2 fix: tc-header.sh utilise adminPassword au lieu de password (review #1) 2026-08-04 21:13:57 +02:00
fabres 5db30ddeb3 test: ajout 22 scripts TC (TC-08 a TC-31) + tc-header.sh partage 2026-08-04 21:08:43 +02:00
fabres 3758d0ca85 fix: compat V14 - _onFirstRender (HTMLElement.show), fermeture dialog TMR 2026-07-27 21:17:50 +02:00
VincentVk d8cfd02807 Regrouper les classes de combat
Elles sont souvent touchées ensemble

Préparation aux changements pour utiliser les Combatants
(pour gérer les empoignades/possessions en cours)

Renommages:
- RdDCombat -> PasseArmes
- RdDCombatManager -> RdDCombat
2026-07-26 22:58:25 +02:00
VincentVk 9aca2c32ef Fix assign dans constructeur
Pas possible de faire Object.assign(this.position car position == null
2026-07-26 22:57:17 +02:00
fabres 882aa5fbcb test: TC-07 feuilles item (44 types)
- creation + ouverture sheet pour les 44 types d'item
- verification scroll via .sheet-body overflow-y:auto
- 4/4 passed, 0 failed
2026-07-24 08:51:07 +02:00
fabres 1272cfdfd1 test: ajout plan de test et scripts TC-01 a TC-06
- rdd_test_plan.md : plan global, 227 cas, 33 sections
- TC-01 : infrastructure coeur (15/15)
- TC-02 : acteur personnage (7/7)
- TC-03 : acteur creature (11/11)
- TC-04 : acteur entite + subtypes + possession (14/14)
- TC-05 : acteur vehicule + structure/resistance (10/10)
- TC-06 : acteur commerce + achat/vente simules (14/14)

Scripts atomiques, async polling, page-state race-free
2026-07-24 08:08:04 +02:00
fabres 2348b2262b fix: champ age en StringField pour compat V14
- age passe de NumberField a StringField (evite erreur validation V14
  'must be a number' sur les acteurs existants)
- beaute reste en NumberField (utilise en calcul)
- expose RdDDice sur game.system.rdd.Dice
2026-07-24 08:07:10 +02:00
fabres 81cb3bb6bd fix: DialogV2 - formes imbriquees, boutons, styles et positionnement
- retire les <form> dans les templates DialogV2 (absorbe par V2)
- ajoute window: {frame, positioned} et enleve height:fit-content (bloquait deplacement vertical)
- ajoute buttons: [Fermer] sur tous les DialogV2 qui en manquaient
- style des dialogs chronologie, calendar-editor, stress
- corrige duplication des boutons calendrier a chaque re-render
- HandlebarsApplicationMixin sur FenetreRechercheTirage
- corrige selecteurs :input→input,select,textarea (jQuery→natif)
- ajoute banniere ASCII console au demarrage
2026-07-22 01:10:24 +02:00
fabres e0e3d4f230 fix: DialogV2 requiert au moins un bouton (buttons vides interdit en V14) 2026-07-22 00:35:47 +02:00
fabres 3afd111711 PUsh all v14 Stuff 2026-07-22 00:31:16 +02:00
fabres 1c288b654a fix: migration V14 - rollMode→messageMode, Token HUD, data-message-id
- remplace core.rollMode → core.messageMode (setting V14)
- migre toutes les cles (gmroll→gm, selfroll→self, blindroll→blind)
- corrige le hook renderTokenHUD pour recuperer le tokenId via app.document
- ajoute un bouton combat personnalise si absent du HUD V14
- corrige le toggle combat : isCombat manquant et guard si hors combat
- chat-utility : data-document-id→data-message-id pour les messages V14
- rdd-utility : idem pour findChatMessage/getChatMessageId
- roll-part-possession : getBestDraconic peut retourner undefined
- distance.mjs : Token→foundry.canvas.placeables.Token
2026-07-22 00:29:56 +02:00
fabres a5843888c0 fix: migration V14 et corrections diverses
migration v14:
- remplace CONFIG.Dice.rollModes → CONFIG.ChatMessage.modes
- canvas optionnel (?.tokens) dans ~27 fichiers
- async/await migrations, waitForRoll, effet draconique
- supprime _warnedAppV1, data-message-id→data-document-id
- corrige sheet registration Items.registerSheet

tmr:
- bouton Lancer un Sort visible en mode visu
- _ensureTMRButtons() en fallback JS
- await d'animation avant render
- corrigé doublon carteTMR.js

css:
- scrollbar (.item-sheet-scrollable-body) sur les 44 fiches item
- normalisation multiligne de tous les less/item/*.less
- style du dialogue description aléatoire (app-personnage-aleatoire)
- hr-sorts et sort réserves en flex-row

dialogue description:
- type=button sur les boutons (évite submit form → crash)
- randomControl passe this.actor au lieu de this.current
- DEFINITION_HEURES : ajout propriété manquante heure

d意:
- supprime 6 fichiers debug (phase*.txt/png, sheets-*.txt/png)
- system.json nom anglais→français
- token-hud : guard token undefined
- item-sort.js : guard sort?.system dans helpers Handlebars
2026-07-22 00:29:56 +02:00
fabres 7d9ebd601a chore: deplace 10 fichiers .js V1 inutilises vers _deprecated/ 2026-07-22 00:29:56 +02:00
fabres 25e3df1f5d Migration Foundry v14 : classes V1→V2, corrections CSS et bugs
- 21 classes Dialog → DialogV2
- 2 classes Application → ApplicationV2 + HandlebarsApplicationMixin
- 1 classe FormApplication → ApplicationV2
- 10 appels new Dialog() → DialogV2 (rdd-confirm, rdd-commands, etc.)
- base-actor-sheet.mjs : 3 appels new Dialog → DialogV2
- Sheet journal : JournalTextPageSheet → JournalEntryPageProseMirrorSheet
- CSS : backdrop-filter, box-shadow, bordures V2 supprimes pour calendar/astrologie
- FenetreRechercheTirage : _getWindowControls + dragDrop ajoutes
- HtmlUtility.showControlWhen : compat jQuery + DOM natif
- rdd-main.js : init calendrier en setTimeout
- Constantes : duplicate → deepClone, _id → id
- renderTemplate global → foundry.applications.handlebars.renderTemplate
2026-07-22 00:29:56 +02:00
fabres 84ce2f78d7 appv2: correction des éditeurs ProseMirror et scrollbar sur les fiches d'acteur
- Migration des fiches acteur V2 : racine <form> → <section>
  (DocumentSheetV2 encapsule déjà dans <form>, élimine les formulaires imbriqués)
- Ajout de submitOnChange: true sur la feuille de base acteur
  (nécessaire pour que les événements change des éditeurs déclenchent la mise à jour)
- Ajout de toggled=true + wrapper <fieldset><div><label> sur tous les formInput
  (comportement identique aux fiches d'objet)
- CSS : .window-content > section ajouté à côté de > form
  (évite la perte du flex layout et des scrollbar en passant de form à section)
- Modèles acteur : passage de StringField à HTMLField pour les champs
  description/notes/biographie/notesmj (permet le rendu ProseMirror)
- Ajout de systemFields dans le contexte _prepareContext()
2026-07-22 00:29:56 +02:00
fabres 8fddbe698c docs: ajoute TODO-v14.md pour suivi compatibilité Foundry v14 2026-07-22 00:29:56 +02:00
fabres d0a434fb0b refactor(styles): migre toutes les directives CSS inline vers LESS
- Remplace les vieilles règles table (mortes) des dialog-gerer-herbes
  et dialog-gerer-ingredients par des règles flex scoped
- Dialog-gerer-herbes/potions/ingredients/deniers : style= flex/gap/padding
  → classes LESS par dialog avec nth-child
- actor-sheet.hbs : gap+flex-wrap → .oeuvre-buttons,
  height:200px → .tab.hautreve
- carac-derivee.hbs : style sur .sust-buttons → déplacé dans LESS
- regles-optionnelles.hbs : width:100% → .btn-voir-tables-encaissement
- dialog-lancer-recette (hbs+js) : status colors → classes .status-*
- roll-part-appelmoral.hbs : font-size conditionnel → classe .moral-low
- rdd-utility.js : span style → .resultat-created
- rdd-import-stats.js : textarea style → #statBlock
- base-actor-sheet.js : select style → .qty-amount
- export-scriptarium/*.hbs : flex-grow, margin-bottom → classes
- app-personnage-aleatoire.hbs : text-align → .random-char-right

Seuls 2 styles dynamiques persistent : background-image option
(partial-roll-coeur.hbs) et width fillPercent barre (inventaire-item.hbs).
2026-07-22 00:29:56 +02:00
fabres 27b232776c fix(dialogs): évite bringToFront du dialog lors du re-render hook
Le hook updateItem/createItem/deleteItem appelait render(true)
déclenchant bringToFront() (ligne 598 ApplicationV2) qui
passait le dialog devant la fiche item ouverte. Utilise
render() sans force pour re-rendre sans changer le z-index.
2026-07-22 00:29:56 +02:00
fabres d06f1de433 fix(dialogs): refresh gestion herbes/potions/ingredients après édition fiche
Souscrit aux hooks updateItem/createItem/deleteItem pour
re-render le dialog quand un item de l'acteur change.
Nettoyage des hooks via _onClose().
2026-07-22 00:29:56 +02:00
fabres 54fc1e8e1a fix(migration): StatusEffects + SystemCompendiums -> ApplicationV2
Plus aucun FormApplication V1 dans module/settings/.
- Tous les settings menus migrés vers HandlebarsApplicationMixin(ApplicationV2)
- Templates sans wrapper <form>
- Scrollbar via LESS pour #status-effects et #system-compendiums
- Listeners jQuery -> DOM natif dans _onRender
2026-07-22 00:29:56 +02:00
fabres 70092ffd8f fix(migration): OptionsAvancees -> ApplicationV2
Migration V1→V2 identique à ReglesOptionnelles.
+ scrollbar dans LESS (#options-avancees > .window-content)
2026-07-22 00:29:56 +02:00
fabres 36b33890db fix(css): titres tables encaissement alternatives lisibles
h2 dans .encaissement-tables utilise --rdd-color-text-primary
(noir) au lieu de l'héritage blanc/clair du thème.
2026-07-22 00:29:56 +02:00
fabres ec2bf050d4 fix(v2): scrollbar ReglesOptionnelles -> .window-content overflow-y:auto
Le DOM réel n'a PAS .window-app ni .applicationv2 sur le form.
La structure: <form id=regles-optionnelles class=application> >
  .window-content. overflow: hidden dans le core V2 étouffe
le contenu. Fix: #regles-optionnelles > .window-content { overflow-y: auto; }
2026-07-22 00:29:56 +02:00
fabres 00edfe8201 fix(v2): scrollbar dans LESS, pas de style inline/template
Les rules flex+overflow pour les forms AppV2 sont dans
foundryvtt-reve-de-dragon.less. Plus aucun style inline
dans _onRender ni <style> dans les templates.
2026-07-22 00:29:56 +02:00
fabres 6fdb07a4e8 fix(v2): scrollbar via .regles-optionnelles.applicationv2 > .window-content
Le id de DEFAULT_OPTIONS est l'id d'application ui.windows,
PAS l'id DOM (#application-N). Les classes ({classes}) sont
appliquées sur .window-app. CSS ciblé via la classe fonctionne.
2026-07-22 00:29:56 +02:00
fabres c4f9150fdb fix(v2): scrollbar inline via _onRender flex styles
Le CSS via template ne fonctionnait pas car l'id V2 n'est pas
un sélecteur DOM fiable. On apply les styles flex/overflow
directement sur this.element et son premier enfant dans _onRender.
2026-07-22 00:29:56 +02:00
fabres 8400d88677 fix(v2): scrollbar dans DialogEncaissementTables aussi 2026-07-22 00:29:56 +02:00
fabres 93a1478cba fix(v2): scrollbar dans ReglesOptionnelles form overflow-y
Le formulaire ne scrollait pas car le form V2 (tag:'form')
n'avait pas de hauteur contrainte + overflow. Fix via CSS
ciblant #regles-optionnelles .window-content form > div.
2026-07-22 00:29:56 +02:00
fabres 51b05d025a fix(v2): wrapper single root element dans les templates
ApplicationV2 exige un unique élément racine par part.
regles-optionnelles.hbs et dialog-encaissement-tables.hbs
enveloppés dans un <div>.
2026-07-22 00:29:56 +02:00
fabres 348536556a fix(migration): ReglesOptionnelles + DialogEncaissementTables -> ApplicationV2
V1 FormApplication déprécié depuis Foundry v13. Migration vers
HandlebarsApplicationMixin(ApplicationV2) :
- DEFAULT_OPTIONS statique remplace defaultOptions getter
- _prepareContext() async remplace getData()
- _onRender() avec DOM natif remplace activateListeners(html)
- PARTS remplace template
- form: {} sub-object remplace closeOnSubmit/submitOnChange plats
- Templates sans <form> wrapper (V2 l'injecte via tag:'form')
2026-07-22 00:29:56 +02:00
fabres afabba430a feat(encaissement): tables révisées optionnelles + consultation dialog
- Nouveau setting 'tables-encaissement-revisees' dans Options alternatives
- Paliers fins (01-20+), pertes statiques, '@end0' vide endurance à 0
- Dialog de consultation avec les 2 tables (mortel/non-mortel)
- Lien 'Voir les tables d'encaissement' dans le menu Règles optionnelles (MJ)
2026-07-22 00:29:56 +02:00
fabres 3e63054b9d refactor(regles): fusion des Règles customs dans Règles optionnelles -> section Options alternatives, suppress fichiers 2026-07-22 00:29:56 +02:00
fabres 07997576cd style(sust): boutons Repas externe et Rations harmonisés avec chat-card-button 2026-07-22 00:29:56 +02:00
fabres d216e8fcf6 fix(sheets): ajout des sections environnement (inventaire) et temporel (blessure, maladie, poison, rencontre, signedraconique) manquantes dans les sheets V2 2026-07-22 00:29:56 +02:00
fabres 29cdcca1bb fix(cd): message stress journal : perte fixe de 1, reste X, conversion Y en xp 2026-07-22 00:29:56 +02:00
fabres 8c9c8f3345 feat(cd): journal enrichi avec stress, perte enchantements, moral 2026-07-22 00:29:56 +02:00
fabres 8e748a1d94 feat(cd): message statut MJ + journal de suivi stress/moral/events par jour 2026-07-22 00:29:56 +02:00
fabres 7599db07ad feat(repos): nouveau mode sommeil avec sélection heures, dialogs CD intégrés, toolbar équipement 2026-07-22 00:29:56 +02:00
fabres 1db733f379 fix(rations): ne plus afficher 'rien à manger' quand les compteurs sont déjà pleins 2026-07-22 00:29:56 +02:00
fabres 07a48fb3cc refactor(sheets): migration item sheets v13 (models, apps, templates, moins + common/mixins) 2026-07-22 00:29:53 +02:00
fabres f52575f413 fix(rations): consomme assez d'unités pour remplir sust/eau en 1 clic + alerte si vide 2026-07-22 00:26:52 +02:00
fabres 5969009eb1 feat(inventaire): boutons +Ration/+Eau, cartouche chat compact
- Boutons +Ration et +Eau dans l'onglet Équipement
- Incrémente la quantité si l'item existe déjà, sinon le crée
- Nouveau template post-item-compact.hbs pour l'aperçu chat
- _MONTRER utilise désormais le cartouche compact
- postItemToChatCompact() awaitable, champs filtrés par type d'inventaire
2026-07-22 00:26:52 +02:00
522 changed files with 21348 additions and 8138 deletions
+28
View File
@@ -0,0 +1,28 @@
# Session Summary: v13 Migration — DataModel + _id/duplicate Cleanup + template.json removal
## Done
### `duplicate()` → `deepClone()` (finding #10, 36 files)
`foundry.utils.duplicate()``foundry.utils.deepClone()` across the system. `deepClone` preserves Document prototypes where `duplicate` stripped them.
### `_id` → `id` on Documents (finding #11, finding #7, ~25 sites)
- 21 read accesses migrated during initial sweep
- Review sweep caught 3 more: `rdd-combat.js:148` (combatant._id), `rdd-combat.js:898` (esquive e._id), `rdd-tmr-dialog.js:261` (casetmr._id)
- Also fixed: `actor.js:1844` (selectedSort._id), `actor.js:2884` (herbeData._id), `base-actor.js:404` (conteneur c._id)
- Remaining `_id` references are on POJOs (`toObject()`/migration data) — correct.
### `_onClose` → `super._onClose()` (finding #9)
All dialog `_onClose` overrides now call `super._onClose()` for proper HandlebarsApplicationMixin cleanup.
### Actor DataModel Migration (3 phases)
- Phase 1: 5 DataModels in `module/models/` + helpers + `CONFIG.Actor.dataModels` registration
- Phase 2: `prepareActorData()` chain → DataModel `prepareDerivedData()` in each model
- Phase 3: template.json deletion, dead code cleanup
### `template.json` elimination (like `fvtt-hawkmoon-cyd`)
- `template.json` deleted. Foundry v13+ uses `documentTypes` in system.json.
- Added `documentTypes.Actor` (5 types) + `documentTypes.Item` (44 types) with `htmlFields`
- Fixed CI/CD files referencing template.json
## Still remaining (v14-blocking)
V1 Application/Dialog classes (24 `extends Dialog`, 6+ V1 sheets) cannot run in v14 — findings #1#6.
+1 -1
View File
@@ -47,7 +47,7 @@ jobs:
run: node ./tools/packCompendiumsToDist.mjs run: node ./tools/packCompendiumsToDist.mjs
- name: Create ZIP file - name: Create ZIP file
run: zip -r ./rddsystem.zip system.json template.json README.md LICENSE.txt assets/ css/ fonts/ icons lang/ module/ packs/ pic/ sounds/ styles/ templates/ run: zip -r ./rddsystem.zip system.json README.md LICENSE.txt assets/ css/ fonts/ icons lang/ module/ packs/ pic/ sounds/ styles/ templates/
- name: setup go - name: setup go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
+3
View File
@@ -13,3 +13,6 @@ todo.md
/packs/*/LOG /packs/*/LOG
/packs/*/LOCK /packs/*/LOCK
.github/ .github/
*.bak
/AGENTS.md
/regles/
-1
View File
@@ -12,7 +12,6 @@ test:
stage: test stage: test
script: script:
- python -mjson.tool 'system.json' > /dev/null - python -mjson.tool 'system.json' > /dev/null
- python -mjson.tool 'template.json' > /dev/null
build: build:
stage: build stage: build
+68
View File
@@ -0,0 +1,68 @@
# TODO v14 — Compatibilité Foundry VTT
## ✅ Bloquant — terminé
### 1. ✅ Sheets acteur V1 → V2
- V1 déplacés vers `_deprecated/module/actor/` (10 fichiers `.js`)
- V2 dans `module/applications/sheets/` (fichiers `.mjs`)
### 2. ✅ Sheet journal V1 → V2
- `module/journal/journal-sheet.mjs``extends JournalEntryPageProseMirrorSheet`
- `_prepareContentContext` override → `RdDTextEditor.enrichHTML()`
- `_onRender` → roll-text/chat-roll-text click listeners
### 3. ✅ 24 classes `extends Dialog` → V2
- Toutes converties en `extends foundry.applications.api.DialogV2`
- `module/tirage/fenetre-recherche-tirage.js``FenetreRechercheTirage` migré V1 App → V2 ApplicationV2
- `module/sommeil/dialog-nouveau-repos-cd.js``new Dialog(...)` direct → `DialogV2.wait()`
### 4. ✅ 3 classes `extends Application` → V2
- `module/tirage/fenetre-recherche-tirage.js` — FAIT (ApplicationV2)
- `module/time/rdd-calendrier.js` (RdDCalendrier) — FAIT (ApplicationV2)
- `module/sommeil/app-astrologie.js` (AppAstrologie) — FAIT (ApplicationV2)
### 5. ✅ 1 classe `extends FormApplication` → V2
- `module/actor/random/app-personnage-aleatoire.js` — FAIT (ApplicationV2)
### 6. ✅ 7 appels `new Dialog({...})` directs → DialogV2
Tous convertis. +3 autres dans `module/applications/sheets/base-actor-sheet.mjs` & `personnage-sheet.mjs`.
## 🟠 Haute priorité
### 7. ✅ `_id` dans `updateEmbeddedDocuments` → utiliser `.id` — **FAIT** (10 occ. changées, 2 déjà OK, 1 string param)
### 8. ✅ `bringToFront()` sur apps V1 → adapter
Rétrocompatibilité V2 gère. Pas d'erreur constatée (les apps converties utilisent V2).
### 9. ✅ `_onClose()` sans `super._onClose()` → **FAIT**
- `module/dialog-gerer-potions.js:45-49`
- `module/dialog-gerer-ingredients.js:45-49`
- `module/dialog-gerer-herbes.js:47-51`
## 🟡 Priorité moyenne
### 10. ✅ `foundry.utils.duplicate()` → `deepClone()` — **FAIT** (36 fichiers)
### 11. ✅ `._id` en lecture → `.id` — **FAIT** (21 occ. changées, 11 conservées sur objets de données)
~45 occurrences : `item._id`, `actor._id`, `combatant._id`, `token._id`
### 12. `render(true)` sur apps V2 → `render({force: true})`
Rétrocompatibilité V2 normalise booléen → pas prioritaire.
~60 occurrences dans module/ — nos fichiers convertis l'utilisent déjà.
### 13. ✅ Incohérence registration sheets
`module/applications/sheets/common-item-sheet.mjs``Items.registerSheet()``foundry.documents.collections.Items.registerSheet()`
## 🟢 Basse priorité / Info
### 14. ✅ `canvas` sans vérification `ready`
27 occurrences dans 11 fichiers sécurisées avec `?.`
### 15. ✅ `element[0]` jQuery
`module/tirage/fenetre-recherche-tirage.js:213` — déjà résolu par refacto antérieur
### 16. ✅ `_warnedAppV1 = true`
Supprimé de `rdd-calendrier.js`; restant dans `_deprecated/` ignoré
### 17. ✅ `system.json` — nom de langue
`"name": "English"``"Français"` pour ne pas tromper sur `lang/fr.json`
@@ -2,7 +2,7 @@ import { RdDUtility } from "./rdd-utility.js";
import { HtmlUtility } from "./html-utility.js"; import { HtmlUtility } from "./html-utility.js";
import { RdDBonus } from "./rdd-bonus.js"; import { RdDBonus } from "./rdd-bonus.js";
import { Misc } from "./misc.js"; import { Misc } from "./misc.js";
import { RdDCombatManager } from "./rdd-combat.js"; import { RdDCombat } from "./combat/rdd-combat.js";
import { CARACS, RdDCarac } from "./rdd-carac.js"; import { CARACS, RdDCarac } from "./rdd-carac.js";
import { DialogSplitItem } from "./dialog-split-item.js"; import { DialogSplitItem } from "./dialog-split-item.js";
import { ReglesOptionnelles } from "./settings/regles-optionnelles.js"; import { ReglesOptionnelles } from "./settings/regles-optionnelles.js";
@@ -13,7 +13,7 @@ import { ACTOR_TYPES, ITEM_TYPES } from "./constants.js";
import { RdDItem } from "./item.js"; import { RdDItem } from "./item.js";
import { RdDItemCompetence } from "./item-competence.js"; import { RdDItemCompetence } from "./item-competence.js";
import { RdDItemBlessure } from "./item/blessure.js"; import { RdDItemBlessure } from "./item/blessure.js";
import { RdDEmpoignade } from "./rdd-empoignade.js"; import { RdDEmpoignade } from "./combat/rdd-empoignade.js";
import { RdDBaseActorSangSheet } from "./actor/base-actor-sang-sheet.js"; import { RdDBaseActorSangSheet } from "./actor/base-actor-sang-sheet.js";
import { RdDCoeur } from "./coeur/rdd-coeur.js"; import { RdDCoeur } from "./coeur/rdd-coeur.js";
import { AppPersonnageAleatoire } from "./actor/random/app-personnage-aleatoire.js"; import { AppPersonnageAleatoire } from "./actor/random/app-personnage-aleatoire.js";
@@ -126,7 +126,7 @@ export class RdDActorSheet extends RdDBaseActorSangSheet {
.forEach(tache => { .forEach(tache => {
if (tache.isTacheCommune()) { if (tache.isTacheCommune()) {
const tacheCommune = tache.getTacheOuCommune() const tacheCommune = tache.getTacheOuCommune()
tache.system = foundry.utils.duplicate(tacheCommune.system) tache.system = foundry.utils.deepClone(tacheCommune.system)
tache.system.tacheCommuneId = tache.system.tacheCommuneId tache.system.tacheCommuneId = tache.system.tacheCommuneId
} }
return tache return tache
@@ -214,7 +214,7 @@ export class RdDActorSheet extends RdDBaseActorSangSheet {
this.html.find('.action-jeu').click(async event => await this.actor.rollJeu(RdDSheetUtility.getItemId(event))) this.html.find('.action-jeu').click(async event => await this.actor.rollJeu(RdDSheetUtility.getItemId(event)))
this.html.find('.action-recettecuisine').click(async event => await this.actor.rollRecetteCuisine(RdDSheetUtility.getItemId(event))) this.html.find('.action-recettecuisine').click(async event => await this.actor.rollRecetteCuisine(RdDSheetUtility.getItemId(event)))
this.html.find('.description-aleatoire').click(async event => new AppPersonnageAleatoire(this.actor).render(true)) this.html.find('.description-aleatoire').click(async event => new AppPersonnageAleatoire(this.actor).render({ force: true }))
this.html.find('.gerer-herbes').click(async event => { this.html.find('.gerer-herbes').click(async event => {
const { DialogGererHerbes } = await import("./dialog-gerer-herbes.js") const { DialogGererHerbes } = await import("./dialog-gerer-herbes.js")
@@ -265,7 +265,7 @@ export class RdDActorSheet extends RdDBaseActorSangSheet {
this.html.find('.roll-init-arme').click(async event => { this.html.find('.roll-init-arme').click(async event => {
let combatant = game.combat.combatants.find(c => c.actor.id == this.actor.id) let combatant = game.combat.combatants.find(c => c.actor.id == this.actor.id)
if (combatant) { if (combatant) {
RdDCombatManager.rollInitiativeAction(combatant._id, this._getActionCombat(event)); RdDCombat.rollInitiativeAction(combatant.id, this._getActionCombat(event));
} else { } else {
ui.notifications.info("Impossible de lancer l'initiative sans être dans un combat."); ui.notifications.info("Impossible de lancer l'initiative sans être dans un combat.");
} }
@@ -323,6 +323,8 @@ export class RdDActorSheet extends RdDBaseActorSangSheet {
this.html.find('.moral-neutre').click(async event => await this.actor.jetDeMoral(MORAL.NEUTRE)) this.html.find('.moral-neutre').click(async event => await this.actor.jetDeMoral(MORAL.NEUTRE))
this.html.find('.moral-heureux').click(async event => await this.actor.jetDeMoral(MORAL.HEUREUX)) this.html.find('.moral-heureux').click(async event => await this.actor.jetDeMoral(MORAL.HEUREUX))
this.html.find('.button-ethylisme').click(async event => await this.actor.jetEthylisme()) this.html.find('.button-ethylisme').click(async event => await this.actor.jetEthylisme())
this.html.find('.btn-sust-externe').click(async event => await this.actor.mangerExterne())
this.html.find('.btn-sust-rations').click(async event => await this.actor.mangerRations())
this.html.find('.ptreve-actuel-plus').click(async event => await this.actor.reveActuelIncDec(1)) this.html.find('.ptreve-actuel-plus').click(async event => await this.actor.reveActuelIncDec(1))
this.html.find('.ptreve-actuel-moins').click(async event => await this.actor.reveActuelIncDec(-1)) this.html.find('.ptreve-actuel-moins').click(async event => await this.actor.reveActuelIncDec(-1))
@@ -371,18 +373,13 @@ export class RdDActorSheet extends RdDBaseActorSangSheet {
content += `<option value="${typeName}">${Misc.typeName('Item', typeName)}</option>` content += `<option value="${typeName}">${Misc.typeName('Item', typeName)}</option>`
} }
content += '</select>'; content += '</select>';
let dialog = new Dialog({ new foundry.applications.api.DialogV2({
title: "Créer une oeuvre", title: "Créer une oeuvre",
content: content, content,
buttons: { buttons: [
create: { { label: "Créer l'oeuvre", action: "create", icon: "fa-solid fa-check", callback: () => this.actor.createItem($(".item-type").val()) }
icon: '<i class="fas fa-check"></i>', ]
label: "Créer l'oeuvre", }).render({ force: true });
callback: () => this.actor.createItem($(".item-type").val())
}
}
});
dialog.render(true);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -428,7 +425,7 @@ export class RdDActorSheet extends RdDBaseActorSangSheet {
async _onSplitItem(item, split) { async _onSplitItem(item, split) {
if (split >= 1 && split < item.system.quantite) { if (split >= 1 && split < item.system.quantite) {
await item.diminuerQuantite(split); await item.diminuerQuantite(split);
const splitItem = foundry.utils.duplicate(item); const splitItem = foundry.utils.deepClone(item);
splitItem.system.quantite = split; splitItem.system.quantite = split;
await this.actor.createEmbeddedDocuments('Item', [splitItem]) await this.actor.createEmbeddedDocuments('Item', [splitItem])
} }
@@ -8,6 +8,8 @@ import { RdDItem } from "../item.js";
import { RdDTextEditor } from "../apps/rdd-text-roll-editor.js"; import { RdDTextEditor } from "../apps/rdd-text-roll-editor.js";
import { ItemAction } from "../item/item-actions.js"; import { ItemAction } from "../item/item-actions.js";
import { RdDItemCompetenceCreature } from "../item-competencecreature.js"; import { RdDItemCompetenceCreature } from "../item-competencecreature.js";
import { DialogVerifierIngredients } from "../dialog-verifier-ingredients.js";
import { DialogLancerRecette } from "../dialog-lancer-recette.js";
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**
@@ -90,6 +92,8 @@ export class RdDBaseActorSheet extends foundry.appv1.sheets.ActorSheet {
this.html.find('.actionItem').click(async event => await ItemAction.onActionItem(event, this.actor, this.options)) this.html.find('.actionItem').click(async event => await ItemAction.onActionItem(event, this.actor, this.options))
this.html.find('.item-edit').click(async event => await this.itemActionEdit(event)) this.html.find('.item-edit').click(async event => await this.itemActionEdit(event))
this.html.find('.item-verify-ingredients').click(async event => await this._onVerifyIngredients(event))
this.html.find('.item-lancer-recette').click(async event => await this._onLancerRecette(event))
this.html.find('.conteneur-name a').click(async event => { this.html.find('.conteneur-name a').click(async event => {
RdDUtility.toggleAfficheContenu(this.getItemId(event)) RdDUtility.toggleAfficheContenu(this.getItemId(event))
this.render(true) this.render(true)
@@ -137,35 +141,57 @@ export class RdDBaseActorSheet extends foundry.appv1.sheets.ActorSheet {
event.preventDefault() event.preventDefault()
const item = this.getItem(event) const item = this.getItem(event)
if (!item?.isInventaire()) return if (!item?.isInventaire()) return
new Dialog({ new foundry.applications.api.DialogV2({
title: `Quantité — ${item.name}`, title: `Quantité — ${item.name}`,
content: `<p>Réduire la quantité de :</p> content: `<p>Réduire la quantité de :</p>
<select class="qty-amount" style="width:100%"> <select class="qty-amount">
<option value="1">1</option> <option value="1">1</option>
<option value="5">5</option> <option value="5">5</option>
<option value="10">10</option> <option value="10">10</option>
<option value="all">Tout jeter</option> <option value="all">Tout jeter</option>
</select>`, </select>`,
buttons: { buttons: [
reduce: { { action: "reduce", label: "Retirer", icon: "fa-solid fa-minus", callback: (event, button, dialog) => {
icon: '<i class="fas fa-minus"></i>', const amount = dialog.element.querySelector('.qty-amount').value
label: 'Retirer', if (amount === 'all') {
callback: html => { item.delete()
const amount = html.find('.qty-amount').val() this.render({ force: true })
if (amount === 'all') { } else {
item.delete() item.update({ 'system.quantite': Math.max(0, item.system.quantite - parseInt(amount)) })
this.render(true)
} else {
item.update({ 'system.quantite': Math.max(0, item.system.quantite - parseInt(amount)) })
}
} }
} }}
} ]
}).render(true) }).render({ force: true })
}) })
this.html.find('.creer-un-objet').click(async event => await this.selectObjetTypeToCreate()) this.html.find('.creer-un-objet').click(async event => await this.selectObjetTypeToCreate())
this.html.find('.nettoyer-conteneurs').click(async event => await this.actor.nettoyerConteneurs()) this.html.find('.nettoyer-conteneurs').click(async event => await this.actor.nettoyerConteneurs())
this.html.find('.quick-add-ration').click(async event => {
const existante = this.actor.items.find(it => it.type === "nourritureboisson" && it.name === "Ration")
if (existante) {
await existante.update({ "system.quantite": Math.max(0, Number(existante.system.quantite) + 1) })
} else {
await this.actor.createEmbeddedDocuments('Item', [{
name: "Ration", type: "nourritureboisson",
img: "systems/foundryvtt-reve-de-dragon/icons/objets/provision_crue.webp",
system: { sust: 1, encombrement: 0.1, quantite: 1, cout: 0.1, boisson: false }
}], { renderSheet: false })
}
this.render(true)
})
this.html.find('.quick-add-eau').click(async event => {
const existante = this.actor.items.find(it => it.type === "nourritureboisson" && it.name === "Eau")
if (existante) {
await existante.update({ "system.quantite": Math.max(0, Number(existante.system.quantite) + 1) })
} else {
await this.actor.createEmbeddedDocuments('Item', [{
name: "Eau", type: "nourritureboisson",
img: "systems/foundryvtt-reve-de-dragon/icons/objets/outre.webp",
system: { desaltere: 1, encombrement: 0.1, quantite: 1, cout: 0.05, boisson: true }
}], { renderSheet: false })
}
this.render(true)
})
this.html.find('.vue-detaillee').click(async event => { this.html.find('.vue-detaillee').click(async event => {
this.options.vueDetaillee = !this.options.vueDetaillee this.options.vueDetaillee = !this.options.vueDetaillee
@@ -174,8 +200,29 @@ export class RdDBaseActorSheet extends foundry.appv1.sheets.ActorSheet {
} }
itemActionEdit(event) { itemActionEdit(event) {
const item = this.getItem(event); const item = this.getItem(event)
return item?.sheet.render(true); if (item) item.sheet.render(true)
}
/* -------------------------------------------- */
async _onVerifyIngredients(event) {
event.preventDefault()
const item = this.getItem(event)
if (!item || item.type !== ITEM_TYPES.recettealchimique) return
DialogVerifierIngredients.create(item, this.actor)
}
/* -------------------------------------------- */
async _onLancerRecette(event) {
event.preventDefault()
const item = this.getItem(event)
if (!item || item.type !== ITEM_TYPES.recettealchimique) return
const ingredients = Misc.safeJsonParse(item.system.ingredients, [])
if (!ingredients.length) {
ui.notifications.warn('Cette recette n\'a pas d\'ingrédients définis.')
return
}
DialogLancerRecette.create(item, this.actor)
} }
_rechercherKeyup(event) { _rechercherKeyup(event) {
@@ -262,18 +309,13 @@ export class RdDBaseActorSheet extends foundry.appv1.sheets.ActorSheet {
content += `<option value="${typeName}">${Misc.typeName('Item', typeName)}</option>` content += `<option value="${typeName}">${Misc.typeName('Item', typeName)}</option>`
} }
content += '</select>'; content += '</select>';
let d = new Dialog({ new foundry.applications.api.DialogV2({
title: "Créer un équipement", title: "Créer un équipement",
content: content, content,
buttons: { buttons: [
create: { { action: "create", label: "Créer l'objet", icon: "fa-solid fa-check", callback: () => this.actor.createItem($(".item-type").val()) }
icon: '<i class="fas fa-check"></i>', ]
label: "Créer l'objet", }).render({ force: true });
callback: () => this.actor.createItem($(".item-type").val())
}
}
});
d.render(true);
} }
getTypesInventaire() { getTypesInventaire() {
@@ -302,7 +344,7 @@ export class RdDBaseActorSheet extends foundry.appv1.sheets.ActorSheet {
async _onSplitItem(item, split) { async _onSplitItem(item, split) {
if (split >= 1 && split < item.system.quantite) { if (split >= 1 && split < item.system.quantite) {
await item.diminuerQuantite(split); await item.diminuerQuantite(split);
const splitItem = foundry.utils.duplicate(item); const splitItem = foundry.utils.deepClone(item);
splitItem.system.quantite = split; splitItem.system.quantite = split;
await this.actor.createEmbeddedDocuments('Item', [splitItem]) await this.actor.createEmbeddedDocuments('Item', [splitItem])
} }
+104
View File
@@ -1,5 +1,109 @@
# 13.0 # 13.0
## 14.0.4 - Le sommeil de Tétragor
### Sommeil & Château Dormant (nouveau mode de repos)
- Insomnie (Queue de Dragon)
- bannière d'avertissement dans le dialogue de repos
- rafraîchissement du drapeau à l'ouverture du dialogue
- vérification en direct dans la boucle de sommeil : si l'insomnie surgit en
cours de nuit, la récupération de fatigue et de rêve est interrompue
- drapeau remis à jour dans les deux sens (levée possible)
- mention dans le dialogue Château Dormant
- Château Dormant automatique : déclenché quand l'heure de fin de nuit (11) est
réellement atteinte, au lieu de seulement après 12 heures de sommeil
- Reprise de nuit après un Rêve de Dragon
- la durée de sommeil est conservée
- les heures restantes sont proposées à la réouverture du dialogue avec une
bannière « Reprise de nuit »
- remise à zéro après une nuit complète
- don « double rêve » : second jet de récupération de rêve par heure (parité
ancien mode)
- garde anti-boucle infinie si l'insomnie apparaît pendant le sommeil
- info-bulle sur le réveil forcé
- perte de rêve des enchantements au Château Dormant : suppression du message en
double (whisper dédié), texte intégré au résumé ; la gemme renvoie son
nouveau total de points de rêve
### Commerces
- achat depuis un commerce en quantité illimitée : la quantité achetée est
correctement utilisée au lieu de celle du commerce
- fiche commerce : le champ Description utilise toute la largeur de la fiche
### Compétences
- ne plus créer d'item « Sans compétence » lors des migrations
## 14.0.3 - Les bases de Tétragor
- XP des compétences : les bases de catégorie peuvent être négatives
(base stockée en entier signé, migration 14.0.3)
- réorganisation des titres CSS
## 14.0.2 - Le grang ménage de Tétragor
### Migration Foundry V14
- Migration des classes restantes de V1 vers V2 (ApplicationV2)
- OptionsAvancees, StatusEffects, SystemCompendiums, ReglesOptionnelles,
DialogEncaissementTables
- DialogV2: formes imbriquées, boutons, styles et positionnement corrigés
- DialogV2 requiert au moins un bouton (boutons vides interdits en V14)
- templates avec wrapper à racine unique
- correction des éditeurs ProseMirror et des scrollbar sur les fiches d'acteur
- rollMode → messageMode, Token HUD, data-message-id
- champ age en StringField pour compat V14
- compat V14: _onFirstRender (HTMLElement.show) pour les jets de dés,
fermeture des dialogs TMR après lecture d'un signe draconique
- 10 fichiers .js V1 inutilisés déplacés vers _deprecated/
- scrollbar des applications V2 corrigées (LESS, plus de style inline)
- migration de toutes les directives CSS inline vers LESS
- titres des tables d'encaissement alternatives lisibles
- le dialog ne passe plus au premier plan lors du re-render (hook)
- refresh des dialogs herbes/potions/ingrédients après édition de fiche
- TODO-v14.md pour le suivi de la compatibilité V14
### Feuilles d'items
- migration des feuilles d'items vers V2 (models, apps, templates, less + common/mixins)
- ajout des sections environnement (inventaire) et temporel (blessure, maladie,
poison, rencontre, signe draconique) manquantes dans les sheets V2
### Sommeil & Château Dormant
- nouveau mode sommeil avec sélection des heures de repos
- dialogs Château Dormant intégrés, toolbar équipement
- message de statut MJ + journal de suivi stress/moral/événements par jour
- journal enrichi avec stress, perte d'enchantements, moral
- message stress journal: perte fixe de 1, reste X, conversion Y en xp
- boutons Repas externe et Rations harmonisés avec les cartouches de tchat
### Inventaire
- boutons +Ration/+Eau et cartouche de tchat compacte
- consommation des rations: assez d'unités pour remplir sustentation/eau en un
clic + alerte si vide
- ne plus afficher « rien à manger » quand les compteurs sont déjà pleins
### Règles & encaissement
- fusion des Règles customs dans Règles optionnelles (section Options alternatives)
- tables d'encaissement révisées optionnelles + dialog de consultation
### Divers
- regroupement des classes de combat
- fix assign dans les constructeurs
### Tests automatisés
- plan de test (227 cas, 33 sections) + scripts TC-01 à TC-07
- 22 scripts TC supplémentaires (TC-08 à TC-31) avec fonctions partagées
- orchestrateur run-all-tests.sh (sélection, exécution, rapport final)
- documentation du lancement des tests dans test/README.md
## 14.0.1 - Les imprécisions de Tétragor ## 14.0.1 - Les imprécisions de Tétragor
- correction de l'indicateur de remplissage des conteneurs - correction de l'indicateur de remplissage des conteneurs
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
.arme-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.armure-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.blessure-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.casetmr-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.chant-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.competence-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.competencecreature-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.conteneur-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.danse-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.empoignade-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.extraitpoetique-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.faune-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.gemme-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.herbe-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.ingredient-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.jeu-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.livre-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.maladie-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.meditation-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+1
View File
@@ -3,5 +3,6 @@
.item-sheet-common(); .item-sheet-common();
.item-sheet-header(); .item-sheet-header();
.item-sheet-scrollable-body();
} }
+2 -1
View File
@@ -1,7 +1,8 @@
// Styles pour la feuille d'objet Monnaie // Styles pour la feuille d'objet Munition
.munition-content { .munition-content {
.item-sheet-common(); .item-sheet-common();
.item-sheet-header(); .item-sheet-header();
.item-sheet-scrollable-body();
} }
+5
View File
@@ -0,0 +1,5 @@
.musique-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.nombreastral-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.nourritureboisson-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.objet-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.oeuvre-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.ombre-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.plante-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.poison-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.possession-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.potion-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.queue-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.race-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+12
View File
@@ -0,0 +1,12 @@
.recettealchimique-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
.ingredient-row {
display: flex;
gap: 4px;
align-items: center;
padding: 2px 0;
}
}
+5
View File
@@ -0,0 +1,5 @@
.recettecuisine-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.rencontre-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.service-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.signedraconique-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.sort-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.sortreserve-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.souffle-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+5
View File
@@ -0,0 +1,5 @@
.tache-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+2 -1
View File
@@ -1,7 +1,8 @@
// Styles pour la feuille d'objet Monnaie // Styles pour la feuille d'objet Tarot
.tarot-content { .tarot-content {
.item-sheet-common(); .item-sheet-common();
.item-sheet-header(); .item-sheet-header();
.item-sheet-scrollable-body();
} }
+5
View File
@@ -0,0 +1,5 @@
.tete-content {
.item-sheet-common();
.item-sheet-header();
.item-sheet-scrollable-body();
}
+26 -4
View File
@@ -74,9 +74,31 @@
} }
} }
.item-sheet-scrollable-body() {
max-height: 75vh;
overflow: hidden;
> section {
display: flex;
flex-direction: column;
min-height: 0;
height: 100%;
}
.sheet-header {
flex-shrink: 0;
}
.sheet-body {
overflow-y: auto;
flex: 1;
min-height: 0;
}
}
.item-sheet-header() { .item-sheet-header() {
background: #011d33 url(../assets/ui/bg_header.webp) no-repeat left top;
color: rgba(255, 255, 255, 1); color: rgba(255, 255, 255, 1);
.sheet-header {
background: #011d33 url(../assets/ui/bg_header.webp) no-repeat left top;
}
.sheet-header-row { .sheet-header-row {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -86,7 +108,7 @@
font-family: CaslonAntique; font-family: CaslonAntique;
font-size: 2.5rem; font-size: 2.5rem;
font-weight: normal; font-weight: normal;
color: var(--window-header-title-color); color: var(--window-header-title-color, #fff);
margin: 4px; margin: 4px;
height: 3rem; height: 3rem;
} }
@@ -100,8 +122,8 @@
input[type="date"], input[type="date"],
input[type="time"] input[type="time"]
) { ) {
color: rgba(255, 255, 255, 0.75); color: #fff;
background: rgba(255, 255, 255, 0.1); background: rgba(0, 0, 0, 0.2);
border: 0 none; border: 0 none;
margin-bottom: 0.2rem; margin-bottom: 0.2rem;
} }
+14
View File
@@ -0,0 +1,14 @@
// Supprimer tous les items du monde
// Coller ce code dans une macro Foundry (type script)
const count = game.items.size
if (!count) return ui.notifications.info("Aucun item dans le monde.")
const confirmed = await Dialog.confirm({
title: "Supprimer tous les items",
content: `<p>⚠️ <b>${count} items</b> vont être définitivement supprimés.</p>`,
})
if (!confirmed) return
await Item.deleteDocuments(game.items.map(i => i.id))
ui.notifications.info(`${count} items supprimés.`)
+1 -1
View File
@@ -12,7 +12,7 @@ export class ChatVente {
return undefined; return undefined;
} }
const nbLots = chatMessage.getFlag(SYSTEM_RDD, NB_LOTS) const nbLots = chatMessage.getFlag(SYSTEM_RDD, NB_LOTS)
const detail = foundry.utils.duplicate(chatMessage.getFlag(SYSTEM_RDD, DETAIL_VENTE)) const detail = foundry.utils.deepClone(chatMessage.getFlag(SYSTEM_RDD, DETAIL_VENTE))
if (!detail.item) { if (!detail.item) {
ui.notifications.warn("Impossible d'acheter: informations sur l'objet manquantes") ui.notifications.warn("Impossible d'acheter: informations sur l'objet manquantes")
return undefined; return undefined;
+57 -59
View File
@@ -1,10 +1,9 @@
import { renderTemplate } from "../constants.js"; import { renderTemplate } from "../constants.js"
import { Misc } from "../misc.js"; import { Misc } from "../misc.js"
import { RdDUtility } from "../rdd-utility.js"; import { RdDUtility } from "../rdd-utility.js"
import { ChatVente } from "./chat-vente.js"; import { ChatVente } from "./chat-vente.js"
export class DialogItemAchat extends Dialog {
export class DialogItemAchat extends foundry.applications.api.DialogV2 {
static preparerAchat(chatButton) { static preparerAchat(chatButton) {
return ChatVente.getDetailAchatVente(RdDUtility.findChatMessageId(chatButton)) return ChatVente.getDetailAchatVente(RdDUtility.findChatMessageId(chatButton))
} }
@@ -21,57 +20,64 @@ export class DialogItemAchat extends Dialog {
choix: { seForcer: false, supprimerSiZero: true }, choix: { seForcer: false, supprimerSiZero: true },
prixLot, prixLot,
isVente: prixLot > 0, isVente: prixLot > 0,
isConsommable: item.type == 'nourritureboisson' && acheteur?.isPersonnage(), isConsommable: item.type == "nourritureboisson" && acheteur?.isPersonnage(),
chatMessageIdVente chatMessageIdVente
} }
if (venteData.vendeur?.id == venteData.acheteur?.id) { if (venteData.vendeur?.id == venteData.acheteur?.id) {
ui.notifications.info("Inutile de se vendre à soi-même") ui.notifications.info("Inutile de se vendre à soi-même")
return return
} }
DialogItemAchat.changeNombreLots(venteData, 1) DialogItemAchat.changeNombreLots(venteData, 1)
const html = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/dialog-item-achat.hbs`, venteData) const content = await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/dialog-item-achat.hbs", venteData)
new DialogItemAchat(html, venteData).render(true) new DialogItemAchat(content, venteData).render({ force: true })
} }
static changeNombreLots(venteData, nombreLots) { static changeNombreLots(venteData, nombreLots) {
venteData.choix.nombreLots = nombreLots; venteData.choix.nombreLots = nombreLots
venteData.prixTotal = (nombreLots * venteData.prixLot).toFixed(2); venteData.prixTotal = (nombreLots * venteData.prixLot).toFixed(2)
if (venteData.isConsommable) { if (venteData.isConsommable) {
const doses = nombreLots * venteData.tailleLot; const doses = nombreLots * venteData.tailleLot
venteData.totalSust = Misc.keepDecimals(doses * (venteData.item.system.sust ?? 0), 2); venteData.totalSust = Misc.keepDecimals(doses * (venteData.item.system.sust ?? 0), 2)
venteData.totalDesaltere = venteData.item.system.boisson venteData.totalDesaltere = venteData.item.system.boisson
? Misc.keepDecimals(doses * (venteData.item.system.desaltere ?? 0), 2) ? Misc.keepDecimals(doses * (venteData.item.system.desaltere ?? 0), 2)
: 0; : 0
} }
} }
constructor(html, venteData) { constructor(content, venteData) {
let options = { classes: ["dialogachat"], width: 400, height: 'fit-content', 'z-index': 99999 }; const actionAchat = venteData.prixLot > 0 ? "Acheter" : "Prendre"
const buttons = []
const actionAchat = venteData.prixLot > 0 ? "Acheter" : "Prendre";
const buttons = {};
if (venteData.isConsommable) { if (venteData.isConsommable) {
buttons["consommer"] = { label: venteData.item.system.boisson ? "Boire" : "Manger", callback: it => this.onAchatConsommer() } buttons.push({
label: venteData.item.system.boisson ? "Boire" : "Manger",
action: "consommer",
callback: () => this.onAchatConsommer()
})
} }
buttons[actionAchat] = { label: actionAchat, callback: it => { this.onAchat(); } }; buttons.push({ label: actionAchat, action: actionAchat, callback: () => this.onAchat() })
buttons["decliner"] = { label: "cliner", callback: it => { } }; buttons.push({ label: "Décliner", action: "decliner", callback: () => {} })
const acheteur = venteData.acheteur?.name ?? 'Un acheteur'; const acheteur = venteData.acheteur?.name ?? "Un acheteur"
const vendeur = venteData.vendeur?.name ?? 'Un vendeur'; const vendeur = venteData.vendeur?.name ?? "Un vendeur"
let conf = { super({
title: `${acheteur} - ${actionAchat} à ${vendeur}`, title: `${acheteur} - ${actionAchat} à ${vendeur}`,
content: html, content,
default: actionAchat, classes: ["dialogachat"],
buttons: buttons position: { width: 400, height: 'fit-content' },
}; buttons
})
this.venteData = venteData
}
super(conf, options); _onRender(context) {
this.venteData = venteData; super._onRender(context)
this.element.querySelector(".nombreLots")?.addEventListener("change", event => this.setNombreLots(Number(event.currentTarget.value)))
this.element.querySelector(".se-forcer")?.addEventListener("change", event => this.setSeForcer(event))
} }
async onAchat() { async onAchat() {
await this.html.find(".nombreLots").change(); const el = this.element
(this.venteData.vendeur ?? this.venteData.acheteur).achatVente({ el.querySelector(".nombreLots")?.dispatchEvent(new Event("change"))
;(this.venteData.vendeur ?? this.venteData.acheteur).achatVente({
userId: game.user.id, userId: game.user.id,
vendeurId: this.venteData.vendeur?.id, vendeurId: this.venteData.vendeur?.id,
acheteurId: this.venteData.acheteur?.id, acheteurId: this.venteData.acheteur?.id,
@@ -79,42 +85,34 @@ export class DialogItemAchat extends Dialog {
chatMessageIdVente: this.venteData.chatMessageIdVente, chatMessageIdVente: this.venteData.chatMessageIdVente,
choix: this.venteData.choix, choix: this.venteData.choix,
vente: this.venteData vente: this.venteData
}); })
} }
async onAchatConsommer() { async onAchatConsommer() {
this.venteData.choix.consommer = true; this.venteData.choix.consommer = true
await this.onAchat(); await this.onAchat()
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
this.html = html;
this.html.find(".nombreLots").change(event => this.setNombreLots(Number(event.currentTarget.value)));
this.html.find(".se-forcer").change(event => this.setSeForcer(event));
} }
setSeForcer(event) { setSeForcer(event) {
this.venteData.choix.seForcer = event.currentTarget.checked; this.venteData.choix.seForcer = event.currentTarget.checked
} }
setNombreLots(nbLots) { setNombreLots(nbLots) {
if (!this.venteData.quantiteIllimite) { if (!this.venteData.quantiteIllimite) {
if (!this.venteData.quantiteIllimite && nbLots > this.venteData.nbLots) { if (nbLots > this.venteData.nbLots) {
ui.notifications.warn(`Seulement ${this.venteData.nbLots} lots disponibles, vous ne pouvez pas en prendre ${nbLots}`) ui.notifications.warn(`Seulement ${this.venteData.nbLots} lots disponibles, vous ne pouvez pas en prendre ${nbLots}`)
} }
nbLots = Math.min(nbLots, this.venteData.nbLots); nbLots = Math.min(nbLots, this.venteData.nbLots)
} }
DialogItemAchat.changeNombreLots(this.venteData, nbLots)
DialogItemAchat.changeNombreLots(this.venteData, nbLots); const el = this.element
const nbl = el.querySelector(".nombreLots")
this.html.find(".nombreLots").val(nbLots); if (nbl) nbl.value = nbLots
this.html.find(".prixTotal").text(this.venteData.prixTotal); const pt = el.querySelector(".prixTotal")
this.html.find("span.total-sust").text(this.venteData.totalSust); if (pt) pt.textContent = this.venteData.prixTotal
this.html.find("span.total-desaltere").text(this.venteData.totalDesaltere); const sust = el.querySelector("span.total-sust")
if (sust) sust.textContent = this.venteData.totalSust
const des = el.querySelector("span.total-desaltere")
if (des) des.textContent = this.venteData.totalDesaltere
} }
}
}
+58 -53
View File
@@ -1,14 +1,13 @@
import { renderTemplate } from "../constants.js"; import { renderTemplate } from "../constants.js"
import { HtmlUtility } from "../html-utility.js"; import { HtmlUtility } from "../html-utility.js";
import { RdDUtility } from "../rdd-utility.js"; import { RdDUtility } from "../rdd-utility.js";
import { ChatVente } from "./chat-vente.js"; import { ChatVente } from "./chat-vente.js";
export class DialogItemVente extends Dialog { export class DialogItemVente extends foundry.applications.api.DialogV2 {
static async display({ item, quantiteMax = undefined }) { static async display({ item, quantiteMax = undefined }) {
const quantite = quantiteMax ?? item.getQuantite() ?? 1; const quantite = quantiteMax ?? item.getQuantite() ?? 1
const venteData = { const venteData = {
item: item, item,
alias: item.actor?.name ?? game.user.name, alias: item.actor?.name ?? game.user.name,
vendeurId: item.actor?.id, vendeurId: item.actor?.id,
prixOrigine: item.calculerPrixCommercant(), prixOrigine: item.calculerPrixCommercant(),
@@ -19,93 +18,99 @@ export class DialogItemVente extends Dialog {
maxLots: quantite, maxLots: quantite,
quantiteMax: quantite, quantiteMax: quantite,
quantiteIllimite: item.isItemCommerce() ? quantiteMax == undefined : !item.parent, quantiteIllimite: item.isItemCommerce() ? quantiteMax == undefined : !item.parent,
isOwned: item.parent, isOwned: item.parent,
} }
const html = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/dialog-item-vente.hbs`, venteData); const content = await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/dialog-item-vente.hbs", venteData)
return new DialogItemVente(venteData, html).render(true); const dialog = new DialogItemVente(venteData, content)
dialog.render({ force: true })
return dialog
} }
constructor(venteData, html) { constructor(venteData, content) {
let options = { classes: ["dialogvente"], width: 400, height: 'fit-content', 'z-index': 99999 }; super({
let conf = {
title: "Proposer", title: "Proposer",
content: html, content,
default: "proposer", classes: ["dialogvente"],
buttons: { "proposer": { label: "Proposer", callback: it => { this.onProposer(it); } } } position: { width: 400, height: 'fit-content' },
}; buttons: [
{ label: "Proposer", action: "proposer", callback: () => this.onProposer() }
super(conf, options); ]
this.venteData = venteData; })
this.venteData = venteData
} }
activateListeners(html) { _onRender(context) {
super.activateListeners(html); super._onRender(context)
const el = this.element
this.html = html; el.querySelector(".tailleLot")?.addEventListener("change", event => this.setTailleLot(Number(event.currentTarget.value)))
this.html.find(".tailleLot").change(event => this.setTailleLot(Number(event.currentTarget.value))); el.querySelector(".nbLots")?.addEventListener("change", event => this.setNbLots(Number(event.currentTarget.value)))
this.html.find(".nbLots").change(event => this.setNbLots(Number(event.currentTarget.value))); el.querySelector(".quantiteIllimite")?.addEventListener("change", event => this.setQuantiteIllimite(event.currentTarget.checked))
this.html.find(".quantiteIllimite").change(event => this.setQuantiteIllimite(event.currentTarget.checked)); el.querySelector(".prixLot")?.addEventListener("change", event => this.setPrixLot(Number(event.currentTarget.value)))
this.html.find(".prixLot").change(event => this.setPrixLot(Number(event.currentTarget.value))); this.setQuantiteIllimite(this.venteData.quantiteIllimite)
this.setQuantiteIllimite(this.venteData.quantiteIllimite);
} }
async onProposer(it) { async onProposer() {
this.updateVente(this.getChoixVente()); this.updateVente(this.getChoixVente())
this.venteData["properties"] = this.venteData.item.getProprietes()
this.venteData["properties"] = this.venteData.item.getProprietes();
if (this.venteData.isOwned) { if (this.venteData.isOwned) {
if (this.venteData.nbLots * this.venteData.tailleLot > this.venteData.quantiteMax) { if (this.venteData.nbLots * this.venteData.tailleLot > this.venteData.quantiteMax) {
ui.notifications.warn(`Vous avez ${this.venteData.quantiteMax} ${this.venteData.item.name}, ce n'est pas suffisant pour vendre ${this.venteData.nbLots} de ${this.venteData.tailleLot}`) ui.notifications.warn(`Vous avez ${this.venteData.quantiteMax} ${this.venteData.item.name}, ce n'est pas suffisant pour vendre ${this.venteData.nbLots} de ${this.venteData.tailleLot}`)
return; return
} }
} }
await ChatVente.displayAchatVente(this.venteData) await ChatVente.displayAchatVente(this.venteData)
} }
updateVente(update) { updateVente(update) {
foundry.utils.mergeObject(this.venteData, update); foundry.utils.mergeObject(this.venteData, update)
} }
getChoixVente() { getChoixVente() {
const el = this.element
return { return {
nbLots: Number(this.html.find(".nbLots").val()), nbLots: Number(el.querySelector(".nbLots")?.value ?? 0),
tailleLot: Number(this.html.find(".tailleLot").val()), tailleLot: Number(el.querySelector(".tailleLot")?.value ?? 0),
quantiteIllimite: this.html.find(".quantiteIllimite").is(':checked'), quantiteIllimite: el.querySelector(".quantiteIllimite")?.checked ?? false,
prixLot: Number(this.html.find(".prixLot").val()) prixLot: Number(el.querySelector(".prixLot")?.value ?? 0)
}; }
} }
/* -------------------------------------------- */
setPrixLot(prixLot) { setPrixLot(prixLot) {
this.venteData.prixLot = prixLot; this.venteData.prixLot = prixLot
} }
setTailleLot(tailleLot) { setTailleLot(tailleLot) {
const maxLots = Math.floor(this.venteData.quantiteMax / tailleLot); const maxLots = Math.floor(this.venteData.quantiteMax / tailleLot)
this.updateVente({ this.updateVente({
tailleLot, tailleLot,
nbLots: Math.min(maxLots, this.venteData.nbLots), nbLots: Math.min(maxLots, this.venteData.nbLots),
maxLots: maxLots, maxLots,
prixLot: (tailleLot * this.venteData.prixOrigine).toFixed(2) prixLot: (tailleLot * this.venteData.prixOrigine).toFixed(2)
}); })
const el = this.element
this.html.find(".prixLot").val(this.venteData.prixLot); const prix = el.querySelector(".prixLot")
this.html.find(".nbLots").val(this.venteData.nbLots); if (prix) prix.value = this.venteData.prixLot
this.html.find(".nbLots").attr("max", this.venteData.maxLots) const nb = el.querySelector(".nbLots")
if (nb) {
nb.value = this.venteData.nbLots
nb.setAttribute("max", this.venteData.maxLots)
}
} }
setNbLots(nbLots) { setNbLots(nbLots) {
this.updateVente({ this.updateVente({
nbLots: this.venteData.isOwned ? Math.max(0, Math.min(nbLots, this.venteData.maxLots)) : nbLots nbLots: this.venteData.isOwned ? Math.max(0, Math.min(nbLots, this.venteData.maxLots)) : nbLots
}) })
this.html.find(".nbLots").val(this.venteData.nbLots); const nb = this.element.querySelector(".nbLots")
if (nb) nb.value = this.venteData.nbLots
} }
setQuantiteIllimite(checked) { setQuantiteIllimite(checked) {
this.updateVente({ quantiteIllimite: checked }) this.updateVente({ quantiteIllimite: checked })
this.html.find(".label-quantiteIllimite").text(this.venteData.quantiteIllimite ? "Illimités" : "disponibles"); const el = this.element
HtmlUtility.showControlWhen(this.html.find(".nbLots"), !this.venteData.quantiteIllimite) const label = el.querySelector(".label-quantiteIllimite")
if (label) label.textContent = checked ? "Illimités" : "disponibles"
const nb = el.querySelector(".nbLots")
if (nb) HtmlUtility.showControlWhen(nb, !checked)
} }
} }
+169 -88
View File
@@ -18,14 +18,16 @@ import { CARACS, LIST_CARAC_PERSONNAGE, RdDCarac } from "./rdd-carac.js";
import { DialogConsommer } from "./dialog-item-consommer.js"; import { DialogConsommer } from "./dialog-item-consommer.js";
import { DialogFabriquerPotion } from "./dialog-fabriquer-potion.js"; import { DialogFabriquerPotion } from "./dialog-fabriquer-potion.js";
import { RollDataAjustements } from "./rolldata-ajustements-v1.js"; import { RollDataAjustements } from "./rolldata-ajustements-v1.js";
import { RdDPossession } from "./rdd-possession.js"; import { RdDPossession } from "./combat/rdd-possession.js";
import { RdDPossessionV2 } from "./combat/rdd-possession-v2.mjs";
import { ACTOR_TYPES, renderTemplate, SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js"; import { ACTOR_TYPES, renderTemplate, SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js";
import { RdDConfirm } from "./rdd-confirm.js"; import { RdDConfirm } from "./rdd-confirm.js";
import { DialogRepos } from "./sommeil/dialog-repos.js"; import { DialogRepos } from "./sommeil/dialog-repos.js";
import { DialogNouveauRepos } from "./sommeil/dialog-nouveau-repos.js";
import { RdDBaseActor } from "./actor/base-actor.js"; import { RdDBaseActor } from "./actor/base-actor.js";
import { RdDTimestamp } from "./time/rdd-timestamp.js"; import { RdDTimestamp } from "./time/rdd-timestamp.js";
import { AppAstrologie } from "./sommeil/app-astrologie.js"; import { AppAstrologie } from "./sommeil/app-astrologie.js";
import { RdDEmpoignade } from "./rdd-empoignade.js"; import { RdDEmpoignade } from "./combat/rdd-empoignade.js";
import { ExperienceLog, XP_TOPIC } from "./actor/experience-log.js"; import { ExperienceLog, XP_TOPIC } from "./actor/experience-log.js";
import { ITEM_TYPES } from "./constants.js"; import { ITEM_TYPES } from "./constants.js";
import { RdDBaseActorSang } from "./actor/base-actor-sang.js"; import { RdDBaseActorSang } from "./actor/base-actor-sang.js";
@@ -34,7 +36,7 @@ import { DialogChoixXpCarac } from "./dialog-choix-xp-carac.js";
import { ATTAQUE_TYPE, RdDItemArme } from "./item/arme.js"; import { ATTAQUE_TYPE, RdDItemArme } from "./item/arme.js";
import { RdDItemBlessure } from "./item/blessure.js"; import { RdDItemBlessure } from "./item/blessure.js";
import { RdDItemTete } from "./item/tete.js"; import { RdDItemTete } from "./documents/_module.mjs";
import { RdDItemSort } from "./item-sort.js"; import { RdDItemSort } from "./item-sort.js";
import { RdDItemRace } from "./item/race.js"; import { RdDItemRace } from "./item/race.js";
import { RdDItemCompetence } from "./item-competence.js"; import { RdDItemCompetence } from "./item-competence.js";
@@ -44,11 +46,10 @@ import { DialogSelect } from "./dialog-select.js";
import { CATEGORIES_COMPETENCES, PAS_DE_DRACONIC, SANS_DRACONIC } from "./item/base-items.js"; import { CATEGORIES_COMPETENCES, PAS_DE_DRACONIC, SANS_DRACONIC } from "./item/base-items.js";
import { RdDRollResult } from "./rdd-roll-result.js"; import { RdDRollResult } from "./rdd-roll-result.js";
import { RdDInitiative } from "./initiative.mjs"; import { RdDInitiative } from "./combat/initiative.mjs";
import RollDialog from "./roll/roll-dialog.mjs"; import RollDialog from "./roll/roll-dialog.mjs";
import { OptionsAvancees, ROLL_DIALOG_V2 } from "./settings/options-avancees.js"; import { OptionsAvancees, ROLL_DIALOG_V2 } from "./settings/options-avancees.js";
import { ROLL_TYPE_COMP, ROLL_TYPE_CUISINE, ROLL_TYPE_JEU, ROLL_TYPE_MEDITATION, ROLL_TYPE_OEUVRE, ROLL_TYPE_SORT, ROLL_TYPE_APPEL_CHANCE, ROLL_TYPE_TACHE } from "./roll/roll-constants.mjs"; import { ROLL_TYPE_COMP, ROLL_TYPE_CUISINE, ROLL_TYPE_JEU, ROLL_TYPE_MEDITATION, ROLL_TYPE_OEUVRE, ROLL_TYPE_SORT, ROLL_TYPE_APPEL_CHANCE, ROLL_TYPE_TACHE } from "./roll/roll-constants.mjs";
import { RdDPossessionV2 } from "./rdd-possession-v2.mjs";
import { Apprecier, MORAL, SITUATION_MORAL } from "./moral/apprecier.mjs"; import { Apprecier, MORAL, SITUATION_MORAL } from "./moral/apprecier.mjs";
import { Distance } from "./combat/distance.mjs"; import { Distance } from "./combat/distance.mjs";
@@ -62,36 +63,6 @@ export const MAINS_DIRECTRICES = ['Droitier', 'Gaucher', 'Ambidextre']
*/ */
export class RdDActor extends RdDBaseActorSang { export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */
/**
* Prepare Character type specific data
*/
prepareActorData() {
RdDItemRace.applyRacialLimits(this)
this.system.carac.melee.value = Math.floor((this.getForce() + parseInt(this.system.carac.agilite.value)) / 2);
this.system.carac.tir.value = Math.floor((parseInt(this.system.carac.vue.value) + parseInt(this.system.carac.dexterite.value)) / 2);
this.system.carac.lancer.value = Math.floor((parseInt(this.system.carac.tir.value) + parseInt(this.system.carac.force.value)) / 2);
this.system.carac.derobee.value = Math.floor(parseInt(((21 - this.system.carac.taille.value)) + parseInt(this.system.carac.agilite.value)) / 2);
super.prepareActorData()
this.system.attributs.sconst.value = RdDCarac.calculSConst(this.getConstitution())
this.system.attributs.sust.value = RdDCarac.getCaracDerivee(this.getTaille()).sust
this.system.sante.fatigue.max = this.getFatigueMax()
this.system.sante.fatigue.value = Math.min(this.system.sante.fatigue.value, this.system.sante.fatigue.max);
//Compteurs
this.system.reve.reve.max = 3 * this.getReve()
this.system.compteurs.chance.max = this.getChance()
this.system.attributs.hautrevant.value = this.itemTypes[ITEM_TYPES.tete].find(it => RdDItemTete.isDonDeHautReve(it))
? "Haut rêvant"
: "";
}
/* -------------------------------------------- */ /* -------------------------------------------- */
canReceive(item) { canReceive(item) {
return ![ITEM_TYPES.competencecreature, ITEM_TYPES.tarot, ITEM_TYPES.service].includes(item.type) return ![ITEM_TYPES.competencecreature, ITEM_TYPES.tarot, ITEM_TYPES.service].includes(item.type)
@@ -243,19 +214,26 @@ export class RdDActor extends RdDBaseActorSang {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async $perteReveEnchantementsChateauDormants() { async $perteReveEnchantementsChateauDormants(message = undefined) {
const toUpdate = this.items.filter(it => [ITEM_TYPES.potion, ITEM_TYPES.gemme].includes(it.type)) const toUpdate = this.items.filter(it => [ITEM_TYPES.potion, ITEM_TYPES.gemme].includes(it.type))
.map(it => it.perteReveChateauDormant()) .map(it => it.perteReveChateauDormant())
.filter(it => it != undefined) .filter(it => it != undefined)
if (toUpdate.length > 0) { if (toUpdate.length > 0) {
console.log('perte de rêve des enchantements', toUpdate) console.log('perte de rêve des enchantements', toUpdate)
const messageUpdates = await Promise.all( if (message) {
toUpdate.map(async it => await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-pertereve-enchantement-chateaudormant.hbs`, it))) message.content += toUpdate
await ChatMessage.create({ .map(it => {
whisper: ChatUtility.getOwners(this), const type = it.item.type === 'potion' ? 'potion' : 'gemme'
content: messageUpdates.reduce(Misc.joining('<br>')) const nouveaupr = it.nouveaupr ?? it.item.system.pr
}) let texte = `La ${type} enchantée ${it.item.name} de ${it.alias} a perdu un Point de Rêve (nouveau total : ${nouveaupr}).`
if (type === 'potion' && nouveaupr === 0) {
texte += ` Sa potion étant désormais à 0 Point de Rêve, elle est inutilisable et peut être jetée.`
}
return texte
})
.reduce(Misc.joining(' ')) + ' '
}
await this.updateEmbeddedDocuments('Item', toUpdate.map(it => it.update)); await this.updateEmbeddedDocuments('Item', toUpdate.map(it => it.update));
} }
} }
@@ -296,7 +274,11 @@ export class RdDActor extends RdDBaseActorSang {
} }
async repos() { async repos() {
await DialogRepos.create(this); if (ReglesOptionnelles.isUsing("nouveau-mode-repos")) {
await DialogNouveauRepos.create(this);
} else {
await DialogRepos.create(this);
}
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -360,15 +342,16 @@ export class RdDActor extends RdDBaseActorSang {
whisper: ChatUtility.getOwners(this), whisper: ChatUtility.getOwners(this),
content: "" content: ""
} }
const stressAvant = this.system.compteurs.stress.value
await this._recuperationSante(message) await this._recuperationSante(message)
await this._recupereMoralChateauDormant(message) await this._recupereMoralChateauDormant(message)
await this._recupereChance() await this._recupereChance()
await this.transformerStress() await this.transformerStress(message)
await this.retourSeuilDeReve(message) await this.retourSeuilDeReve(message)
await this.setBonusPotionSoin(0) await this.setBonusPotionSoin(0)
await this.retourSust(message) await this.retourSust(message)
await this.$perteReveEnchantementsChateauDormants() await this.$perteReveEnchantementsChateauDormants(message)
await this.$suppressionLancementsSort() await this.$suppressionLancementsSort()
await RdDCoeur.applyCoeurChateauDormant(this, message) await RdDCoeur.applyCoeurChateauDormant(this, message)
if (message.content != "") { if (message.content != "") {
@@ -376,6 +359,35 @@ export class RdDActor extends RdDBaseActorSang {
await ChatMessage.create(message) await ChatMessage.create(message)
} }
await this.resetInfoSommeil() await this.resetInfoSommeil()
const stressApres = this.system.compteurs.stress.value
const date = game.system.rdd.calendrier.dateCourante()
const recapMJ = `**${this.name}** — Château Dormant (${date})<br>Stress : ${stressAvant}${stressApres}`
await ChatUtility.tellToGM(message.content ? recapMJ + '<br>' + message.content : recapMJ)
const journalId = game.settings.get(SYSTEM_RDD, 'cd-suivi-journal')
if (journalId) {
const journalEntry = game.journal.get(journalId)
if (journalEntry?.canUserModify(game.user, 'update')) {
const timestamp = game.system.rdd.calendrier.getTimestamp()
const jour = timestamp.jour + 1
const mois = RdDTimestamp.definition(timestamp.mois).label
const annee = timestamp.annee
const section = `${jour} ${mois} ${annee}`
const log = `<p><b>${this.name}</b> — Stress ${stressAvant}${stressApres}</p>` + message.content
let page = journalEntry.pages.find(p => p.type == 'text' && Grammar.equalsInsensitive(p.name, section))
if (page) {
await page.update({ 'text.content': page.text.content + '\n' + log })
} else {
await journalEntry.createEmbeddedDocuments('JournalEntryPage', [new JournalEntryPage({
name: section,
type: 'text',
title: { show: true, level: 1 },
text: { content: log, format: 1 }
})])
}
}
}
} }
async resetInfoSommeil() { async resetInfoSommeil() {
@@ -391,9 +403,7 @@ export class RdDActor extends RdDBaseActorSang {
} }
async setInfoSommeilInsomnie() { async setInfoSommeilInsomnie() {
if (EffetsDraconiques.isSujetInsomnie(this)) { await this.update({ 'system.sommeil.insomnie': EffetsDraconiques.isSujetInsomnie(this) });
await this.update({ 'system.sommeil.insomnie': true });
}
} }
async setInfoSommeilMoral(situationMoral) { async setInfoSommeilMoral(situationMoral) {
@@ -657,6 +667,69 @@ export class RdDActor extends RdDBaseActorSang {
return cumul; return cumul;
} }
/* -------------------------------------------- */
async mangerExterne() {
const besoin = this.system.attributs.sust.value
await this.update({
'system.compteurs.sust.value': besoin,
'system.compteurs.eau.value': besoin
})
ChatUtility.tellToUserAndGM(`${this.name} a mangé et bu pour la journée (repas externe)`)
}
async mangerRations() {
const besoin = this.system.attributs.sust.value
let restantSust = besoin - this.system.compteurs.sust.value
let restantEau = besoin - this.system.compteurs.eau.value
let totalSust = 0, totalEau = 0
let foundAny = false
if (restantSust <= 0 && restantEau <= 0) return
for (const item of this.items) {
if (item.type !== 'nourritureboisson') continue
const qty = item.system.quantite
if (!qty || qty <= 0) continue
const sustUnit = item.system.sust || 0
const eauUnit = item.system.desaltere || 0
if (sustUnit <= 0 && eauUnit <= 0) continue
foundAny = true
let unitNeeded = 0
if (restantSust > 0 && sustUnit > 0) {
unitNeeded = Math.max(unitNeeded, Math.ceil(restantSust / sustUnit))
}
if (restantEau > 0 && eauUnit > 0) {
unitNeeded = Math.max(unitNeeded, Math.ceil(restantEau / eauUnit))
}
const take = Math.min(unitNeeded, qty)
totalSust += Math.min(sustUnit * take, restantSust)
totalEau += Math.min(eauUnit * take, restantEau)
restantSust -= Math.min(sustUnit * take, restantSust)
restantEau -= Math.min(eauUnit * take, restantEau)
await item.diminuerQuantite(take)
}
if (!foundAny) {
ChatUtility.tellToUserAndGM(`${this.name} n'a rien à manger dans ses rations !`)
return
}
if (totalSust > 0) {
await this.update({ 'system.compteurs.sust.value': this.system.compteurs.sust.value + totalSust })
}
if (totalEau > 0) {
await this.update({ 'system.compteurs.eau.value': this.system.compteurs.eau.value + totalEau })
}
ChatUtility.tellToUserAndGM(
`${this.name} a mangé ${totalSust}/${besoin} sust, bu ${totalEau}/${besoin} eau (rations)`
)
}
/* -------------------------------------------- */ /* -------------------------------------------- */
async retourSeuilDeReve(message) { async retourSeuilDeReve(message) {
const seuil = this.system.reve.seuil.value; const seuil = this.system.reve.seuil.value;
@@ -776,7 +849,7 @@ export class RdDActor extends RdDBaseActorSang {
} }
let carac = this.findCaracByName(caracName); let carac = this.findCaracByName(caracName);
if (carac) { if (carac) {
carac = foundry.utils.duplicate(carac); carac = foundry.utils.deepClone(carac);
const fromXp = Number(carac.xp); const fromXp = Number(carac.xp);
const fromValue = Number(carac.value); const fromValue = Number(carac.value);
let toXp = fromXp; let toXp = fromXp;
@@ -916,7 +989,7 @@ export class RdDActor extends RdDBaseActorSang {
async deleteExperienceLog(from, count) { async deleteExperienceLog(from, count) {
if (from >= 0 && count > 0) { if (from >= 0 && count > 0) {
let expLog = foundry.utils.duplicate(this.system.experiencelog) let expLog = foundry.utils.deepClone(this.system.experiencelog)
expLog.splice(from, count) expLog.splice(from, count)
await this.update({ [`system.experiencelog`]: expLog }) await this.update({ [`system.experiencelog`]: expLog })
} }
@@ -1046,7 +1119,7 @@ export class RdDActor extends RdDBaseActorSang {
notifyRefreshTMR() { notifyRefreshTMR() {
game.socket.emit(SYSTEM_SOCKET_ID, { game.socket.emit(SYSTEM_SOCKET_ID, {
msg: "msg_tmr_move", data: { msg: "msg_tmr_move", data: {
actorId: this._id, actorId: this.id,
tmrPos: this.system.reve.tmrpos tmrPos: this.system.reve.tmrpos
} }
}); });
@@ -1256,7 +1329,7 @@ export class RdDActor extends RdDBaseActorSang {
} }
async mangerNourriture(item) { async mangerNourriture(item) {
return (await DialogConsommer.create(this, item)).render(true); return await DialogConsommer.create(this, item);
} }
async actionLire(item) { async actionLire(item) {
@@ -1383,7 +1456,7 @@ export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */ /* -------------------------------------------- */
async saouler(forceAlcool, alcool = undefined) { async saouler(forceAlcool, alcool = undefined) {
let ethylisme = foundry.utils.duplicate(this.system.compteurs.ethylisme); let ethylisme = foundry.utils.deepClone(this.system.compteurs.ethylisme);
const etat = this.getEtatGeneral({ ethylisme: true }); const etat = this.getEtatGeneral({ ethylisme: true });
const nbDoses = Number(this.system.compteurs.ethylisme.nb_doses || 0); const nbDoses = Number(this.system.compteurs.ethylisme.nb_doses || 0);
@@ -1457,7 +1530,7 @@ export class RdDActor extends RdDBaseActorSang {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async transformerStress() { async transformerStress(message = undefined) {
if (!ReglesOptionnelles.isUsing("transformation-stress")) { return } if (!ReglesOptionnelles.isUsing("transformation-stress")) { return }
const fromStress = Number(this.system.compteurs.stress.value); const fromStress = Number(this.system.compteurs.stress.value);
if (this.system.sommeil?.insomnie || fromStress <= 0) { if (this.system.sommeil?.insomnie || fromStress <= 0) {
@@ -1491,6 +1564,11 @@ export class RdDActor extends RdDBaseActorSang {
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-resultat-transformer-stress.hbs`, stressRollData) content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-resultat-transformer-stress.hbs`, stressRollData)
}); });
if (message) {
const reste = Math.max(fromStress - stressRollData.perte - 1, 0)
message.content += `Transformation de ${stressRollData.stress} points de stress : perte fixe de 1, reste ${reste}, conversion de ${stressRollData.convertis} en expérience. `
}
const toStress = Math.max(fromStress - stressRollData.perte - 1, 0); const toStress = Math.max(fromStress - stressRollData.perte - 1, 0);
const fromXpSress = Number(this.system.compteurs.experience.value); const fromXpSress = Number(this.system.compteurs.experience.value);
const toXpStress = fromXpSress + Number(stressRollData.xp); const toXpStress = fromXpSress + Number(stressRollData.xp);
@@ -1543,7 +1621,7 @@ export class RdDActor extends RdDBaseActorSang {
const niveauSuivant = Number(carac.value) + 1; const niveauSuivant = Number(carac.value) + 1;
let xpNeeded = RdDCarac.getCaracNextXp(niveauSuivant); let xpNeeded = RdDCarac.getCaracNextXp(niveauSuivant);
if (carac.xp >= xpNeeded) { if (carac.xp >= xpNeeded) {
carac = foundry.utils.duplicate(carac); carac = foundry.utils.deepClone(carac);
carac.value = niveauSuivant; carac.value = niveauSuivant;
let checkXp = { let checkXp = {
@@ -1570,10 +1648,10 @@ export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */ /* -------------------------------------------- */
async ajoutExperience(rollData, hideChatMessage = 'show') { async ajoutExperience(rollData, hideChatMessage = 'show') {
const rollMode = rollData.current?.rollmode?.key ?? game.settings.get("core", "rollMode") const rollMode = rollData.current?.rollmode?.key ?? game.settings.get("core", "messageMode")
if (!rollData.rolled.isPart || if (!rollData.rolled.isPart ||
rollData.finalLevel >= 0 || rollData.finalLevel >= 0 ||
rollMode == 'selfroll' || rollMode == 'self' ||
!Misc.hasConnectedGM()) { !Misc.hasConnectedGM()) {
return return
} }
@@ -1599,7 +1677,7 @@ export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */ /* -------------------------------------------- */
async appliquerAppelMoral(rollData) { async appliquerAppelMoral(rollData) {
if (!rollData.use.moral || game.settings.get("core", "rollMode") == 'selfroll') { if (!rollData.use.moral || game.settings.get("core", "messageMode") == 'self') {
return return
} }
if (rollData.rolled.isEchec || if (rollData.rolled.isEchec ||
@@ -1641,14 +1719,14 @@ export class RdDActor extends RdDBaseActorSang {
return await this.rollUnSortV2(); return await this.rollUnSortV2();
} }
// Duplication car les pts de reve sont modifiés dans le sort! // Duplication car les pts de reve sont modifiés dans le sort!
let sorts = foundry.utils.duplicate(this.itemTypes[ITEM_TYPES.sort].filter(it => RdDItemSort.isSortOnCoord(it, coord))) let sorts = foundry.utils.deepClone(this.itemTypes[ITEM_TYPES.sort].filter(it => RdDItemSort.isSortOnCoord(it, coord)))
if (sorts.length == 0) { if (sorts.length == 0) {
ui.notifications.info(`Aucun sort disponible en ${TMRUtility.getTMR(coord).label} !`); ui.notifications.info(`Aucun sort disponible en ${TMRUtility.getTMR(coord).label} !`);
return return
} }
const draconicList = this.computeDraconicAndSortIndex(sorts) const draconicList = this.computeDraconicAndSortIndex(sorts)
const reve = foundry.utils.duplicate(this.system.carac.reve) const reve = foundry.utils.deepClone(this.system.carac.reve)
const dialog = await this.openRollDialog({ const dialog = await this.openRollDialog({
name: 'lancer-un-sort', name: 'lancer-un-sort',
@@ -1765,7 +1843,7 @@ export class RdDActor extends RdDBaseActorSang {
console.log('lancement de sort', rollData.selectedSort) console.log('lancement de sort', rollData.selectedSort)
const lancements = RdDItemSort.prepareSortAddLancement(rollData.selectedSort, rollData.selectedSort.system.ptreve_reel) const lancements = RdDItemSort.prepareSortAddLancement(rollData.selectedSort, rollData.selectedSort.system.ptreve_reel)
await this.updateEmbeddedDocuments('Item', await this.updateEmbeddedDocuments('Item',
[{ _id: rollData.selectedSort._id, 'system.lancements': lancements }] [{ _id: rollData.selectedSort.id, 'system.lancements': lancements }]
) )
} }
} }
@@ -1903,7 +1981,7 @@ export class RdDActor extends RdDBaseActorSang {
diffConditions: 0, diffConditions: 0,
use: { libre: false, conditions: true }, use: { libre: false, conditions: true },
carac: { carac: {
[tache.system.carac]: foundry.utils.duplicate(this.system.carac[tache.system.carac]) [tache.system.carac]: foundry.utils.deepClone(this.system.carac[tache.system.carac])
} }
}, },
callbacks: [{ action: r => this._tacheResult(r, options) }] callbacks: [{ action: r => this._tacheResult(r, options) }]
@@ -1965,7 +2043,7 @@ export class RdDActor extends RdDBaseActorSang {
const artData = { const artData = {
art: 'jeu', verbe: 'Jeu', art: 'jeu', verbe: 'Jeu',
use: { libre: true, conditions: true, }, use: { libre: true, conditions: true, },
competence: foundry.utils.duplicate(this.getCompetence('jeu')), competence: foundry.utils.deepClone(this.getCompetence('jeu')),
forceCarac: {} forceCarac: {}
}; };
listCarac.forEach(c => artData.forceCarac[c] = this.system.carac[c]); listCarac.forEach(c => artData.forceCarac[c] = this.system.carac[c]);
@@ -1977,7 +2055,7 @@ export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */ /* -------------------------------------------- */
async rollMeditation(id) { async rollMeditation(id) {
const meditation = foundry.utils.duplicate(this.getMeditation(id)); const meditation = foundry.utils.deepClone(this.getMeditation(id));
if (meditation && OptionsAvancees.isUsing(ROLL_DIALOG_V2)) { if (meditation && OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
const rollData = { const rollData = {
@@ -1988,7 +2066,7 @@ export class RdDActor extends RdDBaseActorSang {
return await RollDialog.create(rollData) return await RollDialog.create(rollData)
} }
const competence = foundry.utils.duplicate(this.getCompetence(meditation.system.competence)); const competence = foundry.utils.deepClone(this.getCompetence(meditation.system.competence));
competence.system.defaut_carac = "intellect"; // Meditation = toujours avec intellect competence.system.defaut_carac = "intellect"; // Meditation = toujours avec intellect
let meditationData = { let meditationData = {
competence: competence, competence: competence,
@@ -2018,7 +2096,7 @@ export class RdDActor extends RdDBaseActorSang {
await this.createEmbeddedDocuments("Item", [RdDItemSigneDraconique.prepareSigneDraconiqueMeditation(meditationRoll.meditation, meditationRoll.rolled)]); await this.createEmbeddedDocuments("Item", [RdDItemSigneDraconique.prepareSigneDraconiqueMeditation(meditationRoll.meditation, meditationRoll.rolled)]);
} }
if (meditationRoll.rolled.isEPart) { if (meditationRoll.rolled.isEPart) {
await this.updateEmbeddedDocuments('Item', [{ _id: meditationRoll.meditation._id, 'system.malus': meditationRoll.meditation.system.malus - 1 }]); await this.updateEmbeddedDocuments('Item', [{ _id: meditationRoll.meditation.id, 'system.malus': meditationRoll.meditation.system.malus - 1 }]);
} }
await this.santeIncDec("fatigue", 2); await this.santeIncDec("fatigue", 2);
await RdDRollResult.displayRollData(meditationRoll, this.name, 'chat-resultat-meditation.hbs'); await RdDRollResult.displayRollData(meditationRoll, this.name, 'chat-resultat-meditation.hbs');
@@ -2050,7 +2128,7 @@ export class RdDActor extends RdDBaseActorSang {
let draconicList = this.getDraconics() let draconicList = this.getDraconics()
.filter(it => it.system.niveau > -11) .filter(it => it.system.niveau > -11)
.map(draconic => { .map(draconic => {
let draconicLecture = foundry.utils.duplicate(draconic) let draconicLecture = foundry.utils.deepClone(draconic)
draconicLecture.system.defaut_carac = "intellect" draconicLecture.system.defaut_carac = "intellect"
return draconicLecture return draconicLecture
}); });
@@ -2100,12 +2178,15 @@ export class RdDActor extends RdDBaseActorSang {
if (rollData.xpSort > 0) { if (rollData.xpSort > 0) {
const fromXp = Number(compData.system.xp_sort); const fromXp = Number(compData.system.xp_sort);
const toXp = fromXp + rollData.xpSort; const toXp = fromXp + rollData.xpSort;
await this.updateEmbeddedDocuments("Item", [{ _id: compData._id, 'system.xp_sort': toXp }]); await this.updateEmbeddedDocuments("Item", [{ _id: compData.id, 'system.xp_sort': toXp }]);
await ExperienceLog.add(this, XP_TOPIC.XPSORT, fromXp, toXp, `${rollData.competence.name} : signe draconique`); await ExperienceLog.add(this, XP_TOPIC.XPSORT, fromXp, toXp, `${rollData.competence.name} : signe draconique`);
} }
await this.deleteEmbeddedDocuments("Item", [rollData.signe._id]); await this.deleteEmbeddedDocuments("Item", [rollData.signe.id]);
await RdDRollResult.displayRollData(rollData, this.name, 'chat-resultat-lecture-signedraconique.hbs'); await RdDRollResult.displayRollData(rollData, this.name, 'chat-resultat-lecture-signedraconique.hbs');
this.tmrApp.close(); if (this.tmrApp) {
this.tmrApp.subdialog = undefined; // débloque $forceClose qui refuse de fermer tant qu'un subdialog est actif
await this.tmrApp.close();
}
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -2205,7 +2286,7 @@ export class RdDActor extends RdDBaseActorSang {
if (xpData.competence) { if (xpData.competence) {
const from = Number(xpData.competence.system.xp); const from = Number(xpData.competence.system.xp);
const to = from + xpData.xpCompetence; const to = from + xpData.xpCompetence;
await this.updateEmbeddedDocuments('Item', [{ _id: xpData.competence._id, 'system.xp': to }]); await this.updateEmbeddedDocuments('Item', [{ _id: xpData.competence.id, 'system.xp': to }]);
xpData.checkComp = await this.checkCompetenceXP(xpData.competence.name, undefined, false) xpData.checkComp = await this.checkCompetenceXP(xpData.competence.name, undefined, false)
await ExperienceLog.add(this, XP_TOPIC.XP, from, to, xpData.competence.name); await ExperienceLog.add(this, XP_TOPIC.XP, from, to, xpData.competence.name);
return [xpData] return [xpData]
@@ -2216,7 +2297,7 @@ export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */ /* -------------------------------------------- */
async _xpCarac(xpData) { async _xpCarac(xpData) {
if (xpData.xpCarac > 0) { if (xpData.xpCarac > 0) {
const carac = foundry.utils.duplicate(this.system.carac) const carac = foundry.utils.deepClone(this.system.carac)
const code = RdDBaseActor._findCaracNode(carac, xpData.caracName) const code = RdDBaseActor._findCaracNode(carac, xpData.caracName)
const selectedCarac = carac[code] const selectedCarac = carac[code]
if (this.isCaracMax(code)) { if (this.isCaracMax(code)) {
@@ -2269,7 +2350,7 @@ export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */ /* -------------------------------------------- */
async deleteNombresAstraux() { async deleteNombresAstraux() {
const deletions = this.itemTypes['nombreastral'].map(it => it._id); const deletions = this.itemTypes['nombreastral'].map(it => it.id);
await this.deleteEmbeddedDocuments("Item", deletions); await this.deleteEmbeddedDocuments("Item", deletions);
} }
@@ -2294,7 +2375,7 @@ export class RdDActor extends RdDBaseActorSang {
if (calendrier) { if (calendrier) {
const toDelete = this.itemTypes['nombreastral'] const toDelete = this.itemTypes['nombreastral']
.filter(it => calendrier.isAfterIndexDate(it.system.jourindex)) .filter(it => calendrier.isAfterIndexDate(it.system.jourindex))
.map(it => it._id); .map(it => it.id);
await this.deleteEmbeddedDocuments("Item", toDelete); await this.deleteEmbeddedDocuments("Item", toDelete);
} }
} }
@@ -2513,7 +2594,7 @@ export class RdDActor extends RdDBaseActorSang {
async incDecItemUse(itemId, shouldIncrease = true) { async incDecItemUse(itemId, shouldIncrease = true) {
if (shouldIncrease) { if (shouldIncrease) {
const currentItemUse = this.getFlag(SYSTEM_RDD, 'itemUse'); const currentItemUse = this.getFlag(SYSTEM_RDD, 'itemUse');
let itemUse = currentItemUse ? foundry.utils.duplicate(currentItemUse) : {}; let itemUse = currentItemUse ? foundry.utils.deepClone(currentItemUse) : {};
itemUse[itemId] = (itemUse[itemId] ?? 0) + 1; itemUse[itemId] = (itemUse[itemId] ?? 0) + 1;
await this.setFlag(SYSTEM_RDD, 'itemUse', itemUse); await this.setFlag(SYSTEM_RDD, 'itemUse', itemUse);
} }
@@ -2610,7 +2691,7 @@ export class RdDActor extends RdDBaseActorSang {
} }
async setPointsCoeur(subActorId, coeurs, options = { immediat: false }) { async setPointsCoeur(subActorId, coeurs, options = { immediat: false }) {
const newSuivants = foundry.utils.duplicate(this.system.subacteurs.suivants) const newSuivants = foundry.utils.deepClone(this.system.subacteurs.suivants)
const amoureux = newSuivants.find(it => it.id == subActorId) const amoureux = newSuivants.find(it => it.id == subActorId)
if (amoureux) { if (amoureux) {
amoureux[options.immediat ? 'coeur' : 'prochainCoeur'] = coeurs amoureux[options.immediat ? 'coeur' : 'prochainCoeur'] = coeurs
@@ -2654,7 +2735,7 @@ export class RdDActor extends RdDBaseActorSang {
if (!this.isAddSubActeurAllowed(subActor)) { if (!this.isAddSubActeurAllowed(subActor)) {
return return
} }
const subActorOnlyId = { id: subActor._id }; const subActorOnlyId = { id: subActor.id };
if (subActor.type == 'vehicule') { if (subActor.type == 'vehicule') {
this.pushSubActeur(subActorOnlyId, this.system.subacteurs.vehicules, `system.subacteurs.vehicules`, `Le véhicule ${subActor.name}`) this.pushSubActeur(subActorOnlyId, this.system.subacteurs.vehicules, `system.subacteurs.vehicules`, `Le véhicule ${subActor.name}`)
} else if (subActor.type == 'creature') { } else if (subActor.type == 'creature') {
@@ -2805,7 +2886,7 @@ export class RdDActor extends RdDBaseActorSang {
} }
} }
await this.createEmbeddedDocuments('Item', [newPotion]) await this.createEmbeddedDocuments('Item', [newPotion])
await this.diminuerQuantiteObjet(herbeData._id, herbeData.nbBrins) await this.diminuerQuantiteObjet(herbeData.id, herbeData.nbBrins)
await ChatMessage.create({ await ChatMessage.create({
whisper: ChatUtility.getOwners(this), whisper: ChatUtility.getOwners(this),
@@ -2980,8 +3061,8 @@ export class RdDActor extends RdDBaseActorSang {
incarnation.name = 'Réincarnation de ' + incarnation.name incarnation.name = 'Réincarnation de ' + incarnation.name
incarnation.system = { incarnation.system = {
carac: foundry.utils.duplicate(this.system.carac), carac: foundry.utils.deepClone(this.system.carac),
heure: RdDTimestamp.defHeure(await RdDDice.rollHeure({ rollMode: "selfroll", showDice: SHOW_DICE })).key, heure: RdDTimestamp.defHeure(await RdDDice.rollHeure({ rollMode: "self", showDice: SHOW_DICE })).key,
age: 18, age: 18,
biographie: '', biographie: '',
notes: '', notes: '',
@@ -3061,7 +3142,7 @@ export class RdDActor extends RdDBaseActorSang {
} }
else { else {
const artData = { art: 'oeuvre', verbe: 'Interpréter' } const artData = { art: 'oeuvre', verbe: 'Interpréter' }
const oeuvre = foundry.utils.duplicate(this.findItemLike(id, artData.art)) const oeuvre = foundry.utils.deepClone(this.findItemLike(id, artData.art))
await this._rollArtV1(artData, oeuvre.system.default_carac, oeuvre) await this._rollArtV1(artData, oeuvre.system.default_carac, oeuvre)
} }
} }
@@ -3072,7 +3153,7 @@ export class RdDActor extends RdDBaseActorSang {
} }
else { else {
const artData = { art: 'chant', verbe: 'Chanter' } const artData = { art: 'chant', verbe: 'Chanter' }
const oeuvre = foundry.utils.duplicate(this.getChant(id)) const oeuvre = foundry.utils.deepClone(this.getChant(id))
await this._rollArtV1(artData, "ouie", oeuvre) await this._rollArtV1(artData, "ouie", oeuvre)
} }
} }
@@ -3083,14 +3164,14 @@ export class RdDActor extends RdDBaseActorSang {
} }
else { else {
const artData = { art: 'danse', verbe: 'Danser', forceCarac: {} } const artData = { art: 'danse', verbe: 'Danser', forceCarac: {} }
const oeuvre = foundry.utils.duplicate(this.findItemLike(id, artData.art)) const oeuvre = foundry.utils.deepClone(this.findItemLike(id, artData.art))
let selectedCarac = this.getCompetence(oeuvre.system.competence)?.system.defaut_carac let selectedCarac = this.getCompetence(oeuvre.system.competence)?.system.defaut_carac
if (oeuvre.system.apparence) { if (oeuvre.system.apparence) {
artData.forceCarac['apparence'] = foundry.utils.duplicate(this.system.carac.apparence) artData.forceCarac['apparence'] = foundry.utils.deepClone(this.system.carac.apparence)
selectedCarac = "apparence" selectedCarac = "apparence"
} }
if (oeuvre.system.agilite) { if (oeuvre.system.agilite) {
artData.forceCarac['agilite'] = foundry.utils.duplicate(this.system.carac.agilite) artData.forceCarac['agilite'] = foundry.utils.deepClone(this.system.carac.agilite)
selectedCarac = "agilite" selectedCarac = "agilite"
} }
await this._rollArtV1(artData, selectedCarac, oeuvre) await this._rollArtV1(artData, selectedCarac, oeuvre)
@@ -3115,17 +3196,17 @@ export class RdDActor extends RdDBaseActorSang {
{ {
oeuvre: oeuvre, oeuvre: oeuvre,
art: oeuvre.type, art: oeuvre.type,
competence: foundry.utils.duplicate(this.getCompetence(artData.compName ?? oeuvre.system.competence ?? artData.art)), competence: foundry.utils.deepClone(this.getCompetence(artData.compName ?? oeuvre.system.competence ?? artData.art)),
diffLibre: - oeuvre.system.niveau, diffLibre: - oeuvre.system.niveau,
diffConditions: 0, diffConditions: 0,
use: { libre: false, conditions: true, surenc: false }, use: { libre: false, conditions: true, surenc: false },
selectedCarac: foundry.utils.duplicate(this.system.carac[selected]) selectedCarac: foundry.utils.deepClone(this.system.carac[selected])
}, },
{ overwrite: false }); { overwrite: false });
artData.competence.system.defaut_carac = selected; artData.competence.system.defaut_carac = selected;
if (!artData.forceCarac) { if (!artData.forceCarac) {
artData.forceCarac = {}; artData.forceCarac = {};
artData.forceCarac[selected] = foundry.utils.duplicate(this.system.carac[selected]); artData.forceCarac[selected] = foundry.utils.deepClone(this.system.carac[selected]);
} }
await this.openRollDialog({ await this.openRollDialog({
+12 -18
View File
@@ -9,7 +9,7 @@ import { ReglesOptionnelles } from "../settings/regles-optionnelles.js";
import { RdDBaseActor } from "./base-actor.js"; import { RdDBaseActor } from "./base-actor.js";
import { ITEM_TYPES } from "../constants.js"; import { ITEM_TYPES } from "../constants.js";
import { StatusEffects, STATUSES } from "../settings/status-effects.js"; import { StatusEffects, STATUSES } from "../settings/status-effects.js";
import { Targets } from "../targets.js"; import { Targets } from "../combat/targets.js";
import { RdDConfirm } from "../rdd-confirm.js"; import { RdDConfirm } from "../rdd-confirm.js";
import { CARACS, RdDCarac } from "../rdd-carac.js"; import { CARACS, RdDCarac } from "../rdd-carac.js";
import { RdDRollResult } from "../rdd-roll-result.js"; import { RdDRollResult } from "../rdd-roll-result.js";
@@ -19,18 +19,19 @@ import { RdDItemCompetence } from "../item-competence.js";
import { ChatUtility } from "../chat-utility.js"; import { ChatUtility } from "../chat-utility.js";
import { DialogValidationEncaissement } from "../dialog-validation-encaissement.js"; import { DialogValidationEncaissement } from "../dialog-validation-encaissement.js";
import { RdDCombat } from "../rdd-combat.js"; import { PasseArme } from "../combat/passe-arme.mjs";
import { RdDEmpoignade } from "../rdd-empoignade.js"; import { RdDEmpoignade } from "../combat/rdd-empoignade.js";
import { RdDPossession } from "../rdd-possession.js"; import { RdDPossession } from "../combat/rdd-possession.js";
import { RdDPossessionV2 } from "../combat/rdd-possession-v2.mjs";
import { BASE_CORPS_A_CORPS, BASE_ESQUIVE, CATEGORIES_COMPETENCES_CREATURES, SANS_DRACONIC } from "../item/base-items.js"; import { BASE_CORPS_A_CORPS, BASE_ESQUIVE, CATEGORIES_COMPETENCES_CREATURES, SANS_DRACONIC } from "../item/base-items.js";
import { RollDataAjustements } from "../rolldata-ajustements-v1.js"; import { RollDataAjustements } from "../rolldata-ajustements-v1.js";
import { MappingCreatureArme } from "../item/mapping-creature-arme.mjs"; import { MappingCreatureArme } from "../item/mapping-creature-arme.mjs";
import RollDialog from "../roll/roll-dialog.mjs"; import RollDialog from "../roll/roll-dialog.mjs";
import { DEFAULT_ROLL_TYPES, DIFF, ROLL_TYPE_ATTAQUE, ROLL_TYPE_COMP, ROLL_TYPE_REVE_RESISTANCE } from "../roll/roll-constants.mjs"; import { DEFAULT_ROLL_TYPES, DIFF, ROLL_TYPE_ATTAQUE, ROLL_TYPE_COMP, ROLL_TYPE_REVE_RESISTANCE } from "../roll/roll-constants.mjs";
import { OptionsAvancees, ROLL_DIALOG_V2 } from "../settings/options-avancees.js"; import { OptionsAvancees, ROLL_DIALOG_V2 } from "../settings/options-avancees.js";
import { RdDInitiative } from "../initiative.mjs"; import { RdDInitiative } from "../combat/initiative.mjs";
import { RdDItemCompetenceCreature } from "../item-competencecreature.js"; import { RdDItemCompetenceCreature } from "../item-competencecreature.js";
import { RdDPossessionV2 } from "../rdd-possession-v2.mjs";
/** /**
* Classe de base pour les acteurs disposant de rêve (donc, pas des objets) * Classe de base pour les acteurs disposant de rêve (donc, pas des objets)
@@ -39,13 +40,6 @@ import { RdDPossessionV2 } from "../rdd-possession-v2.mjs";
*/ */
export class RdDBaseActorReve extends RdDBaseActor { export class RdDBaseActorReve extends RdDBaseActor {
prepareActorData() {
super.prepareActorData()
this.system.attributs.plusdom.value = this.getBonusDegat()
this.system.sante.endurance.max = this.getEnduranceMax()
this.system.sante.endurance.value = Math.min(this.system.sante.endurance.value, this.system.sante.endurance.max)
}
getCarac() { getCarac() {
const carac = super.getCarac() const carac = super.getCarac()
foundry.utils.mergeObject(carac, this.getCaracReveActuel()) foundry.utils.mergeObject(carac, this.getCaracReveActuel())
@@ -369,7 +363,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
selectedCarac: carac, selectedCarac: carac,
competence: competence, competence: competence,
diffLibre: diff ?? 0, diffLibre: diff ?? 0,
current: { rollmode: { key: game.settings.get("core", "rollMode") } }, current: { rollmode: { key: game.settings.get("core", "messageMode") } },
show: { title: options?.title ?? '' } show: { title: options?.title ?? '' }
} }
RollDataAjustements.calcul(rollData, this); RollDataAjustements.calcul(rollData, this);
@@ -531,7 +525,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
RdDPossession.onAttaquePossession(target, this, competence) RdDPossession.onAttaquePossession(target, this, competence)
} }
else { else {
RdDCombat.rddCombatTarget(target, this, token).attaque(competence, arme) PasseArme.rddCombatTarget(target, this, token).attaque(competence, arme)
} }
}); });
return return
@@ -566,7 +560,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
return return
} }
RdDCombat.rddCombatTarget(target, this, token).attaqueV2(); PasseArme.rddCombatTarget(target, this, token).attaqueV2();
}) })
} }
else { else {
@@ -630,7 +624,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
// TODO: vérifier si c'est possible, sinon simplifier // TODO: vérifier si c'est possible, sinon simplifier
return RdDPossession.onAttaquePossession(target, this, comp); return RdDPossession.onAttaquePossession(target, this, comp);
} }
RdDCombat.rddCombatTarget(target, this, token).attaque(comp, arme, maniement) PasseArme.rddCombatTarget(target, this, token).attaque(comp, arme, maniement)
}) })
} }
@@ -696,7 +690,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
{ actor: this })) { actor: this }))
if (!encaissement.hasPlayerOwner && encaissement.endurance != 0) { if (!encaissement.hasPlayerOwner && encaissement.endurance != 0) {
encaissement = foundry.utils.duplicate(encaissement) encaissement = foundry.utils.deepClone(encaissement)
encaissement.isGM = true encaissement.isGM = true
await ChatMessage.create({ await ChatMessage.create({
whisper: ChatUtility.getGMs(), whisper: ChatUtility.getGMs(),
+3 -10
View File
@@ -25,13 +25,6 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
return super._preUpdate(changed, options, user) return super._preUpdate(changed, options, user)
} }
prepareActorData() {
this.system.sante.vie.max = Math.ceil((this.getTaille() + this.getConstitution()) / 2)
this.system.sante.vie.value = Math.min(this.system.sante.vie.value, this.system.sante.vie.max)
super.prepareActorData()
this.system.attributs.encombrement.value = this.getEncombrementMax()
}
getCarac() { getCarac() {
const carac = super.getCarac() const carac = super.getCarac()
foundry.utils.mergeObject(carac, this.getCaracChanceActuelle()) foundry.utils.mergeObject(carac, this.getCaracChanceActuelle())
@@ -109,7 +102,7 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
/* -------------------------------------------- */ /* -------------------------------------------- */
async onAppliquerJetEncaissement(encaissement, attackerToken) { async onAppliquerJetEncaissement(encaissement, attackerToken) {
const santeOrig = foundry.utils.duplicate(this.system.sante); const santeOrig = foundry.utils.deepClone(this.system.sante);
const blessure = this.nouvelleBlessure(encaissement.gravite, { const blessure = this.nouvelleBlessure(encaissement.gravite, {
localisation: encaissement.dmg?.loc.label ?? '', localisation: encaissement.dmg?.loc.label ?? '',
origine: attackerToken?.name ?? '' origine: attackerToken?.name ?? ''
@@ -124,7 +117,7 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
await rollPerteEndurance.evaluate() await rollPerteEndurance.evaluate()
blessure.system.endurance = rollPerteEndurance.total blessure.system.endurance = rollPerteEndurance.total
} }
const isCritique = blessure.system.gravite >= 6; const isCritique = blessure.system.gravite >= 6 || encaissement._reduireEndAZero;
if (isCritique) { if (isCritique) {
blessure.system.endurance = this.getEnduranceActuelle() blessure.system.endurance = this.getEnduranceActuelle()
} }
@@ -156,7 +149,7 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
if (!this.system.sante[name]) { if (!this.system.sante[name]) {
return return
} }
const sante = foundry.utils.duplicate(this.system.sante) const sante = foundry.utils.deepClone(this.system.sante)
const compteur = sante[name] const compteur = sante[name]
const result = { sonne: false } const result = { sonne: false }
let perteEndurance = 0 let perteEndurance = 0
+18 -18
View File
@@ -91,7 +91,7 @@ export class RdDBaseActor extends Actor {
} }
static getRealActor(actorId, tokenId) { static getRealActor(actorId, tokenId) {
const actor = tokenId ? canvas.tokens.get(tokenId)?.actor : undefined const actor = tokenId ? canvas?.tokens?.get(tokenId)?.actor : undefined
return actor ?? game.actors.get(actorId) return actor ?? game.actors.get(actorId)
} }
@@ -148,7 +148,7 @@ export class RdDBaseActor extends Actor {
} }
getCarac() { getCarac() {
return foundry.utils.duplicate(this.system.carac) return foundry.utils.deepClone(this.system.carac)
} }
findCaracByName(name) { findCaracByName(name) {
@@ -220,16 +220,16 @@ export class RdDBaseActor extends Actor {
await this.delayedRenderSheet('_preUpdate', changed, options) await this.delayedRenderSheet('_preUpdate', changed, options)
return super._preUpdate(changed, options, user) return super._preUpdate(changed, options, user)
} }
_onUpdate(changed, options, userId) { _onUpdate(changed, options, userId) {
super._onUpdate(changed, options, userId) super._onUpdate(changed, options, userId)
if (userId == game.user.id){ if (userId == game.user.id) {
this.delayedRenderSheet('_onUpdate', changed, options) this.delayedRenderSheet('_onUpdate', changed, options)
} }
} }
async delayedRenderSheet(caller, data, options) { async delayedRenderSheet(caller, data, options) {
this.refreshDelayCounter = (this.refreshDelayCounter ?? 0)+1 this.refreshDelayCounter = (this.refreshDelayCounter ?? 0) + 1
if (this.refreshDelayCounter == 1) { if (this.refreshDelayCounter == 1) {
this.renderAfterDelay(this.refreshDelayCounter) this.renderAfterDelay(this.refreshDelayCounter)
} }
@@ -250,14 +250,11 @@ export class RdDBaseActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
prepareData() { prepareData() {
super.prepareData() super.prepareData()
this.prepareActorData()
this.cleanupConteneurs() this.cleanupConteneurs()
this.computeEtatGeneral() this.computeEtatGeneral()
this.computeEncTotal() this.computeEncTotal()
} }
prepareActorData() { }
async computeEtatGeneral() { } async computeEtatGeneral() { }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -383,8 +380,8 @@ export class RdDBaseActor extends Actor {
} }
async onTimeChanging(oldTimestamp, newTimestamp) { async onTimeChanging(oldTimestamp, newTimestamp) {
this.items.filter(it => it.isFinPeriode(oldTimestamp, newTimestamp)) await Promise.all(this.items.filter(it => it.isFinPeriode(oldTimestamp, newTimestamp))
.forEach(async it => await it.onFinPeriodeTemporel(oldTimestamp, newTimestamp)) .map(async it => await it.onFinPeriodeTemporel(oldTimestamp, newTimestamp)))
} }
async creerObjetParMJ(object) { async creerObjetParMJ(object) {
@@ -404,7 +401,7 @@ export class RdDBaseActor extends Actor {
if (Misc.isOwnerPlayer(this)) { if (Misc.isOwnerPlayer(this)) {
let updates = this.itemTypes[ITEM_TYPES.conteneur] let updates = this.itemTypes[ITEM_TYPES.conteneur]
.filter(c => c.system.contenu.filter(id => this.getItem(id) == undefined).length > 0) .filter(c => c.system.contenu.filter(id => this.getItem(id) == undefined).length > 0)
.map(c => { return { _id: c._id, 'system.contenu': c.system.contenu.filter(id => this.getItem(id) != undefined) } }); .map(c => { return { _id: c.id, 'system.contenu': c.system.contenu.filter(id => this.getItem(id) != undefined) } });
if (updates.length > 0) { if (updates.length > 0) {
await this.updateEmbeddedDocuments("Item", updates) await this.updateEmbeddedDocuments("Item", updates)
} }
@@ -545,7 +542,7 @@ export class RdDBaseActor extends Actor {
if (cout > 0) { if (cout > 0) {
RdDAudio.PlayContextAudio("argent"); RdDAudio.PlayContextAudio("argent");
} }
const chatAchatItem = foundry.utils.duplicate(achat.vente); const chatAchatItem = foundry.utils.deepClone(achat.vente);
chatAchatItem.quantiteTotal = quantite; chatAchatItem.quantiteTotal = quantite;
await ChatMessage.create({ await ChatMessage.create({
user: achat.userId, user: achat.userId,
@@ -631,17 +628,20 @@ export class RdDBaseActor extends Actor {
return return
} }
const isItemEmpilable = "quantite" in item.system;
const baseItem = { const baseItem = {
id: undefined,
type: item.type, type: item.type,
img: item.img, img: item.img,
name: item.name, name: item.name,
system: foundry.utils.mergeObject(item.system, { quantite: isItemEmpilable ? quantite : undefined }, { inplace: false }) system: foundry.utils.duplicate(item.system)
} }
const createItemArray = "quantite" in item.system
? (it, qt) => { it.system.quantite = qt; return [it] }
: (it, qt) => Array.from({ length: qt }, (_, i) => it)
const newItems = isItemEmpilable ? [baseItem] : Array.from({ length: quantite }, (_, i) => baseItem); const newItems = createItemArray(baseItem, quantite)
const items = await this.createEmbeddedDocuments("Item", newItems); const items = await this.createEmbeddedDocuments("Item", newItems)
return newItems.length > 0 ? items[0].id : undefined; return items.length > 0 ? items[0].id : undefined;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -851,7 +851,7 @@ export class RdDBaseActor extends Actor {
sourceActor.buildSubConteneurObjetList(itemId, itemsList); // Get itemId list sourceActor.buildSubConteneurObjetList(itemId, itemsList); // Get itemId list
const itemsDataToCreate = itemsList.map(it => sourceActor.getItem(it.id)) const itemsDataToCreate = itemsList.map(it => sourceActor.getItem(it.id))
.map(it => foundry.utils.duplicate(it)) .map(it => foundry.utils.deepClone(it))
.map(it => { it.system.contenu = []; return it; }); .map(it => { it.system.contenu = []; return it; });
let newItems = await this.createEmbeddedDocuments('Item', itemsDataToCreate); let newItems = await this.createEmbeddedDocuments('Item', itemsDataToCreate);
+32 -30
View File
@@ -38,7 +38,7 @@ async function randomPoidsKg(taille) {
} }
async function randomHeure() { async function randomHeure() {
return RdDTimestamp.defHeure(await RdDDice.rollHeure({ rollMode: "selfroll", showDice: SHOW_DICE })).key return RdDTimestamp.defHeure(await RdDDice.rollHeure({ rollMode: "self", showDice: SHOW_DICE })).key
} }
const CONTROL_UNKNOWN = { name: 'unknown', path: '', getter: (act) => { undefined }, random: async act => undefined } const CONTROL_UNKNOWN = { name: 'unknown', path: '', getter: (act) => { undefined }, random: async act => undefined }
@@ -79,7 +79,24 @@ const CONTROLS = [
{ name: 'yeux', path: 'system.yeux', getter: act => act.system.yeux, random: async act => await randomFrom(TABLE_COULEURS_YEUX) }, { name: 'yeux', path: 'system.yeux', getter: act => act.system.yeux, random: async act => await randomFrom(TABLE_COULEURS_YEUX) },
] ]
export class AppPersonnageAleatoire extends FormApplication { const { HandlebarsApplicationMixin } = foundry.applications.api;
export class AppPersonnageAleatoire extends HandlebarsApplicationMixin(foundry.applications.api.ApplicationV2) {
static PARTS = {
main: { template: "systems/foundryvtt-reve-de-dragon/templates/actor/random/app-personnage-aleatoire.hbs" }
}
static DEFAULT_OPTIONS = {
classes: ['app-personnage-aleatoire'],
window: {
title: "Génération aléatoire",
controls: []
},
position: {
width: 600,
height: 600
}
}
static preloadHandlebars() { static preloadHandlebars() {
foundry.applications.handlebars.loadTemplates([ foundry.applications.handlebars.loadTemplates([
'systems/foundryvtt-reve-de-dragon/templates/actor/random/champ-aleatoire.hbs', 'systems/foundryvtt-reve-de-dragon/templates/actor/random/champ-aleatoire.hbs',
@@ -87,18 +104,6 @@ export class AppPersonnageAleatoire extends FormApplication {
]) ])
} }
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
template: "systems/foundryvtt-reve-de-dragon/templates/actor/random/app-personnage-aleatoire.hbs",
title: "Génération aléatoire",
width: 'fit-content',
height: 'fit-content',
classes: ['app-personnage-aleatoire'],
popOut: true,
resizable: true
}, { inplace: false })
}
constructor(actor) { constructor(actor) {
super({}) super({})
this.actor = actor this.actor = actor
@@ -106,18 +111,18 @@ export class AppPersonnageAleatoire extends FormApplication {
this.checked = Object.fromEntries(CONTROLS.map(it => [it.name, [0, '', undefined].includes(this.current[it.name])])) this.checked = Object.fromEntries(CONTROLS.map(it => [it.name, [0, '', undefined].includes(this.current[it.name])]))
} }
async getData(options) { _prepareContext() {
return foundry.utils.mergeObject(await super.getData(options), { return {
actor: this.actor, actor: this.actor,
current: this.current, current: this.current,
checked: this.checked, checked: this.checked,
options: { isGM: game.user.isGM } options: { isGM: game.user.isGM }
}) }
} }
activateListeners(html) { _onRender(context, options) {
super.activateListeners(html) super._onRender(context, options)
this.html = html this.html = $(this.element)
this.html.find("input.current-value").change(async event => await this.onChange(event)) this.html.find("input.current-value").change(async event => await this.onChange(event))
this.html.find("a.random").click(async event => await this.onRandom(event)) this.html.find("a.random").click(async event => await this.onRandom(event))
this.html.find("a.reset").click(async event => await this.onReset(event)) this.html.find("a.reset").click(async event => await this.onReset(event))
@@ -132,8 +137,6 @@ export class AppPersonnageAleatoire extends FormApplication {
this.html.find("button.button-apply").click(async event => await this.onApply()) this.html.find("button.button-apply").click(async event => await this.onApply())
} }
async _updateObject(event, formData) { }
async onApply() { async onApply() {
const updates = Object.fromEntries( const updates = Object.fromEntries(
CONTROLS.filter(control => game.user.isGM || control.name != 'name') CONTROLS.filter(control => game.user.isGM || control.name != 'name')
@@ -154,14 +157,14 @@ export class AppPersonnageAleatoire extends FormApplication {
async onSexe(sexe) { async onSexe(sexe) {
this.current['sexe'] = sexe this.current['sexe'] = sexe
this.render() this.render({ force: true })
} }
async onChange(event) { async onChange(event) {
const name = this.getName(event.currentTarget) const name = this.getName(event.currentTarget)
const control = this.getControl(name) const control = this.getControl(name)
this.current[control.name] = event.currentTarget.value this.current[control.name] = event.currentTarget.value
this.render() this.render({ force: true })
} }
@@ -169,20 +172,20 @@ export class AppPersonnageAleatoire extends FormApplication {
const name = this.getName(event.currentTarget) const name = this.getName(event.currentTarget)
const control = this.getControl(name) const control = this.getControl(name)
await this.randomControl(control) await this.randomControl(control)
this.render() this.render({ force: true })
} }
async onReset(event) { async onReset(event) {
const name = this.getName(event.currentTarget) const name = this.getName(event.currentTarget)
const control = this.getControl(name) const control = this.getControl(name)
this.current[control.name] = control.getter(this.actor) this.current[control.name] = control.getter(this.actor)
await this.render() this.render({ force: true })
} }
async onCheckForRandom(event) { async onCheckForRandom(event) {
const name = this.getName(event.currentTarget) const name = this.getName(event.currentTarget)
this.checked[name] = event.currentTarget.checked this.checked[name] = event.currentTarget.checked
this.render() this.render({ force: true })
} }
async onRandomizeSelected() { async onRandomizeSelected() {
@@ -191,11 +194,10 @@ export class AppPersonnageAleatoire extends FormApplication {
.toArray() .toArray()
.map(it => this.getControl(it.attributes['data-field-name'].value)) .map(it => this.getControl(it.attributes['data-field-name'].value))
await Promise.all(controls.map(it => this.randomControl(it))) await Promise.all(controls.map(it => this.randomControl(it)))
this.render() this.render({ force: true })
} }
async randomControl(control) { async randomControl(control) {
this.current[control.name] = await control.random(this.current) this.current[control.name] = await control.random(this.actor)
} }
} }
+52 -5
View File
@@ -1,5 +1,52 @@
export { default as RdDItemBaseSheet} from "./common-item-sheet.mjs" export { RdDBaseActorSheetV2 } from "./base-actor-sheet.mjs"
export { default as RdDMonnaieSheet } from "./monnaie-sheet.mjs" export { RdDCreatureSheetV2 } from "./creature-sheet.mjs"
export { default as RdDMunitionSheet } from "./munition-sheet.mjs" export { RdDActorVehiculeSheetV2 } from "./vehicule-sheet.mjs"
export { default as RdDTarotSheet } from "./tarot-sheet.mjs" export { RdDCommerceSheetV2 } from "./commerce-sheet.mjs"
export { RdDActorEntiteSheetV2 } from "./entite-sheet.mjs"
export { RdDActorSheetV2 } from "./personnage-sheet.mjs"
export { RdDActorExportSheetV2 } from "./export-encart-sheet.mjs"
export { default as RdDItemBaseSheet} from "./common-item-sheet.mjs"
export { default as RdDMonnaieSheet } from "./monnaie-sheet.mjs"
export { default as RdDMunitionSheet } from "./munition-sheet.mjs"
export { default as RdDTarotSheet } from "./tarot-sheet.mjs"
export { default as RdDTeteSheet } from "./tete-sheet.mjs"
export { default as RdDQueueSheet } from "./queue-sheet.mjs"
export { default as RdDSouffleSheet } from "./souffle-sheet.mjs"
export { default as RdDOmbreSheet } from "./ombre-sheet.mjs"
export { default as RdDChantSheet } from "./chant-sheet.mjs"
export { default as RdDMusiqueSheet } from "./musique-sheet.mjs"
export { default as RdDDanseSheet } from "./danse-sheet.mjs"
export { default as RdDJeuSheet } from "./jeu-sheet.mjs"
export { default as RdDRecetteAlchimiqueSheet } from "./recettealchimique-sheet.mjs"
export { default as RdDRecetteCuisineSheet } from "./recettecuisine-sheet.mjs"
export { default as RdDArmeSheet } from "./arme-sheet.mjs"
export { default as RdDArmureSheet } from "./armure-sheet.mjs"
export { default as RdDObjetSheet } from "./objet-sheet.mjs"
export { default as RdDConteneurSheet } from "./conteneur-sheet.mjs"
export { default as RdDNourritureBoissonSheet } from "./nourritureboisson-sheet.mjs"
export { default as RdDGemmeSheet } from "./gemme-sheet.mjs"
export { default as RdDServiceSheet } from "./service-sheet.mjs"
export { default as RdDTacheSheet } from "./tache-sheet.mjs"
export { default as RdDBlessureSheet } from "./blessure-sheet.mjs"
export { default as RdDMaladieSheet } from "./maladie-sheet.mjs"
export { default as RdDPoisonSheet } from "./poison-sheet.mjs"
export { default as RdDCompetenceSheet } from "./competence-sheet.mjs"
export { default as RdDCompetenceCreatureSheet } from "./competencecreature-sheet.mjs"
export { default as RdDPotionSheet } from "./potion-sheet.mjs"
export { default as RdDSigneDraconiqueSheet } from "./signedraconique-sheet.mjs"
export { default as RdDSortSheet } from "./sort-sheet.mjs"
export { default as RdDRaceSheet } from "./race-sheet.mjs"
export { default as RdDRencontreSheet } from "./rencontre-sheet.mjs"
export { default as RdDEmpoignadeSheet } from "./empoignade-sheet.mjs"
export { default as RdDPossessionSheet } from "./possession-sheet.mjs"
export { default as RdDLivreSheet } from "./livre-sheet.mjs"
export { default as RdDFauneSheet } from "./faune-sheet.mjs"
export { default as RdDHerbeSheet } from "./herbe-sheet.mjs"
export { default as RdDPlanteSheet } from "./plante-sheet.mjs"
export { default as RdDIngredientSheet } from "./ingredient-sheet.mjs"
export { default as RdDExtraitPoetiqueSheet } from "./extraitpoetique-sheet.mjs"
export { default as RdDNombreAstralSheet } from "./nombreastral-sheet.mjs"
export { default as RdDCaseTMRSheet } from "./casetmr-sheet.mjs"
export { default as RdDSortReserveSheet } from "./sortreserve-sheet.mjs"
export { default as RdDMeditationSheet } from "./meditation-sheet.mjs"
export { default as RdDOeuvreSheet } from "./oeuvre-sheet.mjs"
+92
View File
@@ -0,0 +1,92 @@
import { TEMPLATE_DESCRIPTION, TEMPLATE_INVENTAIRE } from "../../common/_module.mjs";
import { ITEM_TYPES, ACTOR_TYPES } from "../../constants.js";
import { SystemCompendiums } from "../../settings/system-compendiums.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
export default class RdDArmeSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.arme; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION, TEMPLATE_INVENTAIRE]; }
static MORTALITE_CHOICES = {
mortel: "Mortel",
"non-mortel": "Non mortel",
empoignade: "Empoignade",
}
static CATEGORIE_PARADE_CHOICES = {
"sans-armes": "Sans arme",
"armes-naturelles": "Armes naturelles",
hast: "Armes d'hast",
batons: "Bâtons",
boucliers: "Boucliers",
dagues: "Dagues",
"epees-courtes": "Epées courtes",
"epees-longues": "Epées longues",
"epees-lourdes": "Epées lourdes",
haches: "Haches",
lances: "Lances",
masses: "Masses",
}
static INIT_CHOICES = {
hast: "Armes Hast",
lance: "Lance",
baton: "Bâton",
doubledragonne: "Double Dragonne",
esparlongue: "Esparlongue",
epeedragonne: "Epée Dragonne",
epeebatarde: "Epée Bâtarde",
epeecyane: "Epée Cyane",
epeesorde: "Epée Sorde",
grandehache: "Grande Hache",
hachebataille: "Hache de Bataille",
epeegnome: "Epée Gnome",
masse: "Masse",
gourdin: "Gourdin",
fleau: "Fléaux",
dague: "Dague",
armenaturelle: "Armes Naturelles",
autre: "Autres",
}
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{
classes: ["fvtt-rdd", "item", "arme"],
position: { width: 448 },
window: { contentClasses: ["arme-content"] },
});
static PARTS = {
main: {
template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/arme.hbs",
},
};
async _prepareContext() {
const competences = (await SystemCompendiums.getCompetences(ACTOR_TYPES.personnage))
.filter(it => it.isCompetenceArme())
const competenceMeleeChoices = {}
const competenceLancerChoices = {}
const competenceTirChoices = {}
competences.forEach(c => {
const label = c.name
switch (c.system.categorie) {
case 'melee': competenceMeleeChoices[label] = label; break
case 'lancer': competenceLancerChoices[label] = label; break
case 'tir': competenceTirChoices[label] = label; break
}
})
return Object.assign(
await super._prepareContext(),
{
competenceMeleeChoices,
competenceLancerChoices,
competenceTirChoices,
mortaliteChoices: RdDArmeSheet.MORTALITE_CHOICES,
categorieParadeChoices: RdDArmeSheet.CATEGORIE_PARADE_CHOICES,
initChoices: RdDArmeSheet.INIT_CHOICES,
}
);
}
}
@@ -0,0 +1,23 @@
import { TEMPLATE_DESCRIPTION, TEMPLATE_INVENTAIRE } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
export default class RdDArmureSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.armure; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION, TEMPLATE_INVENTAIRE]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{
classes: ["fvtt-rdd", "item", "armure"],
position: { width: 448 },
window: { contentClasses: ["armure-content"] },
});
static PARTS = {
main: {
template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/armure.hbs",
},
};
}
@@ -0,0 +1,460 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
import { RdDUtility } from "../../rdd-utility.js"
import { Misc } from "../../misc.js"
import { DialogSplitItem } from "../../dialog-split-item.js"
import { RdDSheetUtility } from "../../rdd-sheet-utility.js"
import { Monnaie } from "../../item-monnaie.js"
import { ITEM_TYPES } from "../../constants.js"
import { RdDItem } from "../../item.js"
import { RdDTextEditor } from "../../apps/rdd-text-roll-editor.js"
import { ItemAction } from "../../item/item-actions.js"
import { DialogVerifierIngredients } from "../../dialog-verifier-ingredients.js"
import { DialogLancerRecette } from "../../dialog-lancer-recette.js"
import { ChatUtility } from "../../chat-utility.js"
import { RdDItemBlessure } from "../../item/blessure.js"
import { Grammar } from "../../grammar.js"
import { SYSTEM_RDD } from "../../constants.js"
export class RdDBaseActorSheetV2 extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
get actor() { return this.document }
// Was this.options.* — custom state
vueDetaillee = false
triEquipement = "name"
recherche = undefined
static TYPE = undefined
static DEFAULT_OPTIONS = {
classes: ["rdd", "sheet", "actor"],
position: { width: 640 },
form: { submitOnChange: true, closeOnSubmit: false },
window: { resizable: true, controls: [] },
actions: {
postToChat: RdDBaseActorSheetV2._onPostToChat,
},
}
//#region Registration
static register() {
foundry.documents.collections.Actors.registerSheet(SYSTEM_RDD, this, {
types: [this.TYPE],
makeDefault: true,
})
}
static async registerAll(...sheetClasses) {
const handlebars = []
sheetClasses.forEach(sheetClass => {
if (sheetClass.PARTS) {
Object.values(sheetClass.PARTS).forEach(part => {
if (part.template) handlebars.push(part.template)
})
}
sheetClass.register()
})
await foundry.applications.handlebars.loadTemplates(Misc.distinct(handlebars))
}
//#endregion
//#region Data
async _prepareContext() {
Monnaie.validerMonnaies(this.actor)
this.actor.computeEtatGeneral()
this._sheetOptions = foundry.utils.mergeObject(
RdDSheetUtility.mergeDocumentRights({ ...this.options }, this.actor, this.isEditable),
{ vueDetaillee: this.vueDetaillee, triEquipement: this.triEquipement, recherche: this.recherche },
{ inplace: false }
)
const ctx = {
title: this.title,
id: this.actor.id,
type: this.actor.type,
img: this.actor.img,
name: this.actor.name,
system: this.actor.system,
systemFields: this.actor.system.schema.fields,
description: await RdDTextEditor.enrichHTML(this.actor.system.description, this.actor),
notesmj: await RdDTextEditor.enrichHTML(this.actor.system.notesmj, this.actor),
options: this._sheetOptions,
effects: this.actor.effects,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
limited: this.actor.limited,
owner: this.actor.isOwner,
}
RdDUtility.filterItemsPerTypeForSheet(ctx, this.actor.itemTypes, this.triEquipement)
ctx.calc = {
fortune: Monnaie.toSolsDeniers(this.actor.getFortune()),
prixTotalEquipement: this.actor.computePrixTotalEquipement(),
encTotal: this.actor.getEncTotal(),
}
this.objetVersConteneur = RdDUtility.buildArbreDeConteneurs(ctx.conteneurs, ctx.inventaires)
this._appliquerRechercheObjets(ctx.conteneurs, ctx.inventaires)
ctx.conteneurs = RdDUtility.conteneursRacine(ctx.conteneurs)
ctx.competences.filter(it => it.type == ITEM_TYPES.competencecreature)
.forEach(it => it.isdommages = it.isDommages())
return ctx
}
_appliquerRechercheObjets(conteneurs, inventaires) {
if (this.recherche?.text) {
const recherche = this.recherche
const allVisible = inventaires.filter(it => it.isNomTypeLike(recherche.text)).map(it => it.id)
let addVisible = conteneurs.filter(it => it.isNomTypeLike(recherche.text)).map(it => it.id)
do {
allVisible.push(...addVisible)
const parentsIds = conteneurs.filter(it => it.system.contenu.find(id => allVisible.includes(id))).map(it => it.id)
addVisible = parentsIds.filter(id => !allVisible.includes(id))
}
while (addVisible.length > 0)
inventaires.forEach(it => it.system.isHidden = !allVisible.includes(it.id))
conteneurs.forEach(it => it.system.isHidden = !allVisible.includes(it.id))
}
else {
inventaires.forEach(it => it.system.isHidden = false)
conteneurs.forEach(it => it.system.isHidden = false)
}
}
//#endregion
//#region Rendering
async _renderFrame(options) {
const frame = await super._renderFrame(options)
return frame
}
async _onRender(context, options) {
await super._onRender(context, options)
this.html = $(this.element)
const nav = this.element.querySelector(".sheet-tabs")
if (nav) {
const tab = this.tabGroups?.primary || nav.querySelector("[data-tab]")?.dataset.tab
if (tab) this.changeTab(tab, "primary", { force: true })
}
this.html.find(".actionItem").click(async event => await ItemAction.onActionItem(event, this.actor, this._sheetOptions))
this.html.find(".item-edit").click(async event => await this._onItemEdit(event))
this.html.find(".item-verify-ingredients").click(async event => await this._onVerifyIngredients(event))
this.html.find(".item-lancer-recette").click(async event => await this._onLancerRecette(event))
this.html.find(".conteneur-name a").click(async event => {
RdDUtility.toggleAfficheContenu(this._getItemId(event))
this.render({ force: true })
})
this.html.find(".actor-montrer").click(async event => await this.actor.postActorToChat())
this.html.find(".recherche")
.each((_index, field) => this._rechercheSelectArea(field))
.keyup(async event => this._rechercherKeyup(event))
.change(async event => this._rechercherKeyup(event))
this.html.find(".recherche").prop("disabled", false)
this.html.find(".item-list .item").dblclick(async event => {
const item = this._getItem(event)
if (item) item.sheet.render({ force: true })
})
this.html.find(".tri-equipement").change(event => {
this.triEquipement = event.currentTarget.value
this.render({ force: true })
})
if (!this.isEditable) return
this.html.find(".item-equip-armure").click(async event => await this.actor.equiperObjet(this._getItem(event)))
this.html.find(".item-delete").click(async event => await RdDUtility.confirmActorItemDelete(this._getItem(event), this.actor))
this.html.find(".item-quantite-plus").click(async event => await this.actor.itemQuantiteIncDec(this._getItemId(event), 1))
this.html.find(".item-quantite-moins").click(async event => await this.actor.itemQuantiteIncDec(this._getItemId(event), -1))
this.html.find(".item-quantite").change(async event => {
const input = event.currentTarget
const item = this.actor.items.get(input.dataset.itemId)
if (item) {
const newQty = Math.max(0, parseInt(input.value) || 0)
await item.update({ "system.quantite": newQty })
if (newQty === 0) this.render({ force: true })
}
})
this.html.find(".item-quantite").on("contextmenu", async event => {
event.preventDefault()
const item = this._getItem(event)
if (!item?.isInventaire()) return
new foundry.applications.api.DialogV2({
title: `Quantité — ${item.name}`,
content: `<p>Réduire la quantité de :</p>
<select class="qty-amount">
<option value="1">1</option>
<option value="5">5</option>
<option value="10">10</option>
<option value="all">Tout jeter</option>
</select>`,
buttons: [
{ action: "reduce", label: "Retirer", icon: "fa-solid fa-minus", callback: (event, button, dialog) => {
const amount = dialog.element.querySelector(".qty-amount").value
if (amount === "all") {
item.delete()
this.render({ force: true })
} else {
item.update({ "system.quantite": Math.max(0, item.system.quantite - parseInt(amount)) })
}
}},
],
}).render({ force: true })
})
this.html.find(".creer-un-objet").click(async event => await this._selectObjetTypeToCreate())
this.html.find(".nettoyer-conteneurs").click(async event => await this.actor.nettoyerConteneurs())
this.html.find(".quick-add-ration").click(async event => {
const existante = this.actor.items.find(it => it.type === "nourritureboisson" && it.name === "Ration")
if (existante) {
await existante.update({ "system.quantite": Math.max(0, Number(existante.system.quantite) + 1) })
} else {
await this.actor.createEmbeddedDocuments("Item", [{
name: "Ration", type: "nourritureboisson",
img: "systems/foundryvtt-reve-de-dragon/icons/objets/provision_crue.webp",
system: { sust: 1, encombrement: 0.1, quantite: 1, cout: 0.1, boisson: false },
}], { renderSheet: false })
}
this.render({ force: true })
})
this.html.find(".quick-add-eau").click(async event => {
const existante = this.actor.items.find(it => it.type === "nourritureboisson" && it.name === "Eau")
if (existante) {
await existante.update({ "system.quantite": Math.max(0, Number(existante.system.quantite) + 1) })
} else {
await this.actor.createEmbeddedDocuments("Item", [{
name: "Eau", type: "nourritureboisson",
img: "systems/foundryvtt-reve-de-dragon/icons/objets/outre.webp",
system: { desaltere: 1, encombrement: 0.1, quantite: 1, cout: 0.05, boisson: true },
}], { renderSheet: false })
}
this.render({ force: true })
})
this.html.find(".vue-detaillee").click(async event => {
this.vueDetaillee = !this.vueDetaillee
this.render({ force: true })
})
// Revel listeners (was base-actor-reve-sheet.js)
this.html.find(".button-encaissement").click(async event => await this.actor.encaisser())
this.html.find(".roll-carac").click(async event => {
await this.actor.rollCarac(Grammar.toLowerCaseNoAccent(event.currentTarget.attributes["data-carac-name"].value))
})
this.html.find(".roll-competence").click(async event => await this.actor.rollCompetence(this._getItemId(event)))
this.html.find(".endurance-plus").click(async event => await this.actor.santeIncDec("endurance", 1))
this.html.find(".endurance-moins").click(async event => await this.actor.santeIncDec("endurance", -1))
if (game.user.isGM) {
this.html.find(".button-remise-a-neuf").click(async event => await this.actor.remiseANeuf())
this.html.find(".delete-active-effect").click(async event => await 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())
}
this.html.find(".competence-add").click(async event =>
await this.actor.createEmbeddedDocuments("Item", [{
type: ITEM_TYPES.competencecreature,
name: "Nouvelle competence",
img: "systems/foundryvtt-reve-de-dragon/icons/compcreature-serres.webp",
system: { carac_value: this.actor.getForce() },
}], { renderSheet: true })
)
this.html.find(".roll-text").click(async event => await RdDTextEditor.rollText(event, this.actor))
this.html.find(".chat-roll-text").click(async event => await RdDTextEditor.chatRollText(event))
if (this.vueDetaillee) {
this.html.find(".carac-value").change(async event => {
if (event.currentTarget.name.includes("carac.")) {
const caracName = event.currentTarget.name.replace("carac.", "")
await this.actor.updateCarac(caracName, parseInt(event.currentTarget.value))
}
})
this.html.find(".competence-value").change(async event => {
const compName = event.currentTarget.attributes.compname.value
await this.actor.updateCompetence(compName, parseInt(event.currentTarget.value))
})
}
// Sang listeners (was base-actor-sang-sheet.js)
this.html.find(".creer-blessure-legere").click(async event => await RdDItemBlessure.createBlessure(this.actor, 2))
this.html.find(".creer-blessure-grave").click(async event => await RdDItemBlessure.createBlessure(this.actor, 4))
this.html.find(".creer-blessure-critique").click(async event => await RdDItemBlessure.createBlessure(this.actor, 6))
this.html.find(".subir-blessure-contusion").click(async event => await RdDItemBlessure.applyFullBlessure(this.actor, 0))
this.html.find(".subir-blessure-legere").click(async event => await RdDItemBlessure.applyFullBlessure(this.actor, 2))
this.html.find(".subir-blessure-grave").click(async event => await RdDItemBlessure.applyFullBlessure(this.actor, 4))
this.html.find(".subir-blessure-critique").click(async event => await RdDItemBlessure.applyFullBlessure(this.actor, 6))
this.html.find(".jet-vie").click(async event => await this.actor.jetDeVie())
this.html.find(".jet-endurance").click(async event => await this._jetEndurance())
this.html.find(".vie-plus").click(async event => await this.actor.santeIncDec("vie", 1))
this.html.find(".vie-moins").click(async event => await this.actor.santeIncDec("vie", -1))
}
async _jetEndurance() {
const endurance = this.actor.getEnduranceActuelle()
const result = await this.actor.jetEndurance(endurance)
await 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.getOwners(this.actor),
})
}
_rechercherKeyup(event) {
const currentTarget = event.currentTarget
const nouvelleRecherche = this._optionRecherche(currentTarget)
if (this.recherche?.text != nouvelleRecherche?.text) {
this.recherche = nouvelleRecherche
if (this._timerRecherche) clearTimeout(this._timerRecherche)
this._timerRecherche = setTimeout(() => {
this._timerRecherche = undefined
this.render({ force: true })
}, 500)
}
}
_rechercheSelectArea(field) {
if (this.recherche) {
field.focus()
field.setSelectionRange(this.recherche.start, this.recherche.end)
}
}
_optionRecherche(target) {
if (!target.value?.length) return undefined
return {
text: target.value,
start: target.selectionStart,
end: target.selectionEnd,
}
}
//#endregion
//#region Drop
async _onDropItem(event, item) {
const destItemId = this.html.find(event.target)?.closest(".item").attr("data-item-id")
const previousItemIds = new Set(this.actor.items.keys())
const dropParams = await RdDSheetUtility.prepareItemDropParameters(destItemId, this.actor, { uuid: item.uuid }, this.objetVersConteneur)
if (dropParams) {
const callSuper = await this.actor.processDropItem(dropParams)
if (callSuper) {
await super._onDropItem(event, item)
}
if (destItemId && dropParams.sourceActorId !== this.actor.id) {
const container = this.actor.items.get(destItemId)
if (container?.isConteneur()) {
const dropped = [...this.actor.items].find(i => !previousItemIds.has(i.id))
if (dropped && !this.actor.findConteneur(dropped)) {
await this.actor.ajouterDansConteneur(dropped, container, () => {})
}
}
}
}
}
async _onDropActor(event, actor) {
this.actor.addSubActeur(actor)
await super._onDropActor(event, actor)
}
//#endregion
//#region Helpers
_getItemId(event) {
return RdDSheetUtility.getItemId(event)
}
_getItem(event) {
return RdDSheetUtility.getItem(event, this.actor)
}
async _onItemEdit(event) {
const item = this._getItem(event)
if (item) item.sheet.render({ force: true })
}
async _onVerifyIngredients(event) {
event.preventDefault()
const item = this._getItem(event)
if (!item || item.type !== ITEM_TYPES.recettealchimique) return
DialogVerifierIngredients.create(item, this.actor)
}
async _onLancerRecette(event) {
event.preventDefault()
const item = this._getItem(event)
if (!item || item.type !== ITEM_TYPES.recettealchimique) return
const ingredients = Misc.safeJsonParse(item.system.ingredients, [])
if (!ingredients.length) {
ui.notifications.warn("Cette recette n'a pas d'ingrédients définis.")
return
}
DialogLancerRecette.create(item, this.actor)
}
//#endregion
//#region Actions
static async _onPostToChat(event, target) {
await this.actor.postActorToChat()
}
//#endregion
//#region Item operations
async _selectObjetTypeToCreate() {
const types = this._getTypesInventaire().sort(Misc.ascending(type => Misc.typeName("Item", type)))
let content = `<span class="generic-label">Selectionnez le type d'équipement</span><select class="item-type">`
for (const typeName of types) {
content += `<option value="${typeName}">${Misc.typeName("Item", typeName)}</option>`
}
content += "</select>"
new foundry.applications.api.DialogV2({
title: "Créer un équipement",
content,
buttons: [
{ action: "create", label: "Créer l'objet", icon: "fa-solid fa-check", callback: () => this.actor.createItem($(".item-type").val()) },
],
}).render({ force: true })
}
_getTypesInventaire() {
return RdDItem.getItemTypesInventaire()
}
async splitItem(item) {
const dialog = await DialogSplitItem.create(item, (it, split) => this._onSplitItem(it, split))
dialog.render({ force: true })
}
async _onSplitItem(item, split) {
if (split >= 1 && split < item.system.quantite) {
await item.diminuerQuantite(split)
const splitItem = foundry.utils.deepClone(item)
splitItem.system.quantite = split
await this.actor.createEmbeddedDocuments("Item", [splitItem])
}
}
vendre(item) {
item?.proposerVente(this.actor.getQuantiteDisponible(item))
}
//#endregion
}
@@ -0,0 +1,17 @@
import { TEMPLATE_DESCRIPTION } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
export default class RdDBlessureSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.blessure; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{ classes: ["fvtt-rdd", "item", "blessure"], position: { width: 448 }, window: { contentClasses: ["blessure-content"] } });
static PARTS = {
main: { template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/blessure.hbs" },
};
}
@@ -0,0 +1,23 @@
import { TEMPLATE_DESCRIPTION } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
export default class RdDCaseTMRSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.casetmr; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{
classes: ["fvtt-rdd", "item", "casetmr"],
position: { width: 448 },
window: { contentClasses: ["casetmr-content"] },
});
static PARTS = {
main: {
template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/casetmr.hbs",
},
};
}
@@ -0,0 +1,26 @@
import { TEMPLATE_DESCRIPTION } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
import { CommonAppreciable } from "../../common/appreciable.mjs";
const TEMPLATE_APPRECIABLE = new CommonAppreciable();
export default class RdDChantSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.chant; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION, TEMPLATE_APPRECIABLE]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{
classes: ["fvtt-rdd", "item", "chant"],
position: { width: 448 },
window: { contentClasses: ["chant-content"] },
});
static PARTS = {
main: {
template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/chant.hbs",
},
};
}
@@ -0,0 +1,52 @@
import { RdDBaseActorSheetV2 } from "./base-actor-sheet.mjs"
import { RdDItem } from "../../item.js"
import { ACTOR_TYPES } from "../../constants.js"
export class RdDCommerceSheetV2 extends RdDBaseActorSheetV2 {
static TYPE = ACTOR_TYPES.commerce
static DEFAULT_OPTIONS = Object.assign({}, RdDBaseActorSheetV2.DEFAULT_OPTIONS, {
position: { width: 600, height: 720 },
})
static PARTS = {
form: { template: "systems/foundryvtt-reve-de-dragon/templates/actor/commerce-actor-sheet.hbs" },
}
get title() {
if (this.actor.token && this.actor.token !== this.actor.prototypeToken) {
return this.actor.token.name
}
return super.title
}
async _prepareContext() {
const ctx = await super._prepareContext()
if (this.actor.token && this.actor.token !== this.actor.prototypeToken) {
foundry.utils.mergeObject(ctx, {
title: this.actor.token.name,
token: { img: this.actor.token.texture.src },
}, { overwrite: true })
}
return ctx
}
async _onRender(context, options) {
await super._onRender(context, options)
if (!this.isEditable) return
this.html.find("input.item-quantite").change(async event => {
const newQuantite = Math.max(0, Number.parseInt(this.html.find(event.currentTarget).val()))
await this._getItem(event)?.update({ "system.quantite": newQuantite })
})
this.html.find("input.item-cout").change(async event => {
const newCout = Math.max(0, Number(this.html.find(event.currentTarget).val()))
await this._getItem(event)?.update({ "system.cout": newCout })
})
}
_getTypesInventaire() {
return RdDItem.getItemTypesInventaire("all")
}
}
@@ -18,7 +18,7 @@ export default class RdDItemBaseSheet extends HandlebarsApplicationMixin(foundry
static register(sheetClass) { static register(sheetClass) {
const itemType = sheetClass.ITEM_TYPE const itemType = sheetClass.ITEM_TYPE
Items.registerSheet(SYSTEM_RDD, sheetClass, { foundry.documents.collections.Items.registerSheet(SYSTEM_RDD, sheetClass, {
label: Misc.typeName('Item', itemType), label: Misc.typeName('Item', itemType),
types: [itemType], types: [itemType],
makeDefault: true makeDefault: true
@@ -63,13 +63,15 @@ export default class RdDItemBaseSheet extends HandlebarsApplicationMixin(foundry
resizable: true, resizable: true,
}, },
actions: { actions: {
editImage: RdDItemBaseSheet.#onEditImage, editImage: RdDItemBaseSheet._onEditImage,
postToChat: RdDItemBaseSheet._onPostToChat,
proposerVente: RdDItemBaseSheet._onProposerVente,
} }
} }
/** @override */ /** @override */
async _prepareContext() { async _prepareContext() {
return { const ctx = {
item: this.document, item: this.document,
options: RdDSheetUtility.getOptions(this.document, this.isEditable), options: RdDSheetUtility.getOptions(this.document, this.isEditable),
fields: this.document.schema.fields, fields: this.document.schema.fields,
@@ -77,7 +79,12 @@ export default class RdDItemBaseSheet extends HandlebarsApplicationMixin(foundry
system: this.document.system, system: this.document.system,
source: this.document.toObject(), source: this.document.toObject(),
isEditable: this.isEditable, isEditable: this.isEditable,
canVendre: this.document.isInventaire?.() && this.document.isVideOuNonConteneur?.(),
} }
for (const tpl of this.constructor.TEMPLATES) {
Object.assign(ctx, await tpl.prepareContext(this.document))
}
return ctx
} }
// #region Actions // #region Actions
@@ -91,7 +98,7 @@ export default class RdDItemBaseSheet extends HandlebarsApplicationMixin(foundry
* @returns {Promise} * @returns {Promise}
* @private * @private
*/ */
static async #onEditImage(event, target) { static async _onEditImage(event, target) {
const attr = target.dataset.edit const attr = target.dataset.edit
const current = foundry.utils.getProperty(this.document, attr) const current = foundry.utils.getProperty(this.document, attr)
const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {} const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {}
@@ -107,5 +114,40 @@ export default class RdDItemBaseSheet extends HandlebarsApplicationMixin(foundry
}) })
return fp.browse() return fp.browse()
} }
static async _onPostToChat(event, target) {
await this.document.postItemToChat()
}
static async _onProposerVente(event, target) {
await this.document.proposerVente(1)
}
/** @override */
async _renderFrame(options) {
const frame = await super._renderFrame(options)
const header = frame.querySelector(".window-header")
if (header) {
const closeBtn = header.querySelector("button[data-action=close]")
const chatBtn = document.createElement("button")
chatBtn.type = "button"
chatBtn.className = "header-control icon fa-solid fa-comment"
chatBtn.dataset.action = "postToChat"
chatBtn.dataset.tooltip = "Poster dans le chat"
chatBtn.ariaLabel = "Poster dans le chat"
header.insertBefore(chatBtn, closeBtn)
if (this.document.isInventaire?.() && this.document.isVideOuNonConteneur?.()) {
const sellBtn = document.createElement("button")
sellBtn.type = "button"
sellBtn.className = "header-control icon fa-solid fa-comments-dollar"
sellBtn.dataset.action = "proposerVente"
sellBtn.dataset.tooltip = "Proposer à la vente"
sellBtn.ariaLabel = "Proposer à la vente"
header.insertBefore(sellBtn, chatBtn)
}
}
return frame
}
// #endregion // #endregion
} }
@@ -0,0 +1,17 @@
import { TEMPLATE_DESCRIPTION } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
export default class RdDCompetenceSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.competence; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{ classes: ["fvtt-rdd", "item", "competence"], position: { width: 448 }, window: { contentClasses: ["competence-content"] } });
static PARTS = {
main: { template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/competence.hbs" },
};
}
@@ -0,0 +1,17 @@
import { TEMPLATE_DESCRIPTION } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
export default class RdDCompetenceCreatureSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.competencecreature; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{ classes: ["fvtt-rdd", "item", "competencecreature"], position: { width: 448 }, window: { contentClasses: ["competencecreature-content"] } });
static PARTS = {
main: { template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/competencecreature.hbs" },
};
}
@@ -0,0 +1,31 @@
import { TEMPLATE_DESCRIPTION, TEMPLATE_INVENTAIRE } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
export default class RdDConteneurSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.conteneur; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION, TEMPLATE_INVENTAIRE]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{
classes: ["fvtt-rdd", "item", "conteneur"],
position: { width: 448 },
window: { contentClasses: ["conteneur-content"] },
});
static PARTS = {
main: {
template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/conteneur.hbs",
},
};
async _prepareContext() {
return Object.assign(
await super._prepareContext(),
{
actorId: this.document.parent?.id,
}
);
}
}
@@ -0,0 +1,33 @@
import { RdDBaseActorSheetV2 } from "./base-actor-sheet.mjs"
import { ACTOR_TYPES } from "../../constants.js"
export class RdDCreatureSheetV2 extends RdDBaseActorSheetV2 {
static TYPE = ACTOR_TYPES.creature
static DEFAULT_OPTIONS = Object.assign({}, RdDBaseActorSheetV2.DEFAULT_OPTIONS, {
position: { width: 640, height: 720 },
})
static PARTS = {
form: { template: "systems/foundryvtt-reve-de-dragon/templates/actor-creature-sheet.hbs" },
}
async _onRender(context, options) {
await super._onRender(context, options)
if (!this.isEditable) return
this.html.find(".creature-carac").change(async event => {
const compName = event.currentTarget.attributes.compname.value
await this.actor.updateCreatureCompetence(compName, "carac_value", parseInt(event.target.value))
})
this.html.find(".creature-niveau").change(async event => {
const compName = event.currentTarget.attributes.compname.value
await this.actor.updateCreatureCompetence(compName, "niveau", parseInt(event.target.value))
})
this.html.find(".creature-dommages").change(async event => {
const compName = event.currentTarget.attributes.compname.value
await this.actor.updateCreatureCompetence(compName, "dommages", parseInt(event.target.value))
})
}
}
@@ -0,0 +1,34 @@
import { TEMPLATE_DESCRIPTION } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
import { CommonAppreciable } from "../../common/appreciable.mjs";
const TEMPLATE_APPRECIABLE = new CommonAppreciable();
export default class RdDDanseSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.danse; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION, TEMPLATE_APPRECIABLE]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{
classes: ["fvtt-rdd", "item", "danse"],
position: { width: 448 },
window: { contentClasses: ["danse-content"] },
});
static PARTS = {
main: {
template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/danse.hbs",
},
};
async _prepareContext() {
return Object.assign(
await super._prepareContext(),
{
typeChoices: { recreative: "Récreative", soliste: "Soliste" },
}
);
}
}
@@ -0,0 +1,17 @@
import { TEMPLATE_DESCRIPTION } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
export default class RdDEmpoignadeSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.empoignade; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{ classes: ["fvtt-rdd", "item", "empoignade"], position: { width: 448 }, window: { contentClasses: ["empoignade-content"] } });
static PARTS = {
main: { template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/empoignade.hbs" },
};
}
@@ -0,0 +1,81 @@
import { RdDBaseActorSheetV2 } from "./base-actor-sheet.mjs"
import { RdDSheetUtility } from "../../rdd-sheet-utility.js"
import { RdDUtility } from "../../rdd-utility.js"
import { DialogSelect } from "../../dialog-select.js"
import { ACTOR_TYPES } from "../../constants.js"
export class RdDActorEntiteSheetV2 extends RdDBaseActorSheetV2 {
static TYPE = ACTOR_TYPES.entite
static DEFAULT_OPTIONS = Object.assign({}, RdDBaseActorSheetV2.DEFAULT_OPTIONS, {
position: { width: 640, height: 720 },
})
static PARTS = {
form: { template: "systems/foundryvtt-reve-de-dragon/templates/actor-entite-sheet.hbs" },
}
async _prepareContext() {
const ctx = await super._prepareContext()
ctx.niveau = this.actor.getNiveau()
const { niveau: _, ...caracRest } = ctx.system.carac
ctx.system = { ...ctx.system, carac: caracRest }
ctx.resonances = this.actor.system.sante.resonnance.actors
.map(actorId => game.actors.get(actorId))
.filter(actor => actor != undefined)
.map(actor => ({ id: actor.id, name: actor.name, img: actor.img }))
return ctx
}
async _onRender(context, options) {
await super._onRender(context, options)
if (!this.isEditable) return
this.html.find(".creature-carac").change(async event => {
const compName = event.currentTarget.attributes.compname.value
await this.actor.updateCreatureCompetence(compName, "carac_value", parseInt(event.target.value))
})
this.html.find(".creature-dommages").change(async event => {
const compName = event.currentTarget.attributes.compname.value
await this.actor.updateCreatureCompetence(compName, "dommages", parseInt(event.target.value))
})
this.html.find(".resonance-add").click(async event =>
await DialogSelect.select({
label: "Choisir un acteur à accorder",
list: game.actors.filter(it => true),
},
it => this._resonanceAdd(it.id))
)
this.html.find(".resonance-delete").click(async event => {
const li = RdDSheetUtility.getEventElement(event)
const actorId = li.data("actor-id")
if (actorId) {
const actorResonance = game.actors.get(actorId)
RdDUtility.confirmSubActeurDelete(this, actorResonance, li, () => {
this._resonanceDelete(actorId)
RdDUtility.slideOnDelete(this, li)
})
}
})
}
async _onDropActor(event, dragData) {
const dropActor = fromUuidSync(dragData.uuid)
await this.actor.setEntiteReveAccordee(dropActor)
await super._onDropActor(event, dragData)
}
async _resonanceAdd(actorId) {
const newResonances = [...this.actor.system.sante.resonnance.actors, actorId]
await this.actor.update({ "system.sante.resonnance.actors": newResonances })
}
async _resonanceDelete(actorId) {
console.log("Delete : ", actorId)
const newResonances = this.actor.system.sante.resonnance.actors.filter(id => id !== actorId)
await this.actor.update({ "system.sante.resonnance.actors": newResonances }, { render: false })
}
}
@@ -0,0 +1,118 @@
import { RdDActorSheetV2 } from "./personnage-sheet.mjs"
import { SYSTEM_RDD } from "../../constants.js"
import { Misc } from "../../misc.js"
import { EXPORT_CSV_SCRIPTARIUM, OptionsAvancees } from "../../settings/options-avancees.js"
import { ExportScriptarium } from "../../actor/export-scriptarium/export-scriptarium.js"
import { CATEGORIES_COMPETENCES_BASE, CATEGORIES_DRACONIC, Mapping } from "../../actor/export-scriptarium/mapping.js"
export class RdDActorExportSheetV2 extends RdDActorSheetV2 {
static TYPE = "personnage"
static DEFAULT_OPTIONS = Object.assign({}, RdDActorSheetV2.DEFAULT_OPTIONS, {
position: { width: 550 },
})
static PARTS = {
form: { template: "systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/actor-encart-sheet.hbs" },
}
showCompNiveauBase = false
vueArchetype = false
static async init() {
await foundry.applications.handlebars.loadTemplates([
"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/arme.hbs",
"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/arme-titre.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",
])
}
static register() {
foundry.documents.collections.Actors.registerSheet(SYSTEM_RDD, this, {
types: ["personnage"],
makeDefault: false,
label: "Feuille simplifiée",
})
}
async _prepareContext() {
const ctx = await super._prepareContext()
ctx.context = Mapping.prepareContext(this.actor)
ctx.attaques = this.actor.listActionsAttaque()
ctx.export = this._getMappingValues(ctx.context, this.actor)
ctx.competences = this._getCompetences(CATEGORIES_COMPETENCES_BASE)
ctx.draconic = this._getCompetences(CATEGORIES_DRACONIC)
const legeres = this.actor.countBlessures(it => it.isLegere())
const graves = this.actor.countBlessures(it => it.isGrave())
const critiques = this.actor.countBlessures(it => it.isCritique())
ctx.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],
}
ctx.options.exportScriptarium = OptionsAvancees.isUsing(EXPORT_CSV_SCRIPTARIUM)
return ctx
}
_getMappingValues(context, actor) {
return Object.fromEntries(Mapping.getMapping().map(it => [it.column, {
colName: it.colName ?? it.column,
column: it.column,
rollClass: it.rollClass,
value: String(it.getter(actor, context)),
}]))
}
_getCompetences(categories) {
const competences = Mapping.getCompetencesCategorie(this.actor, categories)
if (competences.length == 0) return ""
const byCategories = Mapping.competencesByCategoriesByNiveau(competences, categories)
const listByCategories = Object.values(byCategories)
.map(it => it.competencesParNiveau)
.map(byNiveau => {
const niveaux = Object.keys(byNiveau).map(it => Number(it)).sort(Misc.ascending())
if (niveaux.length == 0) return undefined
const listCategorieByNiveau = niveaux.map(niveau => {
const list = byNiveau[niveau].sort(Misc.ascending(it => it.name))
return { niveau, list }
})
return Misc.concat(listCategorieByNiveau)
}).filter(it => it != undefined)
return Misc.concat(listByCategories)
}
async _onRender(context, options) {
await super._onRender(context, options)
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 => {
const blessure = this.actor.nouvelleBlessure(this.html.find(event.currentTarget).data("gravite"))
await this.actor.createEmbeddedDocuments("Item", [blessure])
})
this.html.find(".button-export").click(async event => await
ExportScriptarium.INSTANCE.exportActors([this.actor],
`${this.actor.uuid}-${this.actor.name}`
)
)
}
}
@@ -0,0 +1,21 @@
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
export default class RdDExtraitPoetiqueSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.extraitpoetique; }
static get TEMPLATES() { return []; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{
classes: ["fvtt-rdd", "item", "extraitpoetique"],
position: { width: 448 },
window: { contentClasses: ["extraitpoetique-content"] },
});
static PARTS = {
main: {
template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/extraitpoetique.hbs",
},
};
}
@@ -0,0 +1,23 @@
import { TEMPLATE_DESCRIPTION, TEMPLATE_INVENTAIRE, TEMPLATE_COMESTIBLE } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
export default class RdDFauneSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.faune; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION, TEMPLATE_INVENTAIRE, TEMPLATE_COMESTIBLE]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{
classes: ["fvtt-rdd", "item", "faune"],
position: { width: 448 },
window: { contentClasses: ["faune-content"] },
});
static PARTS = {
main: {
template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/faune.hbs",
},
};
}
@@ -0,0 +1,64 @@
import { TEMPLATE_DESCRIPTION, TEMPLATE_INVENTAIRE } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import { RdDItemGemme } from "../../item/gemme.js";
import { RdDTimestamp } from "../../time/rdd-timestamp.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
export default class RdDGemmeSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.gemme; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION, TEMPLATE_INVENTAIRE]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{
classes: ["fvtt-rdd", "item", "gemme"],
position: { width: 448 },
window: { contentClasses: ["gemme-content"] },
actions: {
enchanter: RdDGemmeSheet.#onEnchanter,
},
});
static PARTS = {
main: {
template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/gemme.hbs",
},
};
async _prepareContext() {
const item = this.document
const enchantement = RdDTimestamp.splitIndexDate(item.system.prdate)
return Object.assign(
await super._prepareContext(),
{
gemmeTypeList: RdDItemGemme.getGemmeTypeOptionList(),
isEnchantementPossible: item.isEnchantementPossible,
dateActuelle: game.system.rdd.calendrier.dateCourante(),
enchantement,
}
);
}
_onChangeForm(event, formData) {
const obj = formData?.object
if (!obj) return super._onChangeForm(event, formData)
if (event.target?.classList?.contains('date-enchantement')) {
const jour = Number(this.element?.querySelector('input.date-enchantement[name="enchantement.jour"]')?.value)
const moisEl = this.element?.querySelector('select.date-enchantement[name="enchantement.mois"]')
if (moisEl) {
const mois = RdDTimestamp.definition(moisEl.value)
const indexDate = game.system.rdd.calendrier.getIndexFromDate(jour, mois.heure)
obj['system.prdate'] = indexDate
delete obj['enchantement.jour']
delete obj['enchantement.mois']
}
}
return super._onChangeForm(event, formData)
}
static async #onEnchanter(event, target) {
const { DialogEnchanter } = await import("../../enchantement/dialog-enchanter.js")
DialogEnchanter.enchanter(this.document)
}
}
@@ -0,0 +1,42 @@
import { TEMPLATE_DESCRIPTION, TEMPLATE_INVENTAIRE, TEMPLATE_COMESTIBLE } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
export default class RdDHerbeSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.herbe; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION, TEMPLATE_INVENTAIRE, TEMPLATE_COMESTIBLE]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{
classes: ["fvtt-rdd", "item", "herbe"],
position: { width: 448 },
window: { contentClasses: ["herbe-content"] },
});
static PARTS = {
main: {
template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/herbe.hbs",
},
};
async _prepareContext() {
const ctx = await super._prepareContext()
ctx.isBrut = this.document.getUtilisationCuisine() === 'brut'
return ctx
}
async _onRender(context, options) {
await super._onRender(context, options)
this.element.querySelector('.preparer-nourriture')?.addEventListener('click', async () => {
const actor = this.document.parent
if (actor) await actor.preparerNourriture(this.document)
})
this.element.querySelector('.manger-nourriture')?.addEventListener('click', async () => {
const actor = this.document.parent
if (actor) await actor.mangerNourriture(this.document)
})
}
}
@@ -0,0 +1,42 @@
import { TEMPLATE_DESCRIPTION, TEMPLATE_INVENTAIRE, TEMPLATE_COMESTIBLE } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
export default class RdDIngredientSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.ingredient; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION, TEMPLATE_INVENTAIRE, TEMPLATE_COMESTIBLE]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{
classes: ["fvtt-rdd", "item", "ingredient"],
position: { width: 448 },
window: { contentClasses: ["ingredient-content"] },
});
static PARTS = {
main: {
template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/ingredient.hbs",
},
};
async _prepareContext() {
const ctx = await super._prepareContext()
ctx.isBrut = this.document.getUtilisationCuisine() === 'brut'
return ctx
}
async _onRender(context, options) {
await super._onRender(context, options)
this.element.querySelector('.preparer-nourriture')?.addEventListener('click', async () => {
const actor = this.document.parent
if (actor) await actor.preparerNourriture(this.document)
})
this.element.querySelector('.manger-nourriture')?.addEventListener('click', async () => {
const actor = this.document.parent
if (actor) await actor.mangerNourriture(this.document)
})
}
}
+34
View File
@@ -0,0 +1,34 @@
import { TEMPLATE_DESCRIPTION } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
import { CommonAppreciable } from "../../common/appreciable.mjs";
const TEMPLATE_APPRECIABLE = new CommonAppreciable();
export default class RdDJeuSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.jeu; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION, TEMPLATE_APPRECIABLE]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{
classes: ["fvtt-rdd", "item", "jeu"],
position: { width: 448 },
window: { contentClasses: ["jeu-content"] },
});
static PARTS = {
main: {
template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/jeu.hbs",
},
};
async _prepareContext() {
return Object.assign(
await super._prepareContext(),
{
typeChoices: { adressehasard: "Adresse/Hasard", de: "Dés", carte: "Cartes", reflexion: "Réflexion" },
}
);
}
}
@@ -0,0 +1,23 @@
import { TEMPLATE_DESCRIPTION, TEMPLATE_INVENTAIRE } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
export default class RdDLivreSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.livre; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION, TEMPLATE_INVENTAIRE]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{
classes: ["fvtt-rdd", "item", "livre"],
position: { width: 448 },
window: { contentClasses: ["livre-content"] },
});
static PARTS = {
main: {
template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/livre.hbs",
},
};
}
@@ -0,0 +1,17 @@
import { TEMPLATE_DESCRIPTION } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
export default class RdDMaladieSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.maladie; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{ classes: ["fvtt-rdd", "item", "maladie"], position: { width: 448 }, window: { contentClasses: ["maladie-content"] } });
static PARTS = {
main: { template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/maladie.hbs" },
};
}
@@ -0,0 +1,23 @@
import { TEMPLATE_DESCRIPTION } from "../../common/_module.mjs";
import { ITEM_TYPES } from "../../constants.js";
import RdDItemBaseSheet from "./common-item-sheet.mjs";
export default class RdDMeditationSheet extends RdDItemBaseSheet {
static get ITEM_TYPE() { return ITEM_TYPES.meditation; }
static get TEMPLATES() { return [TEMPLATE_DESCRIPTION]; }
static DEFAULT_OPTIONS = Object.assign({},
RdDItemBaseSheet.DEFAULT_OPTIONS,
{
classes: ["fvtt-rdd", "item", "meditation"],
position: { width: 448 },
window: { contentClasses: ["meditation-content"] },
});
static PARTS = {
main: {
template: "systems/foundryvtt-reve-de-dragon/templates/sheets/item/meditation.hbs",
},
};
}
@@ -24,12 +24,4 @@ export default class RdDMonnaieSheet extends RdDItemBaseSheet {
}, },
} }
/** @override */
async _prepareContext() {
return Object.assign(
await super._prepareContext(),
await TEMPLATE_DESCRIPTION.prepareContext(this.document),
await TEMPLATE_INVENTAIRE.prepareContext(this.document)
)
}
} }
@@ -23,12 +23,4 @@ export default class RdDMunitionSheet extends RdDItemBaseSheet {
}, },
} }
/** @override */
async _prepareContext() {
return Object.assign(
await super._prepareContext(),
await TEMPLATE_DESCRIPTION.prepareContext(this.document),
await TEMPLATE_INVENTAIRE.prepareContext(this.document)
)
}
} }

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