Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -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/
|
||||||
+812
@@ -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.
|
||||||
|
```
|
||||||
|
|
||||||
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 7.4 KiB |
+30
@@ -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;
|
||||||
+108
-1
@@ -20,7 +20,26 @@
|
|||||||
"runeeffect": "Effet de Rune",
|
"runeeffect": "Effet de Rune",
|
||||||
"tendance": "Tendance",
|
"tendance": "Tendance",
|
||||||
"traitchaotique": "Trait Chaotique",
|
"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": {
|
"MOURNBLADE": {
|
||||||
@@ -28,5 +47,93 @@
|
|||||||
"editTrait": "Modifier le trait",
|
"editTrait": "Modifier le trait",
|
||||||
"deleteTrait": "Supprimer 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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,315 @@
|
|||||||
|
/* ==================== 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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,505 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
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,
|
||||||
|
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,27 @@
|
|||||||
|
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();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Macro pour remplacer les chemins d'images dans les compendiums
|
* Macro pour remplacer les chemins d'images dans les compendiums
|
||||||
* Remplace "fvtt-hawkmoon-cyd" par "fvtt-mournblade-cyd2" dans tous les champs 'img'
|
* Remplace "fvtt-hawkmoon-cyd" par "fvtt-mournblade-cyd-2-0" dans tous les champs 'img'
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
content: `<p>Cette macro va :</p>
|
content: `<p>Cette macro va :</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Déverrouiller tous les compendiums</li>
|
<li>Déverrouiller tous les compendiums</li>
|
||||||
<li>Remplacer "fvtt-hawkmoon-cyd" par "fvtt-mournblade-cyd2" dans tous les champs 'img'</li>
|
<li>Remplacer "fvtt-hawkmoon-cyd" par "fvtt-mournblade-cyd-2-0" dans tous les champs 'img'</li>
|
||||||
<li>Reverrouiller les compendiums</li>
|
<li>Reverrouiller les compendiums</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p><strong>Voulez-vous continuer ?</strong></p>`,
|
<p><strong>Voulez-vous continuer ?</strong></p>`,
|
||||||
@@ -49,14 +49,14 @@
|
|||||||
|
|
||||||
// Vérifier le champ img principal
|
// Vérifier le champ img principal
|
||||||
if (doc.img && doc.img.includes("fvtt-hawkmoon-cyd")) {
|
if (doc.img && doc.img.includes("fvtt-hawkmoon-cyd")) {
|
||||||
updateData.img = doc.img.replace(/fvtt-hawkmoon-cyd/g, "fvtt-mournblade-cyd2");
|
updateData.img = doc.img.replace(/fvtt-hawkmoon-cyd/g, "fvtt-mournblade-cyd-2-0");
|
||||||
needsUpdate = true;
|
needsUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pour les acteurs, vérifier aussi prototypeToken.texture.src
|
// Pour les acteurs, vérifier aussi prototypeToken.texture.src
|
||||||
if (doc.documentName === "Actor" && doc.prototypeToken?.texture?.src) {
|
if (doc.documentName === "Actor" && doc.prototypeToken?.texture?.src) {
|
||||||
if (doc.prototypeToken.texture.src.includes("fvtt-hawkmoon-cyd")) {
|
if (doc.prototypeToken.texture.src.includes("fvtt-hawkmoon-cyd")) {
|
||||||
updateData["prototypeToken.texture.src"] = doc.prototypeToken.texture.src.replace(/fvtt-hawkmoon-cyd/g, "fvtt-mournblade-cyd2");
|
updateData["prototypeToken.texture.src"] = doc.prototypeToken.texture.src.replace(/fvtt-hawkmoon-cyd/g, "fvtt-mournblade-cyd-2-0");
|
||||||
needsUpdate = true;
|
needsUpdate = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
// Pour les scènes, vérifier background.src et les tokens
|
// Pour les scènes, vérifier background.src et les tokens
|
||||||
if (doc.documentName === "Scene") {
|
if (doc.documentName === "Scene") {
|
||||||
if (doc.background?.src && doc.background.src.includes("fvtt-hawkmoon-cyd")) {
|
if (doc.background?.src && doc.background.src.includes("fvtt-hawkmoon-cyd")) {
|
||||||
updateData["background.src"] = doc.background.src.replace(/fvtt-hawkmoon-cyd/g, "fvtt-mournblade-cyd2");
|
updateData["background.src"] = doc.background.src.replace(/fvtt-hawkmoon-cyd/g, "fvtt-mournblade-cyd-2-0");
|
||||||
needsUpdate = true;
|
needsUpdate = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,211 +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() || []),
|
|
||||||
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(),
|
|
||||||
profils: foundry.utils.duplicate(this.actor.getProfils() || []),
|
|
||||||
combat: this.actor.getCombatValues(),
|
|
||||||
equipements: foundry.utils.duplicate(this.actor.getEquipments()),
|
|
||||||
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),
|
|
||||||
nbAme: this.actor.system.ame.nbame,
|
|
||||||
ameMax: this.actor.getAmeMax(),
|
|
||||||
ameList: MournbladeCYD2Utility.getAmeList(this.actor.system.ame.nbame, this.actor.getAmeMax()),
|
|
||||||
ameMaxList: MournbladeCYD2Utility.getAmeMaxList(this.actor.system.ame.nbame),
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
/** @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 { 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]
|
const __degatsBonus = [-2, -2, -1, -1, 0, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 8, 8, 9, 9, 10, 10]
|
||||||
@@ -39,14 +39,14 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
|
|
||||||
if (data.type == 'personnage') {
|
if (data.type == 'personnage') {
|
||||||
console.log("Loading skills for personnage")
|
console.log("Loading skills for personnage")
|
||||||
const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd2.skills")
|
const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd-2-0.skills")
|
||||||
data.items = skills.map(i => i.toObject())
|
data.items = skills.map(i => i.toObject())
|
||||||
}
|
}
|
||||||
if (data.type == 'creature') {
|
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 = 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 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-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-cyd-2-0/assets/icons/melee.webp", system: { typearme: "contact", bonusmaniementoff: 0, seuildefense: 0, degats: "0" } })
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.create(data, options);
|
return super.create(data, options);
|
||||||
@@ -145,6 +145,12 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
getRunes() {
|
getRunes() {
|
||||||
return this.getItemSorted(["rune"])
|
return this.getItemSorted(["rune"])
|
||||||
}
|
}
|
||||||
|
getRuneEffects() {
|
||||||
|
return this.getItemSorted(["runeeffect"])
|
||||||
|
}
|
||||||
|
getProfil() {
|
||||||
|
return this.getProfils()[0] ?? null
|
||||||
|
}
|
||||||
getTraitsChaotiques() {
|
getTraitsChaotiques() {
|
||||||
return this.getItemSorted(["traitchaotique"])
|
return this.getItemSorted(["traitchaotique"])
|
||||||
}
|
}
|
||||||
@@ -220,6 +226,9 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
}
|
}
|
||||||
return equipProtection // Uniquement la protection des armures + boucliers
|
return equipProtection // Uniquement la protection des armures + boucliers
|
||||||
}
|
}
|
||||||
|
getProtectionTotal() {
|
||||||
|
return this.getProtection()
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
getCombatValues() {
|
getCombatValues() {
|
||||||
@@ -237,9 +246,6 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
return combat
|
return combat
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
prepareBaseData() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
async prepareData() {
|
async prepareData() {
|
||||||
@@ -267,12 +273,43 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
_preUpdate(changed, options, user) {
|
_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(() => {
|
setTimeout(() => {
|
||||||
this.processCombativite(changed.system.sante)
|
this.processCombativite(changed.system.sante, oldEtat)
|
||||||
}, 800)
|
}, 800)
|
||||||
}
|
}
|
||||||
if (changed?.system?.ame?.etat && changed?.system?.ame?.etat != this.system.ame.etat) {
|
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)
|
// 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
|
let minAme = this.system.ame.max !== undefined ? this.system.ame.max : 0
|
||||||
if (changed.system.ame.etat < minAme) {
|
if (changed.system.ame.etat < minAme) {
|
||||||
@@ -282,8 +319,9 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
if (changed.system.ame.etat > this.system.ame.nbame) {
|
if (changed.system.ame.etat > this.system.ame.nbame) {
|
||||||
changed.system.ame.etat = this.system.ame.nbame
|
changed.system.ame.etat = this.system.ame.nbame
|
||||||
}
|
}
|
||||||
|
const oldEtat = this.system.ame.etat
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.processAme(changed.system.ame)
|
this.processAme(changed.system.ame, oldEtat)
|
||||||
}, 800)
|
}, 800)
|
||||||
}
|
}
|
||||||
// Si le max d'Âme change, ajuster l'état actuel si nécessaire
|
// Si le max d'Âme change, ajuster l'état actuel si nécessaire
|
||||||
@@ -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)
|
let item = this.items.find(item => item.id == itemId)
|
||||||
if (item) {
|
if (item) {
|
||||||
console.log("Item ", item, itemField, dataType, value)
|
console.log("Item ", item, itemField, dataType, value)
|
||||||
@@ -328,7 +366,7 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
value = String(value)
|
value = String(value)
|
||||||
}
|
}
|
||||||
let update = { _id: item.id, [`system.${itemField}`]: value };
|
let update = { _id: item.id, [`system.${itemField}`]: value };
|
||||||
this.updateEmbeddedDocuments("Item", [update])
|
await this.updateEmbeddedDocuments("Item", [update])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,7 +495,15 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
getAttribute(attrKey) {
|
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())
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
@@ -500,38 +546,57 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
processCombativite(sante) {
|
processCombativite(sante, oldEtat = undefined) {
|
||||||
sante = sante || foundry.utils.duplicate(this.system.sante)
|
sante = sante || foundry.utils.duplicate(this.system.sante)
|
||||||
// Gestion des états affaibli et très affaibli
|
const affaibli = this.system.sante.nbcombativite - 2
|
||||||
if (sante.etat == this.system.sante.nbcombativite - 2 || sante.etat == this.system.sante.nbcombativite - 1) {
|
const tresAffaibli = this.system.sante.nbcombativite - 1
|
||||||
if (sante.etat == this.system.sante.nbcombativite - 2 && this.items.find(item => item.type == "talent" && item.name.toLowerCase() == "encaissement")) {
|
// oldEtat permet de détecter les sauts qui franchissent Affaibli ou Très Affaibli
|
||||||
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>` })
|
// sans y atterrir exactement (ex: 0 → 5 doit déclencher les deux seuils)
|
||||||
} else if (sante.etat == this.system.sante.nbcombativite - 1 && this.items.find(item => item.type == "talent" && item.name.toLowerCase().includes("vaillant"))) {
|
const prev = oldEtat !== undefined ? oldEtat : sante.etat
|
||||||
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 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 {
|
} 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)
|
this.incDecAdversite("rouge", 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
processAme(ame) {
|
processAme(ame, oldEtat = undefined) {
|
||||||
ame = ame || foundry.utils.duplicate(this.system.ame)
|
ame = ame || foundry.utils.duplicate(this.system.ame)
|
||||||
let traumatiseValue = this.system.ame.nbame - 2
|
const traumatiseValue = this.system.ame.nbame - 2
|
||||||
let tresTraumatiseValue = this.system.ame.nbame - 1
|
const tresTraumatiseValue = this.system.ame.nbame - 1
|
||||||
let briseValue = this.system.ame.nbame
|
const briseValue = this.system.ame.nbame
|
||||||
|
const prev = oldEtat !== undefined ? oldEtat : ame.etat
|
||||||
|
const curr = ame.etat
|
||||||
|
|
||||||
// Gestion des états Traumatisé, Très Traumatisé et Brisé
|
// Déclencher pour chaque seuil franchi ou atteint, même en cas de saut
|
||||||
if (ame.etat == traumatiseValue) {
|
if (curr >= traumatiseValue && prev < traumatiseValue) {
|
||||||
ChatMessage.create({ content: `<strong>${this.name} est Traumatisé et subit 1 adversité bleue et 1 adversité noire !</strong>` })
|
ChatMessage.create({ content: `<strong>${this.name} est Traumatisé et subit 1 adversité bleue et 1 adversité noire !</strong>` })
|
||||||
this.incDecAdversite("bleue", 1)
|
this.incDecAdversite("bleue", 1)
|
||||||
this.incDecAdversite("noire", 1)
|
this.incDecAdversite("noire", 1)
|
||||||
} else if (ame.etat == tresTraumatiseValue) {
|
}
|
||||||
|
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>` })
|
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("bleue", 1)
|
||||||
this.incDecAdversite("noire", 1)
|
this.incDecAdversite("noire", 1)
|
||||||
} else if (ame.etat >= briseValue) {
|
}
|
||||||
|
if (curr >= briseValue && prev < briseValue) {
|
||||||
ChatMessage.create({ content: `<strong>${this.name} est Brisé et subit 1 adversité noire !</strong>` })
|
ChatMessage.create({ content: `<strong>${this.name} est Brisé et subit 1 adversité noire !</strong>` })
|
||||||
this.incDecAdversite("noire", 1)
|
this.incDecAdversite("noire", 1)
|
||||||
}
|
}
|
||||||
@@ -599,7 +664,7 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
let valueSC = 0
|
let valueSC = 0
|
||||||
for (let monnaie of this.items) {
|
for (let monnaie of this.items) {
|
||||||
if (monnaie.type == "monnaie") {
|
if (monnaie.type == "monnaie") {
|
||||||
valueSC += Number(monnaie.system.prixsc) * Number(monnaie.system.quantite)
|
valueSC += MournbladeCYD2Utility.getItemValueSC(monnaie)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return MournbladeCYD2Utility.computeMonnaieDetails(valueSC)
|
return MournbladeCYD2Utility.computeMonnaieDetails(valueSC)
|
||||||
@@ -610,9 +675,7 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
let valueSC = 0
|
let valueSC = 0
|
||||||
for (let equip of this.items) {
|
for (let equip of this.items) {
|
||||||
if (equip.type == "equipement" || equip.type == "arme" || equip.type == "protection") {
|
if (equip.type == "equipement" || equip.type == "arme" || equip.type == "protection") {
|
||||||
valueSC += Number(equip.system.prixsc) * Number(equip.system.quantite ?? 1)
|
valueSC += MournbladeCYD2Utility.getItemValueSC(equip)
|
||||||
valueSC += (Number(equip.system.prixca) * Number(equip.system.quantite ?? 1)) * 20
|
|
||||||
valueSC += (Number(equip.system.prixpo) * Number(equip.system.quantite ?? 1)) * 400
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return MournbladeCYD2Utility.computeMonnaieDetails(valueSC)
|
return MournbladeCYD2Utility.computeMonnaieDetails(valueSC)
|
||||||
@@ -721,7 +784,7 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
if (attrKey) {
|
if (attrKey) {
|
||||||
rollData.attrKey = attrKey
|
rollData.attrKey = attrKey
|
||||||
if (attrKey != "tochoose") {
|
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])
|
rollData.attr = foundry.utils.duplicate(this.system.attributs[attrKey])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -747,10 +810,9 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
async rollAttribut(attrKey, isInit = false) {
|
async rollAttribut(attrKey, isInit = false) {
|
||||||
let rollData = this.getCommonRollData(attrKey)
|
let rollData = this.getCommonRollData(attrKey)
|
||||||
rollData.multiplier = (isInit) ? 1 : 2
|
rollData.multiplier = 1 // 1d10 + attr (optionally + attr2 if chosen in dialog)
|
||||||
rollData.isInit = isInit
|
rollData.isInit = isInit
|
||||||
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
|
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||||
rollDialog.render(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
@@ -758,8 +820,7 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
let rollData = this.getCommonRollData(attrKey, compId)
|
let rollData = this.getCommonRollData(attrKey, compId)
|
||||||
rollData.multiplier = 1 // Attr multiplier, always 1 in competence mode
|
rollData.multiplier = 1 // Attr multiplier, always 1 in competence mode
|
||||||
console.log("RollDatra", rollData)
|
console.log("RollDatra", rollData)
|
||||||
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
|
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||||
rollDialog.render(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
@@ -772,8 +833,7 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
rollData.arme = arme
|
rollData.arme = arme
|
||||||
MournbladeCYD2Utility.updateWithTarget(rollData)
|
MournbladeCYD2Utility.updateWithTarget(rollData)
|
||||||
console.log("ARME!", rollData)
|
console.log("ARME!", rollData)
|
||||||
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
|
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||||
rollDialog.render(true)
|
|
||||||
}
|
}
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
async rollAssommer() {
|
async rollAssommer() {
|
||||||
@@ -781,8 +841,7 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
rollData.assomer = true
|
rollData.assomer = true
|
||||||
rollData.conditionsCommunes = true
|
rollData.conditionsCommunes = true
|
||||||
MournbladeCYD2Utility.updateWithTarget(rollData)
|
MournbladeCYD2Utility.updateWithTarget(rollData)
|
||||||
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
|
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||||
rollDialog.render(true)
|
|
||||||
}
|
}
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
async rollCoupBas() {
|
async rollCoupBas() {
|
||||||
@@ -790,8 +849,7 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
rollData.coupBas = true
|
rollData.coupBas = true
|
||||||
rollData.conditionsCommunes = true
|
rollData.conditionsCommunes = true
|
||||||
MournbladeCYD2Utility.updateWithTarget(rollData)
|
MournbladeCYD2Utility.updateWithTarget(rollData)
|
||||||
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
|
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||||
rollDialog.render(true)
|
|
||||||
}
|
}
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
async rollImmobiliser() {
|
async rollImmobiliser() {
|
||||||
@@ -800,8 +858,7 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
rollData.conditionsCommunes = true
|
rollData.conditionsCommunes = true
|
||||||
rollData.cibleconsciente = true
|
rollData.cibleconsciente = true
|
||||||
MournbladeCYD2Utility.updateWithTarget(rollData)
|
MournbladeCYD2Utility.updateWithTarget(rollData)
|
||||||
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
|
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||||
rollDialog.render(true)
|
|
||||||
}
|
}
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
async rollRepousser() {
|
async rollRepousser() {
|
||||||
@@ -810,8 +867,7 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
rollData.conditionsCommunes = true
|
rollData.conditionsCommunes = true
|
||||||
rollData.cibleconsciente = true
|
rollData.cibleconsciente = true
|
||||||
MournbladeCYD2Utility.updateWithTarget(rollData)
|
MournbladeCYD2Utility.updateWithTarget(rollData)
|
||||||
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
|
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||||
rollDialog.render(true)
|
|
||||||
}
|
}
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
async rollDesengager() {
|
async rollDesengager() {
|
||||||
@@ -819,8 +875,34 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
rollData.desengager = true
|
rollData.desengager = true
|
||||||
rollData.conditionsCommunes = true
|
rollData.conditionsCommunes = true
|
||||||
MournbladeCYD2Utility.updateWithTarget(rollData)
|
MournbladeCYD2Utility.updateWithTarget(rollData)
|
||||||
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
|
await MournbladeCYD2RollDialog.create(this, rollData)
|
||||||
rollDialog.render(true)
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
@@ -858,6 +940,8 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
roll = await new Roll("1d10+" + arme.system.totalDegats + "+" + bonus + "+" + bonus2).roll()
|
roll = await new Roll("1d10+" + arme.system.totalDegats + "+" + bonus + "+" + bonus2).roll()
|
||||||
}
|
}
|
||||||
await MournbladeCYD2Utility.showDiceSoNice(roll, game.settings.get("core", "rollMode"));
|
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
|
let nbEtatPerdus = 0
|
||||||
if (targetVigueur) {
|
if (targetVigueur) {
|
||||||
nbEtatPerdus = Math.floor(roll.total / targetVigueur)
|
nbEtatPerdus = Math.floor(roll.total / targetVigueur)
|
||||||
@@ -865,6 +949,7 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
//console.log(roll)
|
//console.log(roll)
|
||||||
let rollData = {
|
let rollData = {
|
||||||
arme: arme,
|
arme: arme,
|
||||||
|
diceResult: roll.dice[0]?.total ?? roll.total,
|
||||||
finalResult: roll.total,
|
finalResult: roll.total,
|
||||||
formula: roll.formula,
|
formula: roll.formula,
|
||||||
alias: this.name,
|
alias: this.name,
|
||||||
@@ -876,7 +961,7 @@ export class MournbladeCYD2Actor extends Actor {
|
|||||||
nbEtatPerdus: nbEtatPerdus
|
nbEtatPerdus: nbEtatPerdus
|
||||||
}
|
}
|
||||||
MournbladeCYD2Utility.createChatWithRollMode(rollData.alias, {
|
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) {
|
if (rollDataInput?.defenderTokenId && nbEtatPerdus) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
|
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 {
|
export class MournbladeCYD2Commands {
|
||||||
@@ -88,8 +88,8 @@ export class MournbladeCYD2Commands {
|
|||||||
}
|
}
|
||||||
if (command && command.func) {
|
if (command && command.func) {
|
||||||
const result = command.func(content, msg, params);
|
const result = command.func(content, msg, params);
|
||||||
if (result == false) {
|
if (result === false) {
|
||||||
RdDCommands._chatAnswer(msg, command.descr);
|
MournbladeCYD2Commands._chatAnswer(msg, command.descr);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -98,8 +98,7 @@ export class MournbladeCYD2Commands {
|
|||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
async createChar(msg) {
|
async createChar(msg) {
|
||||||
game.system.MournbladeCYD2.creator = new MournbladeCYD2ActorCreate();
|
ui.notifications.warn("La création automatique de personnage n'est pas disponible dans cette version.")
|
||||||
game.system.MournbladeCYD2.creator.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|||||||
@@ -1,18 +1,28 @@
|
|||||||
|
const localizeOrFallback = (key, fallback) => globalThis.game?.i18n?.localize?.(key) ?? fallback
|
||||||
|
|
||||||
export const MOURNBLADECYD2_CONFIG = {
|
export const MOURNBLADECYD2_CONFIG = {
|
||||||
|
attributs: {
|
||||||
|
adr: "Adresse",
|
||||||
|
pui: "Puissance",
|
||||||
|
cla: "Clairvoyance",
|
||||||
|
pre: "Présence",
|
||||||
|
tre: "Trempe"
|
||||||
|
},
|
||||||
allegeanceOptions: {
|
allegeanceOptions: {
|
||||||
tous: 'Tous',
|
tous: localizeOrFallback("MNBL.all", "Tous"),
|
||||||
chaos: 'Chaos',
|
chaos: localizeOrFallback("MNBL.chaos", "Chaos"),
|
||||||
loi: 'Loi',
|
loi: localizeOrFallback("MNBL.law", "Loi"),
|
||||||
betes: 'Seigneurs des Bêtes',
|
balance: localizeOrFallback("MNBL.balance", "Balance"),
|
||||||
elementaires: 'Seigneurs des Eléments'
|
betes: localizeOrFallback("MNBL.betes", "Bêtes"),
|
||||||
|
elementaires: localizeOrFallback("MNBL.elementaires", "Élémentaires")
|
||||||
},
|
},
|
||||||
lancementRuneOptions: {
|
lancementRuneOptions: {
|
||||||
prononcer: 'Prononcer',
|
prononcer: localizeOrFallback("MNBL.pronouncerune", "Prononcer"),
|
||||||
inscrire: 'Tracer'
|
inscrire: localizeOrFallback("MNBL.tracerune", "Tracer")
|
||||||
},
|
},
|
||||||
effetRuneOptions: {
|
effetRuneOptions: {
|
||||||
prononcee: 'Prononcée',
|
prononcee: localizeOrFallback("MNBL.pronounced", "Prononcée"),
|
||||||
inscrite: 'Inscrite'
|
inscrite: localizeOrFallback("MNBL.traced", "Tracée")
|
||||||
},
|
},
|
||||||
|
|
||||||
optionsDifficulte: [
|
optionsDifficulte: [
|
||||||
@@ -45,9 +55,9 @@ export const MOURNBLADECYD2_CONFIG = {
|
|||||||
{ key: "30", label: "Pure Folie (30)" }
|
{ key: "30", label: "Pure Folie (30)" }
|
||||||
],
|
],
|
||||||
optionsDistanceTir: [
|
optionsDistanceTir: [
|
||||||
{ key: "porteecourte", label: "Courte ({protectionDefenseur}+5)" },
|
{ key: "porteecourte", label: "Courte (base +5)" },
|
||||||
{ key: "porteemoyenne", label: "Moyenne ({protectionDefenseur}+9)" },
|
{ key: "porteemoyenne", label: "Moyenne (base +9)" },
|
||||||
{ key: "porteelongue", label: "Longue ({protectionDefenseur}+14)" }
|
{ key: "porteelongue", label: "Longue (base +14)" }
|
||||||
],
|
],
|
||||||
optionsBonusMalus: [
|
optionsBonusMalus: [
|
||||||
{ key: "-10", label: "-10" },
|
{ key: "-10", label: "-10" },
|
||||||
@@ -109,9 +119,104 @@ export const MOURNBLADECYD2_CONFIG = {
|
|||||||
{ key: "personnage", label: "Personnage" },
|
{ key: "personnage", label: "Personnage" },
|
||||||
{ key: "traitespece", label: "Trait d'espèce" }
|
{ key: "traitespece", label: "Trait d'espèce" }
|
||||||
],
|
],
|
||||||
|
optionsSousTypeCreature: [
|
||||||
|
{ key: "creature", label: localizeOrFallback("MNBL.creature", "Créature") },
|
||||||
|
{ key: "demon", label: localizeOrFallback("MNBL.demon", "Démon") },
|
||||||
|
{ key: "automata", label: localizeOrFallback("MNBL.automata", "Automata") }
|
||||||
|
],
|
||||||
|
|
||||||
|
// Configuration des ActiveEffects
|
||||||
|
effectTypes: {
|
||||||
|
bonus: "Bonus",
|
||||||
|
malus: "Malus",
|
||||||
|
rune: "Effet de Rune",
|
||||||
|
don: "Effet de Don",
|
||||||
|
talent: "Effet de Talent",
|
||||||
|
trait: "Effet de Trait",
|
||||||
|
temporaire: "Effet Temporaire",
|
||||||
|
permanent: "Effet Permanent"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Clés des attributs pour les modifications d'effets
|
||||||
|
effectAttributeKeys: {
|
||||||
|
// Attributs
|
||||||
|
adr: "system.attributs.adr.value",
|
||||||
|
pui: "system.attributs.pui.value",
|
||||||
|
cla: "system.attributs.cla.value",
|
||||||
|
pre: "system.attributs.pre.value",
|
||||||
|
tre: "system.attributs.tre.value",
|
||||||
|
|
||||||
|
// Santé
|
||||||
|
vigueur: "system.sante.vigueur",
|
||||||
|
etat: "system.sante.etat",
|
||||||
|
nbcombativite: "system.sante.nbcombativite",
|
||||||
|
|
||||||
|
// Âme
|
||||||
|
nbame: "system.ame.nbame",
|
||||||
|
seuilpouvoir: "system.ame.seuilpouvoir",
|
||||||
|
etatAme: "system.ame.etat",
|
||||||
|
|
||||||
|
// Bonne Aventure
|
||||||
|
bonneaventure: "system.bonneaventure.base",
|
||||||
|
bonneaventureActuelle: "system.bonneaventure.actuelle",
|
||||||
|
eclat: "system.eclat.value",
|
||||||
|
|
||||||
|
// Combat
|
||||||
|
initiative: "system.combat.inittotal",
|
||||||
|
defense: "system.combat.defensetotal",
|
||||||
|
protection: "system.combat.protectiontotal",
|
||||||
|
|
||||||
|
// Adversités
|
||||||
|
adversiteBleue: "system.adversite.bleue",
|
||||||
|
adversiteRouge: "system.adversite.rouge",
|
||||||
|
adversiteNoire: "system.adversite.noire",
|
||||||
|
|
||||||
|
// Balance
|
||||||
|
loi: "system.balance.loi",
|
||||||
|
chaos: "system.balance.chaos",
|
||||||
|
aspect: "system.balance.aspect",
|
||||||
|
|
||||||
|
// Ressources
|
||||||
|
ressources: "system.ressources.value",
|
||||||
|
|
||||||
|
// Vitesse
|
||||||
|
vitesse: "system.vitesse.value"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Types de bonus/malus supportés (groupés par catégorie)
|
||||||
|
effectTypesConfig: {
|
||||||
|
attribut: {
|
||||||
|
label: "Attribut",
|
||||||
|
keys: ["adr", "pui", "cla", "pre", "tre"]
|
||||||
|
},
|
||||||
|
sante: {
|
||||||
|
label: "Santé",
|
||||||
|
keys: ["vigueur", "etat", "nbcombativite"]
|
||||||
|
},
|
||||||
|
ame: {
|
||||||
|
label: "Âme",
|
||||||
|
keys: ["nbame", "seuilpouvoir", "etatAme"]
|
||||||
|
},
|
||||||
|
combat: {
|
||||||
|
label: "Combat",
|
||||||
|
keys: ["initiative", "defense", "protection"]
|
||||||
|
},
|
||||||
|
bonneAventure: {
|
||||||
|
label: "Bonne Aventure",
|
||||||
|
keys: ["bonneaventure", "bonneaventureActuelle", "eclat"]
|
||||||
|
},
|
||||||
|
adversite: {
|
||||||
|
label: "Adversité",
|
||||||
|
keys: ["adversiteBleue", "adversiteRouge", "adversiteNoire"]
|
||||||
|
},
|
||||||
|
balance: {
|
||||||
|
label: "Balance",
|
||||||
|
keys: ["loi", "chaos", "aspect"]
|
||||||
|
}
|
||||||
|
},
|
||||||
optionsUseTalent: [
|
optionsUseTalent: [
|
||||||
{ key: "permanent", label: "Permanent" },
|
{ key: "permanent", label: "Permanent" },
|
||||||
{ key: "sceance", label: "Une fois par scéance" },
|
{ key: "sceance", label: "Une fois par séance" },
|
||||||
{ key: "scenario", label: "Une fois par scénario" },
|
{ key: "scenario", label: "Une fois par scénario" },
|
||||||
{ key: "jour", label: "Une fois par jour" },
|
{ key: "jour", label: "Une fois par jour" },
|
||||||
{ key: "unique", label: "Unique" }
|
{ key: "unique", label: "Unique" }
|
||||||
@@ -125,5 +230,23 @@ export const MOURNBLADECYD2_CONFIG = {
|
|||||||
{ key: "vigueur", label: "Vigueur" },
|
{ key: "vigueur", label: "Vigueur" },
|
||||||
{ key: "seuilpouvoir", label: "Seuil de Pouvoir" },
|
{ key: "seuilpouvoir", label: "Seuil de Pouvoir" },
|
||||||
{ key: "bonus-defensif", label: "Bonus au Seuil de Défense" }
|
{ key: "bonus-defensif", label: "Bonus au Seuil de Défense" }
|
||||||
|
],
|
||||||
|
etatsCombativite: [
|
||||||
|
{ key: 0, label: "Combatif" },
|
||||||
|
{ key: 1, label: "Éprouvé 1" },
|
||||||
|
{ key: 2, label: "Éprouvé 2" },
|
||||||
|
{ key: 3, label: "Affaibli" },
|
||||||
|
{ key: 4, label: "Très Affaibli" },
|
||||||
|
{ key: 5, label: "Vaincu" }
|
||||||
|
],
|
||||||
|
etatsAme: [
|
||||||
|
{ key: 0, label: "Serein" },
|
||||||
|
{ key: 1, label: "Stressé 1" },
|
||||||
|
{ key: 2, label: "Stressé 2" },
|
||||||
|
{ key: 3, label: "Stressé 3" },
|
||||||
|
{ key: 4, label: "Stressé 4" },
|
||||||
|
{ key: 5, label: "Traumatisé" },
|
||||||
|
{ key: 6, label: "Très Traumatisé" },
|
||||||
|
{ key: 7, label: "Brisé" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/**
|
|
||||||
* Extend the basic ActorSheet with some very simple modifications
|
|
||||||
* @extends {ActorSheet}
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { MournbladeCYD2ActorSheet } from "./mournblade-cyd2-actor-sheet.js";
|
|
||||||
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
|
|
||||||
import { MournbladeCYD2Automation } from "./mournblade-cyd2-automation.js";
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
export class MournbladeCYD2CreatureSheet extends MournbladeCYD2ActorSheet {
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
static get defaultOptions() {
|
|
||||||
|
|
||||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
||||||
classes: ["fvtt-mournblade-cyd2", "sheet", "actor"],
|
|
||||||
template: "systems/fvtt-mournblade-cyd2/templates/creature-sheet.html",
|
|
||||||
width: 640,
|
|
||||||
height: 720,
|
|
||||||
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }],
|
|
||||||
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
|
|
||||||
editScore: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,807 @@
|
|||||||
|
/**
|
||||||
|
* Gestion des ActiveEffects pour Mournblade CYD 2.0
|
||||||
|
* Ce module fournit des utilitaires pour créer, appliquer et gérer les effets actifs
|
||||||
|
* sur les Acteurs et les Items.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class MournbladeCYD2Effects {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise le système de gestion des effets
|
||||||
|
*/
|
||||||
|
static init() {
|
||||||
|
console.log("MournbladeCYD2 | Initializing ActiveEffects management");
|
||||||
|
|
||||||
|
// Hook pour appliquer les modifications des effets
|
||||||
|
Hooks.on("applyActiveEffect", (effect, change, current, delta, changes) => {
|
||||||
|
return this._onApplyActiveEffect(effect, change, current, delta, changes);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hook pour supprimer les modifications des effets
|
||||||
|
Hooks.on("removeActiveEffect", (effect, change, current, delta, changes) => {
|
||||||
|
return this._onRemoveActiveEffect(effect, change, current, delta, changes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse une valeur d'effet en nombre
|
||||||
|
* Gère les strings comme "+2", "-3", "5"
|
||||||
|
* @param {string|number} value - Valeur à parser
|
||||||
|
* @returns {number} - Valeur numérique
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
static _parseEffectValue(value) {
|
||||||
|
if (typeof value === 'number') return value;
|
||||||
|
if (typeof value !== 'string') return 0;
|
||||||
|
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (!trimmed) return 0;
|
||||||
|
|
||||||
|
if (trimmed.startsWith('+')) {
|
||||||
|
return parseFloat(trimmed.substring(1)) || 0;
|
||||||
|
} else if (trimmed.startsWith('-')) {
|
||||||
|
return -(parseFloat(trimmed.substring(1)) || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseFloat(trimmed) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook appelé lorsqu'un effet est appliqué
|
||||||
|
* Permet de personnaliser le calcul des modifications
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
static _onApplyActiveEffect(effect, change, current, delta, changes) {
|
||||||
|
// Pour Mournblade, nous voulons gérer les valeurs string (ex: "+1", "-2")
|
||||||
|
// Convertir delta en nombre si nécessaire
|
||||||
|
const numericDelta = this._parseEffectValue(delta);
|
||||||
|
const numericCurrent = current != null ? Number(current) : 0;
|
||||||
|
|
||||||
|
if (!isNaN(numericDelta) && !isNaN(numericCurrent)) {
|
||||||
|
return numericCurrent + numericDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si on ne peut pas calculer, retourner delta tel quel
|
||||||
|
return delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook appelé lorsqu'un effet est supprimé
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
static _onRemoveActiveEffect(effect, change, current, delta, changes) {
|
||||||
|
// Logique inverse de l'application
|
||||||
|
// Foundry gère déjà la suppression, ce hook est pour des calculs personnalisés
|
||||||
|
const numericDelta = this._parseEffectValue(delta);
|
||||||
|
const numericCurrent = current != null ? Number(current) : 0;
|
||||||
|
|
||||||
|
if (!isNaN(numericDelta) && !isNaN(numericCurrent)) {
|
||||||
|
return numericCurrent - numericDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Méthodes de création d'effets */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet simple de bonus/malus à un attribut
|
||||||
|
* @param {string} name - Nom de l'effet
|
||||||
|
* @param {string} attribute - Attribut cible (adr, pui, cla, pre, tre, vigueur, etc.)
|
||||||
|
* @param {number|string} value - Valeur du bonus/malus
|
||||||
|
* @param {object} options - Options supplémentaires
|
||||||
|
* @returns {Object|null} - Données de l'effet ou null
|
||||||
|
*/
|
||||||
|
static createSimpleEffect(name, attribute, value, options = {}) {
|
||||||
|
// Validation des paramètres
|
||||||
|
if (!name || typeof name !== "string") {
|
||||||
|
console.warn("MournbladeCYD2 | Effect name must be a non-empty string");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
console.warn("MournbladeCYD2 | Effect value cannot be null or undefined");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributeKey = this.getAttributeKey(attribute);
|
||||||
|
if (!attributeKey) {
|
||||||
|
console.warn(`MournbladeCYD2 | Unknown attribute: ${attribute}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normaliser la valeur en string
|
||||||
|
const valueString = String(value).trim();
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: name.trim(),
|
||||||
|
icon: options.icon || "systems/fvtt-mournblade-cyd-2-0/assets/icons/capacite.webp",
|
||||||
|
description: (options.description || "").trim(),
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: attributeKey,
|
||||||
|
mode: CONST.ActiveEffect.MODES.ADD,
|
||||||
|
value: valueString,
|
||||||
|
priority: options.priority ?? 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
disabled: Boolean(options.disabled),
|
||||||
|
duration: options.duration || {},
|
||||||
|
origin: options.origin || null,
|
||||||
|
tint: options.tint || "",
|
||||||
|
transfer: options.transfer !== false,
|
||||||
|
statuses: options.statuses || [],
|
||||||
|
flags: options.flags || {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet de bonus permanent
|
||||||
|
* @param {string} name - Nom de l'effet
|
||||||
|
* @param {string} attribute - Attribut cible
|
||||||
|
* @param {number|string} value - Valeur du bonus
|
||||||
|
* @returns {Object} - Données de l'effet
|
||||||
|
*/
|
||||||
|
static createPermanentEffect(name, attribute, value) {
|
||||||
|
return this.createSimpleEffect(name, attribute, value, {
|
||||||
|
duration: {},
|
||||||
|
type: "base"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet temporaire (rounds, turns, etc.)
|
||||||
|
* @param {string} name - Nom de l'effet
|
||||||
|
* @param {string} attribute - Attribut cible
|
||||||
|
* @param {number|string} value - Valeur du bonus/malus
|
||||||
|
* @param {string} durationType - Type de durée (rounds, turns, seconds, combat)
|
||||||
|
* @param {number} durationValue - Valeur de la durée
|
||||||
|
* @returns {Object} - Données de l'effet
|
||||||
|
*/
|
||||||
|
static createTemporaryEffect(name, attribute, value, durationType, durationValue) {
|
||||||
|
return this.createSimpleEffect(name, attribute, value, {
|
||||||
|
duration: { type: durationType, value: durationValue },
|
||||||
|
type: "temp"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet avec plusieurs modifications
|
||||||
|
* @param {string} name - Nom de l'effet
|
||||||
|
* @param {Array} changes - Array de modifications {key, mode, value}
|
||||||
|
* @param {object} options - Options supplémentaires
|
||||||
|
* @returns {Object} - Données de l'effet
|
||||||
|
*/
|
||||||
|
static createMultiEffect(name, changes, options = {}) {
|
||||||
|
return {
|
||||||
|
name: name,
|
||||||
|
icon: options.icon || "systems/fvtt-mournblade-cyd-2-0/assets/icons/capacite.webp",
|
||||||
|
description: options.description || "",
|
||||||
|
changes: changes.map(c => ({
|
||||||
|
key: c.key,
|
||||||
|
mode: c.mode || CONST.ActiveEffect.MODES.ADD,
|
||||||
|
value: c.value.toString(),
|
||||||
|
priority: c.priority || 0
|
||||||
|
})),
|
||||||
|
disabled: options.disabled || false,
|
||||||
|
duration: options.duration || {},
|
||||||
|
origin: options.origin || null,
|
||||||
|
tint: options.tint || "",
|
||||||
|
transfer: options.transfer !== false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Méthodes d'application d'effets */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applique un effet à un acteur
|
||||||
|
* @param {Actor} actor - L'acteur cible
|
||||||
|
* @param {Object|ActiveEffect} effectData - Données de l'effet ou instance ActiveEffect
|
||||||
|
* @returns {Promise<ActiveEffect|null>} - L'effet créé ou null
|
||||||
|
*/
|
||||||
|
static async applyEffectToActor(actor, effectData) {
|
||||||
|
if (!actor || !actor.canUserModify(game.user, "update")) return null;
|
||||||
|
|
||||||
|
let effect;
|
||||||
|
if (effectData instanceof foundry.documents.ActiveEffect) {
|
||||||
|
effect = effectData;
|
||||||
|
} else if (effectData?.toObject) {
|
||||||
|
effect = effectData;
|
||||||
|
} else {
|
||||||
|
effect = new CONFIG.ActiveEffect.documentClass(effectData);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const createdEffects = await actor.createEmbeddedDocuments("ActiveEffect", [effect.toObject()]);
|
||||||
|
return createdEffects[0];
|
||||||
|
} catch (error) {
|
||||||
|
console.error("MournbladeCYD2 | Failed to apply effect:", error);
|
||||||
|
ui.notifications?.error(
|
||||||
|
game.i18n?.localize("EFFECT.applyError") ||
|
||||||
|
`Erreur: Impossible d'appliquer l'effet (${error.message})`
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applique les effets d'un item à un acteur
|
||||||
|
* @param {Item} item - L'item source
|
||||||
|
* @param {Actor} actor - L'acteur cible
|
||||||
|
* @returns {Promise<Array<ActiveEffect>>} - Liste des effets appliqués
|
||||||
|
*/
|
||||||
|
static async applyItemEffectsToActor(item, actor) {
|
||||||
|
if (!item?.effects?.length || !actor) return [];
|
||||||
|
if (!actor.canUserModify(game.user, "update")) return [];
|
||||||
|
|
||||||
|
const effectsToApply = [];
|
||||||
|
for (const effectData of item.effects) {
|
||||||
|
// Par défaut, appliquer automatiquement SAUF si explicitement désactivé
|
||||||
|
const autoApply = effectData.getFlag("mournblade-cyd2", "autoApply");
|
||||||
|
if (autoApply !== false) {
|
||||||
|
effectsToApply.push({
|
||||||
|
...effectData.toObject(),
|
||||||
|
origin: item.uuid,
|
||||||
|
name: `${item.name}: ${effectData.name}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effectsToApply.length === 0) return [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const createdEffects = await actor.createEmbeddedDocuments("ActiveEffect", effectsToApply);
|
||||||
|
return createdEffects;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("MournbladeCYD2 | Failed to apply item effects:", error);
|
||||||
|
ui.notifications?.error(
|
||||||
|
game.i18n?.localize("EFFECT.applyItemError") ||
|
||||||
|
`Erreur: Impossible d'appliquer les effets de l'item`
|
||||||
|
);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Méthodes de gestion d'effets */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Désactive un effet
|
||||||
|
* @param {Actor|Item} owner - Le propriétaire de l'effet
|
||||||
|
* @param {string} effectId - ID de l'effet
|
||||||
|
* @returns {Promise<ActiveEffect|null>} - L'effet désactivé
|
||||||
|
*/
|
||||||
|
static async disableEffect(owner, effectId) {
|
||||||
|
if (!owner?.canUserModify(game.user, "update")) return null;
|
||||||
|
|
||||||
|
const effect = owner.effects.get(effectId);
|
||||||
|
if (!effect) return null;
|
||||||
|
|
||||||
|
await effect.update({ disabled: true });
|
||||||
|
return effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active un effet
|
||||||
|
* @param {Actor|Item} owner - Le propriétaire de l'effet
|
||||||
|
* @param {string} effectId - ID de l'effet
|
||||||
|
* @returns {Promise<ActiveEffect|null>} - L'effet activé
|
||||||
|
*/
|
||||||
|
static async enableEffect(owner, effectId) {
|
||||||
|
if (!owner?.canUserModify(game.user, "update")) return null;
|
||||||
|
|
||||||
|
const effect = owner.effects.get(effectId);
|
||||||
|
if (!effect) return null;
|
||||||
|
|
||||||
|
await effect.update({ disabled: false });
|
||||||
|
return effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle l'état d'un effet (actif/désactivé)
|
||||||
|
* @param {Actor|Item} owner - Le propriétaire de l'effet
|
||||||
|
* @param {string} effectId - ID de l'effet
|
||||||
|
* @returns {Promise<ActiveEffect|null>} - L'effet togglé
|
||||||
|
*/
|
||||||
|
static async toggleEffect(owner, effectId) {
|
||||||
|
if (!owner?.canUserModify(game.user, "update")) return null;
|
||||||
|
|
||||||
|
const effect = owner.effects.get(effectId);
|
||||||
|
if (!effect) return null;
|
||||||
|
|
||||||
|
await effect.update({ disabled: !effect.disabled });
|
||||||
|
return effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supprime un effet
|
||||||
|
* @param {Actor|Item} owner - Le propriétaire de l'effet
|
||||||
|
* @param {string} effectId - ID de l'effet
|
||||||
|
* @returns {Promise<ActiveEffect|null>} - L'effet supprimé
|
||||||
|
*/
|
||||||
|
static async deleteEffect(owner, effectId) {
|
||||||
|
if (!owner?.canUserModify(game.user, "delete")) return null;
|
||||||
|
|
||||||
|
const effect = owner.effects.get(effectId);
|
||||||
|
if (!effect) return null;
|
||||||
|
|
||||||
|
await owner.deleteEmbeddedDocuments("ActiveEffect", [effectId]);
|
||||||
|
return effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Méthodes utilitaires */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtient la clé complète pour un attribut
|
||||||
|
* @param {string} attribute - Attribut court (adr, pui, cla, pre, tre, vigueur, etc.)
|
||||||
|
* @returns {string|null} - Clé complète ou null
|
||||||
|
*/
|
||||||
|
static getAttributeKey(attribute) {
|
||||||
|
if (!attribute) return null;
|
||||||
|
|
||||||
|
const config = game.system.mournbladecyd2?.config;
|
||||||
|
if (!config?.effectAttributeKeys) return null;
|
||||||
|
|
||||||
|
// Normaliser en minuscules pour correspondre à la config
|
||||||
|
const normalizedAttribute = attribute.toLowerCase().trim();
|
||||||
|
return config.effectAttributeKeys[normalizedAttribute] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtient tous les attributs modifiables
|
||||||
|
* @returns {Object} - Map des attributs courts vers les clés complètes
|
||||||
|
*/
|
||||||
|
static getAllAttributeKeys() {
|
||||||
|
const config = game.system.mournbladecyd2?.config;
|
||||||
|
return config?.effectAttributeKeys || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtient les effets actifs d'un acteur
|
||||||
|
* @param {Actor} actor - L'acteur
|
||||||
|
* @returns {Array<ActiveEffect>} - Liste des effets actifs (non désactivés)
|
||||||
|
*/
|
||||||
|
static getActiveEffects(actor) {
|
||||||
|
if (!actor?.effects) return [];
|
||||||
|
return actor.effects.filter(e => !e.disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtient les effets désactivés d'un acteur
|
||||||
|
* @param {Actor} actor - L'acteur
|
||||||
|
* @returns {Array<ActiveEffect>} - Liste des effets désactivés
|
||||||
|
*/
|
||||||
|
static getDisabledEffects(actor) {
|
||||||
|
if (!actor?.effects) return [];
|
||||||
|
return actor.effects.filter(e => e.disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtient les effets par origine
|
||||||
|
* @param {Actor} actor - L'acteur
|
||||||
|
* @param {string} originUuid - UUID de l'origine
|
||||||
|
* @returns {Array<ActiveEffect>} - Liste des effets de cette origine
|
||||||
|
*/
|
||||||
|
static getEffectsByOrigin(actor, originUuid) {
|
||||||
|
if (!actor?.effects) return [];
|
||||||
|
return actor.effects.filter(e => e.origin === originUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtient les effets temporaires en cours
|
||||||
|
* @param {Actor} actor - L'acteur
|
||||||
|
* @returns {Array<ActiveEffect>} - Liste des effets temporaires actifs
|
||||||
|
*/
|
||||||
|
static getActiveTemporaryEffects(actor) {
|
||||||
|
if (!actor?.effects) return [];
|
||||||
|
return actor.effects.filter(e => !e.disabled && e.duration?.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule la valeur totale des modifications pour une clé donnée
|
||||||
|
* @param {Actor} actor - L'acteur
|
||||||
|
* @param {string} key - Clé à vérifier
|
||||||
|
* @returns {number} - Somme des modifications
|
||||||
|
*/
|
||||||
|
static getTotalModificationForKey(actor, key) {
|
||||||
|
if (!actor?.effects) return 0;
|
||||||
|
|
||||||
|
let total = 0;
|
||||||
|
for (const effect of actor.effects) {
|
||||||
|
if (effect.disabled) continue;
|
||||||
|
|
||||||
|
for (const change of effect.changes || []) {
|
||||||
|
if (change.key === key && change.mode === CONST.ActiveEffect.MODES.ADD) {
|
||||||
|
total += Number(change.value) || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtient toutes les modifications actives groupées par clé
|
||||||
|
* @param {Actor} actor - L'acteur
|
||||||
|
* @returns {Object} - Objet avec les clés et les valeurs totales
|
||||||
|
*/
|
||||||
|
static getAllActiveModifications(actor) {
|
||||||
|
if (!actor?.effects) return {};
|
||||||
|
|
||||||
|
const modifications = {};
|
||||||
|
|
||||||
|
for (const effect of actor.effects) {
|
||||||
|
if (effect.disabled) continue;
|
||||||
|
|
||||||
|
for (const change of effect.changes || []) {
|
||||||
|
if (!modifications[change.key]) {
|
||||||
|
modifications[change.key] = {
|
||||||
|
value: 0,
|
||||||
|
effects: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appliquer selon le mode
|
||||||
|
const numericValue = Number(change.value) || 0;
|
||||||
|
switch (change.mode) {
|
||||||
|
case CONST.ActiveEffect.MODES.ADD:
|
||||||
|
modifications[change.key].value += numericValue;
|
||||||
|
break;
|
||||||
|
case CONST.ActiveEffect.MODES.OVERRIDE:
|
||||||
|
modifications[change.key].value = numericValue;
|
||||||
|
modifications[change.key].overridden = true;
|
||||||
|
break;
|
||||||
|
case CONST.ActiveEffect.MODES.MULTIPLY:
|
||||||
|
// Ne peut pas être additionné, stocké séparément
|
||||||
|
if (!modifications[change.key].multipliers) {
|
||||||
|
modifications[change.key].multipliers = [];
|
||||||
|
}
|
||||||
|
modifications[change.key].multipliers.push(numericValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifications[change.key].effects.push(effect.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Méthodes de création d'effets prédéfinis */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet de bonus d'attribut
|
||||||
|
* @param {string} attribute - Attribut (adr, pui, cla, pre, tre)
|
||||||
|
* @param {number} value - Valeur du bonus
|
||||||
|
* @param {string} source - Source de l'effet
|
||||||
|
* @returns {Object} - Données de l'effet
|
||||||
|
*/
|
||||||
|
static createAttributeBonusEffect(attribute, value, source = "Effet") {
|
||||||
|
const attrNames = {
|
||||||
|
adr: "Adresse",
|
||||||
|
pui: "Puissance",
|
||||||
|
cla: "Clairvoyance",
|
||||||
|
pre: "Présence",
|
||||||
|
tre: "Trempe"
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.createSimpleEffect(
|
||||||
|
`${source}: Bonus de ${attrNames[attribute] || attribute}`,
|
||||||
|
attribute,
|
||||||
|
`+${value}`,
|
||||||
|
{
|
||||||
|
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/attributs.webp",
|
||||||
|
type: "base"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet de malus d'attribut
|
||||||
|
* @param {string} attribute - Attribut
|
||||||
|
* @param {number} value - Valeur du malus (positif)
|
||||||
|
* @param {string} source - Source de l'effet
|
||||||
|
* @returns {Object} - Données de l'effet
|
||||||
|
*/
|
||||||
|
static createAttributeMalusEffect(attribute, value, source = "Effet") {
|
||||||
|
const attrNames = {
|
||||||
|
adr: "Adresse",
|
||||||
|
pui: "Puissance",
|
||||||
|
cla: "Clairvoyance",
|
||||||
|
pre: "Présence",
|
||||||
|
tre: "Trempe"
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.createSimpleEffect(
|
||||||
|
`${source}: Malus de ${attrNames[attribute] || attribute}`,
|
||||||
|
attribute,
|
||||||
|
`-${value}`,
|
||||||
|
{
|
||||||
|
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/malus.webp",
|
||||||
|
type: "base"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet de bonus à la Vigueur
|
||||||
|
* @param {number} value - Valeur du bonus
|
||||||
|
* @param {string} source - Source de l'effet
|
||||||
|
* @returns {Object} - Données de l'effet
|
||||||
|
*/
|
||||||
|
static createVigueurBonusEffect(value, source = "Effet") {
|
||||||
|
return this.createSimpleEffect(
|
||||||
|
`${source}: Bonus de Vigueur`,
|
||||||
|
"vigueur",
|
||||||
|
`+${value}`,
|
||||||
|
{
|
||||||
|
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/vigueur.webp",
|
||||||
|
type: "base"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet de bonus au Seuil de Pouvoir
|
||||||
|
* @param {number} value - Valeur du bonus
|
||||||
|
* @param {string} source - Source de l'effet
|
||||||
|
* @returns {Object} - Données de l'effet
|
||||||
|
*/
|
||||||
|
static createSeuilPouvoirBonusEffect(value, source = "Effet") {
|
||||||
|
return this.createSimpleEffect(
|
||||||
|
`${source}: Bonus au Seuil de Pouvoir`,
|
||||||
|
"seuilPouvoir",
|
||||||
|
`+${value}`,
|
||||||
|
{
|
||||||
|
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/ame.webp",
|
||||||
|
type: "base"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet de bonus à la Bonne Aventure
|
||||||
|
* @param {number} value - Valeur du bonus
|
||||||
|
* @param {string} source - Source de l'effet
|
||||||
|
* @returns {Object} - Données de l'effet
|
||||||
|
*/
|
||||||
|
static createBonneAventureBonusEffect(value, source = "Effet") {
|
||||||
|
return this.createSimpleEffect(
|
||||||
|
`${source}: Bonus de Bonne Aventure`,
|
||||||
|
"bonneAventure",
|
||||||
|
`+${value}`,
|
||||||
|
{
|
||||||
|
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/bonneaventure.webp",
|
||||||
|
type: "base"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet de bonus à l'Initiative
|
||||||
|
* @param {number} value - Valeur du bonus
|
||||||
|
* @param {string} source - Source de l'effet
|
||||||
|
* @returns {Object} - Données de l'effet
|
||||||
|
*/
|
||||||
|
static createInitiativeBonusEffect(value, source = "Effet") {
|
||||||
|
return this.createSimpleEffect(
|
||||||
|
`${source}: Bonus d'Initiative`,
|
||||||
|
"initiative",
|
||||||
|
`+${value}`,
|
||||||
|
{
|
||||||
|
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/initiative.webp",
|
||||||
|
type: "temp",
|
||||||
|
duration: { type: "rounds", value: 1 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet de bonus à la Défense
|
||||||
|
* @param {number} value - Valeur du bonus
|
||||||
|
* @param {string} source - Source de l'effet
|
||||||
|
* @returns {Object} - Données de l'effet
|
||||||
|
*/
|
||||||
|
static createDefenseBonusEffect(value, source = "Effet") {
|
||||||
|
return this.createSimpleEffect(
|
||||||
|
`${source}: Bonus de Défense`,
|
||||||
|
"defense",
|
||||||
|
`+${value}`,
|
||||||
|
{
|
||||||
|
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/defense.webp",
|
||||||
|
type: "temp",
|
||||||
|
duration: { type: "rounds", value: 1 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet de bonus à la Protection
|
||||||
|
* @param {number} value - Valeur du bonus
|
||||||
|
* @param {string} source - Source de l'effet
|
||||||
|
* @returns {Object} - Données de l'effet
|
||||||
|
*/
|
||||||
|
static createProtectionBonusEffect(value, source = "Effet") {
|
||||||
|
return this.createSimpleEffect(
|
||||||
|
`${source}: Bonus de Protection`,
|
||||||
|
"protection",
|
||||||
|
`+${value}`,
|
||||||
|
{
|
||||||
|
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/protection.webp",
|
||||||
|
type: "base"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Méthodes de gestion des statuts */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet qui applique un statut
|
||||||
|
* @param {string} status - Nom du statut
|
||||||
|
* @param {string} source - Source de l'effet
|
||||||
|
* @returns {Object} - Données de l'effet
|
||||||
|
*/
|
||||||
|
static createStatusEffect(status, source = "Effet") {
|
||||||
|
return {
|
||||||
|
name: `${source}: ${status}`,
|
||||||
|
icon: `systems/fvtt-mournblade-cyd-2-0/assets/icons/status_${status.toLowerCase()}.webp`,
|
||||||
|
description: `Applique le statut ${status}`,
|
||||||
|
changes: [],
|
||||||
|
statuses: [status],
|
||||||
|
disabled: false,
|
||||||
|
duration: {},
|
||||||
|
origin: null,
|
||||||
|
tint: "",
|
||||||
|
transfer: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet d'adversité bleue
|
||||||
|
* @param {number} value - Nombre d'adversités
|
||||||
|
* @returns {Object|null} - Données de l'effet ou null
|
||||||
|
*/
|
||||||
|
static createAdversiteBleueEffect(value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
|
||||||
|
return this.createSimpleEffect(
|
||||||
|
`Adversité Bleue: +${value}`,
|
||||||
|
"adversite.bleue",
|
||||||
|
value,
|
||||||
|
{
|
||||||
|
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/gemme_bleue.webp",
|
||||||
|
duration: { type: "rounds", value: 1 },
|
||||||
|
statuses: ["adversite-bleue"]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet d'adversité rouge
|
||||||
|
* @param {number} value - Nombre d'adversités
|
||||||
|
* @returns {Object|null} - Données de l'effet ou null
|
||||||
|
*/
|
||||||
|
static createAdversiteRougeEffect(value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
|
||||||
|
return this.createSimpleEffect(
|
||||||
|
`Adversité Rouge: +${value}`,
|
||||||
|
"adversite.rouge",
|
||||||
|
value,
|
||||||
|
{
|
||||||
|
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/gemme_rouge.webp",
|
||||||
|
duration: { type: "rounds", value: 1 },
|
||||||
|
statuses: ["adversite-rouge"]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet d'adversité noire
|
||||||
|
* @param {number} value - Nombre d'adversités
|
||||||
|
* @returns {Object|null} - Données de l'effet ou null
|
||||||
|
*/
|
||||||
|
static createAdversiteNoireEffect(value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
|
||||||
|
return this.createSimpleEffect(
|
||||||
|
`Adversité Noire: +${value}`,
|
||||||
|
"adversite.noire",
|
||||||
|
value,
|
||||||
|
{
|
||||||
|
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/gemme_noire.webp",
|
||||||
|
duration: { type: "rounds", value: 1 },
|
||||||
|
statuses: ["adversite-noire"]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Méthodes pour les Runes */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet de Rune prononcée
|
||||||
|
* @param {Object} rune - Données de la rune
|
||||||
|
* @param {number} pointsAme - Points de pouvoir dépensés
|
||||||
|
* @returns {Object|null} - Données de l'effet ou null
|
||||||
|
*/
|
||||||
|
static createRunePrononceeEffect(rune, pointsAme) {
|
||||||
|
if (!rune || !rune.name || pointsAme == null) return null;
|
||||||
|
|
||||||
|
// Utiliser une icône par défaut si l'image de la rune est l'image par défaut
|
||||||
|
const icon = rune.img?.includes('/blank.png') || !rune.img
|
||||||
|
? "systems/fvtt-mournblade-cyd-2-0/assets/icons/rune.webp"
|
||||||
|
: rune.img;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: `Rune: ${rune.name} (Prononcée)`,
|
||||||
|
icon: icon,
|
||||||
|
description: rune.system?.description || "",
|
||||||
|
changes: [], // Les modifications spécifiques peuvent être ajoutées par les appels
|
||||||
|
disabled: false,
|
||||||
|
duration: { type: "rounds", value: Math.ceil(pointsAme / 3) },
|
||||||
|
origin: rune.uuid || null,
|
||||||
|
tint: "#00ff00",
|
||||||
|
transfer: true,
|
||||||
|
flags: {
|
||||||
|
"mournblade-cyd2": {
|
||||||
|
runeId: rune._id,
|
||||||
|
runeType: "prononcee",
|
||||||
|
pointsAme: pointsAme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un effet de Rune tracée
|
||||||
|
* @param {Object} rune - Données de la rune
|
||||||
|
* @param {number} pointsAme - Points de pouvoir dépensés
|
||||||
|
* @returns {Object|null} - Données de l'effet ou null
|
||||||
|
*/
|
||||||
|
static createRuneTraceeEffect(rune, pointsAme) {
|
||||||
|
if (!rune || !rune.name || pointsAme == null) return null;
|
||||||
|
|
||||||
|
// Utiliser une icône par défaut si l'image de la rune est l'image par défaut
|
||||||
|
const icon = rune.img?.includes('/blank.png') || !rune.img
|
||||||
|
? "systems/fvtt-mournblade-cyd-2-0/assets/icons/rune.webp"
|
||||||
|
: rune.img;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: `Rune: ${rune.name} (Tracée)`,
|
||||||
|
icon: icon,
|
||||||
|
description: rune.system?.description || "",
|
||||||
|
changes: [], // Les modifications spécifiques peuvent être ajoutées par les appels
|
||||||
|
disabled: false,
|
||||||
|
duration: { type: "rounds", value: Math.ceil(pointsAme / 3) * 2 },
|
||||||
|
origin: rune.uuid || null,
|
||||||
|
tint: "#0000ff",
|
||||||
|
transfer: true,
|
||||||
|
flags: {
|
||||||
|
"mournblade-cyd2": {
|
||||||
|
runeId: rune._id,
|
||||||
|
runeType: "tracee",
|
||||||
|
pointsAme: pointsAme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialisation automatique
|
||||||
|
Hooks.once("init", () => {
|
||||||
|
MournbladeCYD2Effects.init();
|
||||||
|
});
|
||||||
@@ -17,6 +17,10 @@ export class MournbladeCYD2TokenHud {
|
|||||||
static async addExtensionHud(app, html, tokenId) {
|
static async addExtensionHud(app, html, tokenId) {
|
||||||
|
|
||||||
let token = canvas.tokens.get(tokenId)
|
let token = canvas.tokens.get(tokenId)
|
||||||
|
if (!token) {
|
||||||
|
console.warn("MournbladeCYD2TokenHud.addExtensionHud : token introuvable", tokenId)
|
||||||
|
return
|
||||||
|
}
|
||||||
let actor = token.actor
|
let actor = token.actor
|
||||||
app.hasExtension = true
|
app.hasExtension = true
|
||||||
|
|
||||||
@@ -24,7 +28,7 @@ export class MournbladeCYD2TokenHud {
|
|||||||
|
|
||||||
const controlIconActions = $(html).find('.control-icon[data-action=combat]');
|
const controlIconActions = $(html).find('.control-icon[data-action=combat]');
|
||||||
// initiative
|
// initiative
|
||||||
await MournbladeCYD2TokenHud._configureSubMenu(controlIconActions, 'systems/fvtt-mournblade-cyd2/templates/hud-adversites.html', hudData,
|
await MournbladeCYD2TokenHud._configureSubMenu(controlIconActions, 'systems/fvtt-mournblade-cyd-2-0/templates/hud-adversites.hbs', hudData,
|
||||||
(event) => {
|
(event) => {
|
||||||
let adversite = event.currentTarget.attributes['data-action-index'].value
|
let adversite = event.currentTarget.attributes['data-action-index'].value
|
||||||
let value = Number(event.currentTarget.attributes['data-action-value'].value)
|
let value = Number(event.currentTarget.attributes['data-action-value'].value)
|
||||||
|
|||||||
@@ -1,228 +0,0 @@
|
|||||||
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extend the basic ItemSheet with some very simple modifications
|
|
||||||
* @extends {ItemSheet}
|
|
||||||
*/
|
|
||||||
export class MournbladeCYD2ItemSheet extends foundry.appv1.sheets.ItemSheet {
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
static get defaultOptions() {
|
|
||||||
|
|
||||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
||||||
classes: ["fvtt-mournblade-cyd2", "sheet", "item"],
|
|
||||||
template: "systems/fvtt-mournblade-cyd2/templates/item-sheet.html",
|
|
||||||
dragDrop: [{ dragSelector: null, dropSelector: null }],
|
|
||||||
width: 620,
|
|
||||||
height: 550,
|
|
||||||
tabs: [{navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description"}]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
_getHeaderButtons() {
|
|
||||||
let buttons = super._getHeaderButtons();
|
|
||||||
// Add "Post to chat" button
|
|
||||||
// We previously restricted this to GM and editable items only. If you ever find this comment because it broke something: eh, sorry!
|
|
||||||
buttons.unshift(
|
|
||||||
{
|
|
||||||
class: "post",
|
|
||||||
icon: "fas fa-comment",
|
|
||||||
onclick: ev => { }
|
|
||||||
})
|
|
||||||
return buttons
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
/** @override */
|
|
||||||
setPosition(options = {}) {
|
|
||||||
const position = super.setPosition(options);
|
|
||||||
const sheetBody = this.element.find(".sheet-body");
|
|
||||||
const bodyHeight = position.height - 192;
|
|
||||||
sheetBody.css("height", bodyHeight);
|
|
||||||
if (this.item.type.includes('weapon')) {
|
|
||||||
position.width = 640;
|
|
||||||
}
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
async getData() {
|
|
||||||
const objectData = foundry.utils.duplicate(this.object)
|
|
||||||
let formData = {
|
|
||||||
title: this.title,
|
|
||||||
id: this.id,
|
|
||||||
type: objectData.type,
|
|
||||||
img: objectData.img,
|
|
||||||
name: objectData.name,
|
|
||||||
editable: this.isEditable,
|
|
||||||
cssClass: this.isEditable ? "editable" : "locked",
|
|
||||||
attributs: MournbladeCYD2Utility.getAttributs(),
|
|
||||||
system: objectData.system,
|
|
||||||
limited: this.object.limited,
|
|
||||||
options: this.options,
|
|
||||||
owner: this.document.isOwner,
|
|
||||||
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.description, {async: true}),
|
|
||||||
mr: (this.object.type == 'specialisation'),
|
|
||||||
isGM: game.user.isGM,
|
|
||||||
config: game.system.mournbladecyd2.config
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( objectData.type == "don") {
|
|
||||||
formData.sacrifice = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.sacrifice, {async: true})
|
|
||||||
}
|
|
||||||
//this.options.editable = !(this.object.origin == "embeddedItem");
|
|
||||||
console.log("ITEM DATA", formData, this);
|
|
||||||
return formData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
_getHeaderButtons() {
|
|
||||||
let buttons = super._getHeaderButtons();
|
|
||||||
buttons.unshift({
|
|
||||||
class: "post",
|
|
||||||
icon: "fas fa-comment",
|
|
||||||
onclick: ev => this.postItem()
|
|
||||||
});
|
|
||||||
return buttons
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
postItem() {
|
|
||||||
let chatData = foundry.utils.duplicate(MournbladeCYD2Utility.data(this.item));
|
|
||||||
if (this.actor) {
|
|
||||||
chatData.actor = { id: this.actor.id };
|
|
||||||
}
|
|
||||||
// Don't post any image for the item (which would leave a large gap) if the default image is used
|
|
||||||
if (chatData.img.includes("/blank.png")) {
|
|
||||||
chatData.img = null;
|
|
||||||
}
|
|
||||||
// JSON object for easy creation
|
|
||||||
chatData.jsondata = JSON.stringify(
|
|
||||||
{
|
|
||||||
compendium: "postedItem",
|
|
||||||
payload: chatData,
|
|
||||||
});
|
|
||||||
|
|
||||||
renderTemplate('systems/fvtt-mournblade-cyd2/templates/post-item.html', chatData).then(html => {
|
|
||||||
let chatOptions = MournbladeCYD2Utility.chatDataSetup(html);
|
|
||||||
ChatMessage.create(chatOptions)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
/** @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")
|
|
||||||
const item = this.object.options.actor.getOwnedItem(li.data("item-id"))
|
|
||||||
item.sheet.render(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
html.find('.delete-subitem').click(ev => {
|
|
||||||
this.deleteSubitem(ev);
|
|
||||||
})
|
|
||||||
html.find('.edit-predilection').change(ev => {
|
|
||||||
const li = $(ev.currentTarget).parents(".prediction-item")
|
|
||||||
let index = li.data("prediction-index")
|
|
||||||
let pred = foundry.utils.duplicate(this.object.system.predilections)
|
|
||||||
pred[index].name = ev.currentTarget.value
|
|
||||||
pred[index].id = pred[index].id || randomID(16)
|
|
||||||
this.object.update( { 'system.predilections': pred })
|
|
||||||
})
|
|
||||||
html.find('.edit-predilection-description').change(ev => {
|
|
||||||
const li = $(ev.currentTarget).parents(".prediction-item")
|
|
||||||
let index = li.data("prediction-index")
|
|
||||||
let pred = foundry.utils.duplicate(this.object.system.predilections)
|
|
||||||
pred[index].description = ev.currentTarget.value
|
|
||||||
pred[index].id = pred[index].id || randomID(16)
|
|
||||||
this.object.update( { 'system.predilections': pred })
|
|
||||||
})
|
|
||||||
html.find('.predilection-acquise').change(ev => {
|
|
||||||
const li = $(ev.currentTarget).parents(".prediction-item")
|
|
||||||
let index = li.data("prediction-index")
|
|
||||||
let pred = foundry.utils.duplicate(this.object.system.predilections)
|
|
||||||
pred[index].acquise = ev.currentTarget.checked
|
|
||||||
pred[index].id = pred[index].id || randomID(16)
|
|
||||||
this.object.update( { 'system.predilections': pred })
|
|
||||||
})
|
|
||||||
html.find('.predilection-maitrise').change(ev => {
|
|
||||||
const li = $(ev.currentTarget).parents(".prediction-item")
|
|
||||||
let index = li.data("prediction-index")
|
|
||||||
let pred = foundry.utils.duplicate(this.object.system.predilections)
|
|
||||||
pred[index].maitrise = ev.currentTarget.checked
|
|
||||||
pred[index].id = pred[index].id || randomID(16)
|
|
||||||
this.object.update( { 'system.predilections': pred })
|
|
||||||
})
|
|
||||||
html.find('.predilection-used').change(ev => {
|
|
||||||
const li = $(ev.currentTarget).parents(".prediction-item")
|
|
||||||
let index = li.data("prediction-index")
|
|
||||||
let pred = foundry.utils.duplicate(this.object.system.predilections)
|
|
||||||
pred[index].used = ev.currentTarget.checked
|
|
||||||
pred[index].id = pred[index].id || randomID(16)
|
|
||||||
this.object.update( { 'system.predilections': pred })
|
|
||||||
})
|
|
||||||
|
|
||||||
html.find('#add-predilection').click(ev => {
|
|
||||||
let pred = foundry.utils.duplicate(this.object.system.predilections)
|
|
||||||
pred.push( { name: "Nouvelle prédilection", id: randomID(16), used: false })
|
|
||||||
this.object.update( { 'system.predilections': pred })
|
|
||||||
})
|
|
||||||
html.find('.delete-prediction').click(ev => {
|
|
||||||
const li = $(ev.currentTarget).parents(".prediction-item")
|
|
||||||
let index = li.data("prediction-index")
|
|
||||||
let pred = foundry.utils.duplicate(this.object.system.predilections)
|
|
||||||
pred.splice(index,1)
|
|
||||||
this.object.update( { 'system.predilections': pred })
|
|
||||||
})
|
|
||||||
|
|
||||||
html.find('#add-automation').click(ev => {
|
|
||||||
let autom = foundry.utils.duplicate(this.object.system.automations)
|
|
||||||
autom.push( { eventtype: "on-drop", name: "Automatisation 1", bonusname: "vigueur", bonus: 0, competence: "", minLevel: 0, baCost: 0, id: randomID(16) })
|
|
||||||
this.object.update( { 'system.automations': autom })
|
|
||||||
})
|
|
||||||
html.find('.delete-automation').click(ev => {
|
|
||||||
const li = $(ev.currentTarget).parents(".automation-item")
|
|
||||||
let index = li.data("automation-index")
|
|
||||||
let autom = foundry.utils.duplicate(this.object.system.automations)
|
|
||||||
autom.splice(index,1)
|
|
||||||
this.object.update( { 'system.automations': autom })
|
|
||||||
})
|
|
||||||
html.find('.automation-edit-field').change(ev => {
|
|
||||||
let index = $(ev.currentTarget).data("automation-index")
|
|
||||||
let field = $(ev.currentTarget).data("automation-field")
|
|
||||||
let auto = foundry.utils.duplicate(this.object.system.automations)
|
|
||||||
auto[index][field] = ev.currentTarget.value
|
|
||||||
auto[index].id = auto[index].id || randomID(16)
|
|
||||||
this.object.update( { 'system.automations': auto })
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update Inventory Item
|
|
||||||
html.find('.item-delete').click(ev => {
|
|
||||||
const li = $(ev.currentTarget).parents(".item");
|
|
||||||
let itemId = li.data("item-id");
|
|
||||||
let itemType = li.data("item-type");
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
get template() {
|
|
||||||
let type = this.item.type;
|
|
||||||
return `systems/fvtt-mournblade-cyd2/templates/item-${type}-sheet.html`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
/** @override */
|
|
||||||
_updateObject(event, formData) {
|
|
||||||
return this.object.update(formData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
|
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
|
||||||
|
|
||||||
export const defaultItemImg = {
|
export const defaultItemImg = {
|
||||||
competence: "systems/fvtt-mournblade-cyd2/assets/icons/competence.webp",
|
competence: "systems/fvtt-mournblade-cyd-2-0/assets/icons/competence.webp",
|
||||||
arme: "systems/fvtt-mournblade-cyd2/assets/icons/melee.webp",
|
arme: "systems/fvtt-mournblade-cyd-2-0/assets/icons/melee.webp",
|
||||||
equipement: "systems/fvtt-mournblade-cyd2/assets/icons/equipement.webp",
|
equipement: "systems/fvtt-mournblade-cyd-2-0/assets/icons/equipement.webp",
|
||||||
monnaie: "systems/fvtt-mournblade-cyd2/assets/icons/monnaie.webp",
|
monnaie: "systems/fvtt-mournblade-cyd-2-0/assets/icons/monnaie.webp",
|
||||||
predilection: "systems/fvtt-mournblade-cyd2/assets/icons/predilection.webp",
|
predilection: "systems/fvtt-mournblade-cyd-2-0/assets/icons/competence.webp",
|
||||||
protection: "systems/fvtt-mournblade-cyd2/assets/icons/protection.webp",
|
protection: "systems/fvtt-mournblade-cyd-2-0/assets/icons/protection.webp",
|
||||||
talent: "systems/fvtt-mournblade-cyd2/assets/icons/talent.webp",
|
talent: "systems/fvtt-mournblade-cyd-2-0/assets/icons/talent.webp",
|
||||||
historique: "systems/fvtt-mournblade-cyd2/assets/icons/historique.webp",
|
historique: "systems/fvtt-mournblade-cyd-2-0/assets/icons/historique.webp",
|
||||||
profil: "systems/fvtt-mournblade-cyd2/assets/icons/profil.webp",
|
profil: "systems/fvtt-mournblade-cyd-2-0/assets/icons/profil.webp",
|
||||||
ressource: "systems/fvtt-mournblade-cyd2/assets/icons/ressources.webp",
|
ressource: "systems/fvtt-mournblade-cyd-2-0/assets/icons/ressources.webp",
|
||||||
traitchaotique: "systems/fvtt-mournblade-cyd2/assets/icons/traitchaotique.webp",
|
traitchaotique: "systems/fvtt-mournblade-cyd-2-0/assets/icons/traitchaotique.webp",
|
||||||
traitespece: "systems/fvtt-mournblade-cyd2/assets/icons/traitespece.webp",
|
traitespece: "systems/fvtt-mournblade-cyd-2-0/assets/icons/traitespece.webp",
|
||||||
don: "systems/fvtt-mournblade-cyd2/assets/icons/don.webp",
|
don: "systems/fvtt-mournblade-cyd-2-0/assets/icons/don.webp",
|
||||||
pacte: "systems/fvtt-mournblade-cyd2/assets/icons/pacte.webp",
|
pacte: "systems/fvtt-mournblade-cyd-2-0/assets/icons/pacte.webp",
|
||||||
rune: "systems/fvtt-mournblade-cyd2/assets/icons/rune.webp",
|
rune: "systems/fvtt-mournblade-cyd-2-0/assets/icons/rune.webp",
|
||||||
tendance: "systems/fvtt-mournblade-cyd2/assets/icons/tendance.webp",
|
tendance: "systems/fvtt-mournblade-cyd-2-0/assets/icons/tendance.webp",
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,15 +9,17 @@
|
|||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
// Import Modules
|
// Import Modules
|
||||||
import { MournbladeCYD2Actor } from "./mournblade-cyd2-actor.js";
|
import { MournbladeCYD2Actor } from "./mournblade-cyd2-actor.js";
|
||||||
import { MournbladeCYD2ItemSheet } from "./mournblade-cyd2-item-sheet.js";
|
import * as sheets from "./applications/sheets/_module.mjs";
|
||||||
import { MournbladeCYD2ActorSheet } from "./mournblade-cyd2-actor-sheet.js";
|
|
||||||
import { MournbladeCYD2CreatureSheet } from "./mournblade-cyd2-creature-sheet.js";
|
|
||||||
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
|
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
|
||||||
import { MournbladeCYD2Combat } from "./mournblade-cyd2-combat.js";
|
import { MournbladeCYD2Combat } from "./mournblade-cyd2-combat.js";
|
||||||
import { MournbladeCYD2Item } from "./mournblade-cyd2-item.js";
|
import { MournbladeCYD2Item } from "./mournblade-cyd2-item.js";
|
||||||
import { MournbladeCYD2Automation } from "./mournblade-cyd2-automation.js";
|
import { MournbladeCYD2Automation } from "./mournblade-cyd2-automation.js";
|
||||||
import { MournbladeCYD2TokenHud } from "./mournblade-cyd2-hud.js";
|
import { MournbladeCYD2TokenHud } from "./mournblade-cyd2-hud.js";
|
||||||
import { MOURNBLADECYD2_CONFIG } from "./mournblade-cyd2-config.js";
|
import { MOURNBLADECYD2_CONFIG } from "./mournblade-cyd2-config.js";
|
||||||
|
import { MournbladeCYD2Effects } from "./mournblade-cyd2-effects.js";
|
||||||
|
|
||||||
|
// Import DataModels
|
||||||
|
import * as models from "./models/index.mjs";
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Foundry VTT Initialization */
|
/* Foundry VTT Initialization */
|
||||||
@@ -39,29 +41,72 @@ Hooks.once("init", async function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
game.socket.on("system.fvtt-mournblade-cyd2", data => {
|
game.socket.on("system.fvtt-mournblade-cyd-2-0", data => {
|
||||||
MournbladeCYD2Utility.onSocketMesssage(data)
|
MournbladeCYD2Utility.onSocketMessage(data)
|
||||||
});
|
});
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
// Define custom Entity classes
|
// Define custom Entity classes
|
||||||
CONFIG.Combat.documentClass = MournbladeCYD2Combat
|
CONFIG.Combat.documentClass = MournbladeCYD2Combat
|
||||||
CONFIG.Actor.documentClass = MournbladeCYD2Actor
|
CONFIG.Actor.documentClass = MournbladeCYD2Actor
|
||||||
|
CONFIG.Actor.dataModels = {
|
||||||
|
personnage: models.PersonnageDataModel,
|
||||||
|
creature: models.CreatureDataModel
|
||||||
|
}
|
||||||
CONFIG.Item.documentClass = MournbladeCYD2Item
|
CONFIG.Item.documentClass = MournbladeCYD2Item
|
||||||
|
CONFIG.Item.dataModels = {
|
||||||
|
talent: models.TalentDataModel,
|
||||||
|
historique: models.HistoriqueDataModel,
|
||||||
|
profil: models.ProfilDataModel,
|
||||||
|
competence: models.CompetenceDataModel,
|
||||||
|
arme: models.ArmeDataModel,
|
||||||
|
protection: models.ProtectionDataModel,
|
||||||
|
monnaie: models.MonnaieDataModel,
|
||||||
|
equipement: models.EquipementDataModel,
|
||||||
|
ressource: models.RessourceDataModel,
|
||||||
|
don: models.DonDataModel,
|
||||||
|
pacte: models.PacteDataModel,
|
||||||
|
rune: models.RuneDataModel,
|
||||||
|
runeeffect: models.RuneEffectDataModel,
|
||||||
|
tendance: models.TendanceDataModel,
|
||||||
|
traitchaotique: models.TraitChaotiqueDataModel,
|
||||||
|
traitespece: models.TraitEspeceDataModel,
|
||||||
|
traitdemoniaque: models.TraitDemoniaqueDataModel,
|
||||||
|
pouvoirselementaire: models.PouvoirElementaireDataModel,
|
||||||
|
capaciteautomata: models.CapaciteAutomataDataModel
|
||||||
|
}
|
||||||
game.system.mournbladecyd2 = {
|
game.system.mournbladecyd2 = {
|
||||||
MournbladeCYD2Utility,
|
MournbladeCYD2Utility,
|
||||||
MournbladeCYD2Automation,
|
MournbladeCYD2Automation,
|
||||||
|
MournbladeCYD2Effects,
|
||||||
config: MOURNBLADECYD2_CONFIG
|
config: MOURNBLADECYD2_CONFIG
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
// Register sheet application classes (AppV2)
|
||||||
// Regster sheet application classes
|
|
||||||
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
|
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
|
||||||
foundry.documents.collections.Actors.registerSheet("fvtt-mournblade-cyd2", MournbladeCYD2ActorSheet, { types: ["personnage"], makeDefault: true })
|
foundry.documents.collections.Actors.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2PersonnageSheet, { types: ["personnage"], makeDefault: true });
|
||||||
foundry.documents.collections.Actors.registerSheet("fvtt-mournblade-cyd2", MournbladeCYD2CreatureSheet, { types: ["creature"], makeDefault: true })
|
foundry.documents.collections.Actors.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2CreatureSheet, { types: ["creature"], makeDefault: true });
|
||||||
|
|
||||||
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
|
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
|
||||||
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd2", MournbladeCYD2ItemSheet, { makeDefault: true })
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2CompetenceSheet, { types: ["competence"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2TalentSheet, { types: ["talent"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2HistoriqueSheet, { types: ["historique"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2ProfilSheet, { types: ["profil"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2ArmeSheet, { types: ["arme"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2ProtectionSheet, { types: ["protection"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2MonnaieSheet, { types: ["monnaie"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2EquipementSheet, { types: ["equipement"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2RessourceSheet, { types: ["ressource"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2DonSheet, { types: ["don"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2PacteSheet, { types: ["pacte"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2RuneSheet, { types: ["rune"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2RuneEffectSheet, { types: ["runeeffect"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2TendanceSheet, { types: ["tendance"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2TraitChaotiqueSheet, { types: ["traitchaotique"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2TraitEspeceSheet, { types: ["traitespece"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2TraitDemoniaqueSheet, { types: ["traitdemoniaque"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2PouvoirElementaireSheet, { types: ["pouvoirselementaire"], makeDefault: true });
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2CapaciteAutomataSheet, { types: ["capaciteautomata"], makeDefault: true });
|
||||||
|
|
||||||
MournbladeCYD2Utility.init()
|
MournbladeCYD2Utility.init()
|
||||||
MournbladeCYD2Automation.init()
|
MournbladeCYD2Automation.init()
|
||||||
@@ -74,19 +119,51 @@ function welcomeMessage() {
|
|||||||
ChatMessage.create({
|
ChatMessage.create({
|
||||||
user: game.user.id,
|
user: game.user.id,
|
||||||
whisper: [game.user.id],
|
whisper: [game.user.id],
|
||||||
content: `<div id="welcome-message-MournbladeCYD2"><span class="rdd-roll-part">
|
content: `<div class="mournblade-welcome-message">
|
||||||
<strong>Bienvenue dans Mournblade CYD 2.0 !</strong>
|
<div class="welcome-header">
|
||||||
<p>Les livres de Mournblade sont nécessaires pour jouer : https://www.titam-france.fr ainsi que le supplément de conversion de règle pour le CYD 2.0</p>
|
<div class="welcome-icon"><i class="fas fa-sword"></i></div>
|
||||||
<p>Mournblade est un jeu de rôle publié par Titam France/Sombres projets, tous les droits leur appartiennent.</p>
|
<h2 class="welcome-title">Mournblade CYD 2.0</h2>
|
||||||
<p>Système développé par LeRatierBretonnien, support sur le <a href="https://discord.gg/pPSDNJk">Discord FR de Foundry</a>.</p>
|
<div class="welcome-subtitle">Système FoundryVTT — Bienvenue !</div>
|
||||||
` });
|
</div>
|
||||||
|
<div class="welcome-content">
|
||||||
|
<div class="welcome-section">
|
||||||
|
<div class="section-icon"><i class="fas fa-book-open"></i></div>
|
||||||
|
<div class="section-text">
|
||||||
|
<strong>Livres nécessaires</strong>
|
||||||
|
<p>Les livres de Mournblade sont nécessaires pour jouer. Supplément de conversion CYD 2.0 requis.</p>
|
||||||
|
<a class="welcome-link" href="https://www.lahiette.com/leratierbretonnien/mournblade-et-le-cyd-2-0/" target="_blank"><i class="fas fa-external-link-alt"></i>Règles PAO 0.9</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="welcome-section">
|
||||||
|
<div class="section-icon"><i class="fas fa-copyright"></i></div>
|
||||||
|
<div class="section-text">
|
||||||
|
<strong>Droits</strong>
|
||||||
|
<p>Mournblade est un jeu Titam.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="welcome-section">
|
||||||
|
<div class="section-icon"><i class="fab fa-discord"></i></div>
|
||||||
|
<div class="section-text">
|
||||||
|
<strong>Support</strong>
|
||||||
|
<p>Système développé par LeRatierBretonnien.</p>
|
||||||
|
<a class="welcome-link" href="https://discord.gg/pPSDNJk" target="_blank"><i class="fab fa-discord"></i>Discord FR Foundry</a>
|
||||||
|
<a class="welcome-link" href="https://www.uberwald.me" target="_blank"><i class="fas fa-external-link-alt"></i>Uberwald</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="welcome-footer">
|
||||||
|
<i class="fas fa-dice-d20"></i>
|
||||||
|
<span>Bonne partie !</span>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
async function importDefaultScene() {
|
async function importDefaultScene() {
|
||||||
let exists = game.scenes.find(j => j.name == "Accueil");
|
let exists = game.scenes.find(j => j.name == "Accueil");
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
const scenes = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd2.scenes")
|
const scenes = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd-2-0.scenes")
|
||||||
let newDocuments = scenes.filter(i => i.name == "Accueil");
|
let newDocuments = scenes.filter(i => i.name == "Accueil");
|
||||||
if (newDocuments) {
|
if (newDocuments) {
|
||||||
await game.scenes.documentClass.create(newDocuments);
|
await game.scenes.documentClass.create(newDocuments);
|
||||||
@@ -130,7 +207,7 @@ Hooks.on("chatMessage", (html, content, msg) => {
|
|||||||
if (content[0] == '/') {
|
if (content[0] == '/') {
|
||||||
let regExp = /(\S+)/g;
|
let regExp = /(\S+)/g;
|
||||||
let commands = content.match(regExp);
|
let commands = content.match(regExp);
|
||||||
if (game.system.mournblade.commands.processChatCommand(commands, content, msg)) {
|
if (game.system.mournbladecyd2.commands?.processChatCommand(commands, content, msg)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
|
|
||||||
|
|
||||||
export class MournbladeCYD2RollDialog extends Dialog {
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
static async create(actor, rollData ) {
|
|
||||||
|
|
||||||
let options = { classes: ["MournbladeCYD2Dialog"], width: 320, height: 'fit-content', 'z-index': 99999 };
|
|
||||||
let html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-mournblade-cyd2/templates/roll-dialog-generic.html', rollData);
|
|
||||||
|
|
||||||
return new MournbladeCYD2RollDialog(actor, rollData, html, options );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
constructor(actor, rollData, html, options, close = undefined) {
|
|
||||||
let conf = {
|
|
||||||
title: "Test de Capacité",
|
|
||||||
content: html,
|
|
||||||
buttons: {
|
|
||||||
rolld10: {
|
|
||||||
icon: '<i class="fas fa-check"></i>',
|
|
||||||
label: "Lancer 1d10",
|
|
||||||
callback: () => { this.roll("d10") }
|
|
||||||
},
|
|
||||||
rolld20: {
|
|
||||||
icon: '<i class="fas fa-check"></i>',
|
|
||||||
label: "Lancer 1d20",
|
|
||||||
callback: () => { this.roll("d20") }
|
|
||||||
},
|
|
||||||
cancel: {
|
|
||||||
icon: '<i class="fas fa-times"></i>',
|
|
||||||
label: "Annuler",
|
|
||||||
callback: () => { this.close() }
|
|
||||||
} },
|
|
||||||
close: close
|
|
||||||
}
|
|
||||||
|
|
||||||
super(conf, options);
|
|
||||||
|
|
||||||
this.actor = actor
|
|
||||||
this.rollData = rollData
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
roll ( dice) {
|
|
||||||
this.rollData.mainDice = dice
|
|
||||||
MournbladeCYD2Utility.rollMournbladeCYD2( this.rollData )
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
activateListeners(html) {
|
|
||||||
super.activateListeners(html);
|
|
||||||
|
|
||||||
var dialog = this;
|
|
||||||
function onLoad() {
|
|
||||||
}
|
|
||||||
$(function () { onLoad(); });
|
|
||||||
|
|
||||||
html.find('#modificateur').change(async (event) => {
|
|
||||||
this.rollData.modificateur = Number(event.currentTarget.value)
|
|
||||||
})
|
|
||||||
html.find('#difficulte').change( (event) => {
|
|
||||||
console.log("Difficulte: " + event.currentTarget.value)
|
|
||||||
this.rollData.difficulte = Number(event.currentTarget.value)
|
|
||||||
})
|
|
||||||
html.find('#attrKey').change(async (event) => {
|
|
||||||
this.rollData.attrKey = String(event.currentTarget.value)
|
|
||||||
})
|
|
||||||
html.find('#attrKey2').change(async (event) => {
|
|
||||||
this.rollData.attrKey2 = String(event.currentTarget.value)
|
|
||||||
})
|
|
||||||
html.find('#select-maitrise').change(async (event) => {
|
|
||||||
this.rollData.maitriseId = String(event.currentTarget.value)
|
|
||||||
})
|
|
||||||
html.find('#competence-talents').change((event) => {
|
|
||||||
this.rollData.selectedTalents = $('#competence-talents').val()
|
|
||||||
})
|
|
||||||
html.find('#taille-cible').change((event) => {
|
|
||||||
this.rollData.tailleCible = String(event.currentTarget.value)
|
|
||||||
})
|
|
||||||
html.find('#tireur-deplacement').change((event) => {
|
|
||||||
this.rollData.tireurDeplacement = String(event.currentTarget.value)
|
|
||||||
})
|
|
||||||
html.find('#cible-couvert').change((event) => {
|
|
||||||
this.rollData.cibleCouvert = String(event.currentTarget.value)
|
|
||||||
})
|
|
||||||
html.find('#distance-tir').change((event) => {
|
|
||||||
this.rollData.distanceTir = String(event.currentTarget.value)
|
|
||||||
})
|
|
||||||
html.find('#bonus-malus-context').change((event) => {
|
|
||||||
this.rollData.bonusMalusContext = Number(event.currentTarget.value)
|
|
||||||
})
|
|
||||||
html.find('#defenseur-au-sol').change((event) => {
|
|
||||||
this.rollData.defenseurAuSol = event.currentTarget.checked
|
|
||||||
})
|
|
||||||
html.find('#ambidextre-1').change((event) => {
|
|
||||||
this.rollData.ambidextre1 = event.currentTarget.checked
|
|
||||||
})
|
|
||||||
html.find('#ambidextre-2').change((event) => {
|
|
||||||
this.rollData.ambidextre2 = event.currentTarget.checked
|
|
||||||
})
|
|
||||||
html.find('#attaque-monte').change((event) => {
|
|
||||||
this.rollData.attqueMonte = event.currentTarget.checked
|
|
||||||
})
|
|
||||||
html.find('#defenseur-aveugle').change((event) => {
|
|
||||||
this.rollData.defenseurAveugle = event.currentTarget.checked
|
|
||||||
})
|
|
||||||
html.find('#defenseur-de-dos').change((event) => {
|
|
||||||
this.rollData.defenseurDeDos = event.currentTarget.checked
|
|
||||||
})
|
|
||||||
html.find('#defenseur-restreint').change((event) => {
|
|
||||||
this.rollData.defenseurRestreint = event.currentTarget.checked
|
|
||||||
})
|
|
||||||
html.find('#defenseur-immobilise').change((event) => {
|
|
||||||
this.rollData.defenseurImmobilise = event.currentTarget.checked
|
|
||||||
})
|
|
||||||
html.find('#attaque-charge').change((event) => {
|
|
||||||
this.rollData.attaqueCharge = event.currentTarget.checked
|
|
||||||
})
|
|
||||||
html.find('#charge-cavalerie').change((event) => {
|
|
||||||
this.rollData.chargeCavalerie = event.currentTarget.checked
|
|
||||||
})
|
|
||||||
html.find('#attaquants-multiple').change((event) => {
|
|
||||||
this.rollData.attaquantsMultiples = event.currentTarget.checked
|
|
||||||
})
|
|
||||||
html.find('#soutiens').change((event) => {
|
|
||||||
this.rollData.soutiens = Number(event.currentTarget.value)
|
|
||||||
})
|
|
||||||
html.find('#feinte').change((event) => {
|
|
||||||
this.rollData.feinte = event.currentTarget.checked
|
|
||||||
})
|
|
||||||
html.find('#contenir').change((event) => {
|
|
||||||
this.rollData.contenir = event.currentTarget.checked
|
|
||||||
})
|
|
||||||
html.find('#attaque-desarme').change((event) => {
|
|
||||||
this.rollData.attaqueDesarme = event.currentTarget.checked
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,6 +12,26 @@ const __tailleCible = { normal: 0, main: 10, enfant: 3, maison: -10 }
|
|||||||
export class MournbladeCYD2Utility {
|
export class MournbladeCYD2Utility {
|
||||||
|
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
// Helper pour calculer la valeur totale d'un item en SC (Sous de Cuivre / Pièces de Bronze)
|
||||||
|
// Conversion selon le lore Mournblade :
|
||||||
|
// 1 SA (Sou d'Argent / Pièce d'Argent) = 10 PB (Pièces de Bronze / Sous de Cuivre)
|
||||||
|
// 1 PO (Pièce d'Or) = 10 SA = 100 PB
|
||||||
|
// Donc : 1 PA (Pièce d'Argent) = 10 SC, 1 PO (Pièce d'Or) = 100 SC
|
||||||
|
static calculateItemValueSC(prixpo, prixca, prixsc) {
|
||||||
|
const po = parseInt(prixpo) || 0;
|
||||||
|
const ca = parseInt(prixca) || 0;
|
||||||
|
const sc = parseInt(prixsc) || 0;
|
||||||
|
return po * 100 + ca * 10 + sc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper pour calculer la valeur SC d'un item avec quantité
|
||||||
|
static getItemValueSC(item) {
|
||||||
|
const value = this.calculateItemValueSC(item.system?.prixpo, item.system?.prixca, item.system?.prixsc);
|
||||||
|
const quantity = item.system?.quantite || 1;
|
||||||
|
return value * quantity;
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
static async init() {
|
static async init() {
|
||||||
Hooks.on('renderChatLog', (log, html, data) => MournbladeCYD2Utility.chatListeners(html))
|
Hooks.on('renderChatLog', (log, html, data) => MournbladeCYD2Utility.chatListeners(html))
|
||||||
@@ -34,11 +54,11 @@ export class MournbladeCYD2Utility {
|
|||||||
return text.toUpperCase();
|
return text.toUpperCase();
|
||||||
})
|
})
|
||||||
Handlebars.registerHelper('lower', function (text) {
|
Handlebars.registerHelper('lower', function (text) {
|
||||||
return text.toLowerCase()
|
return text.toLowerCase();
|
||||||
})
|
})
|
||||||
Handlebars.registerHelper('upperFirst', function (text) {
|
Handlebars.registerHelper('upperFirst', function (text) {
|
||||||
if (typeof text !== 'string') return text
|
if (typeof text !== 'string') return text;
|
||||||
return text.charAt(0).toUpperCase() + text.slice(1)
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
||||||
})
|
})
|
||||||
Handlebars.registerHelper('notEmpty', function (list) {
|
Handlebars.registerHelper('notEmpty', function (list) {
|
||||||
return list.length > 0;
|
return list.length > 0;
|
||||||
@@ -46,8 +66,46 @@ export class MournbladeCYD2Utility {
|
|||||||
Handlebars.registerHelper('mul', function (a, b) {
|
Handlebars.registerHelper('mul', function (a, b) {
|
||||||
return parseInt(a) * parseInt(b);
|
return parseInt(a) * parseInt(b);
|
||||||
})
|
})
|
||||||
|
Handlebars.registerHelper('subtract', function (a, b) {
|
||||||
|
return parseInt(a) - parseInt(b);
|
||||||
|
})
|
||||||
|
|
||||||
game.settings.register("fvtt-mournblade-cyd2", "mournblade-cyd2-pause-logo", {
|
// Helper Handlebars pour les templates
|
||||||
|
Handlebars.registerHelper('calculateItemValueSC', function (prixpo, prixca, prixsc) {
|
||||||
|
return MournbladeCYD2Utility.calculateItemValueSC(prixpo, prixca, prixsc);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper pour localiser les valeurs d'allégeance
|
||||||
|
Handlebars.registerHelper('localizeAllegiance', function (value) {
|
||||||
|
if (!value) return value;
|
||||||
|
const allegianceMap = {
|
||||||
|
'tous': 'MNBL.all',
|
||||||
|
'chaos': 'MNBL.chaos',
|
||||||
|
'loi': 'MNBL.law',
|
||||||
|
'betes': 'MNBL.betes',
|
||||||
|
'elementaires': 'MNBL.elementaires',
|
||||||
|
'balance': 'MNBL.balance'
|
||||||
|
};
|
||||||
|
const key = allegianceMap[value?.toLowerCase?.()] || value;
|
||||||
|
return game.i18n?.localize?.(key) ?? value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper pour joindre les prédilections sans virgule superflue
|
||||||
|
Handlebars.registerHelper('joinPredilections', function (predilections) {
|
||||||
|
if (!predilections || !Array.isArray(predilections)) return '';
|
||||||
|
return predilections
|
||||||
|
.filter(pred => pred && pred.acquise && !pred.used)
|
||||||
|
.map(pred => pred.name)
|
||||||
|
.join(', ');
|
||||||
|
});
|
||||||
|
|
||||||
|
Handlebars.registerHelper('select', function(value, opts) {
|
||||||
|
const html = opts.fn(this);
|
||||||
|
const escaped = String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
return html.replace(new RegExp(`value="${escaped}"`, 'g'), `value="${value}" selected="selected"`);
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register("fvtt-mournblade-cyd-2-0", "mournblade-cyd2-pause-logo", {
|
||||||
name: "Logo de pause",
|
name: "Logo de pause",
|
||||||
scope: "world",
|
scope: "world",
|
||||||
config: true,
|
config: true,
|
||||||
@@ -60,6 +118,11 @@ export class MournbladeCYD2Utility {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Initialise les listes de sélection dès le hook init (avant le rendu des fiches)
|
||||||
|
game.system.mournbladecyd2.config.listeNiveauSkill = this.createDirectOptionList(0, 10)
|
||||||
|
game.system.mournbladecyd2.config.listeNiveauCreature = this.createDirectOptionList(0, 35)
|
||||||
|
game.system.mournbladecyd2.config.pointsAmeOptions = this.createDirectOptionList(1, 20)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
@@ -102,16 +165,13 @@ export class MournbladeCYD2Utility {
|
|||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
static async ready() {
|
static async ready() {
|
||||||
const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd2.skills")
|
const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd-2-0.skills")
|
||||||
this.skills = skills.map(i => i.toObject())
|
this.skills = skills.map(i => i.toObject())
|
||||||
|
|
||||||
// Setup pause logo
|
// Setup pause logo
|
||||||
let logoPause = "systems/fvtt-mournblade-cyd2/assets/logos/" + game.settings.get("fvtt-mournblade-cyd2", "mournblade-cyd2-pause-logo") + ".webp"
|
let logoPause = "systems/fvtt-mournblade-cyd-2-0/assets/logos/" + game.settings.get("fvtt-mournblade-cyd-2-0", "mournblade-cyd2-pause-logo") + ".webp"
|
||||||
let logoImg = document.querySelector('#pause').children[0]
|
let logoImg = document.querySelector('#pause').children[0]
|
||||||
logoImg.setAttribute('style', `content: url(${logoPause})`)
|
logoImg.setAttribute('style', `content: url(${logoPause})`)
|
||||||
|
|
||||||
game.system.mournbladecyd2.config.listeNiveauSkill = this.createDirectOptionList(0, 10)
|
|
||||||
game.system.mournbladecyd2.config.listeNiveauCreature = this.createDirectOptionList(0, 35)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
@@ -150,7 +210,7 @@ export class MournbladeCYD2Utility {
|
|||||||
static async chatListeners(html) {
|
static async chatListeners(html) {
|
||||||
|
|
||||||
$(html).on("click", '.predilection-reroll', async event => {
|
$(html).on("click", '.predilection-reroll', async event => {
|
||||||
let predIdx = $(event.currentTarget).data("predilection-index")
|
let predIdx = event.currentTarget.dataset.predilectionIndex
|
||||||
let messageId = MournbladeCYD2Utility.findChatMessageId(event.currentTarget)
|
let messageId = MournbladeCYD2Utility.findChatMessageId(event.currentTarget)
|
||||||
let message = game.messages.get(messageId)
|
let message = game.messages.get(messageId)
|
||||||
let rollData = message.getFlag("world", "mournblade-cyd2-roll")
|
let rollData = message.getFlag("world", "mournblade-cyd2-roll")
|
||||||
@@ -182,13 +242,15 @@ export class MournbladeCYD2Utility {
|
|||||||
static async preloadHandlebarsTemplates() {
|
static async preloadHandlebarsTemplates() {
|
||||||
|
|
||||||
const templatePaths = [
|
const templatePaths = [
|
||||||
'systems/fvtt-mournblade-cyd2/templates/editor-notes-gm.html',
|
'systems/fvtt-mournblade-cyd-2-0/templates/editor-notes-gm.hbs',
|
||||||
'systems/fvtt-mournblade-cyd2/templates/partial-item-header.html',
|
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-header.hbs',
|
||||||
'systems/fvtt-mournblade-cyd2/templates/partial-item-description.html',
|
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-description.hbs',
|
||||||
'systems/fvtt-mournblade-cyd2/templates/partial-item-nav.html',
|
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-nav.hbs',
|
||||||
'systems/fvtt-mournblade-cyd2/templates/partial-item-prix.html',
|
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-prix.hbs',
|
||||||
'systems/fvtt-mournblade-cyd2/templates/partial-automation.html',
|
'systems/fvtt-mournblade-cyd-2-0/templates/partial-automation.hbs',
|
||||||
'systems/fvtt-mournblade-cyd2/templates/hud-adversites.html',
|
'systems/fvtt-mournblade-cyd-2-0/templates/partial-active-effects.hbs',
|
||||||
|
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-effects.hbs',
|
||||||
|
'systems/fvtt-mournblade-cyd-2-0/templates/hud-adversites.hbs',
|
||||||
]
|
]
|
||||||
return foundry.applications.handlebars.loadTemplates(templatePaths);
|
return foundry.applications.handlebars.loadTemplates(templatePaths);
|
||||||
}
|
}
|
||||||
@@ -222,15 +284,6 @@ export class MournbladeCYD2Utility {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
static createDirectOptionList(min, max) {
|
|
||||||
let options = {};
|
|
||||||
for (let i = min; i <= max; i++) {
|
|
||||||
options[`${i}`] = `${i}`;
|
|
||||||
}
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
static buildListOptions(min, max) {
|
static buildListOptions(min, max) {
|
||||||
let options = ""
|
let options = ""
|
||||||
@@ -272,13 +325,13 @@ export class MournbladeCYD2Utility {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
static onSocketMesssage(msg) {
|
static onSocketMessage(msg) {
|
||||||
if (msg.msg == "msg_apply_combativite") {
|
if (msg.msg == "msg_apply_combativite") {
|
||||||
let defender = game.canvas.tokens.get(msg.data.defenderTokenId)?.actor
|
let defender = game.canvas.tokens.get(msg.data.defenderTokenId)?.actor
|
||||||
if (defender) {
|
if (defender) {
|
||||||
defender.changeEtatCombativite(msg.data.value)
|
defender.changeEtatCombativite(msg.data.value)
|
||||||
} else {
|
} else {
|
||||||
console.warn("MournbladeCYD2Utility.onSocketMesssage : Impossible de trouver le token pour appliquer la combativité", msg.defenderTokenId)
|
console.warn("MournbladeCYD2Utility.onSocketMessage : Impossible de trouver le token pour appliquer la combativité", msg.defenderTokenId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,9 +383,11 @@ export class MournbladeCYD2Utility {
|
|||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
static computeMonnaieDetails(valueSC) {
|
static computeMonnaieDetails(valueSC) {
|
||||||
let po = Math.floor(valueSC / 400)
|
// Conversion selon le lore Mournblade :
|
||||||
let pa = Math.floor((valueSC - (po * 400)) / 20)
|
// 1 PO = 100 SC, 1 CA (PA) = 10 SC
|
||||||
let sc = valueSC - (po * 400) - (pa * 20)
|
let po = Math.floor(valueSC / 100)
|
||||||
|
let pa = Math.floor((valueSC - (po * 100)) / 10)
|
||||||
|
let sc = valueSC - (po * 100) - (pa * 10)
|
||||||
return {
|
return {
|
||||||
po, pa, sc, valueSC
|
po, pa, sc, valueSC
|
||||||
}
|
}
|
||||||
@@ -340,11 +395,11 @@ export class MournbladeCYD2Utility {
|
|||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
static computeResult(rollData) {
|
static computeResult(rollData) {
|
||||||
rollData.diceResult = rollData.roll.terms[0].results[0].result
|
rollData.diceResult = rollData.roll?.terms?.[0]?.results?.[0]?.result ?? 0
|
||||||
if (rollData.mainDice.includes("d20")) {
|
if (rollData.mainDice.includes("d20")) {
|
||||||
let diceValue = rollData.roll.terms[0].results[0].result
|
let diceValue = rollData.roll?.terms?.[0]?.results?.[0]?.result ?? 0
|
||||||
if (diceValue % 2 == 1) {
|
if (diceValue % 2 == 1) {
|
||||||
//console.log("PAIR/IMP2", diceValue)
|
rollData.isD20Impair = true
|
||||||
rollData.finalResult -= rollData.roll.terms[0].results[0].result // Substract value
|
rollData.finalResult -= rollData.roll.terms[0].results[0].result // Substract value
|
||||||
if (diceValue == 1 || diceValue == 11) {
|
if (diceValue == 1 || diceValue == 11) {
|
||||||
rollData.isDramatique = true
|
rollData.isDramatique = true
|
||||||
@@ -369,10 +424,15 @@ export class MournbladeCYD2Utility {
|
|||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
static applyCombativite(rollData, value) {
|
static applyCombativite(rollData, value) {
|
||||||
if (game.user.isGM) {
|
if (game.user.isGM) {
|
||||||
let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor
|
const token = game.canvas.tokens.get(rollData.defenderTokenId)
|
||||||
|
const defender = token?.actor
|
||||||
|
if (!defender) {
|
||||||
|
console.warn("MournbladeCYD2Utility.applyCombativite : token défenseur introuvable", rollData.defenderTokenId)
|
||||||
|
return
|
||||||
|
}
|
||||||
defender.changeEtatCombativite(value)
|
defender.changeEtatCombativite(value)
|
||||||
} else {
|
} else {
|
||||||
game.socket.emit("system.fvtt-mournblade-cyd2", { msg: "msg_apply_combativite", data: { defenderTokenId: rollData.defenderTokenId, value } });
|
game.socket.emit("system.fvtt-mournblade-cyd-2-0", { msg: "msg_apply_combativite", data: { defenderTokenId: rollData.defenderTokenId, value } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
@@ -383,7 +443,7 @@ export class MournbladeCYD2Utility {
|
|||||||
rollData.attrKey = "adr"
|
rollData.attrKey = "adr"
|
||||||
}
|
}
|
||||||
if (!rollData.attr) {
|
if (!rollData.attr) {
|
||||||
rollData.actionImg = "systems/fvtt-mournblade-cyd2/assets/icons/" + actor.system.attributs[rollData.attrKey].labelnorm + ".webp"
|
rollData.actionImg = "systems/fvtt-mournblade-cyd-2-0/assets/icons/" + actor.system.attributs[rollData.attrKey].labelnorm + ".webp"
|
||||||
rollData.attr = foundry.utils.duplicate(actor.system.attributs[rollData.attrKey])
|
rollData.attr = foundry.utils.duplicate(actor.system.attributs[rollData.attrKey])
|
||||||
}
|
}
|
||||||
if (rollData.attrKey2 != "none") {
|
if (rollData.attrKey2 != "none") {
|
||||||
@@ -424,7 +484,8 @@ export class MournbladeCYD2Utility {
|
|||||||
} else if (rollData.attr2) {
|
} else if (rollData.attr2) {
|
||||||
rollData.diceFormula += `+${rollData.attr.value}+${rollData.attr2.value}+${rollData.modificateur}+${rollData.bonusMalusContext}`
|
rollData.diceFormula += `+${rollData.attr.value}+${rollData.attr2.value}+${rollData.modificateur}+${rollData.bonusMalusContext}`
|
||||||
} else {
|
} else {
|
||||||
rollData.diceFormula += `+${rollData.attr.value}*${rollData.multiplier}+${rollData.modificateur}+${rollData.bonusMalusContext}`
|
const attrPart = rollData.multiplier > 1 ? `${rollData.attr.value}*${rollData.multiplier}` : `${rollData.attr.value}`
|
||||||
|
rollData.diceFormula += `+${attrPart}+${rollData.modificateur}+${rollData.bonusMalusContext}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bonus arme naturelle en défense
|
// Bonus arme naturelle en défense
|
||||||
@@ -490,6 +551,21 @@ export class MournbladeCYD2Utility {
|
|||||||
|
|
||||||
rollData.finalResult = myRoll.total
|
rollData.finalResult = myRoll.total
|
||||||
this.computeResult(rollData)
|
this.computeResult(rollData)
|
||||||
|
|
||||||
|
// Rune post-roll: calculate duration and apply soul cost
|
||||||
|
if (rollData.rune) {
|
||||||
|
rollData.runeduree = Math.ceil(rollData.runeame / 3)
|
||||||
|
if (rollData.runemode == "inscrire") {
|
||||||
|
rollData.runeduree *= 2
|
||||||
|
}
|
||||||
|
let subAme = rollData.runeame
|
||||||
|
if (!rollData.isSuccess && !rollData.isDramatique) {
|
||||||
|
subAme = Math.ceil(rollData.runeame / 2)
|
||||||
|
}
|
||||||
|
rollData.runeAmeCout = subAme
|
||||||
|
actor.subPointsAme(rollData.runemode, subAme)
|
||||||
|
}
|
||||||
|
|
||||||
if (rollData.isInit) {
|
if (rollData.isInit) {
|
||||||
actor.setFlag("world", "last-initiative", rollData.finalResult)
|
actor.setFlag("world", "last-initiative", rollData.finalResult)
|
||||||
}
|
}
|
||||||
@@ -503,17 +579,20 @@ export class MournbladeCYD2Utility {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.createChatWithRollMode(rollData.alias, {
|
this.createChatWithRollMode(rollData.alias, {
|
||||||
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-mournblade-cyd2/templates/chat-generic-result.html`, rollData)
|
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-mournblade-cyd-2-0/templates/chat-generic-result.hbs`, rollData)
|
||||||
}, rollData)
|
}, rollData)
|
||||||
|
|
||||||
if ((rollData.coupBas || rollData.arme) && rollData.isSuccess && rollData.defenderTokenId) {
|
if ((rollData.coupBas || rollData.arme) && rollData.isSuccess && rollData.defenderTokenId) {
|
||||||
this.applyCombativite(rollData, rollData.nbCombativitePerdu)
|
this.applyCombativite(rollData, rollData.nbCombativitePerdu)
|
||||||
}
|
}
|
||||||
if (rollData.coupBas && rollData.isSuccess && rollData.defenderTokenId) {
|
if (rollData.coupBas && rollData.isSuccess && rollData.defenderTokenId) {
|
||||||
let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor
|
const token = game.canvas.tokens.get(rollData.defenderTokenId)
|
||||||
|
const defender = token?.actor
|
||||||
|
if (defender) {
|
||||||
defender.incDecAdversite("bleue", -2)
|
defender.incDecAdversite("bleue", -2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
static getCombativiteList(nbActivite) {
|
static getCombativiteList(nbActivite) {
|
||||||
@@ -597,7 +676,7 @@ export class MournbladeCYD2Utility {
|
|||||||
rollData.bonusFormula = rollData.addedBonus
|
rollData.bonusFormula = rollData.addedBonus
|
||||||
console.log("Bonus Roll MournbladeCYD2", rollData.bonusFormula)
|
console.log("Bonus Roll MournbladeCYD2", rollData.bonusFormula)
|
||||||
|
|
||||||
if (!Number(rollData.bonusFormula)) {
|
if (isNaN(Number(rollData.bonusFormula))) {
|
||||||
let bonusRoll = await new Roll(rollData.bonusFormula).roll()
|
let bonusRoll = await new Roll(rollData.bonusFormula).roll()
|
||||||
await this.showDiceSoNice(bonusRoll, game.settings.get("core", "rollMode"));
|
await this.showDiceSoNice(bonusRoll, game.settings.get("core", "rollMode"));
|
||||||
rollData.bonusRoll = foundry.utils.duplicate(bonusRoll)
|
rollData.bonusRoll = foundry.utils.duplicate(bonusRoll)
|
||||||
@@ -610,7 +689,7 @@ export class MournbladeCYD2Utility {
|
|||||||
this.computeResultQuality(rollData)
|
this.computeResultQuality(rollData)
|
||||||
|
|
||||||
this.createChatWithRollMode(rollData.alias, {
|
this.createChatWithRollMode(rollData.alias, {
|
||||||
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-mournblade-cyd2/templates/chat-generic-result.html`, rollData)
|
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-mournblade-cyd-2-0/templates/chat-generic-result.hbs`, rollData)
|
||||||
}, rollData)
|
}, rollData)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -641,7 +720,7 @@ export class MournbladeCYD2Utility {
|
|||||||
chatGM.whisper = this.getUsers(user => user.isGM);
|
chatGM.whisper = this.getUsers(user => user.isGM);
|
||||||
chatGM.content = "Blinde message of " + game.user.name + "<br>" + chatOptions.content;
|
chatGM.content = "Blinde message of " + game.user.name + "<br>" + chatOptions.content;
|
||||||
console.log("blindMessageToGM", chatGM);
|
console.log("blindMessageToGM", chatGM);
|
||||||
game.socket.emit("system.fvtt-mournblade-cyd2", { msg: "msg_gm_chat_message", data: chatGM });
|
game.socket.emit("system.fvtt-mournblade-cyd-2-0", { msg: "msg_gm_chat_message", data: chatGM });
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
@@ -767,9 +846,19 @@ export class MournbladeCYD2Utility {
|
|||||||
this.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions, rollData)
|
this.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions, rollData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
static getActorAlignment(actor) {
|
||||||
|
const loi = actor.system?.balance?.loi ?? 0
|
||||||
|
const chaos = actor.system?.balance?.chaos ?? 0
|
||||||
|
if (loi > chaos) return 'loyal'
|
||||||
|
if (chaos > loi) return 'chaotique'
|
||||||
|
return 'neutre'
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
static applyBonneAventureRoll(li, changed, addedBonus) {
|
static applyBonneAventureRoll(li, changed, addedBonus) {
|
||||||
let msgId = $(li).data("message-id")
|
const el = li instanceof HTMLElement ? li : li[0];
|
||||||
|
let msgId = el.dataset.messageId ?? el.closest("[data-message-id]")?.dataset.messageId
|
||||||
let msg = game.messages.get(msgId)
|
let msg = game.messages.get(msgId)
|
||||||
if (msg) {
|
if (msg) {
|
||||||
let rollData = msg.getFlag("world", "mournblade-cyd2-roll")
|
let rollData = msg.getFlag("world", "mournblade-cyd2-roll")
|
||||||
@@ -788,7 +877,8 @@ export class MournbladeCYD2Utility {
|
|||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
static applyEclatRoll(li, changed, addedBonus) {
|
static applyEclatRoll(li, changed, addedBonus) {
|
||||||
let msgId = $(li).data("message-id")
|
const el = li instanceof HTMLElement ? li : li[0];
|
||||||
|
let msgId = el.dataset.messageId ?? el.closest("[data-message-id]")?.dataset.messageId
|
||||||
let msg = game.messages.get(msgId)
|
let msg = game.messages.get(msgId)
|
||||||
if (msg) {
|
if (msg) {
|
||||||
let rollData = msg.getFlag("world", "mournblade-cyd2-roll")
|
let rollData = msg.getFlag("world", "mournblade-cyd2-roll")
|
||||||
@@ -807,49 +897,79 @@ export class MournbladeCYD2Utility {
|
|||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
static chatRollMenu(html, options) {
|
static chatRollMenu(html, options) {
|
||||||
let canApply = li => canvas.tokens.controlled.length && li.find(".mournblade-cyd2-roll").length
|
let canApply = li => {
|
||||||
let canApplyBA = function (li) {
|
const el = li instanceof HTMLElement ? li : li[0];
|
||||||
let message = game.messages.get($(li).attr("data-message-id"))
|
return canvas.tokens.controlled.length && el.querySelector(".mournblade-cyd2-roll");
|
||||||
|
}
|
||||||
|
let getActor = function (li) {
|
||||||
|
const el = li instanceof HTMLElement ? li : li[0];
|
||||||
|
let message = game.messages.get(el.dataset.messageId)
|
||||||
let rollData = message.getFlag("world", "mournblade-cyd2-roll")
|
let rollData = message.getFlag("world", "mournblade-cyd2-roll")
|
||||||
|
return MournbladeCYD2Utility.getActorFromRollData(rollData)
|
||||||
|
}
|
||||||
|
let getRollData = function (li) {
|
||||||
|
const el = li instanceof HTMLElement ? li : li[0];
|
||||||
|
let message = game.messages.get(el.dataset.messageId)
|
||||||
|
return message.getFlag("world", "mournblade-cyd2-roll")
|
||||||
|
}
|
||||||
|
let canApplyBA = function (li) {
|
||||||
|
let rollData = getRollData(li)
|
||||||
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
|
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
|
||||||
return (!rollData.isReroll && actor.getBonneAventure() > 0)
|
return (!rollData.isReroll && actor.getBonneAventure() > 0)
|
||||||
}
|
}
|
||||||
let canApplyPE = function (li) {
|
let canApplyPE = function (li) {
|
||||||
let message = game.messages.get($(li).attr("data-message-id"))
|
let rollData = getRollData(li)
|
||||||
let rollData = message.getFlag("world", "mournblade-cyd2-roll")
|
|
||||||
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
|
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
|
||||||
return (!rollData.isReroll && actor.getEclat() > 0)
|
return (!rollData.isReroll && actor.getEclat() > 0)
|
||||||
}
|
}
|
||||||
options.push(
|
let isLoyal = function (li) {
|
||||||
{
|
return MournbladeCYD2Utility.getActorAlignment(getActor(li)) === 'loyal'
|
||||||
name: "Ajouer +3 (1 point de Bonne Aventure)",
|
}
|
||||||
icon: "<i class='fas fa-user-plus'></i>",
|
let isChaotique = function (li) {
|
||||||
condition: canApply && canApplyBA,
|
return MournbladeCYD2Utility.getActorAlignment(getActor(li)) === 'chaotique'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bonne Aventure — loyal : +3 fixe
|
||||||
|
options.push({
|
||||||
|
name: "Ajouter +3 (1 point de Bonne Aventure — Loyal)",
|
||||||
|
icon: "<i class='fas fa-balance-scale'></i>",
|
||||||
|
condition: li => canApply(li) && canApplyBA(li) && !isChaotique(li),
|
||||||
callback: li => MournbladeCYD2Utility.applyBonneAventureRoll(li, -1, "+3")
|
callback: li => MournbladeCYD2Utility.applyBonneAventureRoll(li, -1, "+3")
|
||||||
}
|
})
|
||||||
)
|
// Bonne Aventure — chaotique : +1d6
|
||||||
options.push(
|
options.push({
|
||||||
{
|
name: "Ajouter +1d6 (1 point de Bonne Aventure — Chaotique)",
|
||||||
name: "Ajouter +10 (1 Point d'Eclat)",
|
icon: "<i class='fas fa-dice'></i>",
|
||||||
icon: "<i class='fas fa-user-plus'></i>",
|
condition: li => canApply(li) && canApplyBA(li) && !isLoyal(li),
|
||||||
condition: canApply && canApplyPE,
|
callback: li => MournbladeCYD2Utility.applyBonneAventureRoll(li, -1, "+1d6")
|
||||||
|
})
|
||||||
|
// Éclat — loyal : +10
|
||||||
|
options.push({
|
||||||
|
name: "Ajouter +10 (1 point d'Éclat — Loyal)",
|
||||||
|
icon: "<i class='fas fa-star'></i>",
|
||||||
|
condition: li => canApply(li) && canApplyPE(li) && !isChaotique(li),
|
||||||
callback: li => MournbladeCYD2Utility.applyEclatRoll(li, -1, "+10")
|
callback: li => MournbladeCYD2Utility.applyEclatRoll(li, -1, "+10")
|
||||||
}
|
})
|
||||||
)
|
// Éclat — chaotique : +1d20
|
||||||
options.push(
|
options.push({
|
||||||
{
|
name: "Ajouter +1d20 (1 point d'Éclat — Chaotique)",
|
||||||
name: "Relancer le dé (1 point d'Eclat)",
|
icon: "<i class='fas fa-dice-d20'></i>",
|
||||||
icon: "<i class='fas fa-user-plus'></i>",
|
condition: li => canApply(li) && canApplyPE(li) && !isLoyal(li),
|
||||||
condition: canApply && canApplyPE,
|
callback: li => MournbladeCYD2Utility.applyEclatRoll(li, -1, "+1d20")
|
||||||
|
})
|
||||||
|
// Éclat — relancer (tous)
|
||||||
|
options.push({
|
||||||
|
name: "Relancer le dé (1 point d'Éclat)",
|
||||||
|
icon: "<i class='fas fa-redo'></i>",
|
||||||
|
condition: li => canApply(li) && canApplyPE(li),
|
||||||
callback: li => MournbladeCYD2Utility.applyEclatRoll(li, -1, "reroll")
|
callback: li => MournbladeCYD2Utility.applyEclatRoll(li, -1, "reroll")
|
||||||
}
|
})
|
||||||
)
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
static async confirmDelete(actorSheet, li) {
|
static async confirmDelete(actorSheet, li) {
|
||||||
let itemId = li.data("item-id");
|
let itemId = li.dataset?.itemId ?? li.dataset?.["item-id"];
|
||||||
let msgTxt = "<p>Etes vous certain de vouloir supprimer cet item ?";
|
let msgTxt = "<p>Etes vous certain de vouloir supprimer cet item ?";
|
||||||
let buttons = {
|
let buttons = {
|
||||||
delete: {
|
delete: {
|
||||||
@@ -857,7 +977,7 @@ export class MournbladeCYD2Utility {
|
|||||||
label: "Oui !",
|
label: "Oui !",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]);
|
actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]);
|
||||||
li.slideUp(200, () => actorSheet.render(false));
|
actorSheet.render(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cancel: {
|
cancel: {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "fvtt-mournblade-cyd-2-0",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"description": "Mournblade CYD 2.0 RPG for FoundryVTT - French",
|
||||||
|
"scripts": {
|
||||||
|
"build": "gulp build",
|
||||||
|
"watch": "gulp watch"
|
||||||
|
},
|
||||||
|
"author": "Uberwald/LeRatierBretonnien",
|
||||||
|
"license": "SEE LICENSE IN LICENCE.txt",
|
||||||
|
"devDependencies": {
|
||||||
|
"gulp": "^4.0.2",
|
||||||
|
"gulp-less": "^5.0.0",
|
||||||
|
"gulp-sourcemaps": "^3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
MANIFEST-000372
|
MANIFEST-000451
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
2025/10/26-14:17:34.089807 7f058c9f96c0 Recovering log #370
|
2026/06/07-23:29:46.605565 7f15ce3fc6c0 Recovering log #449
|
||||||
2025/10/26-14:17:34.150991 7f058c9f96c0 Delete type=3 #368
|
2026/06/07-23:29:46.614959 7f15ce3fc6c0 Delete type=3 #447
|
||||||
2025/10/26-14:17:34.151050 7f058c9f96c0 Delete type=0 #370
|
2026/06/07-23:29:46.614979 7f15ce3fc6c0 Delete type=0 #449
|
||||||
2025/10/26-14:43:50.641754 7f0586bff6c0 Level-0 table #375: started
|
2026/06/07-23:51:38.199273 7f15cdbfb6c0 Level-0 table #454: started
|
||||||
2025/10/26-14:43:50.641778 7f0586bff6c0 Level-0 table #375: 0 bytes OK
|
2026/06/07-23:51:38.199286 7f15cdbfb6c0 Level-0 table #454: 0 bytes OK
|
||||||
2025/10/26-14:43:50.648553 7f0586bff6c0 Delete type=0 #373
|
2026/06/07-23:51:38.205066 7f15cdbfb6c0 Delete type=0 #452
|
||||||
2025/10/26-14:43:50.648712 7f0586bff6c0 Manual compaction at level-0 from '!journal!gVybbv17TFY8o3Y4' @ 72057594037927935 : 1 .. '!journal.pages!gVybbv17TFY8o3Y4.fQidyqfF1TbsZKHM' @ 0 : 0; will stop at (end)
|
2026/06/07-23:51:38.211362 7f15cdbfb6c0 Manual compaction at level-0 from '!journal!gVybbv17TFY8o3Y4' @ 72057594037927935 : 1 .. '!journal.pages!gVybbv17TFY8o3Y4.fQidyqfF1TbsZKHM' @ 0 : 0; will stop at (end)
|
||||||
2025/10/26-14:43:50.648735 7f0586bff6c0 Manual compaction at level-1 from '!journal!gVybbv17TFY8o3Y4' @ 72057594037927935 : 1 .. '!journal.pages!gVybbv17TFY8o3Y4.fQidyqfF1TbsZKHM' @ 0 : 0; will stop at (end)
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
2025/10/26-14:10:00.107133 7f058d1fa6c0 Recovering log #367
|
2026/06/07-22:26:23.395630 7f15cfbff6c0 Recovering log #445
|
||||||
2025/10/26-14:10:00.170337 7f058d1fa6c0 Delete type=0 #367
|
2026/06/07-22:26:23.404603 7f15cfbff6c0 Delete type=3 #443
|
||||||
2025/10/26-14:10:00.170407 7f058d1fa6c0 Delete type=3 #366
|
2026/06/07-22:26:23.404618 7f15cfbff6c0 Delete type=0 #445
|
||||||
2025/10/26-14:11:11.035066 7f0586bff6c0 Level-0 table #371: started
|
2026/06/07-23:28:18.037162 7f15cdbfb6c0 Level-0 table #450: started
|
||||||
2025/10/26-14:11:11.035107 7f0586bff6c0 Level-0 table #371: 0 bytes OK
|
2026/06/07-23:28:18.037182 7f15cdbfb6c0 Level-0 table #450: 0 bytes OK
|
||||||
2025/10/26-14:11:11.041259 7f0586bff6c0 Delete type=0 #369
|
2026/06/07-23:28:18.043745 7f15cdbfb6c0 Delete type=0 #448
|
||||||
2025/10/26-14:11:11.052382 7f0586bff6c0 Manual compaction at level-0 from '!journal!gVybbv17TFY8o3Y4' @ 72057594037927935 : 1 .. '!journal.pages!gVybbv17TFY8o3Y4.fQidyqfF1TbsZKHM' @ 0 : 0; will stop at (end)
|
2026/06/07-23:28:18.050582 7f15cdbfb6c0 Manual compaction at level-0 from '!journal!gVybbv17TFY8o3Y4' @ 72057594037927935 : 1 .. '!journal.pages!gVybbv17TFY8o3Y4.fQidyqfF1TbsZKHM' @ 0 : 0; will stop at (end)
|
||||||
2025/10/26-14:11:11.058628 7f0586bff6c0 Manual compaction at level-1 from '!journal!gVybbv17TFY8o3Y4' @ 72057594037927935 : 1 .. '!journal.pages!gVybbv17TFY8o3Y4.fQidyqfF1TbsZKHM' @ 0 : 0; will stop at (end)
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-1
@@ -1 +1 @@
|
|||||||
MANIFEST-000252
|
MANIFEST-000446
|
||||||
|
|||||||
+7
-11
@@ -1,11 +1,7 @@
|
|||||||
2025/10/26-14:17:33.329160 7f0587fff6c0 Recovering log #250
|
2026/06/07-23:29:46.486418 7f15ce3fc6c0 Recovering log #444
|
||||||
2025/10/26-14:17:33.329199 7f0587fff6c0 Recovering log #369
|
2026/06/07-23:29:46.496573 7f15ce3fc6c0 Delete type=3 #442
|
||||||
2025/10/26-14:17:33.500980 7f0587fff6c0 Delete type=2 #352
|
2026/06/07-23:29:46.496587 7f15ce3fc6c0 Delete type=0 #444
|
||||||
2025/10/26-14:17:33.501082 7f0587fff6c0 Delete type=0 #369
|
2026/06/07-23:51:38.132071 7f15cdbfb6c0 Level-0 table #449: started
|
||||||
2025/10/26-14:17:33.501136 7f0587fff6c0 Delete type=3 #248
|
2026/06/07-23:51:38.132089 7f15cdbfb6c0 Level-0 table #449: 0 bytes OK
|
||||||
2025/10/26-14:17:33.501178 7f0587fff6c0 Delete type=0 #250
|
2026/06/07-23:51:38.138027 7f15cdbfb6c0 Delete type=0 #447
|
||||||
2025/10/26-14:43:50.551774 7f0586bff6c0 Level-0 table #372: started
|
2026/06/07-23:51:38.149430 7f15cdbfb6c0 Manual compaction at level-0 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
|
||||||
2025/10/26-14:43:50.551804 7f0586bff6c0 Level-0 table #372: 0 bytes OK
|
|
||||||
2025/10/26-14:43:50.557801 7f0586bff6c0 Delete type=0 #370
|
|
||||||
2025/10/26-14:43:50.570810 7f0586bff6c0 Manual compaction at level-0 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
|
|
||||||
2025/10/26-14:43:50.570851 7f0586bff6c0 Manual compaction at level-1 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
|
|
||||||
|
|||||||
+7
-8
@@ -1,8 +1,7 @@
|
|||||||
2025/10/26-11:41:45.111518 7fe806ffd6c0 Recovering log #246
|
2026/06/07-22:26:23.277424 7f15cf3fe6c0 Recovering log #440
|
||||||
2025/10/26-11:41:45.121406 7fe806ffd6c0 Delete type=3 #244
|
2026/06/07-22:26:23.287266 7f15cf3fe6c0 Delete type=3 #438
|
||||||
2025/10/26-11:41:45.121474 7fe806ffd6c0 Delete type=0 #246
|
2026/06/07-22:26:23.287281 7f15cf3fe6c0 Delete type=0 #440
|
||||||
2025/10/26-11:42:27.673791 7fe804ff96c0 Level-0 table #251: started
|
2026/06/07-23:28:17.956926 7f15cdbfb6c0 Level-0 table #445: started
|
||||||
2025/10/26-11:42:27.673818 7fe804ff96c0 Level-0 table #251: 0 bytes OK
|
2026/06/07-23:28:17.956939 7f15cdbfb6c0 Level-0 table #445: 0 bytes OK
|
||||||
2025/10/26-11:42:27.702748 7fe804ff96c0 Delete type=0 #249
|
2026/06/07-23:28:17.962818 7f15cdbfb6c0 Delete type=0 #443
|
||||||
2025/10/26-11:42:27.787796 7fe804ff96c0 Manual compaction at level-0 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
|
2026/06/07-23:28:17.968789 7f15cdbfb6c0 Manual compaction at level-0 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
|
||||||
2025/10/26-11:42:27.787822 7fe804ff96c0 Manual compaction at level-1 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-1
@@ -1 +1 @@
|
|||||||
MANIFEST-000251
|
MANIFEST-000345
|
||||||
|
|||||||
+7
-8
@@ -1,8 +1,7 @@
|
|||||||
2025/10/26-14:17:33.616386 7f05877fe6c0 Recovering log #249
|
2026/06/07-23:29:46.519301 7f15cebfd6c0 Recovering log #343
|
||||||
2025/10/26-14:17:33.676039 7f05877fe6c0 Delete type=3 #247
|
2026/06/07-23:29:46.528855 7f15cebfd6c0 Delete type=3 #341
|
||||||
2025/10/26-14:17:33.676103 7f05877fe6c0 Delete type=0 #249
|
2026/06/07-23:29:46.528876 7f15cebfd6c0 Delete type=0 #343
|
||||||
2025/10/26-14:43:50.589531 7f0586bff6c0 Level-0 table #254: started
|
2026/06/07-23:51:38.149435 7f15cdbfb6c0 Level-0 table #348: started
|
||||||
2025/10/26-14:43:50.589558 7f0586bff6c0 Level-0 table #254: 0 bytes OK
|
2026/06/07-23:51:38.149444 7f15cdbfb6c0 Level-0 table #348: 0 bytes OK
|
||||||
2025/10/26-14:43:50.595890 7f0586bff6c0 Delete type=0 #252
|
2026/06/07-23:51:38.155961 7f15cdbfb6c0 Delete type=0 #346
|
||||||
2025/10/26-14:43:50.596069 7f0586bff6c0 Manual compaction at level-0 from '!items!5dGXNiL3WN4cAk7X' @ 72057594037927935 : 1 .. '!items!zzz9JrtWjELdoAfK' @ 0 : 0; will stop at (end)
|
2026/06/07-23:51:38.162070 7f15cdbfb6c0 Manual compaction at level-0 from '!items!5dGXNiL3WN4cAk7X' @ 72057594037927935 : 1 .. '!items!zzz9JrtWjELdoAfK' @ 0 : 0; will stop at (end)
|
||||||
2025/10/26-14:43:50.596098 7f0586bff6c0 Manual compaction at level-1 from '!items!5dGXNiL3WN4cAk7X' @ 72057594037927935 : 1 .. '!items!zzz9JrtWjELdoAfK' @ 0 : 0; will stop at (end)
|
|
||||||
|
|||||||
+7
-8
@@ -1,8 +1,7 @@
|
|||||||
2025/10/26-11:41:45.148682 7fe8057fa6c0 Recovering log #245
|
2026/06/07-22:26:23.309282 7f15cfbff6c0 Recovering log #339
|
||||||
2025/10/26-11:41:45.159005 7fe8057fa6c0 Delete type=3 #243
|
2026/06/07-22:26:23.319052 7f15cfbff6c0 Delete type=3 #337
|
||||||
2025/10/26-11:41:45.159071 7fe8057fa6c0 Delete type=0 #245
|
2026/06/07-22:26:23.319068 7f15cfbff6c0 Delete type=0 #339
|
||||||
2025/10/26-11:42:27.892214 7fe804ff96c0 Level-0 table #250: started
|
2026/06/07-23:28:17.984230 7f15cdbfb6c0 Level-0 table #344: started
|
||||||
2025/10/26-11:42:27.892241 7fe804ff96c0 Level-0 table #250: 0 bytes OK
|
2026/06/07-23:28:17.984244 7f15cdbfb6c0 Level-0 table #344: 0 bytes OK
|
||||||
2025/10/26-11:42:27.929964 7fe804ff96c0 Delete type=0 #248
|
2026/06/07-23:28:17.993689 7f15cdbfb6c0 Delete type=0 #342
|
||||||
2025/10/26-11:42:27.930136 7fe804ff96c0 Manual compaction at level-0 from '!items!5dGXNiL3WN4cAk7X' @ 72057594037927935 : 1 .. '!items!zzz9JrtWjELdoAfK' @ 0 : 0; will stop at (end)
|
2026/06/07-23:28:17.999974 7f15cdbfb6c0 Manual compaction at level-0 from '!items!5dGXNiL3WN4cAk7X' @ 72057594037927935 : 1 .. '!items!zzz9JrtWjELdoAfK' @ 0 : 0; will stop at (end)
|
||||||
2025/10/26-11:42:27.930151 7fe804ff96c0 Manual compaction at level-1 from '!items!5dGXNiL3WN4cAk7X' @ 72057594037927935 : 1 .. '!items!zzz9JrtWjELdoAfK' @ 0 : 0; will stop at (end)
|
|
||||||
|
|||||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user