Compare commits
87 Commits
cddc4ce48a
..
14.0.4
| Author | SHA1 | Date | |
|---|---|---|---|
| 5eeffd5e24 | |||
| dd0276e7e1 | |||
| d1fbe611ef | |||
| 5a30ffb00f | |||
| 3aefdeb42e | |||
| 939247e731 | |||
| 6dea5ba479 | |||
| 494ec3ea84 | |||
| cb4b255b35 | |||
| 116ac66a8a | |||
| b4a4737d5b | |||
| 4c33607b2b | |||
| 0d4bd37f30 | |||
| 0aefe8bea8 | |||
| 083b02ff96 | |||
| 59fa8c72ff | |||
| 4f675cb5c1 | |||
| d1c7c74085 | |||
| c5628586f4 | |||
| 13c0d801c3 | |||
| fc24b94784 | |||
| 29e9230422 | |||
| fc1be1513a | |||
| 5c66c29d24 | |||
| b34857325d | |||
| 51a4df73ab | |||
| c0bc37e32a | |||
| 92ea0164a2 | |||
| 0425ccf723 | |||
| 37ff6ebf1d | |||
| 1c73faeb00 | |||
| 0c42b6ab34 | |||
| d6e7b62c31 | |||
| a3f304c77b | |||
| 1b81b0a3ac | |||
| 3ff2b8e9bb | |||
| 38525c3257 | |||
| f035bcfae2 | |||
| a8bf356d20 | |||
| cd70b70088 | |||
| 14763cc5b3 | |||
| 0258c2e8b7 | |||
| 9b3d34c5d7 | |||
| 335238df3d | |||
| a1519e7a60 | |||
| e55b5cbe15 | |||
| f28719fc6f | |||
| d0423b2017 | |||
| 156672d853 | |||
| 5ab03920d6 | |||
| 9dd6fbd2e7 | |||
| 76ed974352 | |||
| c65a55225d | |||
| adc104b757 | |||
| 2e14c70a02 | |||
| 2d5b844796 | |||
| c62131ac97 | |||
| cc92f5a418 | |||
| b877262283 | |||
| d120da6718 | |||
| ef609136e2 | |||
| d9e770a250 | |||
| 7c500f3ef4 | |||
| 56b58565d1 | |||
| 1fb80f6abe | |||
| e001ec0dc9 | |||
| b255a628fe | |||
| f0969c9eb4 | |||
| e67ecd9238 | |||
| 21c3c5b88b | |||
| 0ce42d0fe3 | |||
| 28cab15b37 | |||
| c28414dd22 | |||
| 9754bbc3a8 | |||
| 23b105a47e | |||
| faf452f797 | |||
| 9fd13b2615 | |||
| 7a2a3df391 | |||
| a11b3495a5 | |||
| a37ad2cc82 | |||
| 4def580296 | |||
| f5a5c25533 | |||
| a39d214f1b | |||
| 2fac292459 | |||
| 3c776f85fd | |||
| 40e11aca1b | |||
| 2dc7665a25 |
@@ -0,0 +1,66 @@
|
||||
name: Release Creation
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "💡 The ${{ gitea.repository }} repository will be cloned to the runner."
|
||||
|
||||
- uses: RouxAntoine/checkout@v3.5.4
|
||||
|
||||
# get part of the tag after the `v`
|
||||
- name: Extract tag version number
|
||||
id: get_version
|
||||
uses: battila7/get-version-action@v2
|
||||
|
||||
# Substitute the Manifest and Download URLs in the system.json
|
||||
- name: Substitute Manifest and Download Links For Versioned Ones
|
||||
id: sub_manifest_link_version
|
||||
uses: microsoft/variable-substitution@v1
|
||||
with:
|
||||
files: "system.json"
|
||||
env:
|
||||
version: ${{steps.get_version.outputs.version-without-v}}
|
||||
url: https://www.uberwald.me/gitea/${{gitea.repository}}
|
||||
manifest: https://www.uberwald.me/gitea/public/fvtt-mournblade-cyd-2-0/releases/download/latest/system.json
|
||||
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-mournblade-cyd-2-0.zip
|
||||
|
||||
# Build CSS from LESS sources before packaging
|
||||
- name: Install Node.js dependencies and build styles
|
||||
run: |
|
||||
apt update -y
|
||||
apt install -y nodejs npm zip
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
# Create a zip file with all files required by the system
|
||||
- run: zip -r ./fvtt-mournblade-cyd-2-0.zip system.json README.md LICENCE.txt assets/ lang/ modules/ packs/ styles/ templates/
|
||||
|
||||
- name: Setup Go
|
||||
uses: https://github.com/actions/setup-go@v4
|
||||
with:
|
||||
go-version: ">=1.20.1"
|
||||
|
||||
- name: Upload release assets
|
||||
id: use-go-action
|
||||
uses: https://gitea.com/actions/release-action@main
|
||||
with:
|
||||
files: |-
|
||||
./fvtt-mournblade-cyd-2-0.zip
|
||||
system.json
|
||||
api_key: "${{secrets.ALLOW_PUSH_RELEASE}}"
|
||||
|
||||
# - name: Publish to Foundry server
|
||||
# uses: https://github.com/djlechuck/foundryvtt-publish-package-action@v1
|
||||
# with:
|
||||
# token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
|
||||
# id: 'fvtt-mournblade-cyd-2-0'
|
||||
# version: ${{github.event.release.tag_name}}
|
||||
# manifest: 'https://www.uberwald.me/gitea/public/fvtt-mournblade-cyd-2-0/releases/download/latest/system.json'
|
||||
# notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-mournblade-cyd-2-0.zip'
|
||||
# compatibility-minimum: '14'
|
||||
# compatibility-verified: '14'
|
||||
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
styles/mournblade-cyd2.css.map
|
||||
package-lock.json
|
||||
.github/
|
||||
.history/
|
||||
@@ -0,0 +1,812 @@
|
||||
# Corrections apportées au module Mournblade CYD 2.0
|
||||
|
||||
## Date : 2026-06-07
|
||||
|
||||
## Dernière mise à jour : 2026-06-07
|
||||
|
||||
## Nouveautés et Améliorations (2026-06-07)
|
||||
|
||||
## Problèmes identifiés et corrigés
|
||||
|
||||
### 1. ❌ Erreur de chargement des partials Handlebars
|
||||
|
||||
**Problème :**
|
||||
Les feuilles de personnage et de créature généraient une erreur lors du rendu :
|
||||
```
|
||||
Failed to render template part "sheet":
|
||||
The partial systems/fvtt-mournblade-cyd-2-0/templates/partial-active-effects.hbs could not be found
|
||||
```
|
||||
|
||||
**Cause :**
|
||||
La fonction `preloadHandlebarsTemplates()` dans `modules/mournblade-cyd2-utility.js` ne préchargeait pas tous les partials nécessaires. Seuls 7 templates étaient préchargés sur 9 utilisés.
|
||||
|
||||
**Partials manquants :**
|
||||
- `partial-active-effects.hbs` - Utilisé dans les feuilles actor-sheet.hbs et creature-sheet.hbs
|
||||
- `partial-item-effects.hbs` - Utilisé dans de nombreux templates d'items
|
||||
|
||||
**Solution :**
|
||||
Ajout des deux partials manquants à la liste des templates préchargés dans la fonction `preloadHandlebarsTemplates()`.
|
||||
|
||||
**Fichier modifié :**
|
||||
- `modules/mournblade-cyd2-utility.js` (lignes 189-201)
|
||||
|
||||
**Code avant :**
|
||||
```javascript
|
||||
const templatePaths = [
|
||||
'systems/fvtt-mournblade-cyd-2-0/templates/editor-notes-gm.hbs',
|
||||
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-header.hbs',
|
||||
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-description.hbs',
|
||||
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-nav.hbs',
|
||||
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-prix.hbs',
|
||||
'systems/fvtt-mournblade-cyd-2-0/templates/partial-automation.hbs',
|
||||
'systems/fvtt-mournblade-cyd-2-0/templates/hud-adversites.hbs',
|
||||
]
|
||||
```
|
||||
|
||||
**Code après :**
|
||||
```javascript
|
||||
const templatePaths = [
|
||||
'systems/fvtt-mournblade-cyd-2-0/templates/editor-notes-gm.hbs',
|
||||
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-header.hbs',
|
||||
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-description.hbs',
|
||||
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-nav.hbs',
|
||||
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-prix.hbs',
|
||||
'systems/fvtt-mournblade-cyd-2-0/templates/partial-automation.hbs',
|
||||
'systems/fvtt-mournblade-cyd-2-0/templates/partial-active-effects.hbs', // ✅ Ajouté
|
||||
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-effects.hbs', // ✅ Ajouté
|
||||
'systems/fvtt-mournblade-cyd-2-0/templates/hud-adversites.hbs',
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. ❌ Erreur de création d'effet actif
|
||||
|
||||
**Problème :**
|
||||
```
|
||||
base-actor-sheet.mjs:357 MournbladeCYD2 | Failed to create effect: TypeError:
|
||||
Cannot read properties of undefined (reading 'create')
|
||||
```
|
||||
|
||||
**Cause :**
|
||||
La fonction `ActiveEffectDialog.create()` n'existe pas dans Foundry VTT v14. L'API a changé et cette méthode a été supprimée.
|
||||
|
||||
**Solution :**
|
||||
Remplacement de l'appel à `foundry.applications.api.ActiveEffectDialog.create()` par une création directe via `document.createEmbeddedDocuments("ActiveEffect", [data])`, suivie de l'ouverture de la feuille d'édition.
|
||||
|
||||
**Fichiers modifiés :**
|
||||
- `modules/applications/sheets/base-actor-sheet.mjs` (lignes 328-363)
|
||||
- `modules/applications/sheets/base-item-sheet.mjs` (lignes 189-224)
|
||||
|
||||
**Code avant :**
|
||||
```javascript
|
||||
const effect = await foundry.applications.api.ActiveEffectDialog.create({
|
||||
document: this.document,
|
||||
effect: defaultEffectData
|
||||
});
|
||||
|
||||
if (effect) {
|
||||
await this.document.createEmbeddedDocuments("ActiveEffect", [effect.toObject()]);
|
||||
}
|
||||
```
|
||||
|
||||
**Code après :**
|
||||
```javascript
|
||||
const [effect] = await this.document.createEmbeddedDocuments("ActiveEffect", [defaultEffectData]);
|
||||
|
||||
if (effect) {
|
||||
effect.sheet.render(true);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. ❌ Boucle infinie de chargement d'icône (effect.webp introuvable) ✅
|
||||
|
||||
**Problème :**
|
||||
```
|
||||
404 (Not Found) - GET https://localhost:31000/systems/fvtt-mournblade-cyd-2-0/assets/icons/effect.webp
|
||||
Boucle infinie de tentatives de chargement
|
||||
```
|
||||
|
||||
**Cause :**
|
||||
L'icône `effect.webp` était référencée dans plusieurs fichiers mais n'existait pas dans le dossier `assets/icons/`. Chaque fois que la dialog de création d'effet s'ouvrait, le navigateur essayait de charger cette image manquante en boucle.
|
||||
|
||||
**Fichiers concernés :**
|
||||
- `modules/applications/sheets/base-actor-sheet.mjs` (ligne 336)
|
||||
- `modules/applications/sheets/base-item-sheet.mjs` (ligne 197)
|
||||
- `modules/mournblade-cyd2-effects.js` (lignes 120, 180)
|
||||
- `templates/partial-active-effects.hbs` (ligne 30)
|
||||
- `templates/partial-item-effects.hbs` (ligne 30)
|
||||
|
||||
**Solution :**
|
||||
Remplacement de toutes les références à `effect.webp` par `capacite.webp`, une icône existante dans le dossier `assets/icons/`.
|
||||
|
||||
**Code avant :**
|
||||
```javascript
|
||||
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/effect.webp"
|
||||
```
|
||||
|
||||
**Code après :**
|
||||
```javascript
|
||||
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/capacite.webp"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. ❌ Propriété dépréciée ActiveEffectDuration.type ✅
|
||||
|
||||
**Problème :**
|
||||
```
|
||||
foundry.mjs:1555 Error: You are accessing ActiveEffectDuration#type,
|
||||
which is now at ActiveEffectDuration#units.
|
||||
Deprecated since Version 14
|
||||
Backwards-compatible support will be removed in Version 16
|
||||
```
|
||||
|
||||
**Cause :**
|
||||
En Foundry VTT v14, la propriété `duration.type` a été renommée en `duration.units`. L'ancien nom était encore supporté pour la compatibilité, mais générait des avertissements et sera supprimé en v16.
|
||||
|
||||
**Fichiers concernés :**
|
||||
- `templates/partial-active-effects.hbs` (lignes 55-61)
|
||||
- `templates/partial-item-effects.hbs` (lignes 51-56)
|
||||
|
||||
**Solution :**
|
||||
Remplacement de toutes les occurrences de `effect.duration.type` par `effect.duration.units` dans les templates.
|
||||
|
||||
**Code avant :**
|
||||
```handlebars
|
||||
{{#if effect.duration.type}}
|
||||
{{#if (eq effect.duration.type "rounds")}}🔄{{/if}}
|
||||
{{#if (eq effect.duration.type "turns")}}🎭{{/if}}
|
||||
{{/if}}
|
||||
```
|
||||
|
||||
**Code après :**
|
||||
```handlebars
|
||||
{{#if effect.duration.units}}
|
||||
{{#if (eq effect.duration.units "rounds")}}🔄{{/if}}
|
||||
{{#if (eq effect.duration.units "turns")}}🎭{{/if}}
|
||||
{{/if}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. ❌ Helper Handlebars "subtract" manquant ✅
|
||||
|
||||
**Problème :**
|
||||
```
|
||||
Failed to render Application "MournbladeCYD2PersonnageSheet":
|
||||
Missing helper: "subtract"
|
||||
```
|
||||
|
||||
**Cause :**
|
||||
Le template utilisait le helper `subtract` dans `{{#unless (eq index (subtract effect.changes.length 1))}}` mais ce helper n'était pas enregistré dans Handlebars.
|
||||
|
||||
**Fichiers concernés :**
|
||||
- `templates/partial-active-effects.hbs` (ligne 44)
|
||||
- `templates/partial-item-effects.hbs` (ligne 44)
|
||||
- `modules/mournblade-cyd2-utility.js` (helper non enregistré)
|
||||
|
||||
**Solution :**
|
||||
Ajout du helper `subtract` dans la méthode `init()` de `MournbladeCYD2Utility` :
|
||||
|
||||
**Code ajouté :**
|
||||
```javascript
|
||||
Handlebars.registerHelper('subtract', function (a, b) {
|
||||
return parseInt(a) - parseInt(b);
|
||||
});
|
||||
```
|
||||
|
||||
**Fonctionnalité :**
|
||||
Le helper permet de soustraire deux nombres dans les templates Handlebars, utilisé pour détecter le dernier élément d'une liste.
|
||||
|
||||
---
|
||||
|
||||
### 6. ❌ Clés i18n manquantes pour les effets ✅
|
||||
|
||||
**Problème :**
|
||||
Les clés de localisation pour les messages d'erreur des effets actifs étaient manquantes dans `lang/fr.json`, ce qui pouvait entraîner l'affichage de messages en anglais ou vides.
|
||||
|
||||
**Clés manquantes identifiées :**
|
||||
- `MOURNBLADECYD2.EFFECT.createError`
|
||||
- `MOURNBLADECYD2.EFFECT.deleteError`
|
||||
- `MOURNBLADECYD2.EFFECT.applyError`
|
||||
- `MOURNBLADECYD2.EFFECT.applyItemError`
|
||||
- `MOURNBLADECYD2.EFFECT.selectActor`
|
||||
- `MOURNBLADECYD2.EFFECT.toggleError`
|
||||
|
||||
**Solution :**
|
||||
Ajout de toutes les clés manquantes dans la section `EFFECT` du fichier `lang/fr.json`.
|
||||
|
||||
**Traductions ajoutées :**
|
||||
```json
|
||||
{
|
||||
"createError": "Erreur lors de la création de l'effet",
|
||||
"deleteError": "Erreur lors de la suppression de l'effet",
|
||||
"applyError": "Erreur lors de l'application de l'effet",
|
||||
"applyItemError": "Erreur lors de l'application de l'effet sur l'item",
|
||||
"selectActor": "Sélectionnez un acteur pour appliquer l'effet",
|
||||
"toggleError": "Erreur lors de l'activation/désactivation de l'effet"
|
||||
}
|
||||
```
|
||||
|
||||
**Fichier modifié :** `lang/fr.json`
|
||||
|
||||
---
|
||||
|
||||
### 7. ❌ Clés i18n MNBL manquantes ✅
|
||||
|
||||
**Problème :**
|
||||
Les clés de localisation `MNBL.details` et `MNBL.description` étaient manquantes dans `lang/fr.json`, ce qui entraînait l'affichage de la clé elle-même au lieu d'une traduction.
|
||||
|
||||
**Clés manquantes identifiées :**
|
||||
- `MNBL.details` - Utilisée dans l'onglet "Détails" des fiches d'items
|
||||
- `MNBL.description` - Utilisée dans l'onglet "Description" des fiches d'items
|
||||
|
||||
**Solution :**
|
||||
Ajout des deux clés manquantes dans la section `MNBL` du fichier `lang/fr.json`.
|
||||
|
||||
**Traductions ajoutées :**
|
||||
```json
|
||||
{
|
||||
"details": "Détails",
|
||||
"description": "Description"
|
||||
}
|
||||
```
|
||||
|
||||
**Fichier modifié :** `lang/fr.json`
|
||||
|
||||
---
|
||||
|
||||
### 8. ❌ Onglet "Effets" manquant dans les fiches d'items ✅
|
||||
|
||||
**Problème :**
|
||||
L'onglet "Effets" n'apparaissait pas dans les fiches d'items, empêchant l'accès à la gestion des effets actifs sur les items.
|
||||
|
||||
**Cause :**
|
||||
Dans `templates/partial-item-nav.hbs`, l'onglet "Effets" n'était affiché que si l'item avait déjà des effets (`{{#if item.effects.length}}`).
|
||||
|
||||
**Solution :**
|
||||
Suppression de la condition pour toujours afficher l'onglet "Effets", même lorsque l'item n'a pas encore d'effets actifs.
|
||||
|
||||
**Fichier modifié :** `templates/partial-item-nav.hbs`
|
||||
|
||||
**Code avant :**
|
||||
```handlebars
|
||||
{{#if item.effects.length}}
|
||||
<a class="item" data-tab="effects" ...>{{localize "MOURNBLADECYD2.EFFECT.activeEffects"}}</a>
|
||||
{{/if}}
|
||||
```
|
||||
|
||||
**Code après :**
|
||||
```handlebars
|
||||
<a class="item" data-tab="effects" ...>{{localize "MOURNBLADECYD2.EFFECT.activeEffects"}}</a>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9. ❌ Erreur de parsing JSON (historique)
|
||||
|
||||
**Problème mentionné :**
|
||||
```
|
||||
SyntaxError: Expected ',' or '}' after property value in JSON at position 3753 (line 118 column 4)
|
||||
```
|
||||
|
||||
**Statut :**
|
||||
Cette erreur concernait probablement une ancienne version du fichier `lang/fr.json`. Le fichier actuel est valide et ne contient pas d'erreur de syntaxe.
|
||||
|
||||
**Vérification :**
|
||||
```bash
|
||||
# Le fichier passe la validation JSON
|
||||
node -e "require('./lang/fr.json')" # ✅ Pas d'erreur
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Nouveautés et Améliorations (2026-06-07)
|
||||
|
||||
### 10. ✅ Amélioration du popup de bienvenue
|
||||
|
||||
**Modification :**
|
||||
Simplification de la mention des droits dans le popup de bienvenue.
|
||||
|
||||
**Avant :**
|
||||
```
|
||||
Mournblade est un jeu publié par Titam France / Sombres Projets, tous les droits leur appartiennent.
|
||||
```
|
||||
|
||||
**Après :**
|
||||
```
|
||||
Mournblade est un jeu Titam.
|
||||
```
|
||||
|
||||
**Fichier modifié :** `modules/mournblade-cyd2-main.js` (ligne 141)
|
||||
|
||||
---
|
||||
|
||||
### 11. ✅ Ajout du coût en Pouvoir pour les invocations en cours
|
||||
|
||||
**Nouveauté :**
|
||||
Ajout d'un champ numérique pour suivre le coût en Pouvoir des invocations en cours dans l'onglet Sorcellerie.
|
||||
|
||||
**Fichiers modifiés :**
|
||||
- `modules/models/personnage.mjs` - Ajout du champ `coutPouvoirInvocations` dans le schéma sorcellerie
|
||||
- `lang/fr.json` - Ajout de la clé `SORCELLERIE.coutPouvoirInvocations`
|
||||
- `templates/actor-sheet.hbs` - Ajout du champ dans le template
|
||||
|
||||
**Clé i18n ajoutée :**
|
||||
```json
|
||||
{
|
||||
"coutPouvoirInvocations": "Coût en Pouvoir des invocations"
|
||||
}
|
||||
```
|
||||
|
||||
**Schémas modifié :**
|
||||
```javascript
|
||||
sorcellerie: new fields.SchemaField({
|
||||
runes: new fields.HTMLField({ initial: "" }),
|
||||
creaturesinvoquees: new fields.HTMLField({ initial: "" }),
|
||||
demonslies: new fields.HTMLField({ initial: "" }),
|
||||
enchantements: new fields.HTMLField({ initial: "" }),
|
||||
invocationsencours: new fields.HTMLField({ initial: "" }),
|
||||
coutPouvoirInvocations: new fields.NumberField({ initial: 0, integer: true }) // ✅ Ajouté
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 12. ✅ Amélioration de l'onglet Détails des Profils
|
||||
|
||||
**Problème :**
|
||||
Les sections de texte (Compétences exercées, Talents Initié, etc.) avaient un rendu peu esthétique.
|
||||
|
||||
**Solution :**
|
||||
Remplacement des sections de texte simples par des `div class="sheet-box"` pour un meilleur rendu visuel.
|
||||
|
||||
**Fichier modifié :** `templates/item-profil-sheet.hbs`
|
||||
|
||||
**Avant :**
|
||||
```handlebars
|
||||
<h3>{{localize "MNBL.exercisedskills"}}</h3>
|
||||
<div class="small-editor item-text-long-line">
|
||||
{{editor competences target="system.competences" button=true owner=owner editable=editable}}
|
||||
</div>
|
||||
```
|
||||
|
||||
**Après :**
|
||||
```handlebars
|
||||
<div class="sheet-box">
|
||||
<h3><label class="items-title-text">{{localize "MNBL.exercisedskills"}}</label></h3>
|
||||
<div class="small-editor item-text-long-line">
|
||||
{{editor competences target="system.competences" button=true owner=owner editable=editable}}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Impact :**
|
||||
- Meilleure cohérence visuelle avec le reste de l'interface
|
||||
- Permet une meilleure organisation des sections
|
||||
- Facilite l'ajout futur de fonctionnalités comme les liens vers le compendium
|
||||
|
||||
---
|
||||
|
||||
## Liste complète des partials Handlebars
|
||||
|
||||
### Partials utilisés dans le système :
|
||||
|
||||
| Partial | Utilisation | Pré-chargé ? |
|
||||
|---------|-------------|--------------|
|
||||
| `partial-item-header.hbs` | En-têtes des items | ✅ Oui |
|
||||
| `partial-item-description.hbs` | Descriptions des items | ✅ Oui |
|
||||
| `partial-item-nav.hbs` | Navigation des items | ✅ Oui |
|
||||
| `partial-item-prix.hbs` | Prix des items | ✅ Oui |
|
||||
| `partial-item-effects.hbs` | Effets des items | ✅ Oui (ajouté) |
|
||||
| `partial-active-effects.hbs` | Effets actifs (actors) | ✅ Oui (ajouté) |
|
||||
| `partial-automation.hbs` | Automatisation | ✅ Oui |
|
||||
| `editor-notes-gm.hbs` | Notes GM | ✅ Oui |
|
||||
| `hud-adversites.hbs` | HUD Adversités | ✅ Oui |
|
||||
|
||||
---
|
||||
|
||||
## Templates principaux
|
||||
|
||||
### Fiches d'acteurs :
|
||||
- `actor-sheet.hbs` - Feuille de personnage
|
||||
- `creature-sheet.hbs` - Feuille de créature
|
||||
|
||||
### Fiches d'items :
|
||||
- `item-arme-sheet.hbs`
|
||||
- `item-capaciteautomata-sheet.hbs`
|
||||
- `item-competence-sheet.hbs`
|
||||
- `item-don-sheet.hbs`
|
||||
- `item-equipement-sheet.hbs`
|
||||
- `item-historique-sheet.hbs`
|
||||
- `item-monnaie-sheet.hbs`
|
||||
- `item-pacte-sheet.hbs`
|
||||
- `item-pouvoirselementaire-sheet.hbs`
|
||||
- `item-profil-sheet.hbs`
|
||||
- `item-protection-sheet.hbs`
|
||||
- `item-ressource-sheet.hbs`
|
||||
- `item-rune-sheet.hbs`
|
||||
- `item-runeeffect-sheet.hbs`
|
||||
- `item-talent-sheet.hbs`
|
||||
- `item-tendance-sheet.hbs`
|
||||
- `item-traitchaotique-sheet.hbs`
|
||||
- `item-traitdemoniaque-sheet.hbs`
|
||||
- `item-traitespece-sheet.hbs`
|
||||
|
||||
---
|
||||
|
||||
## Outils de test
|
||||
|
||||
Un script de test a été créé pour valider les corrections :
|
||||
- **Fichier :** `test-templates.js`
|
||||
- **Exécution :** `node test-templates.js`
|
||||
|
||||
**Fonctionnalités du test :**
|
||||
1. ✅ Vérifie que tous les templates préchargés existent
|
||||
2. ✅ Scanne tous les templates pour trouver les partials utilisés
|
||||
3. ✅ Vérifie que tous les partials utilisés sont préchargés
|
||||
4. ✅ Valide le fichier de localisation JSON
|
||||
|
||||
---
|
||||
|
||||
## Bonnes pratiques rappelées
|
||||
|
||||
### Pré-chargement des templates Handlebars
|
||||
|
||||
En Foundry VTT v12+, il est **obligatoire** de pré-charger tous les partials Handlebars utilisés via la fonction `foundry.applications.handlebars.loadTemplates()` dans le hook `init`.
|
||||
|
||||
**Pourquoi ?**
|
||||
- Les partials ne sont pas chargés automatiquement
|
||||
- Sans pré-chargement, le rendu échouera avec une erreur "partial could not be found"
|
||||
- Le pré-chargement améliore les performances en cacheant les templates
|
||||
|
||||
**Où ?**
|
||||
Dans le hook `init`, avant l'enregistrement des feuilles (sheets) :
|
||||
```javascript
|
||||
Hooks.once("init", async function () {
|
||||
// Pré-charger les templates AVANT d'enregistrer les feuilles
|
||||
await MournbladeCYD2Utility.preloadHandlebarsTemplates();
|
||||
|
||||
// Ensuite enregistrer les feuilles
|
||||
Actors.registerSheet(...);
|
||||
Items.registerSheet(...);
|
||||
});
|
||||
```
|
||||
|
||||
### Gestion des chemins des templates
|
||||
|
||||
Les chemins doivent être **relatifs au répertoire `systems/`** :
|
||||
- ✅ Bon : `'systems/fvtt-mournblade-cyd-2-0/templates/partial-active-effects.hbs'`
|
||||
- ❌ Mauvais : `'./templates/partial-active-effects.hbs'`
|
||||
|
||||
---
|
||||
|
||||
## Impact des corrections
|
||||
|
||||
### Avant les corrections :
|
||||
- ❌ Ouverture des feuilles de personnage → Erreur
|
||||
- ❌ Ouverture des feuilles de créature → Erreur
|
||||
- ❌ Affichage des effets actifs → Impossible
|
||||
- ❌ Utilisation des effets d'items → Problèmes potentiels
|
||||
- ❌ Création d'effets actifs → Erreur TypeError
|
||||
- ❌ Boucle infinie de 404 sur effect.webp
|
||||
- ❌ Avertissements duration.type déprécié
|
||||
- ❌ Helper subtract manquant → Erreur de rendu
|
||||
- ❌ Clés i18n manquantes → Messages en anglais
|
||||
- ❌ Clés MNBL.details et MNBL.description manquantes
|
||||
- ❌ Onglet "Effets" manquant dans les fiches d'items
|
||||
|
||||
### Après les corrections :
|
||||
- ✅ Toutes les feuilles s'ouvrent correctement
|
||||
- ✅ Les effets actifs s'affichent correctement
|
||||
- ✅ Tous les items affichent leurs effets
|
||||
- ✅ Plus d'erreurs de templates manquants
|
||||
- ✅ Création d'effets actifs fonctionne correctement
|
||||
- ✅ Plus de boucles infinies de chargement d'icônes
|
||||
- ✅ Plus d'avertissements de compatibilité
|
||||
- ✅ Helper subtract disponible et fonctionnel
|
||||
- ✅ Toutes les clés i18n présentes → Localisation complète
|
||||
- ✅ Toutes les clés MNBL présentes
|
||||
- ✅ Onglet "Effets" toujours visible dans les fiches d'items
|
||||
|
||||
---
|
||||
|
||||
## Recommandations pour le développement futur
|
||||
|
||||
1. **Toujours pré-charger les nouveaux partials** lorsqu'ils sont ajoutés
|
||||
2. **Utiliser un script de test** pour valider les templates après modification
|
||||
3. **Maintenir une liste à jour** des partials utilisés dans le projet
|
||||
4. **Vérifier les erreurs de console** lors du développement
|
||||
5. **Tester toutes les feuilles** après ajout de nouveaux partials
|
||||
|
||||
---
|
||||
|
||||
## Fichiers modifiés
|
||||
|
||||
| Fichier | Modification | Statut |
|
||||
|---------|--------------|--------|
|
||||
| `modules/mournblade-cyd2-utility.js` | Ajout partials + helper subtract | ✅ Corrigé |
|
||||
| `modules/applications/sheets/base-actor-sheet.mjs` | Correction création effets + icône | ✅ Corrigé |
|
||||
| `modules/applications/sheets/base-item-sheet.mjs` | Correction création effets + icône | ✅ Corrigé |
|
||||
| `modules/mournblade-cyd2-effects.js` | Remplacement effect.webp par capacite.webp | ✅ Corrigé |
|
||||
| `templates/partial-active-effects.hbs` | Remplacement effect.webp + duration.type → duration.units | ✅ Corrigé |
|
||||
| `templates/partial-item-effects.hbs` | Remplacement effect.webp + duration.type → duration.units | ✅ Corrigé |
|
||||
| `templates/partial-item-nav.hbs` | Affichage permanent onglet Effets + clés MNBL | ✅ Corrigé |
|
||||
| `test-templates.js` | Nouveau fichier de test | ✅ Ajouté |
|
||||
| `CORRECTIONS.md` | Documentation des corrections | ✅ Ajouté |
|
||||
| `lang/fr.json` | Ajout des clés i18n EFFECT + MNBL manquantes | ✅ Corrigé |
|
||||
|
||||
## Fichiers modifiés (Nouveautés 2026-06-07)
|
||||
|
||||
| Fichier | Modification | Statut |
|
||||
|---------|--------------|--------|
|
||||
| `modules/mournblade-cyd2-main.js` | Simplification mention Titam dans popup | ✅ Corrigé |
|
||||
| `modules/models/personnage.mjs` | Ajout champ coutPouvoirInvocations | ✅ Corrigé |
|
||||
| `lang/fr.json` | Ajout clé SORCELLERIE.coutPouvoirInvocations | ✅ Corrigé |
|
||||
| `templates/actor-sheet.hbs` | Ajout champ coût Pouvoir dans onglet Sorcellerie | ✅ Corrigé |
|
||||
| `templates/item-profil-sheet.hbs` | Amélioration rendu sections avec sheet-box | ✅ Corrigé |
|
||||
|
||||
## État des fonctionnalités demandées
|
||||
|
||||
| Fonctionnalité | Statut | Remarques |
|
||||
|---------------|--------|----------|
|
||||
| Clés i18n manquantes | ✅ Complété | Toutes les clés EFFECT et MNBL sont présentes |
|
||||
| Onglet Effets des items | ✅ Complété | Toujours visible, même sans effets |
|
||||
| Popup de lancement | ✅ Complété | Lien vers règles PAO 0.9 présent, mention Titam simplifiée |
|
||||
| Onglet Sorcellerie | ✅ Déjà présent | Avec sections Runes, Créatures invoquées, Démons liés, Enchantements/Automata |
|
||||
| Section Invocations en cours | ✅ Complété | Avec champ coût en Pouvoir ajouté |
|
||||
| Case bleue Combat | ✅ Déjà présente | Affiche Initiative, Défense, Protection sur fiche personnage |
|
||||
| Cases PO/PA/SC sur items | ✅ Déjà présent | Via partial-item-prix.hbs avec calcul automatique |
|
||||
| Sections blanches dans Profils | ✅ Complété | Remplacement des blocs simples par sheet-box |
|
||||
| Virgules après Prédilections | ✅ Complété | Plus de virgule finale lorsqu'il n'y a qu'une seule Prédilection |
|
||||
| Valeurs d'Allégeance | ✅ Complété | Affichage propre : Tous, Chaos, Loi, Bêtes, Élémentaires (via helper localizeAllegiance) |
|
||||
| Orthographe Talent | ✅ Complété | "scéance" → "séance" |
|
||||
| Totaux argent/équipement | ✅ Complété | Calcul automatique activé avec conversion lore (1 PO = 100 SC, 1 PA = 10 SC) |
|
||||
|
||||
---
|
||||
|
||||
### 13. ✅ Correction de l'affichage des virgules après les Prédilections
|
||||
|
||||
**Problème :**
|
||||
Une virgule apparaît après une Prédilection même s'il n'y en a qu'une seule affichée.
|
||||
|
||||
**Exemple avant :**
|
||||
```
|
||||
Compétence (Prédilection1,)
|
||||
```
|
||||
|
||||
**Exemple après :**
|
||||
```
|
||||
Compétence (Prédilection1)
|
||||
Compétence (Prédilection1, Prédilection2) // Virgule uniquement entre les éléments
|
||||
```
|
||||
|
||||
**Solution :**
|
||||
Utilisation du helper `subtract` pour vérifier si c'est la dernière prédilection dans la liste et ne pas afficher la virgule dans ce cas.
|
||||
|
||||
**Code avant :**
|
||||
```handlebars
|
||||
{{#each skill.system.predilections as |pred key|}}
|
||||
{{#if (and pred.acquise (not pred.used))}}
|
||||
{{pred.name}},
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
```
|
||||
|
||||
**Code après :**
|
||||
```handlebars
|
||||
{{#each skill.system.predilections as |pred key|}}
|
||||
{{#if (and pred.acquise (not pred.used))}}
|
||||
{{pred.name}}{{#unless (eq key (subtract skill.system.predilections.length 1))}}, {{/unless}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
```
|
||||
|
||||
**Fichiers modifiés :**
|
||||
- `templates/actor-sheet.hbs` (ligne 278)
|
||||
- `templates/creature-sheet.hbs` (ligne 270)
|
||||
|
||||
---
|
||||
|
||||
### 18. ✅ Réduction de la duplication de code dans les modèles items
|
||||
|
||||
**Problème :**
|
||||
Les modèles `equipement.mjs`, `arme.mjs`, `protection.mjs`, et `monnaie.mjs` dupliquaient les champs communs : `prixpo`, `prixca`, `prixsc`, `rarete`, `quantite`, `equipped`, `description`.
|
||||
|
||||
**Solution :**
|
||||
Création d'une classe de base `BaseItemWithPriceDataModel` dans `base-item.mjs` qui contient tous les champs communs. Les 4 modèles étendent maintenant cette classe.
|
||||
|
||||
**Avantages :**
|
||||
- Élimination de la duplication de code
|
||||
- Maintenance plus facile (changement dans un seul endroit)
|
||||
- Cohérence garantie entre tous les items avec prix
|
||||
|
||||
**Fichiers modifiés :**
|
||||
- `modules/models/base-item.mjs` - Ajout de `BaseItemWithPriceDataModel`
|
||||
- `modules/models/equipement.mjs` - Étend `BaseItemWithPriceDataModel`
|
||||
- `modules/models/arme.mjs` - Étend `BaseItemWithPriceDataModel`
|
||||
- `modules/models/protection.mjs` - Étend `BaseItemWithPriceDataModel`
|
||||
- `modules/models/monnaie.mjs` - Étend `BaseItemWithPriceDataModel`
|
||||
|
||||
---
|
||||
|
||||
### 19. ✅ Centralisation de la logique de conversion monétaire
|
||||
|
||||
**Problème :**
|
||||
La logique de conversion entre PO, PA et SC était dupliquée dans `computeRichesse()` et `computeValeurEquipement()`.
|
||||
|
||||
**Solution :**
|
||||
- Créé `MournbladeCYD2Utility.calculateItemValueSC(prixpo, prixca, prixsc)` - méthode statique pour calculer la valeur SC
|
||||
- Créé `MournbladeCYD2Utility.getItemValueSC(item)` - méthode qui calcule la valeur totale d'un item (prix × quantité)
|
||||
- Refactorisé les deux méthodes de l'Actor pour utiliser ces helpers
|
||||
- Le helper Handlebars `calculateItemValueSC` utilise maintenant la méthode statique
|
||||
|
||||
**Avantages :**
|
||||
- Une seule source de vérité pour les conversions monétaires
|
||||
- Maintenance plus facile
|
||||
- Réutilisable dans tout le codebase
|
||||
- Cohérent avec le lore Mournblade (1 PO = 100 SC, 1 PA = 10 SC)
|
||||
|
||||
**Fichiers modifiés :**
|
||||
- `modules/mournblade-cyd2-utility.js` - Ajout des méthodes statiques
|
||||
- `modules/mournblade-cyd2-actor.js` - Refactorisation pour utiliser les helpers
|
||||
|
||||
---
|
||||
|
||||
### 20. ✅ Amélioration des helpers Handlebars
|
||||
|
||||
**Nouveaux helpers ajoutés :**
|
||||
|
||||
1. **`localizeAllegiance(value)`** - Localise les valeurs d'allégeance
|
||||
- Mappe : tous→MNBL.all, chaos→MNBL.chaos, loi→MNBL.law, betes→MNBL.betes, elementaires→MNBL.elementaires, balance→MNBL.balance
|
||||
- Utilise `game.i18n.localize()` pour la traduction
|
||||
|
||||
2. **`joinPredilections(predilections)`** - Joint les prédilections avec des virgules
|
||||
- Filtre les prédilections acquises et non utilisées
|
||||
- Retourne une chaîne vide si aucune prédilection applicable
|
||||
- Évite la virgule finale superflue
|
||||
|
||||
**Fichiers modifiés :**
|
||||
- `modules/mournblade-cyd2-utility.js` - Ajout des helpers
|
||||
- `templates/actor-sheet.hbs` - Utilisation de `joinPredilections` et `localizeAllegiance`
|
||||
- `templates/creature-sheet.hbs` - Utilisation de `joinPredilections`
|
||||
|
||||
---
|
||||
|
||||
## Auteurs
|
||||
|
||||
Corrections réalisées par : Mistral Vibe (via Vibe CLI)
|
||||
Date : 2026-06-07
|
||||
|
||||
---
|
||||
|
||||
### 14. ✅ Correction de l'affichage des valeurs d'Allégeance
|
||||
|
||||
**Problème :**
|
||||
Les valeurs d'allégeance étaient affichées avec des noms trop longs ou non capitalisés.
|
||||
|
||||
**Solution :**
|
||||
Ajout de nouvelles clés de localisation et mise à jour de la configuration des options d'allégeance.
|
||||
|
||||
**Clés i18n ajoutées dans `lang/fr.json` :**
|
||||
```json
|
||||
{
|
||||
"betes": "Bêtes",
|
||||
"elementaires": "Élémentaires"
|
||||
}
|
||||
```
|
||||
|
||||
**Configuration mise à jour dans `mournblade-cyd2-config.js` :**
|
||||
```javascript
|
||||
allegeanceOptions: {
|
||||
tous: localizeOrFallback("MNBL.all", "Tous"),
|
||||
chaos: localizeOrFallback("MNBL.chaos", "Chaos"),
|
||||
loi: localizeOrFallback("MNBL.law", "Loi"),
|
||||
balance: localizeOrFallback("MNBL.balance", "Balance"),
|
||||
betes: localizeOrFallback("MNBL.betes", "Bêtes"),
|
||||
elementaires: localizeOrFallback("MNBL.elementaires", "Élémentaires")
|
||||
}
|
||||
```
|
||||
|
||||
**Résultat :**
|
||||
Les allégeances sont maintenant affichées ainsi :
|
||||
- "Tous", "Chaos", "Loi", "Balance", "Bêtes", "Élémentaires"
|
||||
|
||||
**Fichiers modifiés :**
|
||||
- `lang/fr.json` - Ajout des clés MNBL.betes et MNBL.elementaires
|
||||
- `modules/mournblade-cyd2-config.js` - Mise à jour des allegeanceOptions
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 15. ✅ Correction orthographique "scéance" → "séance"
|
||||
|
||||
**Problème :**
|
||||
Faute d'orthographe dans l'option d'utilisation des Talents : "Une fois par scéance" au lieu de "Une fois par séance".
|
||||
|
||||
**Solution :**
|
||||
Correction directe dans la configuration des options.
|
||||
|
||||
**Modification dans `modules/mournblade-cyd2-config.js` :**
|
||||
```javascript
|
||||
// Avant
|
||||
{ key: "sceance", label: "Une fois par scéance" },
|
||||
|
||||
// Après
|
||||
{ key: "sceance", label: "Une fois par séance" },
|
||||
```
|
||||
|
||||
**Fichier modifié :**
|
||||
- `modules/mournblade-cyd2-config.js` (ligne 214)
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 16. ✅ Activation du calcul automatique des totaux d'argent et d'équipement
|
||||
|
||||
**Problème :**
|
||||
Dans l'onglet Équipement des fiches de personnage, les lignes "Argent Total" et "Valeur Total Équipement" affichaient des valeurs vides ou non mises à jour. Ajouter un équipement avec une valeur en pièces ne changeait rien nulle part.
|
||||
|
||||
**Cause :**
|
||||
Les méthodes `computeRichesse()` et `computeValeurEquipement()` existaient déjà dans `mournblade-cyd2-actor.js`, mais elles n'étaient pas appelées dans le contexte de la feuille.
|
||||
|
||||
**Solution :**
|
||||
Ajout des appels à ces méthodes dans `_prepareContext()` de la feuille personnage.
|
||||
|
||||
**Fonctionnement :**
|
||||
- **Argent Total** : Somme de toutes les monnaies (type "monnaie") converties en SC
|
||||
- **Valeur Total Équipement** : Somme de tous les équipements, armes et protections converties en SC
|
||||
- Les deux utilisent `computeMonnaieDetails()` pour convertir une valeur SC en {po, pa, sc, valueSC}
|
||||
- **Conversions :** 1 PO = 100 SC, 1 PA = 10 SC (selon le lore : 1 PO = 10 SA, 1 SA = 10 PB)
|
||||
|
||||
**Code ajouté dans `mournblade-cyd2-personnage-sheet.mjs` :**
|
||||
```javascript
|
||||
context.richesse = actor.computeRichesse?.() ?? { po: 0, pa: 0, sc: 0, valueSC: 0 };
|
||||
context.valeurEquipement = actor.computeValeurEquipement?.() ?? { po: 0, pa: 0, sc: 0, valueSC: 0 };
|
||||
```
|
||||
|
||||
**Fichier modifié :**
|
||||
- `modules/applications/sheets/mournblade-cyd2-personnage-sheet.mjs`
|
||||
|
||||
**Réponse à la question :**
|
||||
- Les totaux sont des **additions** (pas des soustractions)
|
||||
- Ils apparaissent dans l'onglet Équipement et sont maintenant mis à jour automatiquement
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 17. ✅ Correction des taux de conversion monétaire selon le lore
|
||||
|
||||
**Problème :**
|
||||
Les taux de conversion monétaire dans le système ne correspondaient pas au lore des Jeunes Royaumes.
|
||||
|
||||
**Ancienne conversion (incorrecte) :**
|
||||
- 1 PO = 400 SC
|
||||
- 1 PA = 20 SC
|
||||
- Donc : 1 PO = 20 PA
|
||||
|
||||
**Nouvelle conversion (selon le lore) :**
|
||||
- 1 SA (Sou d'Argent) = 10 PB (Pièces de Bronze)
|
||||
- 1 PO (Pièce d'Or) = 10 SA = 100 PB
|
||||
- Donc : 1 PA/CA = 10 SC, 1 PO = 100 SC
|
||||
|
||||
**Correspondance code ↔ lore :**
|
||||
- SC (Sous de Cuivre dans le code) = PB (Pièces de Bronze dans le lore)
|
||||
- PA/CA (Pièces d'Argent dans le code) = SA (Sous d'Argent dans le lore)
|
||||
- PO (Pièces d'Or) = PO (Pièces d'Or)
|
||||
|
||||
**Fichiers modifiés :**
|
||||
- `modules/mournblade-cyd2-utility.js` : Helper `calculateItemValueSC` et méthode `computeMonnaieDetails`
|
||||
- `modules/mournblade-cyd2-actor.js` : Méthode `computeValeurEquipement`
|
||||
|
||||
**Source :**
|
||||
```
|
||||
LA MONNAIE DANS LES JEUNES ROYAUMES
|
||||
Le sou d'argent (SA), est la monnaie la plus commune...
|
||||
Le bronze est une piécette de très petite valeur.
|
||||
On échange 10 bronzes contre un sou d'argent, et 10 sous d'argent pour un or.
|
||||
```
|
||||
|
||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 807 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 384 KiB |
|
After Width: | Height: | Size: 380 KiB |
@@ -0,0 +1,30 @@
|
||||
const gulp = require('gulp');
|
||||
const less = require('gulp-less');
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
|
||||
const paths = {
|
||||
styles: {
|
||||
src: 'less/**/*.less',
|
||||
dest: 'styles/'
|
||||
}
|
||||
};
|
||||
|
||||
function styles() {
|
||||
return gulp.src('less/mournblade-cyd2.less')
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(less())
|
||||
.pipe(sourcemaps.write('.'))
|
||||
.pipe(gulp.dest(paths.styles.dest));
|
||||
}
|
||||
|
||||
function watchFiles() {
|
||||
gulp.watch(paths.styles.src, styles);
|
||||
}
|
||||
|
||||
const build = gulp.series(styles);
|
||||
const watch = gulp.series(build, watchFiles);
|
||||
|
||||
exports.styles = styles;
|
||||
exports.build = build;
|
||||
exports.watch = watch;
|
||||
exports.default = build;
|
||||
@@ -2,11 +2,9 @@
|
||||
"TYPES": {
|
||||
"Actor": {
|
||||
"personnage": "Personnage",
|
||||
"cellule": "Cellule",
|
||||
"creature": "Créature"
|
||||
},
|
||||
"Item": {
|
||||
"artefact": "Artefact",
|
||||
"arme": "Arme",
|
||||
"talent": "Talent",
|
||||
"historique": "Historique",
|
||||
@@ -16,23 +14,126 @@
|
||||
"monnaie": "Monnaie",
|
||||
"equipement": "Equipement",
|
||||
"ressource": "Ressource",
|
||||
"contact": "Contact",
|
||||
"mutation": "Mutation",
|
||||
"don": "Don",
|
||||
"pacte": "Pacte",
|
||||
"rune": "Rune",
|
||||
"runeeffect": "Effet de Rune",
|
||||
"tendance": "Tendance",
|
||||
"traitchaotique": "Trait Chaotique",
|
||||
"traitespece": "Trait d'Espèce"
|
||||
"traitespece": "Trait d'Espèce",
|
||||
"traitdemoniaque": "Trait Démoniaque",
|
||||
"pouvoirselementaire": "Pouvoir Élémentaire",
|
||||
"capaciteautomata": "Capacité d'Automata"
|
||||
}
|
||||
},
|
||||
"SORCELLERIE": {
|
||||
"tab": "Sorcellerie",
|
||||
"runes": "Runes",
|
||||
"creaturesinvoquees": "Créatures invoquées",
|
||||
"demonslies": "Démons liés",
|
||||
"enchantements": "Enchantements / Automata",
|
||||
"invocationsencours": "Invocations en cours",
|
||||
"coutPouvoirInvocations": "Coût en Pouvoir des invocations"
|
||||
},
|
||||
"SHEETS": {
|
||||
"Item": {
|
||||
"traitdemoniaque": "Trait Démoniaque",
|
||||
"pouvoirselementaire": "Pouvoir Élémentaire",
|
||||
"capaciteautomata": "Capacité d'Automata"
|
||||
}
|
||||
},
|
||||
"MOURNBLADE": {
|
||||
"ui": {
|
||||
"editContact": "Modifier le contact",
|
||||
"deleteContact": "Supprimer le contact",
|
||||
"editTrait": "Modifier le trait",
|
||||
"deleteTrait": "Supprimer le trait"
|
||||
}
|
||||
},
|
||||
"MNBL": {
|
||||
"all": "Tous",
|
||||
"allegiance": "Allégeance",
|
||||
"balance": "Balance",
|
||||
"beastslords": "Seigneurs des Bêtes",
|
||||
"betes": "Bêtes",
|
||||
"chaos": "Chaos",
|
||||
"difficulty": "Difficulté",
|
||||
"duration": "Durée",
|
||||
"details": "Détails",
|
||||
"description": "Description",
|
||||
"demon": "Démon",
|
||||
"automata": "Automata",
|
||||
"elementaires": "Élémentaires",
|
||||
"elementslords": "Seigneurs des Éléments",
|
||||
"equipment": "Equipement",
|
||||
"examples": "Exemples",
|
||||
"exercisedskills": "Compétences exercées",
|
||||
"highlanguage": "Haut-Parler",
|
||||
"initiateTalents": "Talents Initié",
|
||||
"law": "Loi",
|
||||
"mode": "Mode",
|
||||
"mainattribute": "Attribut principal",
|
||||
"none": "Aucun",
|
||||
"aguerriTalents": "Talents Aguerri",
|
||||
"prerequisitesAguerri": "Prérequis Aguerri",
|
||||
"prerequisitesMaitre": "Prérequis Maître",
|
||||
"prerequisites": "Prérequis",
|
||||
"maitreTalents": "Talents Maître",
|
||||
"pronounced": "Prononcée",
|
||||
"pronouncedrune": "Rune prononcée",
|
||||
"pronouncerune": "Prononcer",
|
||||
"rune": "Rune",
|
||||
"soulcost": "Coût en points de pouvoir",
|
||||
"soulpoints": "Points de Pouvoir",
|
||||
"traced": "Tracée",
|
||||
"tracedrune": "Rune tracée",
|
||||
"tracerune": "Tracer",
|
||||
"initiative": "Initiative",
|
||||
"initShort": "Init.",
|
||||
"defense": "Défense",
|
||||
"defShort": "Déf.",
|
||||
"protection": "Protection",
|
||||
"protShort": "Prot."
|
||||
},
|
||||
"EFFECT": {
|
||||
"new": "Nouvel Effet",
|
||||
"edit": "Éditer l'effet",
|
||||
"delete": "Supprimer l'effet",
|
||||
"deleteConfirm": "Supprimer l'effet",
|
||||
"deleteConfirmText": "Êtes-vous sûr de vouloir supprimer cet effet ?",
|
||||
"deleteError": "Erreur lors de la suppression de l'effet",
|
||||
"create": "Créer un effet",
|
||||
"createError": "Erreur lors de la création de l'effet",
|
||||
"applyError": "Erreur lors de l'application de l'effet",
|
||||
"applyItemError": "Erreur lors de l'application de l'effet sur l'item",
|
||||
"selectActor": "Sélectionnez un acteur pour appliquer l'effet",
|
||||
"toggleError": "Erreur lors de l'activation/désactivation de l'effet",
|
||||
"name": "Nom de l'effet",
|
||||
"icon": "Icône",
|
||||
"description": "Description",
|
||||
"changes": "Modifications",
|
||||
"addChange": "Ajouter une modification",
|
||||
"duration": "Durée",
|
||||
"durationType": "Type de durée",
|
||||
"durationValue": "Valeur",
|
||||
"disabled": "Désactivé",
|
||||
"transfer": "Transférer au token",
|
||||
"noDuration": "Aucune (permanent)",
|
||||
"rounds": "Rounds",
|
||||
"turns": "Tours",
|
||||
"seconds": "Secondes",
|
||||
"combat": "Jusqu'à la fin du combat",
|
||||
"scene": "Jusqu'à la fin de la scène",
|
||||
"attribute": "Attribut",
|
||||
"value": "Valeur",
|
||||
"mode": "Mode",
|
||||
"modeAdd": "Ajouter",
|
||||
"modeMultiply": "Multiplier",
|
||||
"modeOverride": "Remplacer",
|
||||
"modeUpgrade": "Améliorer",
|
||||
"modeDowngrade": "Dégrader",
|
||||
"activeEffects": "Effets Actifs",
|
||||
"noActiveEffects": "Aucun effet actif",
|
||||
"noItemEffects": "Aucun effet sur cet item",
|
||||
"effectSummary": "Résumé des modifications",
|
||||
"toggleEffect": "Activer/Désactiver"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
/* ==================== Item Sheet Styles ==================== */
|
||||
|
||||
/* Item header with image and name */
|
||||
.fvtt-mournblade-cyd-2-0.item {
|
||||
/* Background pour toute la fiche d'item */
|
||||
background: url("../assets/ui/pc_sheet_bg.webp") repeat;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
/* AppV2 - Remove window content padding */
|
||||
.window-content {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* AppV2 - Main section structure */
|
||||
section {
|
||||
background: url("../assets/ui/pc_sheet_bg.webp") repeat-y;
|
||||
color: black;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* AppV2 Item Sheets - Disabled inputs readability */
|
||||
input:disabled,
|
||||
select:disabled {
|
||||
color: #000000;
|
||||
opacity: 0.8;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* Inputs and selects styling */
|
||||
input[type="text"],
|
||||
input[type="number"],
|
||||
select {
|
||||
color: #000000;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
border: 1px solid #999999;
|
||||
margin: 0;
|
||||
padding: 2px 4px;
|
||||
font-family: "CentaurMT", serif;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
textarea {
|
||||
margin: 0;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin: 0 4px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
flex: 0 0 auto;
|
||||
border-bottom: 1px solid #999;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sheet-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0.5rem;
|
||||
background: url("../assets/ui/pc_sheet_bg.webp") repeat;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.item-sheet-img {
|
||||
flex: 0 0 64px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
object-fit: cover;
|
||||
border: 1px solid #999;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 8px rgba(255, 102, 0, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.header-fields {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
button {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #999;
|
||||
padding: 0.25rem 0.5rem;
|
||||
cursor: pointer;
|
||||
font-family: "CentaurMT", serif;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 102, 0, 0.2);
|
||||
box-shadow: 0 0 5px rgba(255, 102, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.chat-card-button {
|
||||
background: linear-gradient(to bottom, #21374afc 5%, #152833ab 100%);
|
||||
border: 2px ridge #846109;
|
||||
color: #d4b5a8;
|
||||
padding: 0.3rem 0.5rem;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
i {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(to bottom, #800000 5%, #3e0101 100%);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 0 8px rgba(128, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
&:active {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sheet-header h1.charname {
|
||||
height: 50px;
|
||||
padding: 0;
|
||||
margin: 5px 0;
|
||||
border-bottom: 0;
|
||||
font-weight: bold;
|
||||
font-size: 2rem;
|
||||
font-family: "CentaurMT";
|
||||
}
|
||||
|
||||
.sheet-header h1.charname input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
font-family: "CentaurMT";
|
||||
font-size: 2rem;
|
||||
text-align: left;
|
||||
border: 0 none;
|
||||
|
||||
&:focus {
|
||||
outline: 1px solid #ff6600;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tabs - Modern style matching actor sheets */
|
||||
nav.tabs {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #7a7971;
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
background-color: #1c1c1c;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
nav.tabs a.item {
|
||||
padding: 6px 12px;
|
||||
color: #d4af37;
|
||||
text-decoration: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px 4px 0 0;
|
||||
margin-right: 4px;
|
||||
transition: all 0.2s;
|
||||
|
||||
i {
|
||||
display: none; // Hide icons to match actor sheets
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(212, 175, 55, 0.1);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #2a2a2a;
|
||||
border-bottom-color: #d4af37;
|
||||
color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tab content */
|
||||
.tab {
|
||||
display: none;
|
||||
padding: 4px 8px;
|
||||
overflow-y: auto;
|
||||
flex: 1 1 auto;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
/* Item list in details tab */
|
||||
.item-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 2px;
|
||||
padding: 2px 4px;
|
||||
min-height: 24px;
|
||||
border-bottom: none;
|
||||
|
||||
&.flexrow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Labels */
|
||||
.generic-label {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
font-weight: 700;
|
||||
color: #2a2515;
|
||||
font-size: 0.9rem;
|
||||
font-family: "CentaurMT", serif;
|
||||
margin: 0;
|
||||
padding: 0 4px 0 0;
|
||||
}
|
||||
|
||||
/* Field labels */
|
||||
.item-field-label-short {
|
||||
flex: 0 0 60px;
|
||||
max-width: 60px;
|
||||
}
|
||||
|
||||
.item-field-label-medium {
|
||||
flex: 0 0 100px;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.item-field-label-long {
|
||||
flex: 1;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.item-field-label-long1 {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.item-field-label-long2 {
|
||||
flex: 1;
|
||||
min-width: 250px;
|
||||
max-width: 250px;
|
||||
}
|
||||
.item-field-label-long3 {
|
||||
flex: 1;
|
||||
min-width: 350px;
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.numeric-input {
|
||||
text-align: center;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
/* Editor fields */
|
||||
.editor {
|
||||
height: 300px;
|
||||
border: 1px solid #999;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
|
||||
.editor-content {
|
||||
height: 100%;
|
||||
padding: 0.5rem;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
/* Section headings in item sheet tabs */
|
||||
.tab .item-list h3 {
|
||||
font-family: "CentaurMT", "Palatino Linotype", serif;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
color: #f0dfc0 !important;
|
||||
background: rgba(20, 10, 0, 0.65);
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
|
||||
margin: 0.25rem 0;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Section title headings inside sheet-box (Profil, etc.) */
|
||||
.tab .sheet-box h3,
|
||||
.tab .sheet-box .items-title-text {
|
||||
font-family: "CentaurMT", "Palatino Linotype", serif;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
color: #f0dfc0 !important;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
|
||||
margin: 0.25rem 0;
|
||||
padding: 0.2rem 0;
|
||||
}
|
||||
|
||||
/* Suppress formInput label when heading already provides it */
|
||||
.tab .sheet-box .form-group > label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ProseMirror editor in sheet-box — ensure readable background */
|
||||
.tab .sheet-box .editor {
|
||||
min-height: 4rem;
|
||||
background: rgba(255, 250, 240, 0.9);
|
||||
border: 1px solid rgba(139, 69, 19, 0.35);
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Main LESS file for Mournblade CYD 2.0 system
|
||||
// Importing base styles and component-specific styles
|
||||
|
||||
@import "simple-converted";
|
||||
@import "item-styles";
|
||||
@import "actor-styles";
|
||||
@@ -0,0 +1,109 @@
|
||||
import { MournbladeCYD2Utility } from "../mournblade-cyd2-utility.js";
|
||||
|
||||
/**
|
||||
* Dialogue de jet de dé pour MournbladeCYD2 - Version DialogV2
|
||||
*/
|
||||
export class MournbladeCYD2RollDialog {
|
||||
|
||||
/**
|
||||
* Create and display the roll dialog
|
||||
* @param {MournbladeCYD2Actor} actor
|
||||
* @param {Object} rollData
|
||||
*/
|
||||
static async create(actor, rollData) {
|
||||
const context = {
|
||||
...rollData,
|
||||
difficulte: String(rollData.difficulte || 0),
|
||||
img: actor.img,
|
||||
name: actor.name,
|
||||
config: game.system.mournbladecyd2.config,
|
||||
attributs: game.system.mournbladecyd2.config.attributs,
|
||||
};
|
||||
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/fvtt-mournblade-cyd-2-0/templates/roll-dialog-generic.hbs",
|
||||
context
|
||||
);
|
||||
|
||||
return foundry.applications.api.DialogV2.wait({
|
||||
window: { title: "Test de Capacité", icon: "fa-solid fa-dice-d20" },
|
||||
classes: ["mournblade-cyd2-roll-dialog"],
|
||||
position: { width: 470 },
|
||||
modal: false,
|
||||
content,
|
||||
buttons: [
|
||||
{
|
||||
action: "rolld10",
|
||||
label: "Lancer 1d10",
|
||||
icon: "fa-solid fa-dice-d10",
|
||||
default: true,
|
||||
callback: (event, button, dialog) => {
|
||||
MournbladeCYD2RollDialog._updateRollDataFromForm(rollData, button.form, actor);
|
||||
rollData.mainDice = "d10";
|
||||
MournbladeCYD2Utility.rollMournbladeCYD2(rollData);
|
||||
}
|
||||
},
|
||||
{
|
||||
action: "rolld20",
|
||||
label: "Lancer 1d20",
|
||||
icon: "fa-solid fa-dice-d20",
|
||||
callback: (event, button, dialog) => {
|
||||
MournbladeCYD2RollDialog._updateRollDataFromForm(rollData, button.form, actor);
|
||||
rollData.mainDice = "d20";
|
||||
MournbladeCYD2Utility.rollMournbladeCYD2(rollData);
|
||||
}
|
||||
},
|
||||
{
|
||||
action: "cancel",
|
||||
label: "Annuler",
|
||||
icon: "fa-solid fa-times"
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
static _updateRollDataFromForm(rollData, form, actor) {
|
||||
const el = form.elements;
|
||||
const getVal = (name) => el[name]?.value;
|
||||
const getChecked = (name) => {
|
||||
const e = form.querySelector(`#${name}`);
|
||||
return e ? e.checked : false;
|
||||
};
|
||||
|
||||
if (el.modificateur) rollData.modificateur = Number(getVal("modificateur"));
|
||||
if (el["bonus-malus-context"]) rollData.bonusMalusContext = Number(getVal("bonus-malus-context"));
|
||||
if (el.difficulte) rollData.difficulte = Number(getVal("difficulte"));
|
||||
if (el.attrKey) rollData.attrKey = String(getVal("attrKey"));
|
||||
if (el.attrKey2) rollData.attrKey2 = String(getVal("attrKey2"));
|
||||
if (el["select-maitrise"]) rollData.maitriseId = String(getVal("select-maitrise"));
|
||||
if (el["competence-talents"]) {
|
||||
const sel = el["competence-talents"];
|
||||
rollData.selectedTalents = Array.from(sel.selectedOptions).map(o => o.value);
|
||||
}
|
||||
if (el["taille-cible"]) rollData.tailleCible = String(getVal("taille-cible"));
|
||||
if (el["tireur-deplacement"]) rollData.tireurDeplacement = String(getVal("tireur-deplacement"));
|
||||
if (el["cible-couvert"]) rollData.cibleCouvert = String(getVal("cible-couvert"));
|
||||
if (el["distance-tir"]) rollData.distanceTir = String(getVal("distance-tir"));
|
||||
if (el["soutiens"]) rollData.soutiens = Number(getVal("soutiens"));
|
||||
if (el["runemode"]) rollData.runemode = String(getVal("runemode"));
|
||||
if (el["runeame"]) rollData.runeame = Number(getVal("runeame"));
|
||||
|
||||
rollData.defenseurAuSol = getChecked("defenseur-au-sol");
|
||||
rollData.defenseurAveugle = getChecked("defenseur-aveugle");
|
||||
rollData.defenseurDeDos = getChecked("defenseur-de-dos");
|
||||
rollData.defenseurRestreint = getChecked("defenseur-restreint");
|
||||
rollData.defenseurImmobilise = getChecked("defenseur-immobilise");
|
||||
rollData.attaquantsMultiples = getChecked("attaquants-multiple");
|
||||
rollData.ambidextre1 = getChecked("ambidextre-1");
|
||||
rollData.ambidextre2 = getChecked("ambidextre-2");
|
||||
rollData.feinte = getChecked("feinte");
|
||||
rollData.attaqueCharge = getChecked("attaque-charge");
|
||||
rollData.chargeCavalerie = getChecked("charge-cavalerie");
|
||||
rollData.contenir = getChecked("contenir");
|
||||
rollData.attaqueDesarme = getChecked("attaque-desarme");
|
||||
rollData.cibleDeplace = getChecked("tireur-cible-deplace");
|
||||
rollData.cibleCaC = getChecked("cible-cac");
|
||||
rollData.cibleconsciente = getChecked("cibleconsciente");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Index des applications AppV2 pour Mournblade CYD 2.0
|
||||
*/
|
||||
|
||||
// Fiches d'acteurs
|
||||
export { default as MournbladeCYD2PersonnageSheet } from './mournblade-cyd2-personnage-sheet.mjs';
|
||||
export { default as MournbladeCYD2CreatureSheet } from './mournblade-cyd2-creature-sheet.mjs';
|
||||
|
||||
// Fiches d'items
|
||||
export { default as MournbladeCYD2ArmeSheet } from './mournblade-cyd2-arme-sheet.mjs';
|
||||
export { default as MournbladeCYD2CompetenceSheet } from './mournblade-cyd2-competence-sheet.mjs';
|
||||
export { default as MournbladeCYD2DonSheet } from './mournblade-cyd2-don-sheet.mjs';
|
||||
export { default as MournbladeCYD2EquipementSheet } from './mournblade-cyd2-equipement-sheet.mjs';
|
||||
export { default as MournbladeCYD2HistoriqueSheet } from './mournblade-cyd2-historique-sheet.mjs';
|
||||
export { default as MournbladeCYD2MonnaieSheet } from './mournblade-cyd2-monnaie-sheet.mjs';
|
||||
export { default as MournbladeCYD2PacteSheet } from './mournblade-cyd2-pacte-sheet.mjs';
|
||||
export { default as MournbladeCYD2ProfilSheet } from './mournblade-cyd2-profil-sheet.mjs';
|
||||
export { default as MournbladeCYD2ProtectionSheet } from './mournblade-cyd2-protection-sheet.mjs';
|
||||
export { default as MournbladeCYD2RessourceSheet } from './mournblade-cyd2-ressource-sheet.mjs';
|
||||
export { default as MournbladeCYD2RuneSheet } from './mournblade-cyd2-rune-sheet.mjs';
|
||||
export { default as MournbladeCYD2RuneEffectSheet } from './mournblade-cyd2-runeeffect-sheet.mjs';
|
||||
export { default as MournbladeCYD2TalentSheet } from './mournblade-cyd2-talent-sheet.mjs';
|
||||
export { default as MournbladeCYD2TendanceSheet } from './mournblade-cyd2-tendance-sheet.mjs';
|
||||
export { default as MournbladeCYD2TraitChaotiqueSheet } from './mournblade-cyd2-traitchaotique-sheet.mjs';
|
||||
export { default as MournbladeCYD2TraitEspeceSheet } from './mournblade-cyd2-traitespece-sheet.mjs';
|
||||
export { default as MournbladeCYD2TraitDemoniaqueSheet } from './mournblade-cyd2-traitdemoniaque-sheet.mjs';
|
||||
export { default as MournbladeCYD2PouvoirElementaireSheet } from './mournblade-cyd2-pouvoirselementaire-sheet.mjs';
|
||||
export { default as MournbladeCYD2CapaciteAutomataSheet } from './mournblade-cyd2-capaciteautomata-sheet.mjs';
|
||||
@@ -0,0 +1,522 @@
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
import { MournbladeCYD2Utility } from "../../mournblade-cyd2-utility.js";
|
||||
|
||||
export default class MournbladeCYD2ActorSheetV2 extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
|
||||
|
||||
static SHEET_MODES = { EDIT: 0, PLAY: 1 };
|
||||
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
this.#dragDrop = this.#createDragDropHandlers();
|
||||
this._sheetMode = this.constructor.SHEET_MODES.PLAY;
|
||||
}
|
||||
|
||||
#dragDrop;
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["fvtt-mournblade-cyd-2-0", "sheet", "actor"],
|
||||
position: {
|
||||
width: 750,
|
||||
height: 820,
|
||||
},
|
||||
form: {
|
||||
submitOnChange: true,
|
||||
closeOnSubmit: false,
|
||||
},
|
||||
window: {
|
||||
resizable: true,
|
||||
},
|
||||
tabs: [
|
||||
{
|
||||
navSelector: 'nav[data-group="primary"]',
|
||||
contentSelector: "section.sheet-body",
|
||||
initial: "stats",
|
||||
},
|
||||
],
|
||||
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
|
||||
actions: {
|
||||
editImage: MournbladeCYD2ActorSheetV2.#onEditImage,
|
||||
toggleSheet: MournbladeCYD2ActorSheetV2.#onToggleSheet,
|
||||
editItem: MournbladeCYD2ActorSheetV2.#onEditItem,
|
||||
deleteItem: MournbladeCYD2ActorSheetV2.#onDeleteItem,
|
||||
createItem: MournbladeCYD2ActorSheetV2.#onCreateItem,
|
||||
equipItem: MournbladeCYD2ActorSheetV2.#onEquipItem,
|
||||
modifyQuantity: MournbladeCYD2ActorSheetV2.#onModifyQuantity,
|
||||
modifyAdversite: MournbladeCYD2ActorSheetV2.#onModifyAdversite,
|
||||
modifySante: MournbladeCYD2ActorSheetV2.#onModifySante,
|
||||
modifyAme: MournbladeCYD2ActorSheetV2.#onModifyAme,
|
||||
rollAttribut: MournbladeCYD2ActorSheetV2.#onRollAttribut,
|
||||
rollCompetence: MournbladeCYD2ActorSheetV2.#onRollCompetence,
|
||||
rollRune: MournbladeCYD2ActorSheetV2.#onRollRune,
|
||||
rollArmeOffensif: MournbladeCYD2ActorSheetV2.#onRollArmeOffensif,
|
||||
rollArmeSpecial: MournbladeCYD2ActorSheetV2.#onRollArmeSpecial,
|
||||
rollArmeDegats: MournbladeCYD2ActorSheetV2.#onRollArmeDegats,
|
||||
rollAssommer: MournbladeCYD2ActorSheetV2.#onRollAssommer,
|
||||
rollCoupBas: MournbladeCYD2ActorSheetV2.#onRollCoupBas,
|
||||
rollImmobiliser: MournbladeCYD2ActorSheetV2.#onRollImmobiliser,
|
||||
rollRepousser: MournbladeCYD2ActorSheetV2.#onRollRepousser,
|
||||
rollDesengager: MournbladeCYD2ActorSheetV2.#onRollDesengager,
|
||||
rollInitiative: MournbladeCYD2ActorSheetV2.#onRollInitiative,
|
||||
rollFuir: MournbladeCYD2ActorSheetV2.#onRollFuir,
|
||||
// Actions pour les ActiveEffects
|
||||
createEffect: MournbladeCYD2ActorSheetV2.#onCreateEffect,
|
||||
editEffect: MournbladeCYD2ActorSheetV2.#onEditEffect,
|
||||
deleteEffect: MournbladeCYD2ActorSheetV2.#onDeleteEffect,
|
||||
toggleEffect: MournbladeCYD2ActorSheetV2.#onToggleEffect,
|
||||
applyEffect: MournbladeCYD2ActorSheetV2.#onApplyEffect,
|
||||
toggleTalentUsed: MournbladeCYD2ActorSheetV2.#onToggleTalentUsed,
|
||||
},
|
||||
};
|
||||
|
||||
get isPlayMode() {
|
||||
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY;
|
||||
return this._sheetMode === this.constructor.SHEET_MODES.PLAY;
|
||||
}
|
||||
|
||||
get isEditMode() {
|
||||
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY;
|
||||
return this._sheetMode === this.constructor.SHEET_MODES.EDIT;
|
||||
}
|
||||
|
||||
tabGroups = { primary: "stats" };
|
||||
|
||||
/**
|
||||
* Intercept form data before ActorSheetV2 validates it, to sanitize
|
||||
* empty numeric fields that would otherwise fail DataModel validation.
|
||||
* Mirrors the same logic in MournbladeCYD2Actor._preUpdate.
|
||||
* @override
|
||||
*/
|
||||
_prepareSubmitData(event, form, formData) {
|
||||
const fd = formData ?? new foundry.applications.ux.FormDataExtended(form);
|
||||
const data = {};
|
||||
for (const [k, v] of fd) {
|
||||
foundry.utils.setProperty(data, k, v);
|
||||
}
|
||||
this._sanitizeNumericData(data);
|
||||
this.document.validate(data, { partial: true });
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk submitted data and convert empty/invalid values in NumberField
|
||||
* paths to 0 so schema validation passes.
|
||||
* @param {object} data
|
||||
*/
|
||||
_sanitizeNumericData(data) {
|
||||
if (!data?.system || typeof data.system !== "object") return;
|
||||
const fields = this.document.system.schema.fields;
|
||||
for (const [schemaPath, schemaField] of Object.entries(fields)) {
|
||||
if (!(schemaField instanceof foundry.data.fields.SchemaField)) continue;
|
||||
const branch = data.system[schemaPath];
|
||||
if (!branch || typeof branch !== "object") continue;
|
||||
for (const [key, value] of Object.entries(branch)) {
|
||||
if (schemaField.fields[key] instanceof foundry.data.fields.NumberField) {
|
||||
if (value === "" || value === null || value === undefined || isNaN(value)) {
|
||||
data.system[schemaPath][key] = 0;
|
||||
} else {
|
||||
data.system[schemaPath][key] = Number(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const actor = this.document;
|
||||
return {
|
||||
actor,
|
||||
system: actor.system,
|
||||
source: actor.toObject(),
|
||||
fields: actor.schema.fields,
|
||||
systemFields: actor.system.schema.fields,
|
||||
isEditable: this.isEditable,
|
||||
isEditMode: this.isEditMode,
|
||||
isPlayMode: this.isPlayMode,
|
||||
isGM: game.user.isGM,
|
||||
config: game.system.mournbladecyd2.config,
|
||||
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||
actor.system.biodata?.description || "", { async: true }
|
||||
),
|
||||
enrichedHabitat: await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||
actor.system.biodata?.habitat || "", { async: true }
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_onRender(context, options) {
|
||||
super._onRender(context, options);
|
||||
this.#dragDrop.forEach((d) => d.bind(this.element));
|
||||
|
||||
this.element.querySelectorAll('.edit-item-data').forEach(element => {
|
||||
element.addEventListener('change', async (event) => {
|
||||
const target = event.currentTarget;
|
||||
const itemElement = target.closest('[data-item-id]');
|
||||
if (!itemElement) return;
|
||||
const itemId = itemElement.dataset.itemId;
|
||||
const itemField = target.dataset.itemField;
|
||||
const dataType = target.dataset.dtype;
|
||||
const value = dataType === "Number" ? Number(target.value) : target.value;
|
||||
const item = this.document.items.get(itemId);
|
||||
if (item) await item.update({ [`system.${itemField}`]: value });
|
||||
});
|
||||
});
|
||||
|
||||
// Tab navigation
|
||||
const nav = this.element.querySelector('nav.tabs[data-group]');
|
||||
if (nav) {
|
||||
const group = nav.dataset.group;
|
||||
const activeTab = this.tabGroups[group] || "stats";
|
||||
nav.querySelectorAll('[data-tab]').forEach(link => {
|
||||
const tab = link.dataset.tab;
|
||||
link.classList.toggle('active', tab === activeTab);
|
||||
link.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
this.tabGroups[group] = tab;
|
||||
this.render();
|
||||
});
|
||||
});
|
||||
this.element.querySelectorAll(`[data-group="${group}"][data-tab]`).forEach(content => {
|
||||
content.classList.toggle('active', content.dataset.tab === activeTab);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#createDragDropHandlers() {
|
||||
return this.options.dragDrop.map(d => {
|
||||
d.permissions = {
|
||||
dragstart: this._canDragStart.bind(this),
|
||||
drop: this._canDragDrop.bind(this)
|
||||
};
|
||||
d.callbacks = {
|
||||
dragstart: this._onDragStart.bind(this),
|
||||
dragover: this._onDragOver.bind(this),
|
||||
drop: this._onDrop.bind(this)
|
||||
};
|
||||
return new foundry.applications.ux.DragDrop.implementation(d);
|
||||
});
|
||||
}
|
||||
|
||||
_canDragStart(selector) { return this.isEditable; }
|
||||
_canDragDrop(selector) { return this.isEditable; }
|
||||
|
||||
_onDragStart(event) {
|
||||
const li = event.currentTarget.closest("[data-item-id]");
|
||||
if (!li) return;
|
||||
const item = this.document.items.get(li.dataset.itemId);
|
||||
if (!item) return;
|
||||
event.dataTransfer.setData("text/plain", JSON.stringify(item.toDragData()));
|
||||
}
|
||||
|
||||
_onDragOver(event) { event.preventDefault(); }
|
||||
|
||||
async _onDrop(event) {
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
if (data?.type === "Item") return this._onDropItem(event, data);
|
||||
if (data?.type === "Actor") return this._onDropActor(event, data);
|
||||
}
|
||||
|
||||
async _onDropItem(event, data) {
|
||||
if (!this.document.isOwner) return;
|
||||
const item = await Item.fromDropData(data);
|
||||
if (!item) return;
|
||||
if (this.document.uuid === item.parent?.uuid) return;
|
||||
await this.document.createEmbeddedDocuments("Item", [item.toObject()]);
|
||||
this.render();
|
||||
}
|
||||
|
||||
async _onDropActor(event, data) {}
|
||||
|
||||
// #region Actions
|
||||
|
||||
static async #onEditImage(event) {
|
||||
const fp = new FilePicker({
|
||||
type: "image",
|
||||
current: this.document.img,
|
||||
callback: (path) => this.document.update({ img: path })
|
||||
});
|
||||
fp.browse();
|
||||
}
|
||||
|
||||
static #onToggleSheet(event) {
|
||||
this._sheetMode = this.isEditMode
|
||||
? this.constructor.SHEET_MODES.PLAY
|
||||
: this.constructor.SHEET_MODES.EDIT;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #onEditItem(event, target) {
|
||||
const li = target.closest(".item");
|
||||
const item = this.document.items.get(li?.dataset.itemId);
|
||||
item?.sheet.render(true);
|
||||
}
|
||||
|
||||
static async #onDeleteItem(event, target) {
|
||||
const li = target.closest(".item");
|
||||
await MournbladeCYD2Utility.confirmDelete(this, li);
|
||||
}
|
||||
|
||||
static async #onCreateItem(event, target) {
|
||||
const itemType = target.dataset.type;
|
||||
await this.document.createEmbeddedDocuments("Item", [{ name: `Nouveau ${itemType}`, type: itemType }], { renderSheet: true });
|
||||
}
|
||||
|
||||
static async #onEquipItem(event, target) {
|
||||
const li = target.closest(".item");
|
||||
const item = this.document.items.get(li?.dataset.itemId);
|
||||
if (item) await item.update({ "system.equipped": !item.system.equipped });
|
||||
}
|
||||
|
||||
static async #onModifyQuantity(event, target) {
|
||||
const li = target.closest('[data-item-id]');
|
||||
const item = this.document.items.get(li?.dataset.itemId);
|
||||
const value = Number.parseInt(target.dataset.quantiteValue);
|
||||
if (item) {
|
||||
const newQty = Math.max(0, (item.system.quantite || 0) + value);
|
||||
await item.update({ "system.quantite": newQty });
|
||||
}
|
||||
}
|
||||
|
||||
static async #onModifyAdversite(event, target) {
|
||||
const li = target.closest('[data-adversite]');
|
||||
const adversiteKey = li?.dataset.adversite;
|
||||
if (!adversiteKey) return;
|
||||
const value = Number.parseInt(target.dataset.adversiteValue);
|
||||
const current = this.document.system.adversite[adversiteKey] || 0;
|
||||
await this.document.update({ [`system.adversite.${adversiteKey}`]: Math.max(0, current + value) });
|
||||
}
|
||||
|
||||
static async #onModifySante(event, target) {
|
||||
const type = target.dataset.type;
|
||||
const value = Number.parseInt(target.dataset.value);
|
||||
const current = this.document.system.sante[type] || 0;
|
||||
await this.document.update({ [`system.sante.${type}`]: Math.max(0, current + value) });
|
||||
}
|
||||
|
||||
static async #onModifyAme(event, target) {
|
||||
const value = Number.parseInt(target.dataset.value);
|
||||
const current = this.document.system.ame.nbame || 0;
|
||||
await this.document.update({ "system.ame.nbame": Math.max(0, current + value) });
|
||||
}
|
||||
|
||||
static async #onRollAttribut(event, target) {
|
||||
await this.document.rollAttribut(target.dataset.attrKey);
|
||||
}
|
||||
|
||||
static async #onRollCompetence(event, target) {
|
||||
const li = target.closest('[data-item-id]');
|
||||
await this.document.rollCompetence(target.dataset.attrKey, li?.dataset.itemId);
|
||||
}
|
||||
|
||||
static async #onRollRune(event, target) {
|
||||
const li = target.closest('[data-item-id]');
|
||||
await this.document.rollRune(li?.dataset.itemId);
|
||||
}
|
||||
|
||||
static async #onRollArmeOffensif(event, target) {
|
||||
const li = target.closest('[data-item-id]');
|
||||
await this.document.rollArmeOffensif(target.dataset.armeId ?? li?.dataset.itemId);
|
||||
}
|
||||
|
||||
static async #onRollArmeSpecial(event, target) {
|
||||
const li = target.closest('[data-item-id]');
|
||||
await this.document.rollArmeSpecial(target.dataset.armeId ?? li?.dataset.itemId);
|
||||
}
|
||||
|
||||
static async #onRollArmeDegats(event, target) {
|
||||
const li = target.closest('[data-item-id]');
|
||||
await this.document.rollArmeDegats(target.dataset.armeId ?? li?.dataset.itemId);
|
||||
}
|
||||
|
||||
static async #onRollAssommer(event, target) {
|
||||
await this.document.rollAssommer();
|
||||
}
|
||||
|
||||
static async #onRollCoupBas(event, target) {
|
||||
await this.document.rollCoupBas();
|
||||
}
|
||||
|
||||
static async #onRollImmobiliser(event, target) {
|
||||
await this.document.rollImmobiliser();
|
||||
}
|
||||
|
||||
static async #onRollRepousser(event, target) {
|
||||
await this.document.rollRepousser();
|
||||
}
|
||||
|
||||
static async #onRollDesengager(event, target) {
|
||||
await this.document.rollDesengager();
|
||||
}
|
||||
|
||||
static async #onRollInitiative(event, target) {
|
||||
await this.document.rollAttribut("adr", true);
|
||||
}
|
||||
|
||||
static async #onRollFuir(event, target) {
|
||||
await this.document.rollFuir();
|
||||
}
|
||||
|
||||
// #region ActiveEffects Management
|
||||
|
||||
/**
|
||||
* Crée un nouvel effet actif
|
||||
* @param {Event} event - Événement
|
||||
* @param {HTMLElement} target - Éléments cible
|
||||
* @private
|
||||
*/
|
||||
static async #onCreateEffect(event, target) {
|
||||
event.preventDefault();
|
||||
if (!this.isEditable || !this.document) return;
|
||||
|
||||
try {
|
||||
// Créer les données par défaut pour un nouvel effet
|
||||
const defaultEffectData = {
|
||||
name: game.i18n.localize("EFFECT.new") || "Nouvel Effet",
|
||||
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/capacite.webp",
|
||||
description: "",
|
||||
changes: [],
|
||||
disabled: false,
|
||||
duration: {},
|
||||
origin: this.document.uuid,
|
||||
tint: "",
|
||||
transfer: true,
|
||||
flags: {}
|
||||
};
|
||||
|
||||
// Créer directement l'effet actif sur l'acteur
|
||||
const [effect] = await this.document.createEmbeddedDocuments("ActiveEffect", [defaultEffectData]);
|
||||
|
||||
if (effect) {
|
||||
// Ouvrir la feuille d'édition de l'effet
|
||||
effect.sheet.render(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("MournbladeCYD2 | Failed to create effect:", error);
|
||||
ui.notifications.error(
|
||||
game.i18n.localize("EFFECT.createError") ||
|
||||
"Erreur lors de la création de l'effet"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Édite un effet actif existant
|
||||
* @param {Event} event - Événement
|
||||
* @param {HTMLElement} target - Éléments cible
|
||||
* @private
|
||||
*/
|
||||
static async #onEditEffect(event, target) {
|
||||
event.preventDefault();
|
||||
if (!this.isEditable || !this.document) return;
|
||||
|
||||
const effectId = target?.dataset?.effectId;
|
||||
if (!effectId) return;
|
||||
|
||||
const effect = this.document.effects.get(effectId);
|
||||
if (effect) {
|
||||
// Ouvrir la sheet de l'effet pour édition
|
||||
effect.sheet.render(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime un effet actif
|
||||
* @param {Event} event - Événement
|
||||
* @param {HTMLElement} target - Éléments cible
|
||||
* @private
|
||||
*/
|
||||
static async #onDeleteEffect(event, target) {
|
||||
event.preventDefault();
|
||||
if (!this.isEditable || !this.document) return;
|
||||
|
||||
const effectId = target?.dataset?.effectId;
|
||||
if (!effectId) return;
|
||||
|
||||
const effect = this.document.effects.get(effectId);
|
||||
if (effect) {
|
||||
const effectName = effect.name;
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
title: game.i18n.localize("EFFECT.deleteConfirm") || "Supprimer l'effet",
|
||||
content: game.i18n.localize("EFFECT.deleteConfirmText", {name: effectName}) ||
|
||||
`Êtes-vous sûr de vouloir supprimer l'effet "${effectName}" ?`
|
||||
});
|
||||
|
||||
if (confirmed) {
|
||||
try {
|
||||
await this.document.deleteEmbeddedDocuments("ActiveEffect", [effectId]);
|
||||
} catch (error) {
|
||||
console.error("MournbladeCYD2 | Failed to delete effect:", error);
|
||||
ui.notifications.error(
|
||||
game.i18n.localize("EFFECT.deleteError") ||
|
||||
"Erreur lors de la suppression de l'effet"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle l'état actif/désactivé d'un effet
|
||||
* @param {Event} event - Événement
|
||||
* @param {HTMLElement} target - Éléments cible
|
||||
* @private
|
||||
*/
|
||||
static async #onToggleEffect(event, target) {
|
||||
event.preventDefault();
|
||||
if (!this.isEditable || !this.document) return;
|
||||
|
||||
const effectId = target?.dataset?.effectId;
|
||||
if (!effectId) return;
|
||||
|
||||
const effect = this.document.effects.get(effectId);
|
||||
if (effect) {
|
||||
try {
|
||||
await effect.update({ disabled: !effect.disabled });
|
||||
} catch (error) {
|
||||
console.error("MournbladeCYD2 | Failed to toggle effect:", error);
|
||||
ui.notifications.error(
|
||||
game.i18n.localize("EFFECT.toggleError") ||
|
||||
"Erreur lors du basculement de l'effet"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique un effet à partir d'un item
|
||||
* @param {Event} event - Événement
|
||||
* @param {HTMLElement} target - Éléments cible
|
||||
* @private
|
||||
*/
|
||||
static async #onApplyEffect(event, target) {
|
||||
event.preventDefault();
|
||||
const effectId = target?.dataset?.effectId;
|
||||
if (!effectId) return;
|
||||
|
||||
const effect = this.document.effects.get(effectId);
|
||||
if (effect) {
|
||||
await effect.apply();
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Talent Management
|
||||
|
||||
/**
|
||||
* Toggle the used state of a talent item
|
||||
* @param {Event} event - The click event
|
||||
* @param {HTMLElement} target - The clicked element
|
||||
*/
|
||||
static async #onToggleTalentUsed(event, target) {
|
||||
event.preventDefault();
|
||||
const li = target.closest('[data-item-id]');
|
||||
const item = this.document.items.get(li?.dataset.itemId);
|
||||
if (item) await item.update({ "system.used": !item.system.used });
|
||||
}
|
||||
|
||||
// #endregion
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
import { MournbladeCYD2Utility } from "../../mournblade-cyd2-utility.js";
|
||||
|
||||
export default class MournbladeCYD2ItemSheetV2 extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
|
||||
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
this.#dragDrop = this.#createDragDropHandlers();
|
||||
}
|
||||
|
||||
#dragDrop;
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["fvtt-mournblade-cyd-2-0", "item"],
|
||||
position: {
|
||||
width: 620,
|
||||
height: 600,
|
||||
},
|
||||
form: {
|
||||
submitOnChange: true,
|
||||
},
|
||||
window: {
|
||||
resizable: true,
|
||||
},
|
||||
tabs: [
|
||||
{
|
||||
navSelector: 'nav[data-group="primary"]',
|
||||
contentSelector: "section.sheet-body",
|
||||
initial: "description",
|
||||
},
|
||||
],
|
||||
dragDrop: [{ dragSelector: "[data-drag]", dropSelector: null }],
|
||||
actions: {
|
||||
editImage: MournbladeCYD2ItemSheetV2.#onEditImage,
|
||||
postItem: MournbladeCYD2ItemSheetV2.#onPostItem,
|
||||
addPredilection: MournbladeCYD2ItemSheetV2.#onAddPredilection,
|
||||
deletePredilection: MournbladeCYD2ItemSheetV2.#onDeletePredilection,
|
||||
addAutomation: MournbladeCYD2ItemSheetV2.#onAddAutomation,
|
||||
deleteAutomation: MournbladeCYD2ItemSheetV2.#onDeleteAutomation,
|
||||
// Actions pour les ActiveEffects
|
||||
createEffect: MournbladeCYD2ItemSheetV2.#onCreateEffect,
|
||||
editEffect: MournbladeCYD2ItemSheetV2.#onEditEffect,
|
||||
deleteEffect: MournbladeCYD2ItemSheetV2.#onDeleteEffect,
|
||||
toggleEffect: MournbladeCYD2ItemSheetV2.#onToggleEffect,
|
||||
applyEffect: MournbladeCYD2ItemSheetV2.#onApplyEffect,
|
||||
},
|
||||
};
|
||||
|
||||
tabGroups = { primary: "description" };
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
return {
|
||||
fields: this.document.schema.fields,
|
||||
systemFields: this.document.system.schema.fields,
|
||||
item: this.document,
|
||||
system: this.document.system,
|
||||
source: this.document.toObject(),
|
||||
config: game.system.mournbladecyd2.config,
|
||||
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||
this.document.system.description || "", { async: true }
|
||||
),
|
||||
isEditMode: this.isEditMode,
|
||||
isEditable: this.isEditable,
|
||||
editable: this.isEditable,
|
||||
owner: this.document.isOwner,
|
||||
isGM: game.user.isGM,
|
||||
};
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_onRender(context, options) {
|
||||
super._onRender(context, options);
|
||||
this.#dragDrop.forEach((d) => d.bind(this.element));
|
||||
|
||||
// Tab navigation
|
||||
const nav = this.element.querySelector('nav.tabs[data-group]');
|
||||
if (nav) {
|
||||
const group = nav.dataset.group;
|
||||
const activeTab = this.tabGroups[group] || "description";
|
||||
nav.querySelectorAll('[data-tab]').forEach(link => {
|
||||
const tab = link.dataset.tab;
|
||||
link.classList.toggle('active', tab === activeTab);
|
||||
link.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
this.tabGroups[group] = tab;
|
||||
this.render();
|
||||
});
|
||||
});
|
||||
this.element.querySelectorAll(`[data-group="${group}"][data-tab]`).forEach(content => {
|
||||
content.classList.toggle('active', content.dataset.tab === activeTab);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#createDragDropHandlers() {
|
||||
return this.options.dragDrop.map(d => {
|
||||
d.permissions = { dragstart: () => this.isEditable };
|
||||
d.callbacks = { dragstart: this._onDragStart.bind(this) };
|
||||
return new foundry.applications.ux.DragDrop.implementation(d);
|
||||
});
|
||||
}
|
||||
|
||||
_onDragStart(event) {}
|
||||
|
||||
// #region Actions
|
||||
|
||||
static async #onEditImage(event) {
|
||||
const fp = new FilePicker({
|
||||
type: "image",
|
||||
current: this.document.img,
|
||||
callback: (path) => this.document.update({ img: path })
|
||||
});
|
||||
fp.browse();
|
||||
}
|
||||
|
||||
static async #onPostItem(event) {
|
||||
event.preventDefault();
|
||||
const item = this.document;
|
||||
const chatData = foundry.utils.duplicate(item);
|
||||
if (item.actor) {
|
||||
chatData.actor = { id: item.actor.id };
|
||||
}
|
||||
if (chatData.img?.includes("/blank.png")) {
|
||||
chatData.img = null;
|
||||
}
|
||||
chatData.jsondata = JSON.stringify({
|
||||
compendium: "postedItem",
|
||||
payload: chatData,
|
||||
});
|
||||
const html = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/fvtt-mournblade-cyd-2-0/templates/post-item.hbs",
|
||||
chatData
|
||||
);
|
||||
ChatMessage.create({ user: game.user.id, content: html });
|
||||
}
|
||||
|
||||
static async #onAddPredilection(event) {
|
||||
const preds = foundry.utils.duplicate(this.document.system.predilections || []);
|
||||
preds.push({ id: foundry.utils.randomID(), name: "Nouvelle prédilection", description: "", acquise: false, maitrise: false, used: false });
|
||||
await this.document.update({ "system.predilections": preds });
|
||||
}
|
||||
|
||||
static async #onDeletePredilection(event, target) {
|
||||
const idx = Number(target.dataset.predilectionIndex);
|
||||
const preds = foundry.utils.duplicate(this.document.system.predilections || []);
|
||||
preds.splice(idx, 1);
|
||||
await this.document.update({ "system.predilections": preds });
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
static async #onAddAutomation(event) {
|
||||
const automations = foundry.utils.duplicate(this.document.system.automations || []);
|
||||
automations.push({
|
||||
id: foundry.utils.randomID(),
|
||||
eventtype: "on-drop",
|
||||
name: "",
|
||||
bonusname: "vigueur",
|
||||
bonus: 0,
|
||||
competence: "",
|
||||
minLevel: 0,
|
||||
baCost: 0
|
||||
});
|
||||
await this.document.update({
|
||||
"system.automations": automations,
|
||||
"system.isautomated": true
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
static async #onDeleteAutomation(event, target) {
|
||||
const idx = Number(target.dataset.automationIndex);
|
||||
const automations = foundry.utils.duplicate(this.document.system.automations || []);
|
||||
automations.splice(idx, 1);
|
||||
await this.document.update({ "system.automations": automations });
|
||||
if (automations.length === 0) {
|
||||
await this.document.update({ "system.isautomated": false });
|
||||
}
|
||||
}
|
||||
|
||||
// #region ActiveEffects Management
|
||||
|
||||
/**
|
||||
* Crée un nouvel effet actif sur l'item
|
||||
* @param {Event} event - Événement
|
||||
* @param {HTMLElement} target - Éléments cible
|
||||
* @private
|
||||
*/
|
||||
static async #onCreateEffect(event, target) {
|
||||
event.preventDefault();
|
||||
if (!this.isEditable || !this.document) return;
|
||||
|
||||
try {
|
||||
// Créer les données par défaut pour un nouvel effet
|
||||
const defaultEffectData = {
|
||||
name: game.i18n.localize("EFFECT.new") || "Nouvel Effet",
|
||||
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/capacite.webp",
|
||||
description: "",
|
||||
changes: [],
|
||||
disabled: false,
|
||||
duration: {},
|
||||
origin: this.document.uuid,
|
||||
tint: "",
|
||||
transfer: false,
|
||||
flags: {}
|
||||
};
|
||||
|
||||
// Créer directement l'effet actif sur l'item
|
||||
const [effect] = await this.document.createEmbeddedDocuments("ActiveEffect", [defaultEffectData]);
|
||||
|
||||
if (effect) {
|
||||
// Ouvrir la feuille d'édition de l'effet
|
||||
effect.sheet.render(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("MournbladeCYD2 | Failed to create effect:", error);
|
||||
ui.notifications.error(
|
||||
game.i18n.localize("EFFECT.createError") ||
|
||||
"Erreur lors de la création de l'effet"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Édite un effet actif existant sur l'item
|
||||
* @param {Event} event - Événement
|
||||
* @param {HTMLElement} target - Éléments cible
|
||||
* @private
|
||||
*/
|
||||
static async #onEditEffect(event, target) {
|
||||
event.preventDefault();
|
||||
if (!this.isEditable || !this.document) return;
|
||||
|
||||
const effectId = target?.dataset?.effectId;
|
||||
if (!effectId) return;
|
||||
|
||||
const effect = this.document.effects.get(effectId);
|
||||
if (effect) {
|
||||
// Ouvrir la sheet de l'effet pour édition
|
||||
effect.sheet.render(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime un effet actif de l'item
|
||||
* @param {Event} event - Événement
|
||||
* @param {HTMLElement} target - Éléments cible
|
||||
* @private
|
||||
*/
|
||||
static async #onDeleteEffect(event, target) {
|
||||
event.preventDefault();
|
||||
if (!this.isEditable || !this.document) return;
|
||||
|
||||
const effectId = target?.dataset?.effectId;
|
||||
if (!effectId) return;
|
||||
|
||||
const effect = this.document.effects.get(effectId);
|
||||
if (effect) {
|
||||
const effectName = effect.name;
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
title: game.i18n.localize("EFFECT.deleteConfirm") || "Supprimer l'effet",
|
||||
content: game.i18n.localize("EFFECT.deleteConfirmText", {name: effectName}) ||
|
||||
`Êtes-vous sûr de vouloir supprimer l'effet "${effectName}" ?`
|
||||
});
|
||||
|
||||
if (confirmed) {
|
||||
try {
|
||||
await this.document.deleteEmbeddedDocuments("ActiveEffect", [effectId]);
|
||||
} catch (error) {
|
||||
console.error("MournbladeCYD2 | Failed to delete effect:", error);
|
||||
ui.notifications.error(
|
||||
game.i18n.localize("EFFECT.deleteError") ||
|
||||
"Erreur lors de la suppression de l'effet"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle l'état actif/désactivé d'un effet sur l'item
|
||||
* @param {Event} event - Événement
|
||||
* @param {HTMLElement} target - Éléments cible
|
||||
* @private
|
||||
*/
|
||||
static async #onToggleEffect(event, target) {
|
||||
event.preventDefault();
|
||||
if (!this.isEditable || !this.document) return;
|
||||
|
||||
const effectId = target?.dataset?.effectId;
|
||||
if (!effectId) return;
|
||||
|
||||
const effect = this.document.effects.get(effectId);
|
||||
if (effect) {
|
||||
try {
|
||||
await effect.update({ disabled: !effect.disabled });
|
||||
} catch (error) {
|
||||
console.error("MournbladeCYD2 | Failed to toggle effect:", error);
|
||||
ui.notifications.error(
|
||||
game.i18n.localize("EFFECT.toggleError") ||
|
||||
"Erreur lors du basculement de l'effet"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique un effet à partir de l'item à un acteur sélectionné
|
||||
* Cette méthode n'est pas directement utilisable sans acteur cible
|
||||
* Elle est gardée pour compatibilité mais devrait être appelée avec un acteur
|
||||
* @param {Event} event - Événement
|
||||
* @param {HTMLElement} target - Éléments cible
|
||||
* @private
|
||||
*/
|
||||
static async #onApplyEffect(event, target) {
|
||||
event.preventDefault();
|
||||
if (!this.isEditable) return;
|
||||
|
||||
const effectId = target?.dataset?.effectId;
|
||||
if (!effectId || !this.document) return;
|
||||
|
||||
const effect = this.document.effects.get(effectId);
|
||||
if (!effect) return;
|
||||
|
||||
// Pour appliquer un effet d'item, il faut sélectionner un acteur
|
||||
// Cette méthode est placeholders - l'application devrait être gérée par drag-drop
|
||||
// ou par une action spécifique qui demande à l'utilisateur de sélectionner un acteur
|
||||
ui.notifications.warn(
|
||||
game.i18n.localize("EFFECT.selectActor") ||
|
||||
"Veuillez d'abord sélectionner un acteur pour appliquer cet effet."
|
||||
);
|
||||
}
|
||||
|
||||
// #endregion
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2ArmeSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "arme"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.arme",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-arme-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
#getTabs() {
|
||||
const tabs = {
|
||||
details: { id: "details", group: "primary", label: "Détails" },
|
||||
description: { id: "description", group: "primary", label: "Description" }
|
||||
};
|
||||
for (const v of Object.values(tabs)) {
|
||||
v.active = this.tabGroups[v.group] === v.id;
|
||||
v.cssClass = v.active ? "active" : "";
|
||||
}
|
||||
return tabs;
|
||||
}
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
context.tabs = this.#getTabs();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2CapaciteAutomataSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "capaciteautomata"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.capaciteautomata",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-capaciteautomata-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2CompetenceSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "competence"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.competence",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-competence-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
#getTabs() {
|
||||
const tabs = {
|
||||
details: { id: "details", group: "primary", label: "Détails" },
|
||||
description: { id: "description", group: "primary", label: "Description" }
|
||||
};
|
||||
for (const v of Object.values(tabs)) {
|
||||
v.active = this.tabGroups[v.group] === v.id;
|
||||
v.cssClass = v.active ? "active" : "";
|
||||
}
|
||||
return tabs;
|
||||
}
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
context.tabs = this.#getTabs();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import MournbladeCYD2ActorSheetV2 from "./base-actor-sheet.mjs";
|
||||
import { MournbladeCYD2Utility } from "../../mournblade-cyd2-utility.js";
|
||||
|
||||
export default class MournbladeCYD2CreatureSheet extends MournbladeCYD2ActorSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "creature"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Actor.creature",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
sheet: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/creature-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
tabGroups = { primary: "principal" };
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
const actor = this.document;
|
||||
|
||||
context.skills = actor.getSkills?.() ?? [];
|
||||
context.combativiteList = MournbladeCYD2Utility.getCombativiteList(actor.system.sante?.nbcombativite || 0);
|
||||
context.ameList = MournbladeCYD2Utility.getAmeList(actor.system.ame.nbame, actor.getAmeMax?.() ?? 0);
|
||||
context.ameMaxList = MournbladeCYD2Utility.getAmeMaxList(actor.system.ame.nbame);
|
||||
context.armes = foundry.utils.duplicate(actor.getWeapons?.() ?? []);
|
||||
context.protections = foundry.utils.duplicate(actor.getArmors?.() ?? []);
|
||||
context.runes = foundry.utils.duplicate(actor.getRunes?.() ?? []);
|
||||
context.combat = actor.getCombatValues?.() ?? {};
|
||||
context.equipements = foundry.utils.duplicate(actor.getEquipments?.() ?? []);
|
||||
context.monnaies = foundry.utils.duplicate(actor.getMonnaies?.() ?? []);
|
||||
context.talents = foundry.utils.duplicate(actor.getTalents?.() ?? []);
|
||||
context.traitsChaotiques = foundry.utils.duplicate(actor.getTraitsChaotiques?.() ?? []);
|
||||
context.traitsEspeces = foundry.utils.duplicate(actor.getTraitsEspeces?.() ?? []);
|
||||
context.protectionTotal = actor.getProtectionTotal?.() ?? 0;
|
||||
context.adversiteTotal = (actor.system.adversite?.bleue || 0) + (actor.system.adversite?.rouge || 0) + (actor.system.adversite?.noire || 0);
|
||||
|
||||
// Utiliser les valeurs manuelles si elles existent, sinon les valeurs calculées
|
||||
context.initiative = actor.system.combat?.inittotal !== undefined ? actor.system.combat.inittotal : (context.combat?.initTotal ?? 0);
|
||||
context.combat.defenseTotal = actor.system.combat?.defensetotal !== undefined ? actor.system.combat.defensetotal : context.combat.defenseTotal;
|
||||
context.protectionTotal = actor.system.combat?.protectiontotal !== undefined ? actor.system.combat.protectiontotal : context.protectionTotal;
|
||||
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2DonSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "don"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.don",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-don-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
context.owner = this.document.isOwner;
|
||||
context.editable = this.isEditable;
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2EquipementSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "equipement"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.equipement",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-equipement-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2HistoriqueSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "historique"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.historique",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-historique-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2MonnaieSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "monnaie"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.monnaie",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-monnaie-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2PacteSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "pacte"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.pacte",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-pacte-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
import MournbladeCYD2ActorSheetV2 from "./base-actor-sheet.mjs";
|
||||
import { MournbladeCYD2Utility } from "../../mournblade-cyd2-utility.js";
|
||||
|
||||
export default class MournbladeCYD2PersonnageSheet extends MournbladeCYD2ActorSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Actor.personnage",
|
||||
},
|
||||
actions: {
|
||||
...super.DEFAULT_OPTIONS.actions,
|
||||
removeLinkedActor: MournbladeCYD2PersonnageSheet.#onRemoveLinkedActor,
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
sheet: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/actor-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
tabGroups = { primary: "principal" };
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
const actor = this.document;
|
||||
|
||||
context.combativiteList = MournbladeCYD2Utility.getCombativiteList(actor.system.sante.nbcombativite);
|
||||
context.ameList = MournbladeCYD2Utility.getAmeList(actor.system.ame.nbame, actor.getAmeMax?.() ?? 0);
|
||||
context.ameMaxList = MournbladeCYD2Utility.getAmeMaxList(actor.system.ame.nbame);
|
||||
|
||||
context.skills = actor.getSkills?.() ?? [];
|
||||
context.armes = foundry.utils.duplicate(actor.getWeapons?.() ?? []);
|
||||
context.protections = foundry.utils.duplicate(actor.getArmors?.() ?? []);
|
||||
context.dons = foundry.utils.duplicate(actor.getDons?.() ?? []);
|
||||
context.pactes = foundry.utils.duplicate(actor.getPactes?.() ?? []);
|
||||
context.tendances = foundry.utils.duplicate(actor.getTendances?.() ?? []);
|
||||
context.runes = foundry.utils.duplicate(actor.getRunes?.() ?? []);
|
||||
context.traitsChaotiques = foundry.utils.duplicate(actor.getTraitsChaotiques?.() ?? []);
|
||||
context.traitsEspeces = foundry.utils.duplicate(actor.getTraitsEspeces?.() ?? []);
|
||||
context.profil = actor.getProfil?.() ?? null;
|
||||
context.historiques = foundry.utils.duplicate(actor.getHistoriques?.() ?? []);
|
||||
context.combat = actor.getCombatValues?.() ?? {};
|
||||
context.equipements = foundry.utils.duplicate(actor.getEquipments?.() ?? []);
|
||||
context.monnaies = foundry.utils.duplicate(actor.getMonnaies?.() ?? []);
|
||||
context.runeEffects = foundry.utils.duplicate(actor.getRuneEffects?.() ?? []);
|
||||
context.ressources = foundry.utils.duplicate(actor.getRessources?.() ?? []);
|
||||
context.talents = foundry.utils.duplicate(actor.getTalents?.() ?? []);
|
||||
context.protectionTotal = actor.getProtectionTotal?.() ?? 0;
|
||||
context.adversiteTotal = (actor.system.adversite?.bleue || 0) + (actor.system.adversite?.rouge || 0) + (actor.system.adversite?.noire || 0);
|
||||
context.richesse = actor.computeRichesse?.() ?? { po: 0, pa: 0, sc: 0, valueSC: 0 };
|
||||
context.valeurEquipement = actor.computeValeurEquipement?.() ?? { po: 0, pa: 0, sc: 0, valueSC: 0 };
|
||||
|
||||
// Prepare sorcellerie linked actors for display
|
||||
context.creaturesInvoqueesActors = await this._getLinkedActors(actor.system.sorcellerie.creaturesinvoquees);
|
||||
context.demonsLiesActors = await this._getLinkedActors(actor.system.sorcellerie.demonslies);
|
||||
context.enchantementsActors = await this._getLinkedActors(actor.system.sorcellerie.enchantements);
|
||||
|
||||
// Prepare enriched HTML for sorcellerie fields
|
||||
context.enrichedInvocationsEnCours = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||
actor.system.sorcellerie?.invocationsencours || "", { async: true }
|
||||
);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get actor objects from UUID references
|
||||
* @param {string[]} uuids - Array of actor UUIDs
|
||||
* @returns {Promise<Actor[]>} Array of actor documents
|
||||
*/
|
||||
async _getLinkedActors(uuids) {
|
||||
if (!uuids || !Array.isArray(uuids) || uuids.length === 0) return [];
|
||||
|
||||
const actors = [];
|
||||
for (const uuid of uuids) {
|
||||
const actor = await fromUuid(uuid);
|
||||
if (actor) {
|
||||
actors.push(actor);
|
||||
}
|
||||
}
|
||||
return actors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle actor drop on the sheet
|
||||
* @override
|
||||
*/
|
||||
async _onDropActor(event, data) {
|
||||
if (!this.document.isOwner) return;
|
||||
|
||||
const droppedActor = await Actor.fromDropData(data);
|
||||
if (!droppedActor) return;
|
||||
|
||||
// Only allow dropping Creature type actors
|
||||
if (droppedActor.type !== 'creature') return;
|
||||
|
||||
// Determine which list to add to based on creature subtype
|
||||
let fieldPath = '';
|
||||
const subType = droppedActor.system.soustype || 'creature';
|
||||
|
||||
switch (subType) {
|
||||
case 'demon':
|
||||
fieldPath = 'system.sorcellerie.demonslies';
|
||||
break;
|
||||
case 'automata':
|
||||
fieldPath = 'system.sorcellerie.enchantements';
|
||||
break;
|
||||
case 'creature':
|
||||
default:
|
||||
fieldPath = 'system.sorcellerie.creaturesinvoquees';
|
||||
break;
|
||||
}
|
||||
|
||||
// Add the actor UUID to the appropriate array
|
||||
const currentValue = foundry.utils.getProperty(this.document.system, fieldPath) || [];
|
||||
|
||||
// Avoid duplicates
|
||||
if (!currentValue.includes(droppedActor.uuid)) {
|
||||
currentValue.push(droppedActor.uuid);
|
||||
await this.document.update({ [fieldPath]: currentValue });
|
||||
}
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle removal of a linked actor from sorcellerie sections
|
||||
* @param {Event} event - The click event
|
||||
* @param {HTMLElement} target - The clicked element
|
||||
*/
|
||||
static async #onRemoveLinkedActor(event, target) {
|
||||
const li = target.closest(".item");
|
||||
if (!li) return;
|
||||
|
||||
const field = target.dataset.field;
|
||||
const uuid = target.dataset.uuid;
|
||||
|
||||
if (!field || !uuid) return;
|
||||
|
||||
const fieldPath = `system.sorcellerie.${field}`;
|
||||
const currentValue = foundry.utils.getProperty(this.document.system, fieldPath) || [];
|
||||
|
||||
// Remove the UUID from the array
|
||||
const newValue = currentValue.filter(u => u !== uuid);
|
||||
|
||||
await this.document.update({ [fieldPath]: newValue });
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle click on linked actor name to open its sheet
|
||||
* @param {Event} event - The click event
|
||||
*/
|
||||
static async #onOpenLinkedActor(event) {
|
||||
const target = event.target.closest(".linked-actor-name");
|
||||
if (!target) return;
|
||||
|
||||
const uuid = target.dataset.actorUuid;
|
||||
if (!uuid) return;
|
||||
|
||||
const actor = await fromUuid(uuid);
|
||||
if (actor) {
|
||||
actor.sheet.render(true);
|
||||
}
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_onRender(context, options) {
|
||||
super._onRender(context, options);
|
||||
|
||||
// Add click handler for linked actor names
|
||||
this.element.querySelectorAll('.linked-actor-name').forEach(el => {
|
||||
el.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
this.constructor.#onOpenLinkedActor(event);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2PouvoirElementaireSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "pouvoirselementaire"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.pouvoirselementaire",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-pouvoirselementaire-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2ProfilSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "profil"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.profil",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-profil-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
const item = this.document;
|
||||
|
||||
// Enrich each HTMLField for ProseMirror display
|
||||
for (const field of ["competences", "talentsinitie", "prerequisaguerri", "talentsaguerri", "prerequismaitre", "talentsmaitre"]) {
|
||||
const enrichedKey = `enriched${field.charAt(0).toUpperCase() + field.slice(1)}`;
|
||||
context[enrichedKey] = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||
item.system[field] || "", { async: true }
|
||||
);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2ProtectionSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "protection"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.protection",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-protection-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2RessourceSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "ressource"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.ressource",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-ressource-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2RuneSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "rune"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.rune",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-rune-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
#getTabs() {
|
||||
const tabs = {
|
||||
details: { id: "details", group: "primary", label: "Détails" },
|
||||
description: { id: "description", group: "primary", label: "Description" }
|
||||
};
|
||||
for (const v of Object.values(tabs)) {
|
||||
v.active = this.tabGroups[v.group] === v.id;
|
||||
v.cssClass = v.active ? "active" : "";
|
||||
}
|
||||
return tabs;
|
||||
}
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
context.tabs = this.#getTabs();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2RuneEffectSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "runeeffect"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.runeeffect",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-runeeffect-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2TalentSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "talent"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.talent",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-talent-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
#getTabs() {
|
||||
const tabs = {
|
||||
details: { id: "details", group: "primary", label: "Détails" },
|
||||
description: { id: "description", group: "primary", label: "Description" }
|
||||
};
|
||||
for (const v of Object.values(tabs)) {
|
||||
v.active = this.tabGroups[v.group] === v.id;
|
||||
v.cssClass = v.active ? "active" : "";
|
||||
}
|
||||
return tabs;
|
||||
}
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
context.tabs = this.#getTabs();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2TendanceSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "tendance"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.tendance",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-tendance-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2TraitChaotiqueSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "traitchaotique"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.traitchaotique",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-traitchaotique-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2TraitDemoniaqueSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "traitdemoniaque"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.traitdemoniaque",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-traitdemoniaque-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
|
||||
|
||||
export default class MournbladeCYD2TraitEspeceSheet extends MournbladeCYD2ItemSheetV2 {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes, "traitespece"],
|
||||
window: {
|
||||
...super.DEFAULT_OPTIONS.window,
|
||||
title: "SHEETS.Item.traitespece",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-traitespece-sheet.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext();
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Macro pour remplacer les chemins d'images dans les compendiums
|
||||
* Remplace "fvtt-hawkmoon-cyd" par "fvtt-mournblade-cyd-2-0" dans tous les champs 'img'
|
||||
*/
|
||||
|
||||
(async () => {
|
||||
// Confirmation avant de procéder
|
||||
let confirm = await Dialog.confirm({
|
||||
title: "Remplacement des chemins d'images",
|
||||
content: `<p>Cette macro va :</p>
|
||||
<ul>
|
||||
<li>Déverrouiller tous les compendiums</li>
|
||||
<li>Remplacer "fvtt-hawkmoon-cyd" par "fvtt-mournblade-cyd-2-0" dans tous les champs 'img'</li>
|
||||
<li>Reverrouiller les compendiums</li>
|
||||
</ul>
|
||||
<p><strong>Voulez-vous continuer ?</strong></p>`,
|
||||
defaultYes: false
|
||||
});
|
||||
|
||||
if (!confirm) {
|
||||
ui.notifications.info("Opération annulée");
|
||||
return;
|
||||
}
|
||||
|
||||
ui.notifications.info("Début du traitement des compendiums...");
|
||||
|
||||
let totalUpdated = 0;
|
||||
let compendiumsProcessed = 0;
|
||||
|
||||
// Parcourir tous les compendiums
|
||||
for (let pack of game.packs) {
|
||||
// Filtrer uniquement les compendiums du système mournblade
|
||||
if (!pack.metadata.packageName.includes("mournblade")) continue;
|
||||
|
||||
console.log(`Traitement du compendium: ${pack.metadata.label}`);
|
||||
compendiumsProcessed++;
|
||||
|
||||
try {
|
||||
// Unlock le compendium
|
||||
await pack.configure({ locked: false });
|
||||
|
||||
// Récupérer tous les documents du compendium
|
||||
let documents = await pack.getDocuments();
|
||||
let updatedInPack = 0;
|
||||
|
||||
for (let doc of documents) {
|
||||
let needsUpdate = false;
|
||||
let updateData = {};
|
||||
|
||||
// Vérifier le champ img principal
|
||||
if (doc.img && doc.img.includes("fvtt-hawkmoon-cyd")) {
|
||||
updateData.img = doc.img.replace(/fvtt-hawkmoon-cyd/g, "fvtt-mournblade-cyd-2-0");
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
// Pour les acteurs, vérifier aussi prototypeToken.texture.src
|
||||
if (doc.documentName === "Actor" && doc.prototypeToken?.texture?.src) {
|
||||
if (doc.prototypeToken.texture.src.includes("fvtt-hawkmoon-cyd")) {
|
||||
updateData["prototypeToken.texture.src"] = doc.prototypeToken.texture.src.replace(/fvtt-hawkmoon-cyd/g, "fvtt-mournblade-cyd-2-0");
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Pour les items contenus dans les acteurs
|
||||
if (doc.documentName === "Actor" && doc.items) {
|
||||
for (let item of doc.items) {
|
||||
if (item.img && item.img.includes("fvtt-hawkmoon-cyd")) {
|
||||
// Note: Les items embarqués nécessitent une approche différente
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pour les scènes, vérifier background.src et les tokens
|
||||
if (doc.documentName === "Scene") {
|
||||
if (doc.background?.src && doc.background.src.includes("fvtt-hawkmoon-cyd")) {
|
||||
updateData["background.src"] = doc.background.src.replace(/fvtt-hawkmoon-cyd/g, "fvtt-mournblade-cyd-2-0");
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Effectuer la mise à jour si nécessaire
|
||||
if (needsUpdate) {
|
||||
await doc.update(updateData);
|
||||
updatedInPack++;
|
||||
console.log(` - Mise à jour: ${doc.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Lock le compendium
|
||||
await pack.configure({ locked: true });
|
||||
|
||||
if (updatedInPack > 0) {
|
||||
ui.notifications.info(`${pack.metadata.label}: ${updatedInPack} document(s) mis à jour`);
|
||||
totalUpdated += updatedInPack;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Erreur lors du traitement de ${pack.metadata.label}:`, error);
|
||||
ui.notifications.error(`Erreur sur ${pack.metadata.label}: ${error.message}`);
|
||||
|
||||
// Tenter de reverrouiller en cas d'erreur
|
||||
try {
|
||||
await pack.configure({ locked: true });
|
||||
} catch (lockError) {
|
||||
console.error(`Impossible de reverrouiller ${pack.metadata.label}:`, lockError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui.notifications.info(`Traitement terminé ! ${totalUpdated} document(s) mis à jour dans ${compendiumsProcessed} compendium(s).`);
|
||||
console.log(`=== Résumé ===`);
|
||||
console.log(`Compendiums traités: ${compendiumsProcessed}`);
|
||||
console.log(`Total de documents mis à jour: ${totalUpdated}`);
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Data model pour les armes MournbladeCYD2
|
||||
*/
|
||||
import { BaseItemWithPriceDataModel } from "./base-item.mjs";
|
||||
|
||||
export default class ArmeDataModel extends BaseItemWithPriceDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
typearme: new fields.StringField({ initial: "" }),
|
||||
armenaturelle: new fields.BooleanField({ initial: false }),
|
||||
armefortune: new fields.BooleanField({ initial: false }),
|
||||
bonusmaniementoff: new fields.NumberField({ initial: 0, integer: true }),
|
||||
seuildefense: new fields.NumberField({ initial: 0, integer: true }),
|
||||
onlevelonly: new fields.BooleanField({ initial: false }),
|
||||
degats: new fields.StringField({ initial: "" }),
|
||||
deuxmains: new fields.BooleanField({ initial: false }),
|
||||
percearmure: new fields.BooleanField({ initial: false }),
|
||||
percearmurevalue: new fields.NumberField({ initial: 0, integer: true }),
|
||||
courte: new fields.NumberField({ initial: 0, integer: true }),
|
||||
moyenne: new fields.NumberField({ initial: 0, integer: true }),
|
||||
longue: new fields.NumberField({ initial: 0, integer: true }),
|
||||
tr: new fields.NumberField({ initial: 0, integer: true })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Data model de base pour les items MournbladeCYD2
|
||||
*/
|
||||
export default class BaseItemDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data model de base pour les items avec prix MournbladeCYD2
|
||||
*/
|
||||
export class BaseItemWithPriceDataModel extends BaseItemDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
prixpo: new fields.NumberField({ initial: 0, integer: true }),
|
||||
prixca: new fields.NumberField({ initial: 0, integer: true }),
|
||||
prixsc: new fields.NumberField({ initial: 0, integer: true }),
|
||||
rarete: new fields.NumberField({ initial: 0, integer: true }),
|
||||
quantite: new fields.NumberField({ initial: 1, integer: true }),
|
||||
equipped: new fields.BooleanField({ initial: false })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Data model pour les capacités d'Automata MournbladeCYD2
|
||||
*/
|
||||
export default class CapaciteAutomataDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
bonusmalus: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Data model pour les compétences MournbladeCYD2
|
||||
* Prédilections enrichies (schema Hawkmoon CYD2)
|
||||
*/
|
||||
export default class CompetenceDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
niveau: new fields.NumberField({ initial: 0, integer: true }),
|
||||
attribut1: new fields.StringField({ initial: "" }),
|
||||
attribut2: new fields.StringField({ initial: "" }),
|
||||
attribut3: new fields.StringField({ initial: "" }),
|
||||
doublebonus: new fields.BooleanField({ initial: false }),
|
||||
predilections: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
id: new fields.StringField({ initial: "" }),
|
||||
name: new fields.StringField({ initial: "" }),
|
||||
description: new fields.StringField({ initial: "" }),
|
||||
acquise: new fields.BooleanField({ initial: false }),
|
||||
maitrise: new fields.BooleanField({ initial: false }),
|
||||
used: new fields.BooleanField({ initial: false })
|
||||
}),
|
||||
{ initial: [] }
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
static migrateData(source) {
|
||||
if (Array.isArray(source.predilections)) {
|
||||
source.predilections = source.predilections.map(pred => {
|
||||
if (typeof pred === "string") return { id: "", name: pred, description: "", acquise: false, maitrise: false, used: false };
|
||||
if (!("acquise" in pred)) pred.acquise = false;
|
||||
if (!("maitrise" in pred)) pred.maitrise = false;
|
||||
if (!("id" in pred)) pred.id = "";
|
||||
if (!("description" in pred)) pred.description = "";
|
||||
return pred;
|
||||
});
|
||||
}
|
||||
return super.migrateData(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Data model pour les créatures MournbladeCYD2
|
||||
*/
|
||||
export default class CreatureDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
// Template biodata
|
||||
biodata: new fields.SchemaField({
|
||||
name: new fields.StringField({ initial: "" }),
|
||||
age: new fields.NumberField({ initial: 0, integer: true }),
|
||||
poids: new fields.StringField({ initial: "" }),
|
||||
taille: new fields.StringField({ initial: "" }),
|
||||
cheveux: new fields.StringField({ initial: "" }),
|
||||
sexe: new fields.StringField({ initial: "" }),
|
||||
yeux: new fields.StringField({ initial: "" }),
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
habitat: new fields.HTMLField({ initial: "" }),
|
||||
notes: new fields.HTMLField({ initial: "" }),
|
||||
statut: new fields.StringField({ initial: "" }),
|
||||
gmnotes: new fields.HTMLField({ initial: "" }),
|
||||
statutresistant: new fields.StringField({ initial: "commun" })
|
||||
}),
|
||||
// Template core
|
||||
subactors: new fields.ArrayField(new fields.StringField(), { initial: [] }),
|
||||
attributs: new fields.SchemaField({
|
||||
adr: new fields.SchemaField({
|
||||
label: new fields.StringField({ initial: "Adresse" }),
|
||||
labelnorm: new fields.StringField({ initial: "adresse" }),
|
||||
abbrev: new fields.StringField({ initial: "adr" }),
|
||||
value: new fields.NumberField({ initial: 1, integer: true })
|
||||
}),
|
||||
pui: new fields.SchemaField({
|
||||
label: new fields.StringField({ initial: "Puissance" }),
|
||||
labelnorm: new fields.StringField({ initial: "puissance" }),
|
||||
abbrev: new fields.StringField({ initial: "pui" }),
|
||||
value: new fields.NumberField({ initial: 1, integer: true })
|
||||
}),
|
||||
cla: new fields.SchemaField({
|
||||
label: new fields.StringField({ initial: "Clairvoyance" }),
|
||||
labelnorm: new fields.StringField({ initial: "clairvoyance" }),
|
||||
abbrev: new fields.StringField({ initial: "cla" }),
|
||||
value: new fields.NumberField({ initial: 1, integer: true })
|
||||
}),
|
||||
pre: new fields.SchemaField({
|
||||
label: new fields.StringField({ initial: "Présence" }),
|
||||
labelnorm: new fields.StringField({ initial: "presence" }),
|
||||
abbrev: new fields.StringField({ initial: "pre" }),
|
||||
value: new fields.NumberField({ initial: 1, integer: true })
|
||||
}),
|
||||
tre: new fields.SchemaField({
|
||||
label: new fields.StringField({ initial: "Trempe" }),
|
||||
labelnorm: new fields.StringField({ initial: "trempe" }),
|
||||
abbrev: new fields.StringField({ initial: "tre" }),
|
||||
value: new fields.NumberField({ initial: 1, integer: true })
|
||||
})
|
||||
}),
|
||||
bonneaventure: new fields.SchemaField({
|
||||
base: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
|
||||
actuelle: new fields.NumberField({ initial: 0, integer: true, nullable: true })
|
||||
}),
|
||||
experience: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true, nullable: true })
|
||||
}),
|
||||
eclat: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true, nullable: true })
|
||||
}),
|
||||
sante: new fields.SchemaField({
|
||||
vigueur: new fields.NumberField({ initial: 0, integer: true, min: 0, nullable: true }),
|
||||
etat: new fields.NumberField({ initial: 0, integer: true, min: 0, nullable: true }),
|
||||
vigueurmodifier: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
|
||||
nbcombativite: new fields.NumberField({ initial: 5, integer: true, min: 0, nullable: true })
|
||||
}),
|
||||
ame: new fields.SchemaField({
|
||||
seuilpouvoir: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
|
||||
etat: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
|
||||
seuilpouvoirmodifier: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
|
||||
nbame: new fields.NumberField({ initial: 7, integer: true, nullable: true }),
|
||||
max: new fields.NumberField({ initial: 0, integer: true, nullable: true })
|
||||
}),
|
||||
adversite: new fields.SchemaField({
|
||||
bleue: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
|
||||
rouge: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
|
||||
noire: new fields.NumberField({ initial: 0, integer: true, nullable: true })
|
||||
}),
|
||||
vitesse: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true, nullable: true })
|
||||
}),
|
||||
combat: new fields.SchemaField({
|
||||
initbonus: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
|
||||
inittotal: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
|
||||
vitessebonus: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
|
||||
bonusdegats: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
|
||||
attaquebonus: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
|
||||
defensebonus: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
|
||||
defensetotal: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
|
||||
defensetotale: new fields.BooleanField({ initial: false }),
|
||||
monte: new fields.BooleanField({ initial: false }),
|
||||
protectiontotal: new fields.NumberField({ initial: 0, integer: true, nullable: true })
|
||||
}),
|
||||
balance: new fields.SchemaField({
|
||||
loi: new fields.NumberField({ initial: 0, integer: true }),
|
||||
chaos: new fields.NumberField({ initial: 0, integer: true }),
|
||||
aspect: new fields.NumberField({ initial: 0, integer: true }),
|
||||
marge: new fields.NumberField({ initial: 0, integer: true }),
|
||||
pointschaos: new fields.NumberField({ initial: 0, integer: true }),
|
||||
pointsloi: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
// Spécifique aux créatures
|
||||
ressources: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true, nullable: true })
|
||||
}),
|
||||
soustype: new fields.StringField({ initial: "creature" })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Data model pour les dons MournbladeCYD2
|
||||
*/
|
||||
export default class DonDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
allegeance: new fields.StringField({ initial: "" }),
|
||||
prerequis: new fields.StringField({ initial: "" }),
|
||||
sacrifice: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Data model pour les équipements MournbladeCYD2
|
||||
*/
|
||||
import { BaseItemWithPriceDataModel } from "./base-item.mjs";
|
||||
|
||||
export default class EquipementDataModel extends BaseItemWithPriceDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Data model pour les historiques MournbladeCYD2
|
||||
*/
|
||||
export default class HistoriqueDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
bonusmalus: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Index des DataModels pour MournbladeCYD2
|
||||
* Ce fichier centralise tous les exports des modèles de données
|
||||
*/
|
||||
|
||||
// Modèles d'items
|
||||
export { default as TalentDataModel } from './talent.mjs';
|
||||
export { default as HistoriqueDataModel } from './historique.mjs';
|
||||
export { default as ProfilDataModel } from './profil.mjs';
|
||||
export { default as CompetenceDataModel } from './competence.mjs';
|
||||
export { default as ArmeDataModel } from './arme.mjs';
|
||||
export { default as ProtectionDataModel } from './protection.mjs';
|
||||
export { default as MonnaieDataModel } from './monnaie.mjs';
|
||||
export { default as EquipementDataModel } from './equipement.mjs';
|
||||
export { default as RessourceDataModel } from './ressource.mjs';
|
||||
export { default as DonDataModel } from './don.mjs';
|
||||
export { default as PacteDataModel } from './pacte.mjs';
|
||||
export { default as RuneDataModel } from './rune.mjs';
|
||||
export { default as RuneEffectDataModel } from './runeeffect.mjs';
|
||||
export { default as TendanceDataModel } from './tendance.mjs';
|
||||
export { default as TraitChaotiqueDataModel } from './traitchaotique.mjs';
|
||||
export { default as TraitEspeceDataModel } from './traitespece.mjs';
|
||||
export { default as TraitDemoniaqueDataModel } from './traitdemoniaque.mjs';
|
||||
export { default as PouvoirElementaireDataModel } from './pouvoirselementaire.mjs';
|
||||
export { default as CapaciteAutomataDataModel } from './capaciteautomata.mjs';
|
||||
|
||||
// Modèles d'acteurs
|
||||
export { default as PersonnageDataModel } from './personnage.mjs';
|
||||
export { default as CreatureDataModel } from './creature.mjs';
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Data model pour les monnaies MournbladeCYD2
|
||||
*/
|
||||
import { BaseItemWithPriceDataModel } from "./base-item.mjs";
|
||||
|
||||
export default class MonnaieDataModel extends BaseItemWithPriceDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Data model pour les pactes MournbladeCYD2
|
||||
*/
|
||||
export default class PacteDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
allegeance: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Data model pour les personnages MournbladeCYD2
|
||||
*/
|
||||
export default class PersonnageDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
// Template biodata
|
||||
biodata: new fields.SchemaField({
|
||||
name: new fields.StringField({ initial: "" }),
|
||||
age: new fields.NumberField({ initial: 20, integer: true }),
|
||||
poids: new fields.StringField({ initial: "" }),
|
||||
taille: new fields.StringField({ initial: "" }),
|
||||
cheveux: new fields.StringField({ initial: "" }),
|
||||
sexe: new fields.StringField({ initial: "" }),
|
||||
yeux: new fields.StringField({ initial: "" }),
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
habitat: new fields.HTMLField({ initial: "" }),
|
||||
notes: new fields.HTMLField({ initial: "" }),
|
||||
statut: new fields.StringField({ initial: "" }),
|
||||
gmnotes: new fields.HTMLField({ initial: "" }),
|
||||
statutresistant: new fields.StringField({ initial: "commun" })
|
||||
}),
|
||||
// Template core
|
||||
subactors: new fields.ArrayField(new fields.StringField(), { initial: [] }),
|
||||
attributs: new fields.SchemaField({
|
||||
adr: new fields.SchemaField({
|
||||
label: new fields.StringField({ initial: "Adresse" }),
|
||||
labelnorm: new fields.StringField({ initial: "adresse" }),
|
||||
abbrev: new fields.StringField({ initial: "adr" }),
|
||||
value: new fields.NumberField({ initial: 1, integer: true })
|
||||
}),
|
||||
pui: new fields.SchemaField({
|
||||
label: new fields.StringField({ initial: "Puissance" }),
|
||||
labelnorm: new fields.StringField({ initial: "puissance" }),
|
||||
abbrev: new fields.StringField({ initial: "pui" }),
|
||||
value: new fields.NumberField({ initial: 1, integer: true })
|
||||
}),
|
||||
cla: new fields.SchemaField({
|
||||
label: new fields.StringField({ initial: "Clairvoyance" }),
|
||||
labelnorm: new fields.StringField({ initial: "clairvoyance" }),
|
||||
abbrev: new fields.StringField({ initial: "cla" }),
|
||||
value: new fields.NumberField({ initial: 1, integer: true })
|
||||
}),
|
||||
pre: new fields.SchemaField({
|
||||
label: new fields.StringField({ initial: "Présence" }),
|
||||
labelnorm: new fields.StringField({ initial: "presence" }),
|
||||
abbrev: new fields.StringField({ initial: "pre" }),
|
||||
value: new fields.NumberField({ initial: 1, integer: true })
|
||||
}),
|
||||
tre: new fields.SchemaField({
|
||||
label: new fields.StringField({ initial: "Trempe" }),
|
||||
labelnorm: new fields.StringField({ initial: "trempe" }),
|
||||
abbrev: new fields.StringField({ initial: "tre" }),
|
||||
value: new fields.NumberField({ initial: 1, integer: true })
|
||||
})
|
||||
}),
|
||||
bonneaventure: new fields.SchemaField({
|
||||
base: new fields.NumberField({ initial: 0, integer: true }),
|
||||
actuelle: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
experience: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
eclat: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
sante: new fields.SchemaField({
|
||||
vigueur: new fields.NumberField({ initial: 0, integer: true }),
|
||||
etat: new fields.NumberField({ initial: 0, integer: true }),
|
||||
vigueurmodifier: new fields.NumberField({ initial: 0, integer: true }),
|
||||
nbcombativite: new fields.NumberField({ initial: 5, integer: true })
|
||||
}),
|
||||
ame: new fields.SchemaField({
|
||||
seuilpouvoir: new fields.NumberField({ initial: 0, integer: true }),
|
||||
etat: new fields.NumberField({ initial: 0, integer: true }),
|
||||
seuilpouvoirmodifier: new fields.NumberField({ initial: 0, integer: true }),
|
||||
nbame: new fields.NumberField({ initial: 7, integer: true }),
|
||||
max: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
adversite: new fields.SchemaField({
|
||||
bleue: new fields.NumberField({ initial: 0, integer: true }),
|
||||
rouge: new fields.NumberField({ initial: 0, integer: true }),
|
||||
noire: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
vitesse: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
combat: new fields.SchemaField({
|
||||
initbonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
vitessebonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
bonusdegats: new fields.NumberField({ initial: 0, integer: true }),
|
||||
attaquebonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
defensebonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
defensetotale: new fields.BooleanField({ initial: false }),
|
||||
monte: new fields.BooleanField({ initial: false })
|
||||
}),
|
||||
balance: new fields.SchemaField({
|
||||
loi: new fields.NumberField({ initial: 0, integer: true }),
|
||||
chaos: new fields.NumberField({ initial: 0, integer: true }),
|
||||
aspect: new fields.NumberField({ initial: 0, integer: true }),
|
||||
marge: new fields.NumberField({ initial: 0, integer: true }),
|
||||
pointschaos: new fields.NumberField({ initial: 0, integer: true }),
|
||||
pointsloi: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
// Sorcellerie
|
||||
sorcellerie: new fields.SchemaField({
|
||||
runes: new fields.HTMLField({ initial: "" }),
|
||||
creaturesinvoquees: new fields.ArrayField(new fields.StringField(), { initial: [] }),
|
||||
demonslies: new fields.ArrayField(new fields.StringField(), { initial: [] }),
|
||||
enchantements: new fields.ArrayField(new fields.StringField(), { initial: [] }),
|
||||
invocationsencours: new fields.HTMLField({ initial: "" }),
|
||||
coutPouvoirInvocations: new fields.NumberField({ initial: 0, integer: true })
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Data model pour les pouvoirs élémentaires MournbladeCYD2
|
||||
*/
|
||||
export default class PouvoirElementaireDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
bonusmalus: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Data model pour les profils MournbladeCYD2
|
||||
*/
|
||||
export default class ProfilDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
exemples: new fields.StringField({ initial: "" }),
|
||||
attribut1: new fields.StringField({ initial: "" }),
|
||||
attribut2: new fields.StringField({ initial: "" }),
|
||||
attribut3: new fields.StringField({ initial: "" }),
|
||||
competences: new fields.HTMLField({ initial: "" }),
|
||||
talentsinitie: new fields.HTMLField({ initial: "" }),
|
||||
prerequisaguerri: new fields.HTMLField({ initial: "" }),
|
||||
talentsaguerri: new fields.HTMLField({ initial: "" }),
|
||||
prerequismaitre: new fields.HTMLField({ initial: "" }),
|
||||
talentsmaitre: new fields.HTMLField({ initial: "" }),
|
||||
equipement: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Data model pour les protections MournbladeCYD2
|
||||
*/
|
||||
import { BaseItemWithPriceDataModel } from "./base-item.mjs";
|
||||
|
||||
export default class ProtectionDataModel extends BaseItemWithPriceDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
protection: new fields.NumberField({ initial: 0, integer: true }),
|
||||
adversitepoids: new fields.NumberField({ initial: 0, integer: true })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Data model pour les ressources MournbladeCYD2
|
||||
*/
|
||||
export default class RessourceDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
pointdev: new fields.NumberField({ initial: 0, integer: true })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Data model pour les runes MournbladeCYD2
|
||||
*/
|
||||
export default class RuneDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
formule: new fields.StringField({ initial: "" }),
|
||||
seuil: new fields.NumberField({ initial: 0, integer: true }),
|
||||
prononcee: new fields.StringField({ initial: "" }),
|
||||
tracee: new fields.StringField({ initial: "" }),
|
||||
coutAme: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Data model pour les effets de rune MournbladeCYD2
|
||||
*/
|
||||
export default class RuneEffectDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
rune: new fields.StringField({ initial: "" }),
|
||||
mode: new fields.StringField({ initial: "" }),
|
||||
duree: new fields.StringField({ initial: "" }),
|
||||
pointame: new fields.NumberField({ initial: 0, integer: true })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Data model pour les talents MournbladeCYD2
|
||||
*/
|
||||
export default class TalentDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
isautomated: new fields.BooleanField({ initial: false }),
|
||||
automations: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
id: new fields.StringField({ initial: "" }),
|
||||
eventtype: new fields.StringField({ initial: "on-drop" }),
|
||||
name: new fields.StringField({ initial: "" }),
|
||||
bonusname: new fields.StringField({ initial: "vigueur" }),
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
competence: new fields.StringField({ initial: "" }),
|
||||
minLevel: new fields.NumberField({ initial: 0, integer: true }),
|
||||
baCost: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
{ initial: [] }
|
||||
),
|
||||
talenttype: new fields.StringField({ initial: "" }),
|
||||
utilisation: new fields.StringField({ initial: "" }),
|
||||
prerequis: new fields.StringField({ initial: "" }),
|
||||
resumebonus: new fields.StringField({ initial: "" }),
|
||||
used: new fields.BooleanField({ initial: false })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Data model pour les tendances MournbladeCYD2
|
||||
*/
|
||||
export default class TendanceDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
allegeance: new fields.StringField({ initial: "" }),
|
||||
donlie: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Data model pour les traits chaotiques MournbladeCYD2
|
||||
*/
|
||||
export default class TraitChaotiqueDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
bonusmalus: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Data model pour les traits démoniaques MournbladeCYD2
|
||||
*/
|
||||
export default class TraitDemoniaqueDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
bonusmalus: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Data model pour les traits d'espèce MournbladeCYD2
|
||||
*/
|
||||
export default class TraitEspeceDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
bonusmalus: new fields.StringField({ initial: "" })
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
/**
|
||||
* Extend the basic ActorSheet with some very simple modifications
|
||||
* @extends {ActorSheet}
|
||||
*/
|
||||
|
||||
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
|
||||
import { MournbladeCYD2Automation } from "./mournblade-cyd2-automation.js";
|
||||
|
||||
/* -------------------------------------------- */
|
||||
export class MournbladeCYD2ActorSheet extends foundry.appv1.sheets.ActorSheet {
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["fvtt-mournblade-cyd2", "sheet", "actor"],
|
||||
template: "systems/fvtt-mournblade-cyd2/templates/actor-sheet.html",
|
||||
width: 640,
|
||||
height: 720,
|
||||
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }],
|
||||
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
|
||||
editScore: false
|
||||
})
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
async getData() {
|
||||
const objectData = foundry.utils.duplicate(this.object)
|
||||
|
||||
let formData = {
|
||||
title: this.title,
|
||||
id: objectData.id,
|
||||
type: objectData.type,
|
||||
img: objectData.img,
|
||||
name: objectData.name,
|
||||
editable: this.isEditable,
|
||||
cssClass: this.isEditable ? "editable" : "locked",
|
||||
system: objectData.system,
|
||||
effects: this.object.effects.map(e => foundry.utils.deepClone(e.data)),
|
||||
limited: this.object.limited,
|
||||
skills: this.actor.getSkills(),
|
||||
armes: foundry.utils.duplicate(this.actor.getWeapons()),
|
||||
monnaies: foundry.utils.duplicate(this.actor.getMonnaies()),
|
||||
protections: foundry.utils.duplicate(this.actor.getArmors()),
|
||||
historiques: foundry.utils.duplicate(this.actor.getHistoriques() || []),
|
||||
talents: foundry.utils.duplicate(this.actor.getTalents() || []),
|
||||
mutations: foundry.utils.duplicate(this.actor.getMutations() || []),
|
||||
dons: foundry.utils.duplicate(this.actor.getDons() || []),
|
||||
pactes: foundry.utils.duplicate(this.actor.getPactes() || []),
|
||||
tendances: foundry.utils.duplicate(this.actor.getTendances() || []),
|
||||
runes: foundry.utils.duplicate(this.actor.getRunes() || []),
|
||||
traitsChaotiques: foundry.utils.duplicate(this.actor.getTraitsChaotiques() || []),
|
||||
traitsEspeces: foundry.utils.duplicate(this.actor.getTraitsEspeces() || []),
|
||||
aspect: this.actor.getAspect(),
|
||||
marge: this.actor.getMarge(),
|
||||
talentsCell: this.getCelluleTalents(),
|
||||
profils: foundry.utils.duplicate(this.actor.getProfils() || []),
|
||||
combat: this.actor.getCombatValues(),
|
||||
equipements: foundry.utils.duplicate(this.actor.getEquipments()),
|
||||
artefacts: foundry.utils.duplicate(this.actor.getArtefacts()),
|
||||
richesse: this.actor.computeRichesse(),
|
||||
coupDevastateur: this.actor.items.find(it => it.type == "talent" && it.name.toLowerCase() == "coup devastateur" && !it.system.used),
|
||||
valeurEquipement: this.actor.computeValeurEquipement(),
|
||||
nbCombativite: this.actor.system.sante.nbcombativite,
|
||||
combativiteList: MournbladeCYD2Utility.getCombativiteList(this.actor.system.sante.nbcombativite),
|
||||
initiative: this.actor.getFlag("world", "last-initiative") || -1,
|
||||
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.biodata.description, { async: true }),
|
||||
habitat: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.biodata.habitat, { async: true }),
|
||||
options: this.options,
|
||||
owner: this.document.isOwner,
|
||||
editScore: this.options.editScore,
|
||||
isGM: game.user.isGM,
|
||||
config: game.system.mournbladecyd2.config
|
||||
}
|
||||
this.formData = formData;
|
||||
|
||||
console.log("PC : ", formData, this.object);
|
||||
return formData;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
getCelluleTalents() {
|
||||
let talents = []
|
||||
for (let cellule of game.actors) {
|
||||
if (cellule.type == "cellule") {
|
||||
let found = cellule.system.members.find(it => it.id == this.actor.id)
|
||||
if (found) {
|
||||
talents = talents.concat(cellule.getTalents())
|
||||
}
|
||||
}
|
||||
}
|
||||
return talents
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/** @override */
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
|
||||
// Everything below here is only needed if the sheet is editable
|
||||
if (!this.options.editable) return;
|
||||
|
||||
// Update Inventory Item
|
||||
html.find('.item-edit').click(ev => {
|
||||
const li = $(ev.currentTarget).parents(".item")
|
||||
let itemId = li.data("item-id")
|
||||
const item = this.actor.items.get(itemId)
|
||||
item.sheet.render(true)
|
||||
})
|
||||
// Delete Inventory Item
|
||||
html.find('.item-delete').click(ev => {
|
||||
const li = $(ev.currentTarget).parents(".item");
|
||||
MournbladeCYD2Utility.confirmDelete(this, li);
|
||||
})
|
||||
html.find('.edit-item-data').change(ev => {
|
||||
const li = $(ev.currentTarget).parents(".item")
|
||||
let itemId = li.data("item-id")
|
||||
let itemType = li.data("item-type")
|
||||
let itemField = $(ev.currentTarget).data("item-field")
|
||||
let dataType = $(ev.currentTarget).data("dtype")
|
||||
let value = ev.currentTarget.value
|
||||
this.actor.editItemField(itemId, itemType, itemField, dataType, value)
|
||||
})
|
||||
|
||||
html.find('.adversite-modify').click(event => {
|
||||
const li = $(event.currentTarget).parents(".item")
|
||||
let adv = li.data("adversite")
|
||||
let value = Number($(event.currentTarget).data("adversite-value"))
|
||||
this.actor.incDecAdversite(adv, value)
|
||||
})
|
||||
|
||||
html.find('.quantity-modify').click(event => {
|
||||
const li = $(event.currentTarget).parents(".item")
|
||||
const value = Number($(event.currentTarget).data("quantite-value"))
|
||||
this.actor.incDecQuantity(li.data("item-id"), value);
|
||||
})
|
||||
|
||||
html.find('.roll-initiative').click((event) => {
|
||||
this.actor.rollAttribut("adr", true)
|
||||
})
|
||||
|
||||
html.find('.roll-attribut').click((event) => {
|
||||
const li = $(event.currentTarget).parents(".item")
|
||||
let attrKey = li.data("attr-key")
|
||||
this.actor.rollAttribut(attrKey, false)
|
||||
})
|
||||
html.find('.roll-competence').click((event) => {
|
||||
const li = $(event.currentTarget).parents(".item")
|
||||
let attrKey = $(event.currentTarget).data("attr-key")
|
||||
let compId = li.data("item-id")
|
||||
this.actor.rollCompetence(attrKey, compId)
|
||||
})
|
||||
html.find('.roll-arme-offensif').click((event) => {
|
||||
const li = $(event.currentTarget).parents(".item")
|
||||
let armeId = li.data("item-id")
|
||||
this.actor.rollArmeOffensif(armeId)
|
||||
})
|
||||
|
||||
html.find('.roll-assommer').click((event) => {
|
||||
this.actor.rollAssommer()
|
||||
})
|
||||
html.find('.roll-coup-bas').click((event) => {
|
||||
this.actor.rollCoupBas()
|
||||
})
|
||||
html.find('.roll-immobiliser').click((event) => {
|
||||
this.actor.rollImmobiliser()
|
||||
})
|
||||
html.find('.roll-repousser').click((event) => {
|
||||
this.actor.rollRepousser()
|
||||
})
|
||||
html.find('.roll-desengager').click((event) => {
|
||||
this.actor.rollDesengager()
|
||||
})
|
||||
|
||||
html.find('.roll-arme-degats').click((event) => {
|
||||
const li = $(event.currentTarget).parents(".item")
|
||||
let armeId = li.data("item-id")
|
||||
this.actor.rollArmeDegats(armeId)
|
||||
})
|
||||
|
||||
html.find('.item-add').click((event) => {
|
||||
const itemType = $(event.currentTarget).data("type")
|
||||
this.actor.createEmbeddedDocuments('Item', [{ name: `Nouveau ${itemType}`, type: itemType }], { renderSheet: true })
|
||||
})
|
||||
|
||||
html.find('.lock-unlock-sheet').click((event) => {
|
||||
this.options.editScore = !this.options.editScore;
|
||||
this.render(true);
|
||||
});
|
||||
html.find('.item-equip').click(ev => {
|
||||
const li = $(ev.currentTarget).parents(".item");
|
||||
this.actor.equipItem(li.data("item-id"));
|
||||
this.render(true);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/** @override */
|
||||
setPosition(options = {}) {
|
||||
const position = super.setPosition(options);
|
||||
const sheetBody = this.element.find(".sheet-body");
|
||||
const bodyHeight = position.height - 192;
|
||||
sheetBody.css("height", bodyHeight);
|
||||
return position;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
async _onDropItem(event, dragData) {
|
||||
let data = event.dataTransfer.getData('text/plain')
|
||||
let dataItem = JSON.parse(data)
|
||||
let item = fromUuidSync(dataItem.uuid)
|
||||
if (item.pack) {
|
||||
item = await MournbladeCYD2Utility.searchItem(item)
|
||||
}
|
||||
let autoresult = MournbladeCYD2Automation.processAutomations("on-drop", item, this.actor)
|
||||
if (autoresult.isValid) {
|
||||
super._onDropItem(event, dragData)
|
||||
} else {
|
||||
ui.notifications.warn(autoresult.warningMessage)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/* -------------------------------------------- */
|
||||
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
|
||||
import { MournbladeCYD2RollDialog } from "./mournblade-cyd2-roll-dialog.js";
|
||||
import { MournbladeCYD2RollDialog } from "./applications/mournblade-cyd2-roll-dialog.mjs";
|
||||
|
||||
/* -------------------------------------------- */
|
||||
const __degatsBonus = [-2, -2, -1, -1, 0, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 8, 8, 9, 9, 10, 10]
|
||||
@@ -38,14 +38,15 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
}
|
||||
|
||||
if (data.type == 'personnage') {
|
||||
const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd2.skills")
|
||||
console.log("Loading skills for personnage")
|
||||
const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd-2-0.skills")
|
||||
data.items = skills.map(i => i.toObject())
|
||||
}
|
||||
if (data.type == 'creature') {
|
||||
const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd2.skills-creatures")
|
||||
const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd-2-0.skills-creatures")
|
||||
data.items = skills.map(i => i.toObject())
|
||||
data.items.push({ name: "Arme naturelle 1", type: 'arme', img: "systems/fvtt-mournblade-cyd2/assets/icons/melee.webp", system: { typearme: "contact", bonusmaniementoff: 0, seuildefense: 0, degats: "0" } })
|
||||
data.items.push({ name: "Arme naturelle 2", type: 'arme', img: "systems/fvtt-mournblade-cyd2/assets/icons/melee.webp", system: { typearme: "contact", bonusmaniementoff: 0, seuildefense: 0, degats: "0" } })
|
||||
data.items.push({ name: "Arme naturelle 1", type: 'arme', img: "systems/fvtt-mournblade-cyd-2-0/assets/icons/melee.webp", system: { typearme: "contact", bonusmaniementoff: 0, seuildefense: 0, degats: "0" } })
|
||||
data.items.push({ name: "Arme naturelle 2", type: 'arme', img: "systems/fvtt-mournblade-cyd-2-0/assets/icons/melee.webp", system: { typearme: "contact", bonusmaniementoff: 0, seuildefense: 0, degats: "0" } })
|
||||
}
|
||||
|
||||
return super.create(data, options);
|
||||
@@ -67,10 +68,6 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
|
||||
/* -------------------------------------------- */
|
||||
prepareArme(arme) {
|
||||
if (this.type == "cellule") {
|
||||
return arme
|
||||
}
|
||||
|
||||
arme = foundry.utils.duplicate(arme)
|
||||
let combat = this.getCombatValues()
|
||||
if (arme.system.typearme == "contact" || arme.system.typearme == "contactjet") {
|
||||
@@ -121,9 +118,6 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
getEquipments() {
|
||||
return this.getItemSorted(["equipement"])
|
||||
}
|
||||
getArtefacts() {
|
||||
return this.getItemSorted(["artefact"])
|
||||
}
|
||||
getArmors() {
|
||||
return this.getItemSorted(["protection"])
|
||||
}
|
||||
@@ -139,12 +133,6 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
getRessources() {
|
||||
return this.getItemSorted(["ressource"])
|
||||
}
|
||||
getContacts() {
|
||||
return this.getItemSorted(["contact"])
|
||||
}
|
||||
getMutations() {
|
||||
return this.getItemSorted(["mutation"])
|
||||
}
|
||||
getDons() {
|
||||
return this.getItemSorted(["don"])
|
||||
}
|
||||
@@ -157,6 +145,12 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
getRunes() {
|
||||
return this.getItemSorted(["rune"])
|
||||
}
|
||||
getRuneEffects() {
|
||||
return this.getItemSorted(["runeeffect"])
|
||||
}
|
||||
getProfil() {
|
||||
return this.getProfils()[0] ?? null
|
||||
}
|
||||
getTraitsChaotiques() {
|
||||
return this.getItemSorted(["traitchaotique"])
|
||||
}
|
||||
@@ -232,23 +226,12 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
}
|
||||
return equipProtection // Uniquement la protection des armures + boucliers
|
||||
}
|
||||
getProtectionTotal() {
|
||||
return this.getProtection()
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
getCombatValues() {
|
||||
if (this.type == "cellule") {
|
||||
return {
|
||||
initBase: 0,
|
||||
initTotal: 0,
|
||||
bonusDegats: 0,
|
||||
bonusDegatsTotal: 0,
|
||||
vitesseBase: 0,
|
||||
vitesseTotal: 0,
|
||||
defenseBase: 0,
|
||||
protection: 0,
|
||||
defenseTotal: 0
|
||||
}
|
||||
}
|
||||
|
||||
let combat = {
|
||||
initBase: this.system.attributs.adr.value,
|
||||
initTotal: this.system.attributs.adr.value + this.system.combat.initbonus,
|
||||
@@ -263,9 +246,6 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
return combat
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
prepareBaseData() {
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
async prepareData() {
|
||||
@@ -281,17 +261,75 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
if (vigueur != this.system.sante.vigueur) {
|
||||
this.update({ 'system.sante.vigueur': vigueur })
|
||||
}
|
||||
|
||||
let seuilPouvoirBonus = this.getSeuilPouvoirBonus()
|
||||
let seuilPouvoir = Math.floor((this.system.attributs.tre.value + this.system.attributs.cla.value) / 2) + seuilPouvoirBonus + this.system.ame.seuilpouvoirmodifier
|
||||
if (seuilPouvoir != this.system.ame.seuilpouvoir) {
|
||||
this.update({ 'system.ame.seuilpouvoir': seuilPouvoir })
|
||||
}
|
||||
}
|
||||
super.prepareDerivedData()
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
_preUpdate(changed, options, user) {
|
||||
if (changed?.system?.sante?.etat && changed?.system?.sante?.etat != this.system.sante.etat) {
|
||||
// Clean up numeric fields in various schemas to ensure they are valid numbers
|
||||
const numericCleanup = (schemaPath, dataPath) => {
|
||||
if (changed?.system?.[dataPath]) {
|
||||
const schema = foundry.utils.getProperty(this.system.schema.fields, schemaPath);
|
||||
if (schema) {
|
||||
for (const [key, value] of Object.entries(changed.system[dataPath])) {
|
||||
if (schema.fields[key] instanceof foundry.data.fields.NumberField) {
|
||||
if (value === "" || value === null || value === undefined || isNaN(value)) {
|
||||
changed.system[dataPath][key] = 0;
|
||||
} else {
|
||||
changed.system[dataPath][key] = Number(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Apply cleanup to schemas that may have numeric fields submitted from forms
|
||||
numericCleanup("sante", "sante");
|
||||
numericCleanup("ame", "ame");
|
||||
numericCleanup("combat", "combat");
|
||||
numericCleanup("balance", "balance");
|
||||
numericCleanup("adversite", "adversite");
|
||||
numericCleanup("vitesse", "vitesse");
|
||||
numericCleanup("ressources", "ressources");
|
||||
numericCleanup("eclat", "eclat");
|
||||
numericCleanup("bonneaventure", "bonneaventure");
|
||||
numericCleanup("experience", "experience");
|
||||
|
||||
if (changed?.system?.sante?.etat !== undefined && changed.system.sante.etat != this.system.sante.etat) {
|
||||
const oldEtat = this.system.sante.etat
|
||||
setTimeout(() => {
|
||||
this.processCombativite(changed.system.sante)
|
||||
this.processCombativite(changed.system.sante, oldEtat)
|
||||
}, 800)
|
||||
}
|
||||
if (changed?.system?.ame?.etat !== undefined && changed.system.ame.etat != this.system.ame.etat) {
|
||||
// L'état d'Âme ne peut pas être inférieur au minimum (max dans le système)
|
||||
let minAme = this.system.ame.max !== undefined ? this.system.ame.max : 0
|
||||
if (changed.system.ame.etat < minAme) {
|
||||
changed.system.ame.etat = minAme
|
||||
}
|
||||
// L'état d'Âme ne peut pas dépasser nbame (Brisé)
|
||||
if (changed.system.ame.etat > this.system.ame.nbame) {
|
||||
changed.system.ame.etat = this.system.ame.nbame
|
||||
}
|
||||
const oldEtat = this.system.ame.etat
|
||||
setTimeout(() => {
|
||||
this.processAme(changed.system.ame, oldEtat)
|
||||
}, 800)
|
||||
}
|
||||
// Si le max d'Âme change, ajuster l'état actuel si nécessaire
|
||||
if (changed?.system?.ame?.max !== undefined && changed.system.ame.max != this.system.ame.max) {
|
||||
if (this.system.ame.etat < changed.system.ame.max) {
|
||||
changed.system.ame.etat = changed.system.ame.max
|
||||
}
|
||||
}
|
||||
super._preUpdate(changed, options, user);
|
||||
}
|
||||
|
||||
@@ -318,7 +356,7 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
editItemField(itemId, itemType, itemField, dataType, value) {
|
||||
async editItemField(itemId, itemType, itemField, dataType, value) {
|
||||
let item = this.items.find(item => item.id == itemId)
|
||||
if (item) {
|
||||
console.log("Item ", item, itemField, dataType, value)
|
||||
@@ -328,7 +366,7 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
value = String(value)
|
||||
}
|
||||
let update = { _id: item.id, [`system.${itemField}`]: value };
|
||||
this.updateEmbeddedDocuments("Item", [update])
|
||||
await this.updateEmbeddedDocuments("Item", [update])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,6 +430,31 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
return bonus
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
getSeuilPouvoir() {
|
||||
return this.system.ame.seuilpouvoir
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
getSeuilPouvoirBonus() {
|
||||
let talents = this.items.filter(item => item.type == "talent" && item.system.isautomated)
|
||||
let bonus = 0
|
||||
for (let talent of talents) {
|
||||
for (let auto of talent.system.automations) {
|
||||
if (auto.eventtype == "bonus-permanent" && auto.bonusname == "seuilpouvoir") {
|
||||
bonus += Number(auto.bonus || 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
return bonus
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
getAmeMax() {
|
||||
// Utiliser une vérification stricte car 0 (Serein) est une valeur valide
|
||||
return this.system.ame.max !== undefined ? this.system.ame.max : this.system.ame.nbame
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
getBonneAventure() {
|
||||
return this.system.bonneaventure.actuelle
|
||||
@@ -432,7 +495,15 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
|
||||
/* -------------------------------------------- */
|
||||
getAttribute(attrKey) {
|
||||
return this.system.attributes[attrKey]
|
||||
return this.system.attributs[attrKey]
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
hasTalent(nameOrPattern) {
|
||||
if (nameOrPattern instanceof RegExp) {
|
||||
return !!this.items.find(i => i.type === "talent" && nameOrPattern.test(i.name.toLowerCase()))
|
||||
}
|
||||
return !!this.items.find(i => i.type === "talent" && i.name.toLowerCase() === nameOrPattern.toLowerCase())
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
@@ -457,19 +528,78 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
processCombativite(sante) {
|
||||
changeEtatAme(value) {
|
||||
if (value === "brise") {
|
||||
value = 200
|
||||
}
|
||||
let ame = foundry.utils.duplicate(this.system.ame)
|
||||
ame.etat += Number(value)
|
||||
// L'état ne peut pas être inférieur au minimum (max dans le système)
|
||||
let minAme = this.system.ame.max !== undefined ? this.system.ame.max : 0
|
||||
ame.etat = Math.max(ame.etat, minAme)
|
||||
// L'état ne peut pas dépasser nbame (Brisé)
|
||||
ame.etat = Math.min(ame.etat, this.system.ame.nbame)
|
||||
this.update({ 'system.ame': ame })
|
||||
if (ame.etat >= this.system.ame.nbame) {
|
||||
ChatMessage.create({ content: `<strong>${this.name} est brisé !</strong>` })
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
processCombativite(sante, oldEtat = undefined) {
|
||||
sante = sante || foundry.utils.duplicate(this.system.sante)
|
||||
// Gestion des états affaibli et très affaibli
|
||||
if (sante.etat == this.system.sante.nbcombativite - 2 || sante.etat == this.system.sante.nbcombativite - 1) {
|
||||
if (sante.etat == this.system.sante.nbcombativite - 2 && this.items.find(item => item.type == "talent" && item.name.toLowerCase() == "encaissement")) {
|
||||
ChatMessage.create({ content: `<strong>${this.name} ne subit pas les 2 adversités rouge grâce à Encaissement. Pensez à les ajouter à la fin de la scène !</strong>` })
|
||||
} else if (sante.etat == this.system.sante.nbcombativite - 1 && this.items.find(item => item.type == "talent" && item.name.toLowerCase().includes("vaillant"))) {
|
||||
ChatMessage.create({ content: `<strong>${this.name} ne subit pas les 2 adversités rouge grâce à Vaillant. Pensez à les ajouter à la fin de la scène !</strong>` })
|
||||
const affaibli = this.system.sante.nbcombativite - 2
|
||||
const tresAffaibli = this.system.sante.nbcombativite - 1
|
||||
// oldEtat permet de détecter les sauts qui franchissent Affaibli ou Très Affaibli
|
||||
// sans y atterrir exactement (ex: 0 → 5 doit déclencher les deux seuils)
|
||||
const prev = oldEtat !== undefined ? oldEtat : sante.etat
|
||||
const curr = sante.etat
|
||||
|
||||
const passedAffaibli = curr >= affaibli && prev < affaibli
|
||||
const passedTresAffaibli = curr >= tresAffaibli && prev < tresAffaibli
|
||||
|
||||
if (passedAffaibli) {
|
||||
if (this.hasTalent("encaissement")) {
|
||||
ChatMessage.create({ content: `<strong>${this.name} ne subit pas les 2 adversités rouge (Affaibli) grâce à Encaissement. Pensez à les ajouter à la fin de la scène !</strong>` })
|
||||
} else {
|
||||
ChatMessage.create({ content: `<strong>${this.name} subit 2 adversités rouge !</strong>` })
|
||||
ChatMessage.create({ content: `<strong>${this.name} est Affaibli et subit 2 adversités rouge !</strong>` })
|
||||
this.incDecAdversite("rouge", 2)
|
||||
}
|
||||
}
|
||||
if (passedTresAffaibli) {
|
||||
if (this.hasTalent(/vaillant/)) {
|
||||
ChatMessage.create({ content: `<strong>${this.name} ne subit pas les 2 adversités rouge (Très Affaibli) grâce à Vaillant. Pensez à les ajouter à la fin de la scène !</strong>` })
|
||||
} else {
|
||||
ChatMessage.create({ content: `<strong>${this.name} est Très Affaibli et subit 2 adversités rouge supplémentaires !</strong>` })
|
||||
this.incDecAdversite("rouge", 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
processAme(ame, oldEtat = undefined) {
|
||||
ame = ame || foundry.utils.duplicate(this.system.ame)
|
||||
const traumatiseValue = this.system.ame.nbame - 2
|
||||
const tresTraumatiseValue = this.system.ame.nbame - 1
|
||||
const briseValue = this.system.ame.nbame
|
||||
const prev = oldEtat !== undefined ? oldEtat : ame.etat
|
||||
const curr = ame.etat
|
||||
|
||||
// Déclencher pour chaque seuil franchi ou atteint, même en cas de saut
|
||||
if (curr >= traumatiseValue && prev < traumatiseValue) {
|
||||
ChatMessage.create({ content: `<strong>${this.name} est Traumatisé et subit 1 adversité bleue et 1 adversité noire !</strong>` })
|
||||
this.incDecAdversite("bleue", 1)
|
||||
this.incDecAdversite("noire", 1)
|
||||
}
|
||||
if (curr >= tresTraumatiseValue && prev < tresTraumatiseValue) {
|
||||
ChatMessage.create({ content: `<strong>${this.name} est Très Traumatisé et subit 1 adversité bleue et 1 adversité noire !</strong>` })
|
||||
this.incDecAdversite("bleue", 1)
|
||||
this.incDecAdversite("noire", 1)
|
||||
}
|
||||
if (curr >= briseValue && prev < briseValue) {
|
||||
ChatMessage.create({ content: `<strong>${this.name} est Brisé et subit 1 adversité noire !</strong>` })
|
||||
this.incDecAdversite("noire", 1)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
@@ -534,7 +664,7 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
let valueSC = 0
|
||||
for (let monnaie of this.items) {
|
||||
if (monnaie.type == "monnaie") {
|
||||
valueSC += Number(monnaie.system.prixsc) * Number(monnaie.system.quantite)
|
||||
valueSC += MournbladeCYD2Utility.getItemValueSC(monnaie)
|
||||
}
|
||||
}
|
||||
return MournbladeCYD2Utility.computeMonnaieDetails(valueSC)
|
||||
@@ -545,9 +675,7 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
let valueSC = 0
|
||||
for (let equip of this.items) {
|
||||
if (equip.type == "equipement" || equip.type == "arme" || equip.type == "protection") {
|
||||
valueSC += Number(equip.system.prixsc) * Number(equip.system.quantite ?? 1)
|
||||
valueSC += (Number(equip.system.prixca) * Number(equip.system.quantite ?? 1)) * 20
|
||||
valueSC += (Number(equip.system.prixpo) * Number(equip.system.quantite ?? 1)) * 400
|
||||
valueSC += MournbladeCYD2Utility.getItemValueSC(equip)
|
||||
}
|
||||
}
|
||||
return MournbladeCYD2Utility.computeMonnaieDetails(valueSC)
|
||||
@@ -656,7 +784,7 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
if (attrKey) {
|
||||
rollData.attrKey = attrKey
|
||||
if (attrKey != "tochoose") {
|
||||
rollData.actionImg = "systems/fvtt-mournblade-cyd2/assets/icons/" + this.system.attributs[attrKey].labelnorm + ".webp"
|
||||
rollData.actionImg = "systems/fvtt-mournblade-cyd-2-0/assets/icons/" + this.system.attributs[attrKey].labelnorm + ".webp"
|
||||
rollData.attr = foundry.utils.duplicate(this.system.attributs[attrKey])
|
||||
}
|
||||
}
|
||||
@@ -682,10 +810,9 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
/* -------------------------------------------- */
|
||||
async rollAttribut(attrKey, isInit = false) {
|
||||
let rollData = this.getCommonRollData(attrKey)
|
||||
rollData.multiplier = (isInit) ? 1 : 2
|
||||
rollData.multiplier = 1 // 1d10 + attr (optionally + attr2 if chosen in dialog)
|
||||
rollData.isInit = isInit
|
||||
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
rollDialog.render(true)
|
||||
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
@@ -693,8 +820,7 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
let rollData = this.getCommonRollData(attrKey, compId)
|
||||
rollData.multiplier = 1 // Attr multiplier, always 1 in competence mode
|
||||
console.log("RollDatra", rollData)
|
||||
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
rollDialog.render(true)
|
||||
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
@@ -707,8 +833,7 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
rollData.arme = arme
|
||||
MournbladeCYD2Utility.updateWithTarget(rollData)
|
||||
console.log("ARME!", rollData)
|
||||
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
rollDialog.render(true)
|
||||
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
}
|
||||
/* -------------------------------------------- */
|
||||
async rollAssommer() {
|
||||
@@ -716,8 +841,7 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
rollData.assomer = true
|
||||
rollData.conditionsCommunes = true
|
||||
MournbladeCYD2Utility.updateWithTarget(rollData)
|
||||
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
rollDialog.render(true)
|
||||
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
}
|
||||
/* -------------------------------------------- */
|
||||
async rollCoupBas() {
|
||||
@@ -725,8 +849,7 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
rollData.coupBas = true
|
||||
rollData.conditionsCommunes = true
|
||||
MournbladeCYD2Utility.updateWithTarget(rollData)
|
||||
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
rollDialog.render(true)
|
||||
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
}
|
||||
/* -------------------------------------------- */
|
||||
async rollImmobiliser() {
|
||||
@@ -735,8 +858,7 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
rollData.conditionsCommunes = true
|
||||
rollData.cibleconsciente = true
|
||||
MournbladeCYD2Utility.updateWithTarget(rollData)
|
||||
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
rollDialog.render(true)
|
||||
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
}
|
||||
/* -------------------------------------------- */
|
||||
async rollRepousser() {
|
||||
@@ -745,8 +867,7 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
rollData.conditionsCommunes = true
|
||||
rollData.cibleconsciente = true
|
||||
MournbladeCYD2Utility.updateWithTarget(rollData)
|
||||
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
rollDialog.render(true)
|
||||
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
}
|
||||
/* -------------------------------------------- */
|
||||
async rollDesengager() {
|
||||
@@ -754,8 +875,34 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
rollData.desengager = true
|
||||
rollData.conditionsCommunes = true
|
||||
MournbladeCYD2Utility.updateWithTarget(rollData)
|
||||
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
rollDialog.render(true)
|
||||
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
subPointsAme(runeMode, value) {
|
||||
let ame = foundry.utils.duplicate(this.system.ame)
|
||||
if (runeMode == "prononcer") {
|
||||
ame.etat += value
|
||||
ame.etat = Math.min(ame.etat, ame.nbame)
|
||||
} else {
|
||||
ame.max = (ame.max || 0) + value
|
||||
}
|
||||
this.update({ 'system.ame': ame })
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
async rollRune(runeId) {
|
||||
let comp = this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "savoir : runes")
|
||||
if (!comp) {
|
||||
ui.notifications.warn("La compétence « Savoir : Runes » n'a pas été trouvée sur ce personnage.")
|
||||
return
|
||||
}
|
||||
let rollData = this.getCommonRollData("cla", comp.id)
|
||||
rollData.rune = foundry.utils.duplicate(this.items.get(runeId) || {})
|
||||
rollData.difficulte = rollData.rune?.system?.seuil || 0
|
||||
rollData.runemode = "prononcer"
|
||||
rollData.runeame = 1
|
||||
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
@@ -793,6 +940,8 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
roll = await new Roll("1d10+" + arme.system.totalDegats + "+" + bonus + "+" + bonus2).roll()
|
||||
}
|
||||
await MournbladeCYD2Utility.showDiceSoNice(roll, game.settings.get("core", "rollMode"));
|
||||
// CYD 2.0: états SUPPLÉMENTAIRES au-delà du -1 automatique à la réussite.
|
||||
// Math.floor(total/SV) = 0 (<SV), 1 (≥SV), 2 (≥2×SV) → totaux finaux : 1, 2, 3
|
||||
let nbEtatPerdus = 0
|
||||
if (targetVigueur) {
|
||||
nbEtatPerdus = Math.floor(roll.total / targetVigueur)
|
||||
@@ -800,6 +949,7 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
//console.log(roll)
|
||||
let rollData = {
|
||||
arme: arme,
|
||||
diceResult: roll.dice[0]?.total ?? roll.total,
|
||||
finalResult: roll.total,
|
||||
formula: roll.formula,
|
||||
alias: this.name,
|
||||
@@ -811,7 +961,7 @@ export class MournbladeCYD2Actor extends Actor {
|
||||
nbEtatPerdus: nbEtatPerdus
|
||||
}
|
||||
MournbladeCYD2Utility.createChatWithRollMode(rollData.alias, {
|
||||
content: await renderTemplate(`systems/fvtt-mournblade-cyd2/templates/chat-degats-result.html`, rollData)
|
||||
content: await renderTemplate(`systems/fvtt-mournblade-cyd-2-0/templates/chat-degats-result.hbs`, rollData)
|
||||
})
|
||||
|
||||
if (rollDataInput?.defenderTokenId && nbEtatPerdus) {
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
/**
|
||||
* Extend the basic ActorSheet with some very simple modifications
|
||||
* @extends {ActorSheet}
|
||||
*/
|
||||
|
||||
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
|
||||
import { MournbladeCYD2Automation } from "./mournblade-cyd2-automation.js";
|
||||
|
||||
/* -------------------------------------------- */
|
||||
const __ALLOWED_ITEM_CELLULE = { "talent": 1, "ressource": 1, "contact": 1, "equipement": 1, "protection": 1, "artefact": 1, "arme": 1, "monnaie": 1 }
|
||||
|
||||
/* -------------------------------------------- */
|
||||
export class MournbladeCYD2CelluleSheet extends foundry.appv1.sheets.ActorSheet {
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["fvtt-mournblade-cyd2", "sheet", "actor"],
|
||||
template: "systems/fvtt-mournblade-cyd2/templates/cellule-sheet.html",
|
||||
width: 640,
|
||||
height: 720,
|
||||
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "talents" }],
|
||||
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
|
||||
editScore: false
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
async getData() {
|
||||
const objectData = foundry.utils.duplicate(this.object)
|
||||
|
||||
let formData = {
|
||||
title: this.title,
|
||||
id: objectData.id,
|
||||
type: objectData.type,
|
||||
img: objectData.img,
|
||||
name: objectData.name,
|
||||
editable: this.isEditable,
|
||||
cssClass: this.isEditable ? "editable" : "locked",
|
||||
system: objectData.system,
|
||||
effects: this.object.effects.map(e => foundry.utils.deepClone(e.data)),
|
||||
limited: this.object.limited,
|
||||
talents: foundry.utils.duplicate(this.actor.getTalents() || {}),
|
||||
ressources: foundry.utils.duplicate(this.actor.getRessources()),
|
||||
contacts: foundry.utils.duplicate(this.actor.getContacts()),
|
||||
members: this.getMembers(),
|
||||
equipements: foundry.utils.duplicate(this.actor.getEquipments()),
|
||||
artefacts: foundry.utils.duplicate(this.actor.getArtefacts()),
|
||||
armes: foundry.utils.duplicate(this.actor.getWeapons()),
|
||||
monnaies: foundry.utils.duplicate(this.actor.getMonnaies()),
|
||||
protections: foundry.utils.duplicate(this.actor.getArmors()),
|
||||
richesse: this.actor.computeRichesse(),
|
||||
valeurEquipement: this.actor.computeValeurEquipement(),
|
||||
description: await TextEditor.enrichHTML(this.object.system.description, { async: true }),
|
||||
options: this.options,
|
||||
owner: this.document.isOwner,
|
||||
editScore: this.options.editScore,
|
||||
isGM: game.user.isGM,
|
||||
config: game.system.mournbladecyd2.config
|
||||
}
|
||||
this.formData = formData;
|
||||
|
||||
console.log("CELLULE : ", formData, this.object);
|
||||
return formData;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
getMembers( ) {
|
||||
let membersFull = []
|
||||
for(let def of this.actor.system.members) {
|
||||
let actor = game.actors.get(def.id)
|
||||
membersFull.push( { name: actor.name, id: actor.id, img: actor.img } )
|
||||
}
|
||||
return membersFull
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/** @override */
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
|
||||
// Everything below here is only needed if the sheet is editable
|
||||
if (!this.options.editable) return;
|
||||
|
||||
// Update Inventory Item
|
||||
html.find('.actor-edit').click(ev => {
|
||||
const li = $(ev.currentTarget).parents(".item")
|
||||
let actorId = li.data("actor-id")
|
||||
const actor = game.actors.get(actorId)
|
||||
actor.sheet.render(true)
|
||||
})
|
||||
html.find('.actor-delete').click(ev => {
|
||||
const li = $(ev.currentTarget).parents(".item")
|
||||
let actorId = li.data("actor-id")
|
||||
this.actor.removeMember(actorId)
|
||||
})
|
||||
|
||||
// Update Inventory Item
|
||||
html.find('.item-edit').click(ev => {
|
||||
const li = $(ev.currentTarget).parents(".item")
|
||||
let itemId = li.data("item-id")
|
||||
const item = this.actor.items.get(itemId)
|
||||
item.sheet.render(true)
|
||||
})
|
||||
// Delete Inventory Item
|
||||
html.find('.item-delete').click(ev => {
|
||||
const li = $(ev.currentTarget).parents(".item");
|
||||
MournbladeCYD2Utility.confirmDelete(this, li);
|
||||
})
|
||||
html.find('.edit-item-data').change(ev => {
|
||||
const li = $(ev.currentTarget).parents(".item")
|
||||
let itemId = li.data("item-id")
|
||||
let itemType = li.data("item-type")
|
||||
let itemField = $(ev.currentTarget).data("item-field")
|
||||
let dataType = $(ev.currentTarget).data("dtype")
|
||||
let value = ev.currentTarget.value
|
||||
this.actor.editItemField(itemId, itemType, itemField, dataType, value)
|
||||
})
|
||||
html.find('.quantity-modify').click(event => {
|
||||
const li = $(event.currentTarget).parents(".item")
|
||||
const value = Number($(event.currentTarget).data("quantite-value"))
|
||||
this.actor.incDecQuantity( li.data("item-id"), value );
|
||||
})
|
||||
html.find('.item-add').click((event) => {
|
||||
const itemType = $(event.currentTarget).data("type")
|
||||
this.actor.createEmbeddedDocuments('Item', [{ name: `Nouveau ${itemType}`, type: itemType }], { renderSheet: true })
|
||||
})
|
||||
|
||||
html.find('.lock-unlock-sheet').click((event) => {
|
||||
this.options.editScore = !this.options.editScore;
|
||||
this.render(true);
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
async _onDropActor(event, dragData) {
|
||||
const actor = fromUuidSync(dragData.uuid)
|
||||
if (actor) {
|
||||
this.actor.addMember(actor.id)
|
||||
} else {
|
||||
ui.notifications.warn("Cet acteur n'a pas été trouvé.")
|
||||
}
|
||||
super._onDropActor(event)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
async _onDropItem(event, dragData) {
|
||||
let data = event.dataTransfer.getData('text/plain')
|
||||
let dataItem = JSON.parse(data)
|
||||
let item = fromUuidSync(dataItem.uuid)
|
||||
if (item.pack) {
|
||||
item = await MournbladeCYD2Utility.searchItem(item)
|
||||
}
|
||||
if ( __ALLOWED_ITEM_CELLULE[item.type]) {
|
||||
super._onDropItem(event, dragData)
|
||||
return
|
||||
}
|
||||
ui.notification.info("Ce type d'item n'est pas autorisé sur une Cellule.")
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/** @override */
|
||||
setPosition(options = {}) {
|
||||
const position = super.setPosition(options);
|
||||
const sheetBody = this.element.find(".sheet-body");
|
||||
const bodyHeight = position.height - 192;
|
||||
sheetBody.css("height", bodyHeight);
|
||||
return position;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/* -------------------------------------------- */
|
||||
|
||||
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
|
||||
import { MournbladeCYD2RollDialog } from "./mournblade-cyd2-roll-dialog.js";
|
||||
import { MournbladeCYD2RollDialog } from "./applications/mournblade-cyd2-roll-dialog.mjs";
|
||||
|
||||
/* -------------------------------------------- */
|
||||
export class MournbladeCYD2Commands {
|
||||
@@ -88,8 +88,8 @@ export class MournbladeCYD2Commands {
|
||||
}
|
||||
if (command && command.func) {
|
||||
const result = command.func(content, msg, params);
|
||||
if (result == false) {
|
||||
RdDCommands._chatAnswer(msg, command.descr);
|
||||
if (result === false) {
|
||||
MournbladeCYD2Commands._chatAnswer(msg, command.descr);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -98,8 +98,7 @@ export class MournbladeCYD2Commands {
|
||||
|
||||
/* -------------------------------------------- */
|
||||
async createChar(msg) {
|
||||
game.system.MournbladeCYD2.creator = new MournbladeCYD2ActorCreate();
|
||||
game.system.MournbladeCYD2.creator.start();
|
||||
ui.notifications.warn("La création automatique de personnage n'est pas disponible dans cette version.")
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||