Compare commits

..

31 Commits

Author SHA1 Message Date
04e35228e4 Migration datamodels ! 2026-01-10 16:22:24 +01:00
ec2d5385c5 Migration datamodels ! 2026-01-10 16:06:12 +01:00
438caf3b1c Migration datamodels ! 2026-01-10 16:05:56 +01:00
627ccc707b Fouindry v13 support 2025-04-30 23:51:45 +02:00
fdf28c4978 Fix v12 Wasteland 2025-03-14 14:09:51 +01:00
e0eac58bc9 Fix v12 Wasteland 2025-03-14 14:08:17 +01:00
b463323fbe Add v12 support 2024-05-23 11:38:37 +02:00
bddf772c99 Add v12 support 2024-05-23 11:36:03 +02:00
a716a3b3d1 Enhance stats 2024-02-08 12:57:36 +01:00
5814ef41df First official release 2024-01-08 07:59:19 +01:00
c0fcbe278f First official release 2024-01-08 07:58:04 +01:00
94f7ef8f90 First official release 2024-01-08 07:56:51 +01:00
19409dd547 Import massif des compendiums 2024-01-06 14:05:47 +01:00
217df7ee10 Import massif des compendiums 2024-01-06 14:00:59 +01:00
9990545568 Update fiche creature 2023-12-06 10:14:26 +01:00
d2da332411 Add new translations 2023-12-05 11:12:36 +01:00
028e8bddac Add mutations + update compendiusm 2023-12-04 18:32:55 +01:00
5c889a5153 Fiche de creature 2023-12-02 09:03:58 +01:00
68689add33 Ajout pouvoirs et jets associés 2023-12-01 14:03:04 +01:00
4ed2bcd2ee Ajout pouvoirs et jets associés 2023-12-01 09:50:45 +01:00
0edf336d28 Ajout pouvoirs et jets associés 2023-11-30 22:17:31 +01:00
046cdf4fb2 Ajout pouvoirs et jets associés 2023-11-30 21:27:57 +01:00
cc0faec25e Ajout pouvoirs et jets associés 2023-11-30 18:50:32 +01:00
3419ddf8d6 Add charmes/tours management 2023-11-29 22:21:48 +01:00
5aa117b569 Add charmes/tours management 2023-11-29 22:21:29 +01:00
4b2dd20d49 Add charmes/tours management 2023-11-29 22:14:22 +01:00
3e394734ce Add charmes/tours management 2023-11-29 22:04:17 +01:00
06d0d9b24d Compendiums : Armes et protections 2023-11-28 22:27:34 +01:00
a3c5a9bfec Compendiums : Armes et protections 2023-11-28 22:27:31 +01:00
ca4b7f2803 Compendiums : Armes et protections 2023-11-28 21:42:31 +01:00
d4733f0c20 Compendiums : Armes et protections 2023-11-28 20:46:34 +01:00
3989 changed files with 319426 additions and 3089 deletions

View File

@@ -0,0 +1,63 @@
name: Release Creation
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "💡 The ${{ gitea.repository }} repository will cloned to the runner."
#- uses: actions/checkout@v3
- uses: RouxAntoine/checkout@v3.5.4
# get part of the tag after the `v`
- name: Extract tag version number
id: get_version
uses: battila7/get-version-action@v2
# Substitute the Manifest and Download URLs in the 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-wasteland/releases/download/latest/system.json
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-wasteland.zip
# Create a zip file with all files required by the module to add to the release
- run: |
apt update -y
apt install -y zip
- run: zip -r ./fvtt-wasteland.zip system.json README.md changelog.md assets/ lang/ modules/ packs/ styles/ templates/ template.json
- name: setup go
uses: https://github.com/actions/setup-go@v4
with:
go-version: '>=1.20.1'
- name: Use Go Action
id: use-go-action
uses: https://gitea.com/actions/release-action@main
with:
files: |-
./fvtt-wasteland.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-wasteland'
version: ${{github.event.release.tag_name}}
manifest: 'https://www.uberwald.me/gitea/public/fvtt-wasteland/releases/download/latest/system.json'
notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-wasteland.zip'
compatibility-minimum: '13'
compatibility-verified: '13'

1
.gitignore vendored
View File

@@ -1 +1,2 @@
.history/ .history/
node_modules

View File

@@ -0,0 +1,298 @@
# Améliorations de la Fiche Créature
## Vue d'ensemble
La fiche créature a été complètement alignée avec les dernières améliorations de la fiche personnage pour assurer une expérience utilisateur cohérente à travers tout le système.
## Changements Apportés
## PARTIE 1 : ONGLET ATTRIBUTS (STATS)
### 1. Attributs avec Liste Déroulante (Dropdown)
**Avant:**
```handlebars
<input type="number" name="system.attributs.{{key}}.value" value="{{attr.value}}" />
```
**Après:**
```handlebars
<select name="system.attributs.{{key}}.value" value="{{attr.value}}" data-dtype="Number">
{{selectOptions @root.config.listeNiveauCreature selected=attr.value}}
</select>
```
**Raison:**
- Les créatures peuvent avoir des attributs de 0 à 35 (vs 0-10 pour personnages)
- Utilise `listeNiveauCreature` créé dynamiquement dans `wasteland-main.js`
- Interface plus propre et empêche les valeurs invalides
### 2. Attributs Cliquables (Rollable)
**Avant:**
```handlebars
<span class="item-name">{{attr.label}}</span>
```
**Après:**
```handlebars
<span class="item-name">
<a class="rollable" data-action="rollAttribut" data-attr-key="{{key}}">{{attr.label}}</a>
</span>
```
**Raison:**
- Permet de cliquer sur le nom de l'attribut pour faire un jet
### 3-5. Boutons +/- pour Santé et Psyché
Ajouté des boutons d'ajustement pour:
- Dégâts non létaux
- Dégâts létaux
- Psyché actuelle
**Raison:**
- Facilite l'ajustement rapide en combat
### 6. Terminologie
Changé "Courante" → "Actuelle" pour la psyché
## PARTIE 2 : ONGLET COMBAT
### 7. Valeurs de Combat - Grille 2x2 avec Champs Bonus
**Layout:**
```
┌──────────────────────────────┬──────────────────────────────┐
│ Initiative 12 [+2] = 14 │ Défense 15 [+2] = 17 │
├──────────────────────────────┼──────────────────────────────┤
│ Vitesse 8 [+0] = 8 │ Bonus Dég. +3 [+1] = +4 │
└──────────────────────────────┴──────────────────────────────┘
```
**Code:**
```handlebars
<div class="grid grid-2col">
<div class="combat-stat-row">
<label class="stat-label">Initiative</label>
<div class="stat-values">
<span class="stat-base">{{combat.initBase}}</span>
<input type="number" class="input-numeric-short" name="system.combat.initbonus"
value="{{system.combat.initbonus}}" data-tooltip="Bonus" />
<span class="stat-total">= {{combat.initTotal}}</span>
</div>
</div>
<!-- 3 autres similaires -->
</div>
```
**Raison:**
- **DIFFÉRENCE IMPORTANTE AVEC PERSONNAGE**: Créatures ont besoin des champs bonus éditables
- **Grille 2x2**: Compacte mais lisible, 2 valeurs par ligne
- Format: Nom | Base | [Bonus] | = Total
### 8. Compétences avec Boutons de Roll et Dropdown Niveau
**Avant:**
```handlebars
<span class="item-niveau">Niveau: {{skill.system.niveau}}</span>
```
**Après:**
```handlebars
<select class="status-small-label color-class-common edit-item-data competence-niveau"
data-item-field="niveau" data-dtype="Number">
{{selectOptions @root.config.listeNiveauSkill selected=skill.system.niveau}}
</select>
```
**Raison:**
- **Liste déroulante pour le niveau** (0-10) au lieu de texte statique
- Modification directe depuis la liste sans ouvrir la fiche
- Utilise le handler `edit-item-data` existant
- Appelle `editItemField()` pour mise à jour automatique
- **Comportement identique** à la fiche personnage
**Ajouts:**
- Boutons de roll par attribut (ADR:15, PUI:12, etc.)
- Nom cliquable pour éditer
- Bouton "Ajouter une compétence" en bas
### 9. Armes avec Format Moderne
**Avant:**
```handlebars
<li class="item flexrow list-item items-title-bg">
<span class="item-name-label-header">Armes</span>
<span>Attaque</span> <span>Défense</span> <span>Dégâts</span>
</li>
```
**Après:**
```handlebars
<span class="weapon-stats">
<span class="stat-offensif" data-tooltip="Attaque">
<i class="fas fa-crosshairs"></i> {{arme.system.totalOffensif}}
</span>
{{#if arme.system.isdefense}}
<span class="stat-defensif" data-tooltip="Défense">
<i class="fas fa-shield"></i> {{arme.system.totalDefensif}}
</span>
{{/if}}
</span>
<button class="button-sheet-roll" data-action="rollArmeOffensif">
<i class="fas fa-dice-d20"></i>
</button>
<button class="button-sheet-roll" data-action="rollArmeDegats">
<i class="fas fa-burst"></i>
</button>
```
**Raison:**
- Stats avec icônes visuelles (🎯 crosshairs, 🛡️ shield)
- Boutons avec icônes au lieu de texte
- Suppression du header colonnes
- Nom cliquable pour éditer
- Bouton "Ajouter une arme" en bas
### 10. Capacités
**Ajouts:**
- Nom cliquable pour éditer
- Bouton "Ajouter une capacité" en bas
## Fichiers Modifiés
### templates/actor-creature-sheet.hbs
- **Lignes 26-37**: Attributs avec dropdown et rollable links
- **Lignes 39-110**: Santé et Psyché avec boutons +/-
- **Lignes 107-146**: Valeurs combat en grille 2x2 avec inputs bonus
- **Lignes 148-189**: Compétences avec dropdown niveau et boutons de roll
- **Lignes 191-243**: Armes au format moderne avec icônes
- **Lignes 245-261**: Capacités avec nom cliquable
- **Total**: ~140 lignes modifiées/ajoutées
### less/actor-styles.less
- **Lignes 279-331**: Nouveaux styles pour `.combat-stats` (grille 2x2 créatures)
- **Total**: 52 lignes ajoutées
## Styles CSS Ajoutés
```less
.combat-stats {
margin-bottom: 1rem;
.grid-2col {
gap: 0.5rem;
}
.combat-stat-row {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.2);
border: 1px solid #6a0606;
border-radius: 3px;
padding: 0.4rem 0.6rem;
gap: 0.5rem;
.stat-label {
font-weight: bold;
font-size: 0.85rem;
min-width: 90px;
flex: 0 0 auto;
}
.stat-values {
display: flex;
align-items: center;
gap: 0.3rem;
flex: 1;
.stat-base { font-weight: bold; min-width: 30px; text-align: center; }
.input-numeric-short { width: 50px; text-align: center; padding: 0.2rem; }
.stat-total { font-weight: bold; color: #6a0606; min-width: 40px; }
}
}
}
```
## Actions Utilisées
Toutes réutilisées depuis `base-actor-sheet.mjs`:
- `rollAttribut` - Jets d'attributs
- `incDecSante` - Boutons +/- santé/psyché
- `rollCompetence` - Jets de compétence
- `rollArmeOffensif` / `rollArmeDegats` - Jets d'arme
- `createItem` / `editItem` / `deleteItem` - Gestion items
Handler spécial:
- `edit-item-data` - Modification dropdown niveau compétence
- Appelle `editItemField(itemId, itemType, itemField, dataType, value)` dans wasteland-actor.js
## Bénéfices
### Onglet Attributs
1. **Cohérence**: Même interface que fiche personnage
2. **Facilité**: Boutons +/- pour ajustements rapides
3. **Prévention d'erreurs**: Dropdowns empêchent valeurs invalides
4. **Ergonomie**: Attributs cliquables pour jets rapides
### Onglet Combat
1. **Layout 2x2**: Valeurs de combat compactes
2. **Champs Bonus**: Créatures peuvent ajuster leurs stats
3. **Dropdown Niveau**: Modification compétences sans ouvrir fiche
4. **Jets Rapides**: Boutons directement sur compétences/armes
5. **Interface Moderne**: Icônes et layout épuré
## Comparaison Avant/Après
| Élément | Avant | Après | Bénéfice |
|---------|-------|-------|----------|
| **Attributs** | Input number | Select dropdown (0-35) + rollable | Prévention erreurs + jets rapides |
| **Santé/Psyché** | Input seul | Input + boutons -/+ | Ajustement rapide en combat |
| **Valeurs Combat** | Liste 4 lignes verticale | Grille 2x2 Base [Bonus] = Total | Layout compact + édition |
| **Niveau Compétence** | Texte statique | Dropdown (0-10) éditable | Modification directe |
| **Compétences** | Nom + niveau | Dropdown + boutons roll attributs | Jets + édition rapide |
| **Armes** | Header + colonnes texte | Stats icônes + boutons icônes | Interface moderne |
| **Capacités** | Nom statique | Nom cliquable + bouton ajout | Édition rapide |
## Tests Recommandés
### Onglet Attributs
1. Vérifier dropdown attributs (0-35)
2. Cliquer sur nom d'attribut pour jet
3. Tester boutons +/- santé/psyché
4. Vérifier sauvegarde modifications
### Onglet Combat
1. Vérifier grille 2x2 valeurs combat
2. Modifier les champs bonus
3. **Tester dropdown niveau compétence** (nouveau)
4. Tester boutons roll compétences
5. Cliquer boutons attaque/dégâts armes
6. Vérifier tooltips
7. Tester ajout compétence/arme/capacité
8. Cliquer noms pour éditer
## Notes Importantes
- **Aucune modification JavaScript nécessaire** (actions/handlers déjà existants)
- **Modification CSS mineure** (52 lignes pour grille combat)
- **Changements principalement template Handlebars**
- **Compatible AppV2**
- **Différence clé avec personnage**: Créatures ont champs bonus combat éditables
- **Nouveau**: Dropdown niveau compétence avec modification directe (handler `edit-item-data`)
## Impact Visuel
```
CRÉATURE (Valeurs Combat) - Grille 2x2:
┌──────────────────────────────┬──────────────────────────────┐
│ Initiative 12 [+2] = 14 │ Défense 15 [+2] = 17 │
├──────────────────────────────┼──────────────────────────────┤
│ Vitesse 8 [+0] = 8 │ Bonus Dég. +3 [+1] = +4 │
└──────────────────────────────┴──────────────────────────────┘
CRÉATURE (Compétences):
[🖼️] Mouvements [Dropdown 0-10: 3]
[ADR : 15] [PUI : 12] [✏️] [🗑️]
PERSONNAGE (Compétences):
[🖼️] Mouvements [Dropdown 0-10: 3]
[ADR : 15] [PUI : 12] [✏️] [🗑️]
Prédilections: Escalade, Course
```
Total: ~140 lignes template + 52 lignes CSS = 192 lignes modifiées

180
GITEA_RELEASE_WORKFLOW.md Normal file
View File

@@ -0,0 +1,180 @@
# Workflow de Release Gitea pour Wasteland
## Vue d'ensemble
Ce workflow automatise la création de releases pour le système Wasteland dans Foundry VTT via Gitea Actions.
## Déclenchement
Le workflow se déclenche automatiquement lors de la **publication d'une release** sur Gitea :
```yaml
on:
release:
types: [published]
```
## Étapes du Workflow
### 1. Clone du Repository
- Clone le repository Wasteland
- Utilise `RouxAntoine/checkout@v3.5.4`
### 2. Extraction du Numéro de Version
- Extrait la version depuis le tag de release
- Exemple: tag `v1.2.3` → version `1.2.3`
- Utilise `battila7/get-version-action@v2`
### 3. Substitution des URLs dans system.json
Met à jour automatiquement le `system.json` avec :
```json
{
"version": "1.2.3",
"url": "https://www.uberwald.me/gitea/public/fvtt-wasteland",
"manifest": "https://www.uberwald.me/gitea/public/fvtt-wasteland/releases/download/latest/system.json",
"download": "https://www.uberwald.me/gitea/public/fvtt-wasteland/releases/download/v1.2.3/fvtt-wasteland.zip"
}
```
### 4. Création du ZIP
Crée `fvtt-wasteland.zip` contenant :
- `system.json` (mis à jour)
- `README.md`
- `changelog.md`
- `assets/`
- `lang/`
- `modules/`
- `packs/`
- `styles/`
- `templates/`
- `template.json`
**Note:** Exclut `node_modules/`, `less/`, `gulpfile.js`, etc.
### 5. Upload vers Gitea
- Upload du ZIP et du system.json vers la release
- Utilise le secret `ALLOW_PUSH_RELEASE`
### 6. Publication sur Foundry VTT
- Notifie le site officiel Foundry VTT de la nouvelle version
- Utilise le secret `FOUNDRYVTT_RELEASE_TOKEN`
- Compatibilité: Foundry V13
## Secrets Requis
Dans les paramètres du repository Gitea, configurer :
1. **`ALLOW_PUSH_RELEASE`**
- Token Gitea avec droits d'écriture sur releases
- Généré dans : Paramètres utilisateur → Applications → Tokens
2. **`FOUNDRYVTT_RELEASE_TOKEN`**
- Token du Foundry Package Creator
- Obtenu sur : https://foundryvtt.com/packages/edit/fvtt-wasteland
## Comment Créer une Release
### Méthode 1: Via l'Interface Gitea
1. Aller sur `https://www.uberwald.me/gitea/public/fvtt-wasteland`
2. Cliquer sur "Releases"
3. Cliquer sur "New Release"
4. Remplir :
- **Tag version**: `v1.2.3` (respecter le format `vX.Y.Z`)
- **Release title**: `Version 1.2.3 - Description courte`
- **Description**: Changelog détaillé
5. Cocher "This is a pre-release" si applicable
6. Cliquer sur "Publish Release"
### Méthode 2: Via Git CLI
```bash
# Créer et pousser le tag
git tag -a v1.2.3 -m "Version 1.2.3 - Description"
git push origin v1.2.3
# Puis créer la release via l'interface Gitea
```
## Déroulement Automatique
1. ✅ Détection de la publication de release
2. ✅ Clone du repository
3. ✅ Extraction de la version depuis le tag
4. ✅ Mise à jour du system.json
5. ✅ Création du ZIP
6. ✅ Upload sur Gitea
7. ✅ Publication sur Foundry VTT
## Vérification
Après publication, vérifier :
1. **Sur Gitea** : `https://www.uberwald.me/gitea/public/fvtt-wasteland/releases`
- Le ZIP `fvtt-wasteland.zip` est disponible
- Le `system.json` est disponible
- Les URLs sont correctes
2. **Sur Foundry VTT** : https://foundryvtt.com/packages/fvtt-wasteland
- La nouvelle version apparaît
- Le manifest pointe vers le bon fichier
3. **Dans Foundry** :
- Installer/Mettre à jour le système
- Vérifier que tout fonctionne
## URLs Importantes
- **Repository**: https://www.uberwald.me/gitea/public/fvtt-wasteland
- **Releases**: https://www.uberwald.me/gitea/public/fvtt-wasteland/releases
- **Manifest (latest)**: https://www.uberwald.me/gitea/public/fvtt-wasteland/releases/download/latest/system.json
- **Foundry Package**: https://foundryvtt.com/packages/fvtt-wasteland
## Troubleshooting
### Le workflow ne se déclenche pas
- Vérifier que la release est bien **published** (pas draft)
- Vérifier que le fichier `.gitea/workflows/release.yaml` existe
### Erreur lors de la création du ZIP
- Vérifier que tous les dossiers existent
- Vérifier les permissions
### Erreur lors de la publication Foundry
- Vérifier que `FOUNDRYVTT_RELEASE_TOKEN` est configuré
- Vérifier que l'ID du package est correct (`fvtt-wasteland`)
### Les URLs ne sont pas mises à jour
- Vérifier le format du tag (doit être `vX.Y.Z`)
- Vérifier que `microsoft/variable-substitution` fonctionne
## Exemple de Changelog
Pour `v1.2.3` :
```markdown
# Version 1.2.3 - Améliorations Fiches Créatures
## Nouvelles Fonctionnalités
- Ajout de dropdowns pour niveaux de compétences créatures
- Message de bienvenue stylé
- Grille 2x2 pour valeurs de combat
## Corrections
- Fix dialog render error
- Fix attribute selection
- Fix predilection reroll
## Améliorations
- Interface créature alignée avec personnage
- Styles cohérents pour tous les messages
- Documentation complète
```
## Notes
- Le workflow utilise Ubuntu latest
- Go 1.20.1+ est installé pour l'action de release
- Le système est compatible avec Foundry V13
- Les packs sont inclus dans le ZIP (format NeDB/LevelDB)

View File

@@ -1,11 +1,14 @@
# Système Foundry pour Wasteland (French RPG, Titam France/Sombres Projets)
Système Foundry pour Wasteland (French RPG, Titam France/Sombres Projets)
## EN ## EN
Unofficial system for Wasteland (French version from Titam France). Unofficial system for Wasteland (French RPG from Titam France).
Books are mandatory to play and are available at : http://www.titam-france.fr Books are mandatory to play and are available at : http://www.titam-france.fr
`![alt text](https://www.lahiette.com/leratierbretonnien/wp-content/uploads/2024/01/screenshot-wasteland-01.webp "Wasteland")`
## FR ## FR
Système non-officiel pour le JDR Wasteland (Titam France). Système non-officiel pour le JDR Wasteland (Titam France).
@@ -16,8 +19,9 @@ Les livres du jeu sont nécessaires pour jouer, et sont disponibles ici : http:/
# Credits # Credits
Wasteland, le jeu de rôle de Sword & Sorcery, is a property of Titam France/Sombres Projets. Wasteland is a property of Titam France/Sombres Projets.
# Developmement # Developmement
LeRatierBretonnien Code, CSS and automations : LeRatierBretonnien
Compendiums : Pretre, LeRatierBretonnien

BIN
assets/icons/adresse.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
assets/icons/don.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
assets/icons/hubris.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
assets/icons/presence.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
assets/icons/puissance.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
assets/icons/trempe.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

8
changelog.md Normal file
View File

@@ -0,0 +1,8 @@
# 12.0.1
- Fix v12 version
# 11.0.22
- Version initiale
-

35
gulpfile.js Normal file
View File

@@ -0,0 +1,35 @@
const gulp = require('gulp');
const less = require('gulp-less');
const sourcemaps = require('gulp-sourcemaps');
// Paths
const paths = {
styles: {
src: 'less/**/*.less',
dest: 'styles/'
}
};
// Compile LESS to CSS
function styles() {
return gulp.src('less/wasteland.less')
.pipe(sourcemaps.init())
.pipe(less())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(paths.styles.dest));
}
// Watch files
function watchFiles() {
gulp.watch(paths.styles.src, styles);
}
// Define complex tasks
const build = gulp.series(styles);
const watch = gulp.series(build, watchFiles);
// Export tasks
exports.styles = styles;
exports.build = build;
exports.watch = watch;
exports.default = build;

View File

@@ -1,24 +1,27 @@
{ {
"ACTOR": { "TYPES": {
"TypePersonnage": "Personnage", "Item": {
"TypePNJ": "PNJ" "arme": "Arme",
}, "competence": "Compétence",
"protection": "Protection",
"ITEM": { "monnaie": "Monnaie",
"TypeArme": "Arme", "equipement": "Equipement",
"TypeCompetence": "Compétence", "don": "Don",
"TypeProtection": "Protection", "hubris": "Hubris",
"TypeMonnaie": "Monnaie", "capacite": "Capacité",
"TypeEquipement": "Equipement", "origine": "Origine",
"TypeCapacite": "Capacité", "heritage": "Héritage",
"TypeOrigine": "Origine", "metier": "Métier",
"TypeHeritage": "Héritage", "bouclier": "Bouclier",
"TypeMetier": "Métier", "pouvoir": "Pouvoir",
"TypeBouclier": "Bouclier", "artifex": "Artifex",
"TypePouvoir": "Pouvoir", "mutation": "Mutation",
"TypeArtifex": "Artifex", "charme": "Charme",
"TypeMutation": "Mutation", "peuple": "Peuple"
"TypeCharme": "Charme", },
"TypePeuple": "Peuple" "Actor": {
"personnage": "Personnage",
"creature": "Créature"
}
} }
} }

1201
less/actor-styles.less Normal file

File diff suppressed because it is too large Load Diff

859
less/chat-styles.less Normal file
View File

@@ -0,0 +1,859 @@
/* ============================================ */
/* WASTELAND CHAT MESSAGE STYLES */
/* Post-Apocalyptic Theme */
/* ============================================ */
.wasteland-chat-result {
background: linear-gradient(135deg, rgba(50, 40, 30, 0.95) 0%, rgba(30, 25, 20, 0.95) 100%);
border: 2px solid #8b7355;
border-radius: 2px;
overflow: hidden;
font-family: "Charlemagne", serif;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.1);
position: relative;
// Effet de texture sale/usée
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 0, 0, 0.03) 2px,
rgba(0, 0, 0, 0.03) 4px
);
pointer-events: none;
opacity: 0.5;
}
.chat-result-header {
background: linear-gradient(135deg, #3d2f1f 0%, #2a1f15 100%);
border-bottom: 2px solid #8b7355;
padding: 0.5rem;
display: flex;
align-items: center;
gap: 0.625rem;
position: relative;
box-shadow: inset 0 -2px 4px rgba(0, 0, 0, 0.4);
// Effet rouille sur le bord
&::after {
content: "";
position: absolute;
bottom: -2px;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg,
transparent 0%,
#6a0606 20%,
#8b7355 40%,
#6a0606 60%,
transparent 100%
);
}
.actor-icon {
width: 48px;
height: 48px;
border-radius: 2px;
border: 2px solid #8b7355;
object-fit: cover;
flex-shrink: 0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.6);
filter: contrast(1.1) saturate(0.9);
}
.header-info {
flex: 1;
.actor-name {
margin: 0;
color: #e8dcc4;
font-size: 1.1rem;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9);
font-family: "Charlemagne", serif;
letter-spacing: 0.5px;
}
.action-title {
color: #c9a86a;
font-size: 0.9rem;
margin-top: 0.125rem;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.9);
font-style: italic;
i {
margin-right: 0.25rem;
color: #8b7355;
}
}
}
}
.result-main {
background: linear-gradient(180deg, rgba(230, 220, 200, 0.9) 0%, rgba(210, 200, 180, 0.9) 100%);
padding: 0.25rem 0.5rem;
border-bottom: 1px solid rgba(139, 115, 85, 0.5);
position: relative;
&.damage {
background: linear-gradient(180deg, rgba(200, 180, 160, 0.95) 0%, rgba(180, 160, 140, 0.95) 100%);
}
.result-display {
display: flex;
justify-content: space-around;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.1875rem;
.dice-result,
.total-result,
.difficulty {
text-align: center;
flex: 1;
background: linear-gradient(180deg, rgba(50, 40, 30, 0.7) 0%, rgba(40, 30, 20, 0.8) 100%);
padding: 0.25rem 0.375rem;
border-radius: 2px;
border: 2px solid #8b7355;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.5), 0 2px 4px rgba(0, 0, 0, 0.3);
i {
color: #c9a86a;
font-size: 1rem;
display: block;
margin-bottom: 0.125rem;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}
span {
display: block;
font-weight: bold;
}
.dice-value,
.total-value,
.difficulty-value {
font-size: 1.5rem;
color: #e8dcc4;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9);
font-weight: bold;
line-height: 1.2;
}
.total-label,
.difficulty-label {
font-size: 0.75rem;
color: #c9a86a;
text-transform: uppercase;
font-weight: bold;
line-height: 1.1;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}
}
}
.damage-display {
.damage-total {
text-align: center;
background: linear-gradient(180deg, rgba(60, 50, 40, 0.8) 0%, rgba(50, 40, 30, 0.9) 100%);
padding: 0.5rem;
border-radius: 2px;
border: 2px solid #8b7355;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.6), 0 2px 4px rgba(0, 0, 0, 0.4);
i {
color: #c9a86a;
font-size: 1.2rem;
display: block;
margin-bottom: 0.25rem;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}
.damage-label {
display: block;
font-size: 0.85rem;
color: #c9a86a;
text-transform: uppercase;
font-weight: bold;
margin-bottom: 0.25rem;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}
.damage-value {
display: block;
font-size: 2rem;
color: #e8dcc4;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9), 0 0 8px rgba(200, 0, 0, 0.3);
font-weight: bold;
line-height: 1.2;
}
}
}
.result-badge-container {
display: flex;
justify-content: center;
margin-top: 0.25rem;
.result-badge {
padding: 0.25rem 0.75rem;
border-radius: 2px;
font-weight: bold;
font-size: 0.9rem;
text-transform: uppercase;
text-align: center;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.2);
border: 1px solid rgba(0, 0, 0, 0.4);
i {
margin-right: 0.375rem;
}
&.heroique {
background: linear-gradient(135deg, #c9a86a 0%, #8b7355 100%);
color: #1a1a1a;
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.5);
border-color: #8b7355;
}
&.success {
background: linear-gradient(135deg, #6b8e23 0%, #4a6017 100%);
color: #e8dcc4;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.8);
border-color: #4a6017;
}
&.failure {
background: linear-gradient(135deg, #5a4a3a 0%, #3a2a1a 100%);
color: #c9a86a;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.9);
border-color: #3a2a1a;
}
&.dramatique {
background: linear-gradient(135deg, #4a0404 0%, #2a0202 100%);
color: #e8dcc4;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.9);
border-color: #6a0606;
}
}
}
}
.result-details {
padding: 0.5rem;
background: linear-gradient(180deg, rgba(70, 60, 50, 0.6) 0%, rgba(60, 50, 40, 0.7) 100%);
border-top: 1px solid rgba(139, 115, 85, 0.3);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
.details-section {
display: flex;
flex-direction: column;
gap: 0.1875rem;
.detail-row {
display: flex;
justify-content: space-between;
padding: 0.1875rem 0.375rem;
background: linear-gradient(90deg, rgba(230, 220, 200, 0.6) 0%, rgba(210, 200, 180, 0.5) 100%);
border-radius: 1px;
font-size: 0.85rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
border-left: 2px solid #8b7355;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
&.bonus {
background: linear-gradient(90deg, rgba(200, 220, 180, 0.7) 0%, rgba(180, 200, 160, 0.6) 100%);
border-left-color: #6b8e23;
}
&.malus {
background: linear-gradient(90deg, rgba(220, 180, 160, 0.7) 0%, rgba(200, 160, 140, 0.6) 100%);
border-left-color: #8b4513;
}
&.rune {
background: linear-gradient(90deg, rgba(190, 180, 210, 0.7) 0%, rgba(170, 160, 190, 0.6) 100%);
border-left-color: #6a5acd;
}
.detail-label {
color: #2a1f15;
font-weight: bold;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
.detail-value {
color: #2a1f15;
font-weight: 500;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
}
}
}
.result-effects {
padding: 0.5rem;
background: linear-gradient(180deg, rgba(80, 70, 60, 0.5) 0%, rgba(70, 60, 50, 0.6) 100%);
border-top: 1px solid rgba(139, 115, 85, 0.4);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
.effect-success,
.effect-warning,
.effect-failure,
.effect-damage,
.effect-heroic {
padding: 0.3125rem 0.5rem;
margin-bottom: 0.3125rem;
background: linear-gradient(90deg, rgba(230, 220, 200, 0.8) 0%, rgba(210, 200, 180, 0.7) 100%);
border-radius: 1px;
border-left: 3px solid;
font-size: 0.85rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 2px rgba(0, 0, 0, 0.3);
i {
margin-right: 0.375rem;
}
&:last-child {
margin-bottom: 0;
}
}
.effect-success {
border-left-color: #6b8e23;
color: #2a1f15;
}
.effect-heroic {
border-left-color: #c9a86a;
color: #2a1f15;
font-weight: bold;
background: linear-gradient(90deg, rgba(201, 168, 106, 0.3) 0%, rgba(230, 220, 200, 0.8) 100%);
}
.effect-warning {
border-left-color: #d97706;
color: #2a1f15;
}
.effect-failure {
border-left-color: #8b4513;
color: #2a1f15;
}
.effect-damage {
border-left-color: #6a0606;
color: #2a1f15;
background: linear-gradient(90deg, rgba(200, 100, 100, 0.3) 0%, rgba(210, 200, 180, 0.7) 100%);
}
}
.damage-button-section {
padding: 0.5rem;
background: linear-gradient(180deg, rgba(100, 70, 60, 0.5) 0%, rgba(80, 60, 50, 0.6) 100%);
border-top: 1px solid rgba(139, 115, 85, 0.5);
.chat-card-button {
width: 100%;
padding: 0.5rem;
background: linear-gradient(to bottom, #6a0606 0%, #4a0404 100%);
border: 2px solid #8b7355;
border-radius: 2px;
color: #e8dcc4;
font-size: 0.9rem;
font-weight: bold;
font-family: "Charlemagne", serif;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.1);
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
&:hover {
background: linear-gradient(to bottom, #8a0808 0%, #5a0505 100%);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.2);
border-color: #c9a86a;
}
i {
margin-right: 0.5rem;
}
}
}
.predilection-reroll-section {
padding: 0.75rem;
background: linear-gradient(135deg, rgba(139, 115, 85, 0.3) 0%, rgba(100, 80, 60, 0.4) 100%);
border: 2px solid #8b7355;
border-radius: 2px;
margin: 0.5rem;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
// Cacher toute icône de dé qui pourrait apparaître par erreur
.fa-dice {
display: none;
}
.predilection-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid rgba(139, 115, 85, 0.5);
i {
color: #c9a86a;
font-size: 1.1rem;
}
.predilection-title {
color: #e8dcc4;
font-weight: bold;
font-size: 1rem;
font-family: "Charlemagne", serif;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
}
}
.predilection-results {
display: flex;
gap: 0.75rem;
margin-bottom: 0.75rem;
.predilection-roll {
flex: 1;
padding: 0.5rem;
background: linear-gradient(90deg, rgba(230, 220, 200, 0.4) 0%, rgba(210, 200, 180, 0.3) 100%);
border-radius: 2px;
border-left: 2px solid #8b7355;
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
position: relative;
&.kept {
background: linear-gradient(90deg, rgba(200, 220, 180, 0.6) 0%, rgba(180, 200, 160, 0.5) 100%);
border-left-color: #6b8e23;
box-shadow: 0 0 8px rgba(107, 142, 35, 0.3);
}
.roll-label {
color: #2a1f15;
font-weight: bold;
font-size: 0.75rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
.roll-value {
color: #2a1f15;
font-weight: 700;
font-size: 1rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
i.fa-check-circle {
color: #6b8e23;
font-size: 1.1rem;
margin-left: 0.25rem;
}
}
}
.predilection-kept {
padding: 0.5rem;
background: linear-gradient(90deg, rgba(200, 220, 180, 0.7) 0%, rgba(180, 200, 160, 0.6) 100%);
border: 1px solid #6b8e23;
border-radius: 2px;
text-align: center;
color: #2a1f15;
font-size: 0.95rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
i {
color: #6b8e23;
}
.kept-label {
font-weight: 600;
}
strong {
font-size: 1.1rem;
font-weight: 700;
}
}
}
.predilection-section {
padding: 0.5rem;
background: linear-gradient(180deg, rgba(100, 90, 70, 0.5) 0%, rgba(80, 70, 60, 0.6) 100%);
border-top: 1px solid rgba(139, 115, 85, 0.5);
.chat-card-button {
width: 100%;
padding: 0.5rem;
background: linear-gradient(to bottom, #5a4a3a 0%, #3a2a1a 100%);
border: 2px solid #8b7355;
border-radius: 2px;
color: #e8dcc4;
font-size: 0.9rem;
font-weight: bold;
font-family: "Charlemagne", serif;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.1);
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
&:hover {
background: linear-gradient(to bottom, #6a5a4a 0%, #4a3a2a 100%);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.2);
border-color: #c9a86a;
}
i {
margin-right: 0.5rem;
}
}
}
}
/* ============================================ */
/* WASTELAND ITEM CHAT POSTS */
/* ============================================ */
.wasteland-chat-item {
background: linear-gradient(135deg, rgba(50, 40, 30, 0.95) 0%, rgba(30, 25, 20, 0.95) 100%);
border: 2px solid #8b7355;
border-radius: 2px;
overflow: hidden;
font-family: "Charlemagne", serif;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.1);
position: relative;
// Effet de texture sale/usée
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 0, 0, 0.03) 2px,
rgba(0, 0, 0, 0.03) 4px
);
pointer-events: none;
opacity: 0.5;
}
.chat-item-header {
background: linear-gradient(135deg, #3d2f1f 0%, #2a1f15 100%);
border-bottom: 2px solid #8b7355;
padding: 0.5rem;
display: flex;
align-items: center;
gap: 0.625rem;
position: relative;
box-shadow: inset 0 -2px 4px rgba(0, 0, 0, 0.4);
// Effet rouille sur le bord
&::after {
content: "";
position: absolute;
bottom: -2px;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg,
transparent 0%,
#6a0606 20%,
#8b7355 40%,
#6a0606 60%,
transparent 100%
);
}
.item-icon {
width: 48px;
height: 48px;
border-radius: 2px;
border: 2px solid #8b7355;
object-fit: cover;
flex-shrink: 0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.6);
filter: contrast(1.1) saturate(0.9);
}
.header-info {
flex: 1;
.item-name {
margin: 0;
color: #e8dcc4;
font-size: 1.1rem;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9);
font-weight: bold;
letter-spacing: 0.5px;
}
.item-type {
margin-top: 0.25rem;
color: #c9a86a;
font-size: 0.85rem;
display: flex;
align-items: center;
gap: 0.375rem;
text-transform: capitalize;
i {
color: #8b7355;
font-size: 0.9rem;
}
}
}
}
.chat-item-body {
padding: 0.75rem;
position: relative;
z-index: 1;
.item-description {
color: #e8dcc4;
font-size: 0.9rem;
line-height: 1.5;
margin-bottom: 0.75rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
p {
margin: 0.5rem 0;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
}
.item-properties {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 0.5rem;
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid rgba(139, 115, 85, 0.3);
.property {
background: linear-gradient(90deg, rgba(230, 220, 200, 0.6) 0%, rgba(210, 200, 180, 0.5) 100%);
padding: 0.375rem 0.5rem;
border-radius: 1px;
border-left: 2px solid #8b7355;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
.property-label {
color: #2a1f15;
font-weight: bold;
font-size: 0.85rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
.property-value {
color: #2a1f15;
font-weight: 600;
font-size: 0.9rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
}
}
}
}
/* ============================================ */
/* WASTELAND WELCOME MESSAGE */
/* ============================================ */
.wasteland-welcome-message {
background: linear-gradient(135deg, rgba(61, 47, 31, 0.15) 0%, rgba(42, 31, 21, 0.2) 100%);
border: 2px solid #6a0606;
border-radius: 8px;
padding: 0;
margin: 8px 0;
overflow: hidden;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4),
inset 0 1px 0 rgba(201, 168, 106, 0.1);
font-family: "Charlemagne", serif;
.welcome-header {
background: linear-gradient(135deg, #6a0606 0%, #4a0404 100%);
padding: 10px;
text-align: center;
border-bottom: 2px solid #c9a86a;
position: relative;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
.welcome-icon {
font-size: 1.8rem;
color: #c9a86a;
margin-bottom: 4px;
text-shadow: 0 0 10px rgba(201, 168, 106, 0.5);
animation: pulse 2s ease-in-out infinite;
}
.welcome-title {
margin: 4px 0 2px 0;
font-size: 1.3rem;
font-weight: bold;
color: #e8dcc4;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
font-family: "Charlemagne", serif;
letter-spacing: 0.5px;
}
.welcome-subtitle {
font-size: 0.9rem;
color: #c9a86a;
font-style: italic;
margin-top: 2px;
line-height: 1.2;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
}
.welcome-content {
padding: 12px;
background: linear-gradient(180deg, rgba(230, 220, 200, 0.9) 0%, rgba(210, 200, 180, 0.85) 100%);
.welcome-section {
display: flex;
gap: 10px;
margin-bottom: 10px;
padding: 8px;
background: rgba(255, 255, 255, 0.5);
border-radius: 4px;
border: 1px solid #c9a86a;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
&:last-child {
margin-bottom: 0;
}
.section-icon {
flex-shrink: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #6a0606 0%, #4a0404 100%);
color: #c9a86a;
border-radius: 50%;
font-size: 1rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
i {
line-height: 1;
}
}
.section-text {
flex: 1;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
strong {
display: block;
color: #3d2f1f;
margin-bottom: 4px;
font-size: 0.95rem;
font-weight: 700;
}
p {
margin: 0;
line-height: 1.4;
color: #2a1f15;
font-size: 0.9rem;
}
.welcome-link {
display: inline-block;
margin-top: 4px;
color: #6a0606;
font-weight: 600;
text-decoration: none;
transition: all 0.2s ease;
font-size: 0.9rem;
i {
margin-right: 4px;
}
&:hover {
color: #8b0606;
text-shadow: 0 0 4px rgba(106, 6, 6, 0.3);
}
}
}
}
}
.welcome-footer {
background: linear-gradient(135deg, #4a0404 0%, #6a0606 100%);
padding: 8px;
text-align: center;
color: #c9a86a;
font-style: italic;
font-size: 0.95rem;
border-top: 1px solid #8b7355;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3);
font-family: "Charlemagne", serif;
i {
margin: 0 8px;
opacity: 0.7;
font-size: 0.85rem;
}
span {
vertical-align: middle;
}
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.05);
opacity: 0.9;
}
}
}

569
less/item-styles.less Normal file
View File

@@ -0,0 +1,569 @@
/* ==================== Item Sheet Styles ==================== */
/* Item header with image and name */
.fvtt-wasteland.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: "Charlemagne", 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 100px;
height: 100px;
border: 2px solid #999;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.item-sheet-title {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.25rem;
h1 {
margin: 0;
padding: 0;
font-size: 1.5rem;
border-bottom: none;
input {
background: none;
border: none;
font-size: 1.5rem;
font-family: "Charlemagne", serif;
font-weight: bold;
}
}
.item-subtitle {
font-size: 0.9rem;
color: #666;
font-style: italic;
}
}
/* Navigation tabs - Modern style */
nav.tabs {
display: flex;
border-bottom: 2px solid #403f3e;
margin: 0;
padding: 4px 8px;
background: linear-gradient(to bottom, #2a2520 0%, #1a1510 100%);
flex: 0 0 auto;
gap: 4px;
a.item {
padding: 8px 16px;
color: rgba(218, 218, 218, 0.85);
text-decoration: none;
border: 1px solid transparent;
border-radius: 6px 6px 0 0;
font-family: "Charlemagne", serif;
font-size: 0.9rem;
font-weight: normal;
transition: all 0.3s ease;
background: rgba(64, 63, 62, 0.3);
min-width: 80px;
text-align: center;
i {
display: none; // Hide icons for cleaner look
}
&:hover {
background: rgba(74, 4, 4, 0.4);
color: #f5f5f5;
border-color: rgba(218, 218, 218, 0.2);
}
&.active {
background: linear-gradient(to bottom, #4a0404 0%, #3a0303 100%);
border: 1px solid transparent;
color: #f5f5f5;
font-weight: bold;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
}
}
/* Tab content */
.tab {
display: none;
padding: 8px 12px;
overflow-y: auto;
flex: 1 1 auto;
&.active {
display: block;
}
}
/* Sheet body - scrollable content */
.sheet-body {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 1rem;
&[data-tab] {
display: none;
&.active {
display: block;
}
}
/* Dans l'onglet details, les form-group sont horizontaux par défaut */
&[data-tab="details"] {
.form-group {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.75rem;
label {
font-weight: bold;
font-size: 0.9rem;
color: #464331;
flex: 0 0 auto;
min-width: 160px;
margin: 0;
}
input[type="text"],
input[type="number"],
select,
textarea {
flex: 1;
}
input[type="checkbox"] {
flex: 0 0 auto;
margin-right: 0.5rem;
}
/* Checkbox avec label après (pas avant) */
&:has(input[type="checkbox"]:first-child) {
label {
min-width: auto;
flex: 1;
order: 2;
}
input[type="checkbox"] {
order: 1;
}
}
/* Exception: quand le label contient lui-même le checkbox */
label:has(input[type="checkbox"]) {
min-width: auto;
flex: 1;
display: flex;
align-items: center;
gap: 0.5rem;
}
}
/* Pour les sections avec grilles, garder le comportement vertical */
.grid .form-group {
flex-direction: column;
align-items: flex-start;
gap: 0.25rem;
label {
min-width: auto;
}
}
}
}
/* Form groups - comportement par défaut pour autres onglets */
.form-group {
display: flex;
flex-direction: column;
gap: 0.25rem;
margin-bottom: 0.5rem;
label {
font-weight: bold;
font-size: 0.9rem;
color: #464331;
}
&.horizontal {
flex-direction: row;
align-items: center;
gap: 0.5rem;
label {
flex: 0 0 auto;
min-width: 100px;
}
input, select, textarea {
flex: 1;
}
}
}
/* Grid layouts */
.grid {
display: grid;
gap: 0.5rem;
margin: 0.5rem 0;
&.grid-2col {
grid-template-columns: repeat(2, 1fr);
}
&.grid-3col {
grid-template-columns: repeat(3, 1fr);
}
&.grid-4col {
grid-template-columns: repeat(4, 1fr);
}
}
/* Editor content */
.editor-content {
min-height: 200px;
border: 1px solid #999;
padding: 0.5rem;
background: rgba(255, 255, 255, 0.9);
border-radius: 4px;
}
/* Actions buttons */
.item-actions {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
padding-top: 0.5rem;
border-top: 1px solid #999;
button {
flex: 1;
padding: 0.5rem 1rem;
background: rgba(74, 4, 4, 0.8);
color: white;
border: 1px solid #000;
border-radius: 4px;
cursor: pointer;
font-family: "Charlemagne", serif;
font-size: 0.9rem;
transition: all 0.2s;
&:hover {
background: rgba(74, 4, 4, 1);
box-shadow: 0 0 8px rgba(74, 4, 4, 0.6);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
}
/* Rollable elements */
.rollable {
cursor: pointer;
&:hover {
color: #ff6600;
text-shadow: 0 0 8px rgba(255, 102, 0, 0.8);
}
}
/* Item-specific sections */
.item-details {
display: flex;
flex-direction: column;
gap: 1rem;
.detail-section {
padding: 0.5rem;
background: rgba(255, 255, 255, 0.3);
border: 1px solid #999;
border-radius: 4px;
h3 {
margin: 0 0 0.5rem 0;
padding-bottom: 0.25rem;
border-bottom: 1px solid #999;
font-size: 1.1rem;
color: #2a1510;
font-family: "Charlemagne", serif;
font-weight: bold;
}
}
}
}
/* Specific item type styles */
.fvtt-wasteland.item {
&.arme-content,
&.bouclier-content {
.weapon-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
.stat-box {
padding: 0.5rem;
background: rgba(255, 255, 255, 0.5);
border: 1px solid #999;
border-radius: 4px;
text-align: center;
.stat-label {
font-size: 0.8rem;
color: #666;
margin-bottom: 0.25rem;
}
.stat-value {
font-size: 1.2rem;
font-weight: bold;
}
}
}
}
&.competence-content {
.predilections-container {
margin-top: 0.5rem;
.no-predilections {
text-align: center;
font-style: italic;
color: #999;
padding: 1rem;
margin: 0;
}
}
.predilections-list {
list-style: none;
padding: 0;
margin: 0 0 0.75rem 0;
display: flex;
flex-direction: column;
gap: 0.5rem;
.predilection-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.5rem;
background: rgba(42, 37, 32, 0.3);
border: 1px solid rgba(106, 6, 6, 0.3);
border-radius: 4px;
transition: background 0.2s;
&:hover {
background: rgba(42, 37, 32, 0.5);
border-color: rgba(106, 6, 6, 0.5);
}
.predilection-main {
flex: 1;
.predilection-name {
width: 100%;
padding: 0.375rem 0.5rem;
border: 1px solid rgba(0, 0, 0, 0.3);
border-radius: 3px;
background: rgba(255, 255, 255, 0.9);
font-family: inherit;
&::placeholder {
color: #999;
font-style: italic;
}
&:focus {
outline: none;
border-color: #6a0606;
box-shadow: 0 0 0 2px rgba(106, 6, 6, 0.2);
}
}
}
.predilection-controls {
display: flex;
align-items: center;
gap: 0.75rem;
flex-shrink: 0;
.predilection-used {
display: flex;
align-items: center;
gap: 0.375rem;
cursor: pointer;
user-select: none;
white-space: nowrap;
input[type="checkbox"] {
width: 16px;
height: 16px;
cursor: pointer;
}
span {
font-size: 0.9rem;
color: rgba(218, 218, 218, 0.85);
}
&:hover span {
color: rgba(218, 218, 218, 1);
}
}
.predilection-delete {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
padding: 0;
background: rgba(106, 6, 6, 0.6);
border: 1px solid rgba(106, 6, 6, 0.8);
border-radius: 3px;
cursor: pointer;
transition: all 0.2s;
i {
color: rgba(255, 255, 255, 0.9);
font-size: 0.875rem;
}
&:hover {
background: rgba(106, 6, 6, 0.9);
border-color: #6a0606;
transform: scale(1.05);
i {
color: #fff;
}
}
}
}
}
}
.add-predilection-btn {
width: 100%;
padding: 0.5rem 1rem;
background: linear-gradient(to bottom, #4a0404 0%, #3a0303 100%);
border: 1px solid #6a0606;
border-radius: 4px;
color: rgba(218, 218, 218, 0.95);
font-family: "Charlemagne", serif;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
i {
font-size: 0.875rem;
}
&:hover {
background: linear-gradient(to bottom, #5a0505 0%, #4a0404 100%);
border-color: #8a0808;
color: #fff;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
&:active {
transform: translateY(0);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
}
}
}

View File

@@ -0,0 +1,311 @@
/* ============================================ */
/* WASTELAND ROLL DIALOG STYLES */
/* ============================================ */
.wasteland-roll-dialog {
background: url("../assets/ui/pc_sheet_bg.webp") repeat;
.window-content {
background: transparent;
}
.dialog-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
background: linear-gradient(to bottom, #4a0404 0%, #3a0303 100%);
border-bottom: 2px solid #6a0606;
margin: -0.5rem -0.5rem 0.5rem -0.5rem;
.actor-icon {
width: 48px;
height: 48px;
border: 2px solid #6a0606;
border-radius: 4px;
object-fit: cover;
}
.dialog-title {
flex: 1;
color: #f5f5f5;
h3 {
margin: 0 0 0.15rem 0;
font-size: 1rem;
font-family: "Charlemagne", serif;
font-weight: bold;
text-transform: uppercase;
}
.competence-name {
font-size: 0.85rem;
color: rgba(255, 255, 255, 0.9);
font-style: italic;
.attribut-info {
color: rgba(255, 255, 255, 0.7);
font-size: 0.8rem;
}
}
}
}
.dialog-content {
padding: 0 0.5rem 0.5rem 0.5rem;
.form-group {
margin-bottom: 0.4rem;
label {
display: block;
margin-bottom: 0.2rem;
font-weight: bold;
font-size: 0.85rem;
color: #2a1510;
font-family: "Charlemagne", serif;
}
select,
input[type="number"] {
width: 100%;
background: rgba(255, 255, 255, 0.5);
border: 1px solid #6a0606;
border-radius: 3px;
padding: 0.3rem 0.4rem;
font-size: 0.85rem;
color: #1a1510;
&:hover {
background: rgba(255, 255, 255, 0.6);
}
&:focus {
outline: none;
background: rgba(255, 255, 255, 0.7);
border-color: #aa0a0a;
box-shadow: 0 0 4px rgba(170, 10, 10, 0.3);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
}
.modifiers-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.4rem;
margin-bottom: 0.4rem;
}
.attributes-section {
background: rgba(106, 6, 6, 0.1);
border: 1px solid #6a0606;
border-radius: 4px;
padding: 0.5rem;
margin-bottom: 0.5rem;
label {
color: #6a0606;
}
}
.special-option {
margin-top: 0.5rem;
padding: 0.5rem;
background: rgba(106, 6, 6, 0.05);
border: 2px solid #6a0606;
border-radius: 4px;
.checkbox-label {
display: flex;
align-items: center;
gap: 0.4rem;
margin: 0;
cursor: pointer;
&.highlight {
span {
font-weight: bold;
color: #6a0606;
font-family: "Charlemagne", serif;
font-size: 0.85rem;
}
}
input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
accent-color: #6a0606;
}
span {
flex: 1;
font-size: 0.85rem;
color: #2a1510;
}
}
}
/* Weapon Section */
.weapon-section {
background: rgba(139, 101, 8, 0.1);
padding: 0.5rem;
border-radius: 4px;
border: 1px solid rgba(139, 101, 8, 0.4);
margin-bottom: 0.5rem;
.weapon-info {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.4rem 0.5rem;
background: rgba(139, 101, 8, 0.15);
border-radius: 3px;
margin-bottom: 0.5rem;
.weapon-label {
font-weight: bold;
font-size: 0.9rem;
color: #2a1510;
font-family: "Charlemagne", serif;
}
.weapon-bonus {
font-size: 0.85rem;
color: #8b0000;
font-weight: bold;
}
}
.defense-info {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.4rem 0.5rem;
background: rgba(0, 100, 0, 0.1);
border-radius: 3px;
margin-bottom: 0.5rem;
.defense-label {
font-size: 0.85rem;
color: #2a1510;
font-weight: bold;
}
.defense-value {
font-size: 1rem;
color: #006400;
font-weight: bold;
}
}
}
/* Combat Modifiers */
.combat-modifiers,
.ranged-combat-section {
background: rgba(139, 69, 19, 0.1);
padding: 0.5rem;
border-radius: 4px;
border: 1px solid rgba(139, 69, 19, 0.4);
margin-bottom: 0.5rem;
h4 {
margin: 0 0 0.5rem 0;
color: #2a1510;
font-size: 0.9rem;
font-weight: bold;
font-family: "Charlemagne", serif;
text-transform: uppercase;
text-shadow: 1px 1px 1px rgba(255, 255, 255, 0.3);
}
.checkbox-label {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.3rem 0.4rem;
cursor: pointer;
border-radius: 3px;
margin-bottom: 0.3rem;
transition: background 0.2s ease;
&:hover {
background: rgba(255, 255, 255, 0.2);
}
input[type="checkbox"] {
width: auto;
margin: 0;
cursor: pointer;
accent-color: #8b4513;
}
span {
color: #2a1510;
font-size: 0.85rem;
user-select: none;
}
}
.info-message {
padding: 0.4rem 0.6rem;
background: rgba(201, 168, 106, 0.2);
border-left: 3px solid rgba(201, 168, 106, 0.8);
border-radius: 3px;
font-size: 0.85rem;
color: #2a1510;
margin-bottom: 0.5rem;
font-style: italic;
}
}
.modifiers-columns {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.3rem 0.6rem;
}
}
// Styles pour les boutons du dialog
.dialog-buttons {
display: flex;
gap: 0.4rem;
padding: 0.5rem;
border-top: 1px solid #6a0606;
margin: 0.5rem -0.5rem -0.5rem -0.5rem;
background: rgba(255, 255, 255, 0.1);
button {
flex: 1;
padding: 0.5rem 0.75rem;
background: linear-gradient(to bottom, #4a0404 0%, #3a0303 100%);
border: 1px solid #6a0606;
border-radius: 3px;
color: #f5f5f5;
font-size: 0.85rem;
font-weight: bold;
font-family: "Charlemagne", serif;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background: linear-gradient(to bottom, #5a0505 0%, #4a0404 100%);
box-shadow: 0 0 6px rgba(106, 6, 6, 0.6);
}
&.default {
border-width: 2px;
border-color: #aa0a0a;
}
i {
margin-right: 0.4rem;
}
}
}
}

1365
less/simple-converted.less Normal file

File diff suppressed because it is too large Load Diff

8
less/wasteland.less Normal file
View File

@@ -0,0 +1,8 @@
// Main LESS file for Wasteland system
// Importing base styles and component-specific styles
@import "simple-converted";
@import "item-styles";
@import "actor-styles";
@import "roll-dialog-styles";
@import "chat-styles";

View File

@@ -0,0 +1,27 @@
/**
* Index des applications AppV2 pour Wasteland
* Ce fichier centralise tous les exports des applications
*/
// Applications de feuilles d'acteurs
export { default as WastelandPersonnageSheet } from './wasteland-personnage-sheet.mjs';
export { default as WastelandCreatureSheet } from './wasteland-creature-sheet.mjs';
// Applications de feuilles d'items
export { default as WastelandArmeSheet } from './wasteland-arme-sheet.mjs';
export { default as WastelandArtifexSheet } from './wasteland-artifex-sheet.mjs';
export { default as WastelandBouclierSheet } from './wasteland-bouclier-sheet.mjs';
export { default as WastelandCapaciteSheet } from './wasteland-capacite-sheet.mjs';
export { default as WastelandCharmeSheet } from './wasteland-charme-sheet.mjs';
export { default as WastelandCompetenceSheet } from './wasteland-competence-sheet.mjs';
export { default as WastelandDonSheet } from './wasteland-don-sheet.mjs';
export { default as WastelandEquipementSheet } from './wasteland-equipement-sheet.mjs';
export { default as WastelandHeritageSheet } from './wasteland-heritage-sheet.mjs';
export { default as WastelandHubrisSheet } from './wasteland-hubris-sheet.mjs';
export { default as WastelandMetierSheet } from './wasteland-metier-sheet.mjs';
export { default as WastelandMonnaieSheet } from './wasteland-monnaie-sheet.mjs';
export { default as WastelandMutationSheet } from './wasteland-mutation-sheet.mjs';
export { default as WastelandOrigineSheet } from './wasteland-origine-sheet.mjs';
export { default as WastelandPeupleSheet } from './wasteland-peuple-sheet.mjs';
export { default as WastelandPouvoirSheet } from './wasteland-pouvoir-sheet.mjs';
export { default as WastelandProtectionSheet } from './wasteland-protection-sheet.mjs';

View File

@@ -0,0 +1,382 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
import { WastelandUtility } from "../../wasteland-utility.js"
export default class WastelandActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
/**
* Different sheet modes.
* @enum {number}
*/
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-wasteland", "sheet", "actor"],
position: {
width: 650,
height: 720,
},
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: WastelandActorSheet.#onEditImage,
toggleSheet: WastelandActorSheet.#onToggleSheet,
editItem: WastelandActorSheet.#onEditItem,
deleteItem: WastelandActorSheet.#onDeleteItem,
createItem: WastelandActorSheet.#onCreateItem,
equipItem: WastelandActorSheet.#onEquipItem,
modifyQuantity: WastelandActorSheet.#onModifyQuantity,
incDecSante: WastelandActorSheet.#onIncDecSante,
rollAttribut: WastelandActorSheet.#onRollAttribut,
rollCompetence: WastelandActorSheet.#onRollCompetence,
rollCharme: WastelandActorSheet.#onRollCharme,
rollPouvoir: WastelandActorSheet.#onRollPouvoir,
rollArmeOffensif: WastelandActorSheet.#onRollArmeOffensif,
rollArmeDegats: WastelandActorSheet.#onRollArmeDegats,
resetPredilections: WastelandActorSheet.#onResetPredilections,
rollAssommer: WastelandActorSheet.#onRollAssommer,
rollFuir: WastelandActorSheet.#onRollFuir,
rollImmobiliser: WastelandActorSheet.#onRollImmobiliser,
},
}
/**
* Is the sheet currently in 'Play' mode?
* @type {boolean}
*/
get isPlayMode() {
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY
return this._sheetMode === this.constructor.SHEET_MODES.PLAY
}
/**
* Is the sheet currently in 'Edit' mode?
* @type {boolean}
*/
get isEditMode() {
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY
return this._sheetMode === this.constructor.SHEET_MODES.EDIT
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = { primary: "stats" }
/** @override */
async _prepareContext() {
const actor = this.document
const context = {
actor: 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.wasteland.config,
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.description || "", { async: true }),
enrichedComportement: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.comportement || "", { async: true }),
enrichedHabitat: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.habitat || "", { async: true }),
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
this.#dragDrop.forEach((d) => d.bind(this.element))
// Handle edit-item-data changes
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 itemType = itemElement.dataset.itemType
const itemField = target.dataset.itemField
const dataType = target.dataset.dtype
const value = target.value
await this.document.editItemField(itemId, itemType, itemField, dataType, value)
})
})
// Activate tab navigation manually
const nav = this.element.querySelector('nav.tabs[data-group]')
if (nav) {
const group = nav.dataset.group
// Activate the current tab
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()
})
})
// Show/hide tab content
this.element.querySelectorAll('[data-group="' + group + '"][data-tab]').forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab)
})
}
}
// #region Drag-and-Drop Workflow
/**
* Create drag-and-drop workflow handlers for this Application
*/
#createDragDropHandlers() {
return []
}
// #region Actions
/**
* Handle editing the actor image
* @param {Event} event - The triggering event
*/
static async #onEditImage(event) {
event.preventDefault()
const sheet = this
const filePicker = new FilePicker({
type: "image",
current: sheet.document.img,
callback: (path) => {
sheet.document.update({ img: path })
},
})
filePicker.browse()
}
/**
* Handle toggling the sheet mode
* @param {Event} event - The triggering event
*/
static async #onToggleSheet(event) {
event.preventDefault()
const sheet = this
sheet._sheetMode = sheet._sheetMode === sheet.constructor.SHEET_MODES.PLAY ? sheet.constructor.SHEET_MODES.EDIT : sheet.constructor.SHEET_MODES.PLAY
sheet.render()
}
/**
* Handle editing an item
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onEditItem(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
const item = this.actor.items.get(itemId)
if (item) item.sheet.render(true)
}
/**
* Handle deleting an item
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onDeleteItem(event, target) {
const li = target.closest(".item")
await WastelandUtility.confirmDelete(this, li)
}
/**
* Handle creating an item
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onCreateItem(event, target) {
const itemType = target.dataset.type
await this.actor.createEmbeddedDocuments("Item", [{ name: `Nouveau ${itemType}`, type: itemType }], { renderSheet: true })
}
/**
* Handle equipping an item
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onEquipItem(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
await this.actor.equipItem(itemId)
}
/**
* Handle modifying item quantity
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onModifyQuantity(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
const item = this.actor.items.get(itemId)
if (!item) return
const qty = parseInt(target.dataset.qty) || 0
const currentQty = item.system.quantite || 0
const newQty = Math.max(0, currentQty + qty)
await item.update({ 'system.quantite': newQty })
}
/**
* Handle modifying santé/psyché
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onIncDecSante(event, target) {
const field = target.dataset.field
const value = parseInt(target.dataset.value) || 0
if (field === 'psyche') {
await this.actor.update({ 'system.psyche.value': this.actor.system.psyche.value + value })
} else if (field === 'nonletaux') {
await this.actor.update({ 'system.sante.nonletaux': this.actor.system.sante.nonletaux + value })
} else if (field === 'letaux') {
await this.actor.update({ 'system.sante.letaux': this.actor.system.sante.letaux + value })
}
}
/**
* Handle rolling an attribute
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onRollAttribut(event, target) {
const attrKey = target.dataset.attrKey
await this.actor.rollAttribut(attrKey)
}
/**
* Handle rolling a competence
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onRollCompetence(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
const attrKey = target.dataset.attrKey
if (!itemId) return
await this.actor.rollCompetence(attrKey, itemId)
}
/**
* Handle rolling a charme
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onRollCharme(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
await this.actor.rollCharme(itemId)
}
/**
* Handle rolling a pouvoir
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onRollPouvoir(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
await this.actor.rollPouvoir(itemId)
}
/**
* Handle rolling weapon attack
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onRollArmeOffensif(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
await this.actor.rollArmeOffensif(itemId)
}
/**
* Handle rolling weapon damage
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onRollArmeDegats(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
await this.actor.rollArmeDegats(itemId)
}
/**
* Handle resetting all predilections
* @param {Event} event - The originating click event
* @param {HTMLElement} target - The target element
*/
static async #onResetPredilections(event, target) {
await this.actor.resetAllPredilections()
}
/**
* Handle Assommer roll
* @param {Event} event - The originating click event
* @param {HTMLElement} target - The target element
*/
static async #onRollAssommer(event, target) {
await this.actor.rollAssommer()
}
/**
* Handle Fuir roll
* @param {Event} event - The originating click event
* @param {HTMLElement} target - The target element
*/
static async #onRollFuir(event, target) {
await this.actor.rollFuir()
}
/**
* Handle Immobiliser roll
* @param {Event} event - The originating click event
* @param {HTMLElement} target - The target element
*/
static async #onRollImmobiliser(event, target) {
await this.actor.rollImmobiliser()
}
}

View File

@@ -0,0 +1,158 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class WastelandItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-wasteland", "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: WastelandItemSheet.#onEditImage,
postItem: WastelandItemSheet.#onPostItem,
},
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = { primary: "description" }
/** @override */
async _prepareContext() {
const context = {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
source: this.document.toObject(),
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true }),
isEditMode: true,
isEditable: this.isEditable,
isGM: game.user.isGM,
config: game.system.wasteland.config,
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
this.#dragDrop.forEach((d) => d.bind(this.element))
// Activate tab navigation manually
const nav = this.element.querySelector('nav.tabs[data-group]')
if (nav) {
const group = nav.dataset.group
// Activate the current tab
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()
})
})
// Show/hide tab content
this.element.querySelectorAll('[data-group="' + group + '"][data-tab]').forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab)
})
}
}
// #region Drag-and-Drop Workflow
/**
* Create drag-and-drop workflow handlers for this Application
*/
#createDragDropHandlers() {
return []
}
// #region Actions
/**
* Handle editing the item image
* @param {Event} event - The triggering event
*/
static async #onEditImage(event) {
event.preventDefault()
const filePicker = new FilePicker({
type: "image",
current: this.document.img,
callback: (path) => {
this.document.update({ img: path })
},
})
filePicker.browse()
}
/**
* Handle posting the item to chat
* @param {Event} event - The triggering event
*/
static async #onPostItem(event) {
event.preventDefault()
// Prepare chat data
const chatData = {
name: this.document.name,
img: this.document.img,
type: this.document.type,
system: this.document.system,
}
// Add actor reference if item is owned
if (this.document.actor) {
chatData.actor = { id: this.document.actor.id }
}
// Don't post any image for the item if the default image is used
if (chatData.img && chatData.img.includes("/blank.png")) {
chatData.img = null
}
// JSON object for easy creation
chatData.jsondata = JSON.stringify({
compendium: "postedItem",
payload: chatData,
})
// Render the chat card template
const html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-wasteland/templates/post-item.hbs', chatData)
// Create the chat message
const chatOptions = {
user: game.user.id,
content: html,
speaker: ChatMessage.getSpeaker({ actor: this.document.actor })
}
ChatMessage.create(chatOptions)
}
}

View File

@@ -0,0 +1,49 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandArmeSheet extends WastelandItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["arme"],
position: {
width: 620,
},
window: {
contentClasses: ["arme-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-wasteland/templates/item-arme-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "details",
}
/**
* Prepare an array of form header tabs.
* @returns {Record<string, Partial<ApplicationTab>>}
*/
#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
}
}

View File

@@ -0,0 +1,33 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandArtifexSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["artifex"],
position: { width: 620 },
window: { contentClasses: ["artifex-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-artifex-sheet.hbs" },
}
tabGroups = { primary: "details" }
#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
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,33 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandBouclierSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["bouclier"],
position: { width: 620 },
window: { contentClasses: ["bouclier-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-bouclier-sheet.hbs" },
}
tabGroups = { primary: "details" }
#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
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,32 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandCapaciteSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["capacite"],
position: { width: 620 },
window: { contentClasses: ["capacite-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-capacite-sheet.hbs" },
}
tabGroups = { primary: "description" }
#getTabs() {
const tabs = {
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
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,33 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandCharmeSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["charme"],
position: { width: 620 },
window: { contentClasses: ["charme-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-charme-sheet.hbs" },
}
tabGroups = { primary: "details" }
#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
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,72 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandCompetenceSheet extends WastelandItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["competence"],
position: {
width: 620,
},
window: {
contentClasses: ["competence-content"],
},
actions: {
"add-predilection": this.#onAddPredilection,
"delete-predilection": this.#onDeletePredilection
}
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-wasteland/templates/item-competence-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "details",
}
#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
}
/**
* Handle adding a new predilection
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onAddPredilection(event, target) {
const predilections = foundry.utils.duplicate(this.document.system.predilections)
predilections.push({ name: "Nouvelle prédilection", used: false })
await this.document.update({ "system.predilections": predilections })
}
/**
* Handle deleting a predilection
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The delete button element
*/
static async #onDeletePredilection(event, target) {
const index = parseInt(target.dataset.index)
const predilections = foundry.utils.duplicate(this.document.system.predilections)
predilections.splice(index, 1)
await this.document.update({ "system.predilections": predilections })
}
}

View File

@@ -0,0 +1,40 @@
import WastelandActorSheet from "./base-actor-sheet.mjs"
export default class WastelandCreatureSheet extends WastelandActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "creature"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "Feuille de Créature",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-wasteland/templates/actor-creature-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "stats",
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
// Add creature-specific data
context.skills = actor.getSkills()
context.armes = foundry.utils.duplicate(actor.getWeapons())
context.protections = foundry.utils.duplicate(actor.getArmors())
context.capacites = foundry.utils.duplicate(actor.getCapacites())
context.combat = actor.getCombatValues()
return context
}
}

View File

@@ -0,0 +1,32 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandDonSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["don"],
position: { width: 620 },
window: { contentClasses: ["don-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-don-sheet.hbs" },
}
tabGroups = { primary: "description" }
#getTabs() {
const tabs = {
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
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,33 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandEquipementSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["equipement"],
position: { width: 620 },
window: { contentClasses: ["equipement-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-equipement-sheet.hbs" },
}
tabGroups = { primary: "details" }
#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
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,32 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandHeritageSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["heritage"],
position: { width: 620 },
window: { contentClasses: ["heritage-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-heritage-sheet.hbs" },
}
tabGroups = { primary: "description" }
#getTabs() {
const tabs = {
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
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,33 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandHubrisSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["hubris"],
position: { width: 620 },
window: { contentClasses: ["hubris-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-hubris-sheet.hbs" },
}
tabGroups = { primary: "details" }
#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
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,32 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandMetierSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["metier"],
position: { width: 620 },
window: { contentClasses: ["metier-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-metier-sheet.hbs" },
}
tabGroups = { primary: "description" }
#getTabs() {
const tabs = {
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
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,33 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandMonnaieSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["monnaie"],
position: { width: 620 },
window: { contentClasses: ["monnaie-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-monnaie-sheet.hbs" },
}
tabGroups = { primary: "details" }
#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
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,32 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandMutationSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["mutation"],
position: { width: 620 },
window: { contentClasses: ["mutation-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-mutation-sheet.hbs" },
}
tabGroups = { primary: "description" }
#getTabs() {
const tabs = {
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
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,32 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandOrigineSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["origine"],
position: { width: 620 },
window: { contentClasses: ["origine-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-origine-sheet.hbs" },
}
tabGroups = { primary: "description" }
#getTabs() {
const tabs = {
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
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,59 @@
import WastelandActorSheet from "./base-actor-sheet.mjs"
export default class WastelandPersonnageSheet extends WastelandActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "personnage"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "Feuille de Personnage",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-wasteland/templates/actor-personnage-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "stats",
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
// Add personnage-specific data
context.skills = actor.getSkills()
context.armes = foundry.utils.duplicate(actor.getWeapons())
context.protections = foundry.utils.duplicate(actor.getArmors())
context.pouvoirs = foundry.utils.duplicate(actor.getPouvoirs())
context.dons = foundry.utils.duplicate(actor.getDons())
context.hubrises = foundry.utils.duplicate(actor.getHubris())
context.tours = foundry.utils.duplicate(actor.getTours())
context.artifex = foundry.utils.duplicate(actor.getArtifex())
context.charmes = foundry.utils.duplicate(actor.getCharmes())
context.peuple = foundry.utils.duplicate(actor.getPeuple() || {})
context.origine = foundry.utils.duplicate(actor.getOrigine() || {})
context.heritage = foundry.utils.duplicate(actor.getHeritage() || {})
context.metier = foundry.utils.duplicate(actor.getMetier() || {})
context.combat = actor.getCombatValues()
context.capacites = foundry.utils.duplicate(actor.getCapacites())
context.equipements = foundry.utils.duplicate(actor.getEquipments())
context.monnaies = foundry.utils.duplicate(actor.getMonnaies())
context.mutations = foundry.utils.duplicate(actor.getMutations())
// Enrich HTML fields for biodata
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.notes || "", { async: true })
context.enrichedGMNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.gmnotes || "", { async: true })
context.enrichedSequelles = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.sequelles || "", { async: true })
context.enrichedTraumatismes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.traumatismes || "", { async: true })
return context
}
}

View File

@@ -0,0 +1,32 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandPeupleSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["peuple"],
position: { width: 620 },
window: { contentClasses: ["peuple-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-peuple-sheet.hbs" },
}
tabGroups = { primary: "description" }
#getTabs() {
const tabs = {
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
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,33 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandPouvoirSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["pouvoir"],
position: { width: 620 },
window: { contentClasses: ["pouvoir-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-pouvoir-sheet.hbs" },
}
tabGroups = { primary: "details" }
#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
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,33 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandProtectionSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["protection"],
position: { width: 620 },
window: { contentClasses: ["protection-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-protection-sheet.hbs" },
}
tabGroups = { primary: "details" }
#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
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,156 @@
import { WastelandUtility } from "../wasteland-utility.js"
/**
* Dialogue de jet de dé pour Wasteland - Version DialogV2
*/
export class WastelandRollDialog {
/**
* Create and display the roll dialog
* @param {WastelandActor} actor - The actor making the roll
* @param {Object} rollData - Data for the roll
* @returns {Promise<WastelandRollDialog>}
*/
static async create(actor, rollData) {
// Préparer le contexte pour le template
const context = {
...rollData,
difficulte: String(rollData.difficulte || 0),
img: actor.img,
name: actor.name,
config: game.system.wasteland.config,
}
// Rendre le template en HTML
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-wasteland/templates/roll-dialog-v2.hbs",
context
)
// Utiliser DialogV2.wait avec le HTML rendu
return foundry.applications.api.DialogV2.wait({
window: { title: "Test de Capacité", icon: "fa-solid fa-dice-d20" },
classes: ["wasteland-roll-dialog"],
position: { width: 500 },
modal: false,
content,
buttons: rollData.charme ? [
{
action: "roll",
label: "Lancer",
icon: "fa-solid fa-dice-d20",
default: true,
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements, actor)
WastelandUtility.rollWasteland(rollData)
}
}
] : [
{
action: "rolld10",
label: "Lancer 1d10",
icon: "fa-solid fa-dice-d10",
default: true,
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements, actor)
rollData.mainDice = "1d10"
WastelandUtility.rollWasteland(rollData)
}
},
{
action: "rolld20",
label: "Lancer 1d20",
icon: "fa-solid fa-dice-d20",
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements, actor)
rollData.mainDice = "1d20"
WastelandUtility.rollWasteland(rollData)
}
},
],
rejectClose: false,
})
}
/**
* Mettre à jour rollData avec les valeurs du formulaire
* @param {Object} rollData - L'objet rollData à mettre à jour
* @param {HTMLFormControlsCollection} formElements - Les éléments du formulaire
* @param {WastelandActor} actor - L'acteur pour récupérer les attributs
* @private
*/
static _updateRollDataFromForm(rollData, formElements, actor) {
// Attributs
if (formElements.attrKey) {
rollData.attrKey = formElements.attrKey.value
if (rollData.attrKey !== "tochoose" && actor) {
rollData.attr = foundry.utils.duplicate(actor.system.attributs[rollData.attrKey])
}
}
// Modificateurs de base
if (formElements.difficulte) {
rollData.difficulte = Number(formElements.difficulte.value)
}
if (formElements.modificateur) {
rollData.modificateur = Number(formElements.modificateur.value)
}
// Charme
if (formElements.charmeDice) {
rollData.charmeDice = String(formElements.charmeDice.value)
}
// Combat mêlée
if (formElements.typeAttaque) {
rollData.typeAttaque = String(formElements.typeAttaque.value)
rollData.typeAttaqueLabel = rollData.config.attaques[rollData.typeAttaque]
}
if (formElements.isMonte !== undefined) {
rollData.isMonte = formElements.isMonte.checked
}
// Combat distance
if (formElements.visee !== undefined) {
rollData.visee = formElements.visee.checked
}
if (formElements.cibleconsciente !== undefined) {
rollData.cibleconsciente = formElements.cibleconsciente.checked
}
if (formElements.ciblecourt !== undefined) {
rollData.ciblecourt = formElements.ciblecourt.checked
}
if (formElements.typeCouvert) {
rollData.typeCouvert = String(formElements.typeCouvert.value)
const couvert = rollData.config.couverts[rollData.typeCouvert]
if (rollData.typeCouvert !== "aucun" && couvert) {
rollData.typeCouvertLabel = couvert.name
rollData.typeCouvertValue = couvert.value
}
}
// Désavantages (avantages tactiques)
if (!rollData.desavantages) rollData.desavantages = {}
if (formElements.cibleausol !== undefined) {
rollData.desavantages.cibleausol = formElements.cibleausol.checked
}
if (formElements.cibledesarmee !== undefined) {
rollData.desavantages.cibledesarmee = formElements.cibledesarmee.checked
}
if (formElements.ciblerestreint !== undefined) {
rollData.desavantages.ciblerestreint = formElements.ciblerestreint.checked
}
if (formElements.cibleimmobilisée !== undefined) {
rollData.desavantages.cibleimmobilisée = formElements.cibleimmobilisée.checked
}
if (formElements.ciblesurplomb !== undefined) {
rollData.desavantages.ciblesurplomb = formElements.ciblesurplomb.checked
}
// Double D20
if (formElements.doubleD20 !== undefined) {
rollData.doubleD20 = formElements.doubleD20.checked
}
}
}

26
modules/models/arme.mjs Normal file
View File

@@ -0,0 +1,26 @@
/**
* Data model pour les armes
*/
export default class ArmeDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
typearme: new fields.StringField({ initial: "" }),
isdefense: new fields.BooleanField({ initial: false }),
bonusmaniementoff: new fields.NumberField({ initial: 0, integer: true }),
bonusmaniementdef: new fields.NumberField({ initial: 0, integer: true }),
nobonusdegats: new fields.BooleanField({ initial: false }),
degats: new fields.StringField({ initial: "" }),
nonletaux: new fields.BooleanField({ initial: false }),
deuxmains: new fields.BooleanField({ initial: false }),
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 }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true }),
equipped: new fields.BooleanField({ initial: false })
};
}
}

View File

@@ -0,0 +1,18 @@
/**
* Data model pour les artifex
*/
export default class ArtifexDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
artifextype: new fields.StringField({ initial: "vapeur" }),
competence: new fields.StringField({ initial: "" }),
complexite: new fields.NumberField({ initial: 0, integer: true }),
dureerealisation: new fields.StringField({ initial: "" }),
tempsmiseenroute: new fields.StringField({ initial: "" }),
defautcourant: new fields.StringField({ initial: "" }),
prix: new fields.NumberField({ initial: 0, integer: true })
};
}
}

View File

@@ -0,0 +1,17 @@
/**
* Data model pour les boucliers
*/
export default class BouclierDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
bonusdefense: new fields.NumberField({ initial: 0, integer: true }),
degats: new fields.StringField({ initial: "" }),
nonletaux: new fields.BooleanField({ initial: false }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true }),
equipped: new fields.BooleanField({ initial: false })
};
}
}

View File

@@ -0,0 +1,11 @@
/**
* Data model pour les capacités
*/
export default class CapaciteDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

25
modules/models/charme.mjs Normal file
View File

@@ -0,0 +1,25 @@
/**
* Data model pour les charmes
*/
export default class CharmeDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
charmetype: new fields.StringField({ initial: "tour" }),
resultats: new fields.ArrayField(new fields.SchemaField({
value: new fields.NumberField({ initial: -1, integer: true }),
description: new fields.StringField({ initial: "" })
}), {
initial: [
{ value: -1, description: "" },
{ value: -1, description: "" },
{ value: -1, description: "" },
{ value: -1, description: "" },
{ value: -1, description: "" },
{ value: -1, description: "" }
]
})
};
}
}

View File

@@ -0,0 +1,21 @@
/**
* Data model pour les compétences
*/
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({
name: new fields.StringField({ initial: "" }),
description: new fields.StringField({ initial: "" }),
used: new fields.BooleanField({ initial: false })
}), { initial: [] })
};
}
}

View File

@@ -0,0 +1,99 @@
/**
* Data model pour les créatures
*/
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 }),
alignement: new fields.StringField({ initial: "" }),
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.StringField({ initial: "" }),
comportement: new fields.StringField({ initial: "" }),
psychemultiplier: new fields.NumberField({ initial: 1, integer: true }),
notes: new fields.HTMLField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" })
}),
// Template core
terreur: new fields.SchemaField({
value: new fields.NumberField({ initial: -1, integer: true })
}),
protection: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
ressource: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
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: 0, 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: 0, 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({
base: new fields.NumberField({ initial: 0, integer: true }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
nonletaux: new fields.NumberField({ initial: 0, integer: true }),
letaux: new fields.NumberField({ initial: 0, integer: true }),
sequelles: new fields.StringField({ initial: "" })
}),
psyche: new fields.SchemaField({
fullmax: new fields.NumberField({ initial: 0, integer: true }),
currentmax: new fields.NumberField({ initial: 0, integer: true }),
value: new fields.NumberField({ initial: 0, integer: true }),
traumatismes: new fields.StringField({ initial: "" })
}),
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 }),
defensebonus: new fields.NumberField({ initial: 0, integer: true })
})
};
}
}

12
modules/models/don.mjs Normal file
View File

@@ -0,0 +1,12 @@
/**
* Data model pour les dons
*/
export default class DonDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
coutpsyche: new fields.NumberField({ initial: 0, integer: true })
};
}
}

View File

@@ -0,0 +1,14 @@
/**
* Data model pour les équipements
*/
export default class EquipementDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
quantite: new fields.NumberField({ initial: 1, integer: true, min: 0 }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true })
};
}
}

View File

@@ -0,0 +1,11 @@
/**
* Data model pour les héritages
*/
export default class HeritageDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

12
modules/models/hubris.mjs Normal file
View File

@@ -0,0 +1,12 @@
/**
* Data model pour les hubris
*/
export default class HubrisDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
hubristype: new fields.StringField({ initial: "mental" })
};
}
}

27
modules/models/index.mjs Normal file
View File

@@ -0,0 +1,27 @@
/**
* Index des DataModels pour Wasteland
* Ce fichier centralise tous les exports des modèles de données
*/
// Modèles d'items
export { default as ArmeDataModel } from './arme.mjs';
export { default as BouclierDataModel } from './bouclier.mjs';
export { default as CapaciteDataModel } from './capacite.mjs';
export { default as CompetenceDataModel } from './competence.mjs';
export { default as DonDataModel } from './don.mjs';
export { default as EquipementDataModel } from './equipement.mjs';
export { default as HeritageDataModel } from './heritage.mjs';
export { default as MetierDataModel } from './metier.mjs';
export { default as MonnaieDataModel } from './monnaie.mjs';
export { default as OrigineDataModel } from './origine.mjs';
export { default as ProtectionDataModel } from './protection.mjs';
export { default as MutationDataModel } from './mutation.mjs';
export { default as PouvoirDataModel } from './pouvoir.mjs';
export { default as CharmeDataModel } from './charme.mjs';
export { default as ArtifexDataModel } from './artifex.mjs';
export { default as PeupleDataModel } from './peuple.mjs';
export { default as HubrisDataModel } from './hubris.mjs';
// Modèles d'acteurs
export { default as PersonnageDataModel } from './personnage.mjs';
export { default as CreatureDataModel } from './creature.mjs';

11
modules/models/metier.mjs Normal file
View File

@@ -0,0 +1,11 @@
/**
* Data model pour les métiers
*/
export default class MetierDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,13 @@
/**
* Data model pour les monnaies
*/
export default class MonnaieDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
quantite: new fields.NumberField({ initial: 0, integer: true }),
unite: new fields.StringField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,11 @@
/**
* Data model pour les mutations
*/
export default class MutationDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,11 @@
/**
* Data model pour les origines
*/
export default class OrigineDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,100 @@
/**
* Data model pour les personnages
*/
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: 0, integer: true }),
alignement: new fields.StringField({ initial: "" }),
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.StringField({ initial: "" }),
comportement: new fields.StringField({ initial: "" }),
psychemultiplier: new fields.NumberField({ initial: 1, integer: true }),
notes: new fields.HTMLField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" })
}),
// Template core
terreur: new fields.SchemaField({
value: new fields.NumberField({ initial: -1, integer: true })
}),
protection: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
ressource: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
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: 0, 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: 0, 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({
base: new fields.NumberField({ initial: 0, integer: true }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
nonletaux: new fields.NumberField({ initial: 0, integer: true }),
letaux: new fields.NumberField({ initial: 0, integer: true }),
sequelles: new fields.StringField({ initial: "" })
}),
psyche: new fields.SchemaField({
fullmax: new fields.NumberField({ initial: 0, integer: true }),
currentmax: new fields.NumberField({ initial: 0, integer: true }),
value: new fields.NumberField({ initial: 0, integer: true }),
traumatismes: new fields.StringField({ initial: "" })
}),
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 }),
defensebonus: new fields.NumberField({ initial: 0, integer: true }),
monte: new fields.BooleanField({ initial: false })
})
};
}
}

11
modules/models/peuple.mjs Normal file
View File

@@ -0,0 +1,11 @@
/**
* Data model pour les peuples
*/
export default class PeupleDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,20 @@
/**
* Data model pour les pouvoirs
*/
export default class PouvoirDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
chemin: new fields.StringField({ initial: "force" }),
attribut1: new fields.StringField({ initial: "cla" }),
competence: new fields.StringField({ initial: "" }),
seuil: new fields.NumberField({ initial: 0, integer: true }),
coutpsyche: new fields.NumberField({ initial: 0, integer: true }),
cible: new fields.StringField({ initial: "" }),
duree: new fields.StringField({ initial: "" }),
formulesimple: new fields.StringField({ initial: "" }),
formuleetendue: new fields.StringField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,15 @@
/**
* Data model pour les protections
*/
export default class ProtectionDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
protection: new fields.NumberField({ initial: 0, integer: true }),
equipped: new fields.BooleanField({ initial: false }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true })
};
}
}

View File

@@ -1,176 +0,0 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
import { WastelandUtility } from "./wasteland-utility.js";
import { WastelandRollDialog } from "./wasteland-roll-dialog.js";
/* -------------------------------------------- */
export class WastelandActorSheet extends ActorSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["fvtt-wasteland", "sheet", "actor"],
template: "systems/fvtt-wasteland/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 = duplicate(this.object)
let actorData = objectData
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",
data: actorData.system,
effects: this.object.effects.map(e => foundry.utils.deepClone(e.data)),
limited: this.object.limited,
skills: this.actor.getSkills(),
armes: duplicate(this.actor.getWeapons()),
protections: duplicate(this.actor.getArmors()),
pouvoirs:duplicate(this.actor.getPouvoirs()),
origine: duplicate(this.actor.getOrigine() || {}),
heritage: duplicate(this.actor.getHeritage() || {}),
metier: duplicate(this.actor.getMetier() || {}),
combat: this.actor.getCombatValues(),
equipements: duplicate(this.actor.getEquipments()),
monnaies: duplicate(this.actor.getMonnaies()),
description: await TextEditor.enrichHTML(this.object.system.biodata.description, {async: true}),
options: this.options,
owner: this.document.isOwner,
editScore: this.options.editScore,
isGM: game.user.isGM
}
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");
WastelandUtility.confirmDelete(this, li);
})
html.find('.edit-item-data').change(ev => {
const li = $(ev.currentTarget).parents(".item")
let itemId = li.data("item-id")
let itemType = li.data("item-type")
let itemField = $(ev.currentTarget).data("item-field")
let dataType = $(ev.currentTarget).data("dtype")
let value = ev.currentTarget.value
this.actor.editItemField(itemId, itemType, itemField, dataType, value)
})
html.find('.quantity-minus').click(event => {
const li = $(event.currentTarget).parents(".item");
this.actor.incDecQuantity( li.data("item-id"), -1 );
} );
html.find('.quantity-plus').click(event => {
const li = $(event.currentTarget).parents(".item");
this.actor.incDecQuantity( li.data("item-id"), +1 );
} );
html.find('.roll-attribut').click((event) => {
const li = $(event.currentTarget).parents(".item")
let attrKey = li.data("attr-key")
this.actor.rollAttribut(attrKey)
})
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-pouvoir').click((event) => {
const li = $(event.currentTarget).parents(".item")
let pouvoirId = li.data("item-id")
this.actor.rollPouvoir(pouvoirId)
})
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-arme-degats').click((event) => {
const li = $(event.currentTarget).parents(".item")
let armeId = li.data("item-id")
this.actor.rollArmeDegats(armeId)
})
html.find('.quantity-modify').click(event => {
const li = $(event.currentTarget).parents(".item")
const value = Number($(event.currentTarget).data("quantite-value"))
this.actor.incDecQuantity( li.data("item-id"), value );
})
html.find('.item-add').click((event) => {
const itemType = $(event.currentTarget).data("type")
this.actor.createEmbeddedDocuments('Item', [{ name: `Nouveau ${itemType}`, type: itemType }], { renderSheet: true })
})
html.find('.lock-unlock-sheet').click((event) => {
this.options.editScore = !this.options.editScore;
this.render(true);
});
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 item = await WastelandUtility.searchItem( dragData)
this.actor.preprocessItem( event, item, true )
super._onDropItem(event, dragData)
}*/
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
// Update the Actor
return this.object.update(formData);
}
}

View File

@@ -1,6 +1,6 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
import { WastelandUtility } from "./wasteland-utility.js"; import { WastelandUtility } from "./wasteland-utility.js";
import { WastelandRollDialog } from "./wasteland-roll-dialog.js"; import { WastelandRollDialog } from "./applications/wasteland-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]
@@ -37,11 +37,12 @@ export class WastelandActor extends Actor {
return actor; return actor;
} }
const skills = await WastelandUtility.loadCompendium("fvtt-wasteland.skills")
if (data.type == 'personnage') { if (data.type == 'personnage') {
const skills = await WastelandUtility.loadCompendium("fvtt-wasteland.skills")
data.items = skills.map(i => i.toObject()) data.items = skills.map(i => i.toObject())
} }
if (data.type == 'pnj') { if (data.type == 'creature') {
data.items = skills.filter(i=>i.name.toLowerCase().includes("mêlée")).map(i => i.toObject())
} }
return super.create(data, options); return super.create(data, options);
@@ -49,10 +50,11 @@ export class WastelandActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
prepareArme(arme) { prepareArme(arme) {
arme = duplicate(arme) arme = foundry.utils.duplicate(arme)
let combat = this.getCombatValues() let combat = this.getCombatValues()
if (arme.system.typearme == "contact" || arme.system.typearme == "contactjet") { if (arme.system.typearme == "contact" || arme.system.typearme == "contactjet") {
arme.system.competence = duplicate(this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "mêlée")) arme.system.isMelee = true
arme.system.competence = foundry.utils.duplicate(this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "mêlée"))
arme.system.attrKey = "pui" arme.system.attrKey = "pui"
arme.system.totalDegats = arme.system.degats + "+" + combat.bonusDegatsTotal arme.system.totalDegats = arme.system.degats + "+" + combat.bonusDegatsTotal
arme.system.totalOffensif = this.system.attributs.pui.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff arme.system.totalOffensif = this.system.attributs.pui.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff
@@ -61,7 +63,8 @@ export class WastelandActor extends Actor {
} }
} }
if (arme.system.typearme == "jet" || arme.system.typearme == "tir") { if (arme.system.typearme == "jet" || arme.system.typearme == "tir") {
arme.system.competence = duplicate(this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "armes à distance")) arme.system.isDistance = true
arme.system.competence = foundry.utils.duplicate(this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "armes à distance"))
arme.system.attrKey = "adr" arme.system.attrKey = "adr"
arme.system.totalOffensif = this.system.attributs.adr.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff arme.system.totalOffensif = this.system.attributs.adr.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff
arme.system.totalDegats = arme.system.degats arme.system.totalDegats = arme.system.degats
@@ -73,9 +76,9 @@ export class WastelandActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
prepareBouclier(bouclier) { prepareBouclier(bouclier) {
bouclier = duplicate(bouclier) bouclier = foundry.utils.duplicate(bouclier)
let combat = this.getCombatValues() let combat = this.getCombatValues()
bouclier.system.competence = duplicate(this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "mêlée")) bouclier.system.competence = foundry.utils.duplicate(this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "mêlée"))
bouclier.system.attrKey = "pui" bouclier.system.attrKey = "pui"
bouclier.system.totalDegats = bouclier.system.degats + "+" + combat.bonusDegatsTotal bouclier.system.totalDegats = bouclier.system.degats + "+" + combat.bonusDegatsTotal
bouclier.system.totalOffensif = this.system.attributs.pui.value + bouclier.system.competence.system.niveau bouclier.system.totalOffensif = this.system.attributs.pui.value + bouclier.system.competence.system.niveau
@@ -106,18 +109,46 @@ export class WastelandActor extends Actor {
WastelandUtility.sortArrayObjectsByName(items) WastelandUtility.sortArrayObjectsByName(items)
return items return items
} }
getCharmes() {
let items = this.items.filter(item => item.type=="charme" && item.system?.charmetype == "charme") || []
WastelandUtility.sortArrayObjectsByName(items)
return items
}
getTours() {
let items = this.items.filter(item => item.type=="charme" && item.system?.charmetype == "tour") || []
WastelandUtility.sortArrayObjectsByName(items)
return items
}
getArtifex() {
return this.getItemSorted(["artifex"])
}
getCapacites() {
return this.getItemSorted(["capacite"])
}
getPouvoirs() { getPouvoirs() {
return this.getItemSorted(["pouvoir"]) return this.getItemSorted(["pouvoir"])
} }
getDons() {
return this.getItemSorted(["don"])
}
getHubris() {
return this.getItemSorted(["hubris"])
}
getEquipments() { getEquipments() {
return this.getItemSorted(["equipement"]) return this.getItemSorted(["equipement"])
} }
getMonnaies() { getMonnaies() {
return this.getItemSorted(["monnaie"]) return this.getItemSorted(["monnaie"])
} }
getMutations() {
return this.getItemSorted(["mutation"])
}
getArmors() { getArmors() {
return this.getItemSorted(["protection"]) return this.getItemSorted(["protection"])
} }
getPeuple() {
return this.items.find(item => item.type == "peuple")
}
getOrigine() { getOrigine() {
return this.items.find(item => item.type == "origine") return this.items.find(item => item.type == "origine")
} }
@@ -131,7 +162,7 @@ export class WastelandActor extends Actor {
getSkills() { getSkills() {
let comp = [] let comp = []
for (let item of this.items) { for (let item of this.items) {
item = duplicate(item) item = foundry.utils.duplicate(item)
if (item.type == "competence") { if (item.type == "competence") {
item.system.attribut1total = item.system.niveau + (this.system.attributs[item.system.attribut1]?.value || 0) item.system.attribut1total = item.system.niveau + (this.system.attributs[item.system.attribut1]?.value || 0)
item.system.attribut2total = item.system.niveau + (this.system.attributs[item.system.attribut2]?.value || 0) item.system.attribut2total = item.system.niveau + (this.system.attributs[item.system.attribut2]?.value || 0)
@@ -193,7 +224,7 @@ export class WastelandActor extends Actor {
if (this.system.sante.base != newSante) { if (this.system.sante.base != newSante) {
this.update({ 'system.sante.base': newSante }) this.update({ 'system.sante.base': newSante })
} }
let newPsyche = (this.system.attributs.cla.value + this.system.attributs.tre.value) * this.system.biodata.psychemultiplier + 5 let newPsyche = ((this.system.attributs.cla.value + this.system.attributs.tre.value) * 2) + 5
if (this.system.psyche.fullmax != newPsyche) { if (this.system.psyche.fullmax != newPsyche) {
this.update({ 'system.psyche.fullmax': newPsyche }) this.update({ 'system.psyche.fullmax': newPsyche })
} }
@@ -207,12 +238,17 @@ export class WastelandActor extends Actor {
super._preUpdate(changed, options, user); super._preUpdate(changed, options, user);
} }
/* -------------------------------------------- */
incDecSante(value) {
let sante = foundry.utils.duplicate(this.system.sante)
sante.letaux += value
this.update({ 'system.sante': sante })
}
/* -------------------------------------------- */ /* -------------------------------------------- */
getItemById(id) { getItemById(id) {
let item = this.items.find(item => item.id == id); let item = this.items.find(item => item.id == id);
if (item) { if (item) {
item = duplicate(item) item = foundry.utils.duplicate(item)
} }
return item; return item;
} }
@@ -262,12 +298,16 @@ export class WastelandActor extends Actor {
changeEclat(value) { changeEclat(value) {
let newE = this.system.eclat.value let newE = this.system.eclat.value
newE += value newE += value
// Empêcher que l'éclat devienne négatif
if (newE < 0) {
newE = 0
}
this.update({ 'system.eclat.value': newE }) this.update({ 'system.eclat.value': newE })
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
subPointsPsyche(value) { subPointsPsyche(value) {
let psyche = duplicate(this.system.psyche) let psyche = foundry.utils.duplicate(this.system.psyche)
psyche.currentmax -= value psyche.currentmax -= value
this.update( {'system.psyche': psyche}) this.update( {'system.psyche': psyche})
} }
@@ -306,13 +346,13 @@ export class WastelandActor extends Actor {
getSubActors() { getSubActors() {
let subActors = []; let subActors = [];
for (let id of this.system.subactors) { for (let id of this.system.subactors) {
subActors.push(duplicate(game.actors.get(id))); subActors.push(foundry.utils.duplicate(game.actors.get(id)));
} }
return subActors; return subActors;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async addSubActor(subActorId) { async addSubActor(subActorId) {
let subActors = duplicate(this.system.subactors); let subActors = foundry.utils.duplicate(this.system.subactors);
subActors.push(subActorId); subActors.push(subActorId);
await this.update({ 'system.subactors': subActors }); await this.update({ 'system.subactors': subActors });
} }
@@ -343,11 +383,37 @@ export class WastelandActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
async setPredilectionUsed(compId, predIdx) { async setPredilectionUsed(compId, predIdx) {
let comp = this.items.get(compId) let comp = this.items.get(compId)
let pred = duplicate(comp.system.predilections) let pred = foundry.utils.duplicate(comp.system.predilections)
pred[predIdx].used = true pred[predIdx].used = true
await this.updateEmbeddedDocuments('Item', [{ _id: compId, 'system.predilections': pred }]) await this.updateEmbeddedDocuments('Item', [{ _id: compId, 'system.predilections': pred }])
} }
/* -------------------------------------------- */
async resetAllPredilections() {
let updates = []
for (let item of this.items) {
if (item.type === "competence" && item.system.predilections && item.system.predilections.length > 0) {
let pred = foundry.utils.duplicate(item.system.predilections)
let hasUsed = false
for (let p of pred) {
if (p.used) {
p.used = false
hasUsed = true
}
}
if (hasUsed) {
updates.push({ _id: item.id, 'system.predilections': pred })
}
}
}
if (updates.length > 0) {
await this.updateEmbeddedDocuments('Item', updates)
ui.notifications.info(`${updates.length} prédilection(s) réinitialisée(s) pour ${this.name}`)
} else {
ui.notifications.info(`Aucune prédilection à réinitialiser pour ${this.name}`)
}
}
/* -------------------------------------------- */ /* -------------------------------------------- */
getInitiativeScore( ) { getInitiativeScore( ) {
return Number(this.system.attributs.adr.value) + Number(this.system.combat.initbonus) return Number(this.system.attributs.adr.value) + Number(this.system.combat.initbonus)
@@ -366,7 +432,7 @@ export class WastelandActor extends Actor {
} }
if ( arme.system.totalDefensif > maxDef) { if ( arme.system.totalDefensif > maxDef) {
maxDef = arme.system.totalDefensif maxDef = arme.system.totalDefensif
bestArme = duplicate(arme) bestArme = foundry.utils.duplicate(arme)
} }
} }
return bestArme return bestArme
@@ -380,60 +446,70 @@ export class WastelandActor extends Actor {
rollData.actorId = this.id rollData.actorId = this.id
rollData.tokenId = this.token?.id rollData.tokenId = this.token?.id
rollData.img = this.img rollData.img = this.img
rollData.canEclatDoubleD20 = true // Always true in Wastelan rollData.canEclatDoubleD20 = this.getEclat() >= 1 // Vérifier que l'acteur a au moins 1 point d'éclat
rollData.doubleD20 = false rollData.doubleD20 = false
rollData.attributs = WastelandUtility.getAttributs() rollData.attributs = WastelandUtility.getAttributs()
rollData.config = foundry.utils.duplicate(game.system.wasteland.config)
rollData.desavantages = {}
rollData.isMonte = this.system.combat.monte
if (attrKey) { if (attrKey) {
rollData.attrKey = attrKey rollData.attrKey = attrKey
if (attrKey != "tochoose") { if (attrKey != "tochoose") {
rollData.actionImg = "systems/fvtt-wasteland/assets/icons/" + this.system.attributs[attrKey].labelnorm + ".webp" rollData.actionImg = "systems/fvtt-wasteland/assets/icons/" + this.system.attributs[attrKey].labelnorm + ".webp"
rollData.attr = duplicate(this.system.attributs[attrKey]) rollData.attr = foundry.utils.duplicate(this.system.attributs[attrKey])
} }
} }
if (compId) { if (compId) {
rollData.competence = duplicate(this.items.get(compId) || {}) rollData.competence = foundry.utils.duplicate(this.items.get(compId) || {})
rollData.actionImg = rollData.competence?.img rollData.actionImg = rollData.competence?.img
} }
if (compName) { if (compName) {
rollData.competence = duplicate(this.items.find( item => item.name.toLowerCase() == compName.toLowerCase()) || {}) rollData.competence = foundry.utils.duplicate(this.items.find( item => item.name.toLowerCase() == compName.toLowerCase()) || {})
rollData.actionImg = rollData.competence?.img rollData.actionImg = rollData.competence?.img
} }
return rollData return rollData
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async rollAttribut(attrKey) { async launchRoll(rollData) {
console.log("RollData", rollData)
await WastelandRollDialog.create(this, rollData)
}
/* -------------------------------------------- */
rollAttribut(attrKey) {
let rollData = this.getCommonRollData(attrKey) let rollData = this.getCommonRollData(attrKey)
let rollDialog = await WastelandRollDialog.create(this, rollData) this.launchRoll(rollData)
rollDialog.render(true)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async rollCompetence(attrKey, compId) { rollCompetence(attrKey, compId) {
let rollData = this.getCommonRollData(attrKey, compId) let rollData = this.getCommonRollData(attrKey, compId)
console.log("RollDatra", rollData) console.log("RollDatra", rollData)
let rollDialog = await WastelandRollDialog.create(this, rollData) this.launchRoll(rollData)
rollDialog.render(true)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async rollPouvoir(pouvoirId) { async rollCharme(charmeId) {
let comp = this.items.find(comp => comp.type == "competence" && comp.name.toLowerCase() == "savoir : runes") let rollData = this.getCommonRollData("cla")
if ( !comp) { rollData.charme = foundry.utils.duplicate(this.items.get(charmeId) || {})
ui.notifications.warn("La compétence Savoirs : Runes n'a pas été trouvée, abandon.") rollData.charmeDice = "1d4"
return this.launchRoll(rollData)
}
let rollData = this.getCommonRollData("cla", undefined, "Savoir : Runes")
rollData.rune = duplicate(this.items.get(runeId) || {})
rollData.difficulte = rollData.rune?.system?.seuil || 0
rollData.runemode = "prononcer"
rollData.runeame = 1
console.log("runeData", rollData)
let rollDialog = await WastelandRollDialog.create(this, rollData)
rollDialog.render(true)
} }
/* -------------------------------------------- */
async rollPouvoir(pouvoirId) {
let pouvoir = foundry.utils.duplicate(this.items.get(pouvoirId) || {})
if (pouvoir?.system) {
let rollData = this.getCommonRollData(pouvoir.system.attribut1, undefined, pouvoir.system.competence)
if (!rollData.competence) {
ui.notifications.error("Le pouvoir " + pouvoir.name + " n'a pas de compétence associée. Editez le pouvoir avec la compétence associée.")
return
}
rollData.pouvoir = pouvoir
rollData.difficulte = pouvoir.system.seuil
this.launchRoll(rollData)
}
}
/* -------------------------------------------- */ /* -------------------------------------------- */
async rollArmeOffensif(armeId) { async rollArmeOffensif(armeId) {
let arme = this.items.get(armeId) let arme = this.items.get(armeId)
@@ -445,9 +521,13 @@ export class WastelandActor extends Actor {
} }
let rollData = this.getCommonRollData(arme.system.attrKey, arme.system.competence._id) let rollData = this.getCommonRollData(arme.system.attrKey, arme.system.competence._id)
rollData.arme = arme rollData.arme = arme
console.log("ARME!", rollData) rollData.typeAttaque = "assaut"
let rollDialog = await WastelandRollDialog.create(this, rollData) rollData.typeCouvert = "aucun"
rollDialog.render(true) rollData.hasDesavantageBonus = true
rollData.visee = false
rollData.ciblecourt = false
rollData.cibleconsciente = false
this.launchRoll(rollData)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -459,7 +539,7 @@ export class WastelandActor extends Actor {
if (arme.type == "bouclier") { if (arme.type == "bouclier") {
arme = this.prepareBouclier(arme) arme = this.prepareBouclier(arme)
} }
let roll = new Roll(arme.system.totalDegats).roll({ async: false }) let roll = await new Roll(arme.system.totalDegats).roll()
await WastelandUtility.showDiceSoNice(roll, game.settings.get("core", "rollMode")); await WastelandUtility.showDiceSoNice(roll, game.settings.get("core", "rollMode"));
let rollData = { let rollData = {
arme: arme, arme: arme,
@@ -470,8 +550,52 @@ export class WastelandActor extends Actor {
actionImg: arme.img, actionImg: arme.img,
} }
WastelandUtility.createChatWithRollMode(rollData.alias, { WastelandUtility.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-wasteland/templates/chat-degats-result.html`, rollData) content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-wasteland/templates/chat-degats-result-v2.hbs`, rollData)
}) })
} }
/* -------------------------------------------- */
async rollAssommer() {
let rollData = this.getCommonRollData("adr", undefined, "Mouvements")
rollData.specialAction = "Assommer"
rollData.typeAttaque = "assommer"
rollData.typeCouvert = "aucun"
rollData.hasDesavantageBonus = true
if (rollData.defender) {
rollData.selectDifficulte = false
rollData.difficulte = rollData.defender.system.attributs.tre.value * 2
}
this.launchRoll(rollData)
}
/* -------------------------------------------- */
async rollFuir() {
let rollData = this.getCommonRollData("adr", undefined, "Mouvements")
rollData.specialAction = "Fuir"
rollData.typeAttaque = "fuir"
rollData.typeCouvert = "aucun"
rollData.hasDesavantageBonus = true
if (rollData.defender) {
rollData.selectDifficulte = false
let comp = rollData.defender.items.find(it => it.type == "competence" && it.name.toLowerCase() == "mouvements")
rollData.difficulte = rollData.defender.system.attributs.adr.value + ((comp) ? comp.system.niveau : rollData.defender.system.attributs.adr.value)
}
this.launchRoll(rollData)
}
/* -------------------------------------------- */
async rollImmobiliser() {
let rollData = this.getCommonRollData("pui", undefined, "Mêlée")
rollData.specialAction = "Immobiliser"
rollData.typeAttaque = "immobiliser"
rollData.typeCouvert = "aucun"
rollData.hasDesavantageBonus = true
if (rollData.defender) {
rollData.selectDifficulte = false
rollData.difficulte = rollData.armeDefense ? rollData.armeDefense.system.totalDefensif : 10
}
this.launchRoll(rollData)
}
} }

View File

@@ -10,7 +10,7 @@ export class WastelandCombat extends Combat {
const c = this.combatants.get(ids[cId]); const c = this.combatants.get(ids[cId]);
let id = c._id || c.id; let id = c._id || c.id;
let initBonus = c.actor ? c.actor.getInitiativeScore() : 0 let initBonus = c.actor ? c.actor.getInitiativeScore() : 0
let roll = new Roll("1d10 + "+initBonus).roll({ async: false}) let roll = await new Roll("1d10 + "+initBonus).roll()
await WastelandUtility.showDiceSoNice(roll, game.settings.get("core", "rollMode")) await WastelandUtility.showDiceSoNice(roll, game.settings.get("core", "rollMode"))
//console.log("Init bonus", initBonus, roll.total) //console.log("Init bonus", initBonus, roll.total)
await this.updateEmbeddedDocuments("Combatant", [ { _id: id, initiative: roll.total } ]); await this.updateEmbeddedDocuments("Combatant", [ { _id: id, initiative: roll.total } ]);

View File

@@ -1,7 +1,7 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
import { WastelandUtility } from "./wasteland-utility.js"; import { WastelandUtility } from "./wasteland-utility.js";
import { WastelandRollDialog } from "./wasteland-roll-dialog.js"; import { WastelandRollDialog } from "./applications/wasteland-roll-dialog.mjs";
/* -------------------------------------------- */ /* -------------------------------------------- */
export class WastelandCommands { export class WastelandCommands {

105
modules/wasteland-config.js Normal file
View File

@@ -0,0 +1,105 @@
export const WASTELAND_CONFIG = {
attributs: {
"adr": "Adresse",
"pui": "Puissance",
"cla": "Clairvoyance",
"pre": "Présence",
"tre": "Trempe"
},
cheminpouvoir : {
"force": "Chemin des Forces",
"forge": "Chemin des Forges",
"echo": "Chemin des Échos",
"reflet": "Chemin des Reflets",
"ame": "Chemin des Âmes",
"mort": "Chemin des Morts",
"vie": "Chemin de Vie",
"guerre": "Chemin des guerres",
"horizon": "Chemin vers l'horizon",
"voleurinvisible": "Chemin du Voleur invisible",
"nuit": "Chemin des Nuits",
"oiseaux": "Chemin des Oiseaux"
},
hubrisType: {
"mental": "Mental",
"physique": "Physique",
},
charmetype: {
tour: "Tour",
charme: "Charme",
},
dices: {
"1d4": "1d4",
"1d6": "1d6",
"1d8": "1d8",
"1d10": "1d10",
"1d12": "1d12",
"1d20": "1d20",
},
artifexType: {
"vapeur": "Vapeur",
"mecanique": "Mécanique",
"chimie": "Chimie",
"electricite": "Électricité",
"chimerie": "Chimérie",
},
typeArmeOptions: {
contact: "Arme de contact",
contactjet: "Arme de contact et de Jet",
jet: "Arme de Jet",
tir: "Arme de Tir",
special: "Spécial (capacité/don)"
},
attaques: {
assaut: "Assaut",
precise: "Attaque Précise",
feinte: "Feinte",
coupbas: "Coup Bas",
charger: "Charger",
contenir: "Contenir",
desarmer: "Désarmer"
},
couverts: {
aucun: { name: "Aucun", value: 0 },
rondache: { name: "Couvert Léger", value: -2 },
pavois: { name: "Couvert Moyen", value: -5 },
complet: { name: "Couvert Complet", value: -10 },
},
listePortees: {
"10": "Moins que Courte",
"15": "Courte ou plus",
"20": "Moyenne ou plus",
"25": "Longue ou plus"
},
difficulteOptions: {
"0": "Aucune/Inconnue",
"5": "Facile (5)",
"6": "(6)",
"7": "(7)",
"8": "(8)",
"9": "(9)",
"10": "Moyenne (10)",
"11": "(11)",
"12": "(12)",
"13": "(13)",
"14": "(14)",
"15": "Ardue (15)",
"16": "(16)",
"17": "(17)",
"18": "(18)",
"19": "(19)",
"20": "Hasardeuse (20)",
"21": "(21)",
"22": "(22)",
"23": "(23)",
"24": "(24)",
"25": "Insensée (25)",
"26": "(26)",
"27": "(27)",
"28": "(28)",
"29": "(29)",
"30": "Pure Folie (30)"
}
}

View File

@@ -1,179 +0,0 @@
import { WastelandUtility } from "./wasteland-utility.js";
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
*/
export class WastelandItemSheet extends ItemSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["fvtt-wasteland", "sheet", "item"],
template: "systems/fvtt-wasteland/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 = duplicate(this.object)
let itemData = objectData
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: WastelandUtility.getAttributs(),
data: itemData.system,
limited: this.object.limited,
options: this.options,
owner: this.document.isOwner,
description: await TextEditor.enrichHTML(this.object.system.description, {async: true}),
mr: (this.object.type == 'specialisation'),
isGM: game.user.isGM
}
if ( objectData.type == "don") {
formData.sacrifice = await TextEditor.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 = duplicate(WastelandUtility.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-Wasteland-rpg/templates/post-item.html', chatData).then(html => {
let chatOptions = WastelandUtility.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-prediction').change(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = duplicate(this.object.system.predilections)
pred[index].name = ev.currentTarget.value
this.object.update( { 'data.predilections': pred })
})
html.find('.delete-prediction').click(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = duplicate(this.object.system.predilections)
pred.splice(index,1)
this.object.update( { 'data.predilections': pred })
})
html.find('.use-prediction').change(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = duplicate(this.object.system.predilections)
pred[index].used = ev.currentTarget.checked
this.object.update( { 'data.predilections': pred })
})
html.find('#add-predilection').click(ev => {
let pred = duplicate(this.object.system.predilections)
pred.push( { name: "Nouvelle prédilection", used: false })
this.object.update( { 'data.predilections': pred })
})
// 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-wasteland/templates/item-${type}-sheet.html`;
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
return this.object.update(formData);
}
}

View File

@@ -12,8 +12,13 @@ export const defaultItemImg = {
pouvoir: "systems/fvtt-wasteland/assets/icons/power.webp", pouvoir: "systems/fvtt-wasteland/assets/icons/power.webp",
origine: "systems/fvtt-wasteland/assets/icons/origin.webp", origine: "systems/fvtt-wasteland/assets/icons/origin.webp",
protection: "systems/fvtt-wasteland/assets/icons/armor.webp", protection: "systems/fvtt-wasteland/assets/icons/armor.webp",
metier: "systems/fvtt-wasteland/assets/icons/work.webp",
artifex: "systems/fvtt-wasteland/assets/icons/artifact.webp", artifex: "systems/fvtt-wasteland/assets/icons/artifact.webp",
heritage: "systems/fvtt-wasteland/assets/icons/legacy.webp",
charme: "systems/fvtt-wasteland/assets/icons/charm.webp", charme: "systems/fvtt-wasteland/assets/icons/charm.webp",
peuple: "systems/fvtt-wasteland/assets/icons/people.webp",
don: "systems/fvtt-wasteland/assets/icons/don.webp",
hubris: "systems/fvtt-wasteland/assets/icons/hubris.webp",
} }
/** /**

View File

@@ -9,12 +9,16 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
// Import Modules // Import Modules
import { WastelandActor } from "./wasteland-actor.js"; import { WastelandActor } from "./wasteland-actor.js";
import { WastelandItemSheet } from "./wasteland-item-sheet.js";
import { WastelandActorSheet } from "./wasteland-actor-sheet.js";
//import { WastelandNPCSheet } from "./wasteland-npc-sheet.js";
import { WastelandUtility } from "./wasteland-utility.js"; import { WastelandUtility } from "./wasteland-utility.js";
import { WastelandCombat } from "./wasteland-combat.js"; import { WastelandCombat } from "./wasteland-combat.js";
import { WastelandItem } from "./wasteland-item.js"; import { WastelandItem } from "./wasteland-item.js";
import { WASTELAND_CONFIG } from "./wasteland-config.js";
// Import DataModels
import * as models from "./models/index.mjs";
// Import AppV2 Sheets
import * as sheets from "./applications/sheets/_module.mjs";
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Foundry VTT Initialization */ /* Foundry VTT Initialization */
@@ -29,7 +33,7 @@ Hooks.once("init", async function () {
WastelandUtility.preloadHandlebarsTemplates(); WastelandUtility.preloadHandlebarsTemplates();
/* -------------------------------------------- */ /* -------------------------------------------- */
// Set an initiative formula for the system // Set an initiative formula for the system
CONFIG.Combat.initiative = { CONFIG.Combat.initiative = {
formula: "1d6", formula: "1d6",
decimals: 1 decimals: 1
@@ -44,58 +48,123 @@ Hooks.once("init", async function () {
// Define custom Entity classes // Define custom Entity classes
CONFIG.Combat.documentClass = WastelandCombat CONFIG.Combat.documentClass = WastelandCombat
CONFIG.Actor.documentClass = WastelandActor CONFIG.Actor.documentClass = WastelandActor
CONFIG.Actor.dataModels = {
personnage: models.PersonnageDataModel,
creature: models.CreatureDataModel
}
CONFIG.Item.documentClass = WastelandItem CONFIG.Item.documentClass = WastelandItem
game.system.wasteland = { } CONFIG.Item.dataModels = {
arme: models.ArmeDataModel,
artifex: models.ArtifexDataModel,
bouclier: models.BouclierDataModel,
capacite: models.CapaciteDataModel,
charme: models.CharmeDataModel,
competence: models.CompetenceDataModel,
don: models.DonDataModel,
equipement: models.EquipementDataModel,
heritage: models.HeritageDataModel,
hubris: models.HubrisDataModel,
metier: models.MetierDataModel,
monnaie: models.MonnaieDataModel,
mutation: models.MutationDataModel,
origine: models.OrigineDataModel,
peuple: models.PeupleDataModel,
pouvoir: models.PouvoirDataModel,
protection: models.ProtectionDataModel
}
game.system.wasteland = {
config: WASTELAND_CONFIG,
models,
sheets
}
// Initialize dynamic config options that need to be available immediately
// Create option lists inline to ensure they're available
const listeNiveauSkill = {}
for (let i = 0; i <= 10; i++) {
listeNiveauSkill[`${i}`] = `${i}`
}
game.system.wasteland.config.listeNiveauSkill = listeNiveauSkill
const listeNiveauAttribut = {}
for (let i = 0; i <= 10; i++) {
listeNiveauAttribut[`${i}`] = `${i}`
}
game.system.wasteland.config.listeNiveauAttribut = listeNiveauAttribut
const listeNiveauCreature = {}
for (let i = 0; i <= 35; i++) {
listeNiveauCreature[`${i}`] = `${i}`
}
game.system.wasteland.config.listeNiveauCreature = listeNiveauCreature
const modificateurOptions = []
for (let i = -15; i <= 15; i++) {
modificateurOptions.push({ key: i, label: `${i >= 0 ? '+' : ''}${i}` })
}
game.system.wasteland.config.modificateurOptions = modificateurOptions
const pointsAmeOptions = {}
for (let i = 0; i <= 20; i++) {
pointsAmeOptions[`${i}`] = `${i}`
}
game.system.wasteland.config.pointsAmeOptions = pointsAmeOptions
/* -------------------------------------------- */ /* -------------------------------------------- */
// Register sheet application classes // Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("fvtt-wasteland", WastelandActorSheet, { types: ["personnage"], makeDefault: true }) // Register AppV2 Actor Sheets
//Actors.registerSheet("fvtt-wasteland", WastelandNPCSheet, { types: ["npc"], makeDefault: false }); foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
foundry.documents.collections.Actors.registerSheet("fvtt-wasteland", sheets.WastelandPersonnageSheet, { types: ["personnage"], makeDefault: true });
foundry.documents.collections.Actors.registerSheet("fvtt-wasteland", sheets.WastelandCreatureSheet, { types: ["creature"], makeDefault: true });
Items.unregisterSheet("core", ItemSheet); // Register AppV2 Item Sheets
Items.registerSheet("fvtt-wasteland", WastelandItemSheet, { makeDefault: true }) foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandArmeSheet, { types: ["arme"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandArtifexSheet, { types: ["artifex"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandBouclierSheet, { types: ["bouclier"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandCapaciteSheet, { types: ["capacite"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandCharmeSheet, { types: ["charme"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandCompetenceSheet, { types: ["competence"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandDonSheet, { types: ["don"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandEquipementSheet, { types: ["equipement"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandHeritageSheet, { types: ["heritage"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandHubrisSheet, { types: ["hubris"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandMetierSheet, { types: ["metier"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandMonnaieSheet, { types: ["monnaie"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandMutationSheet, { types: ["mutation"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandOrigineSheet, { types: ["origine"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandPeupleSheet, { types: ["peuple"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandPouvoirSheet, { types: ["pouvoir"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandProtectionSheet, { types: ["protection"], makeDefault: true });
WastelandUtility.init(); WastelandUtility.init();
}); });
/* -------------------------------------------- */ /* -------------------------------------------- */
function welcomeMessage() { async function welcomeMessage() {
const templateData = {};
const html = await foundry.applications.handlebars.renderTemplate("systems/fvtt-wasteland/templates/chat-welcome-message.hbs", templateData);
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-Wasteland"><span class="rdd-roll-part"> content: html
<strong>Bienvenue dans les Jeunes Royaumes de Wasteland !</strong> });
<p>Les livres de Wasteland sont nécessaires pour jouer : https://www.titam-france.fr</p>
<p>Wasteland est jeu de rôle publié par Titam France/Sombres projets, tout les droits leur appartiennent.</p>
<p>Système développé par LeRatierBretonnien, support sur le <a href="https://discord.gg/pPSDNJk">Discord FR de Foundry</a>.</p>
` });
} }
/* -------------------------------------------- */
// Register world usage statistics
function registerUsageCount( registerKey ) {
if ( game.user.isGM ) {
game.settings.register(registerKey, "world-key", {
name: "Unique world key",
scope: "world",
config: false,
default: "",
type: String
});
let worldKey = game.settings.get(registerKey, "world-key") /* -------------------------------------------- */
if ( worldKey == undefined || worldKey == "" ) { async function importDefaultScene() {
worldKey = randomID(32) let exists = game.scenes.find(j => j.name == "Accueil");
game.settings.set(registerKey, "world-key", worldKey ) if (!exists) {
} const scenes = await WastelandUtility.loadCompendium("fvtt-wasteland.scenes")
// Simple API counter let newDocuments = scenes.filter(i => i.name == "Accueil");
let regURL = `https://www.uberwald.me/fvtt_appcount/count.php?name="${registerKey}"&worldKey="${worldKey}"&version="${game.release.generation}.${game.release.build}"&system="${game.system.id}"&systemversion="${game.system.version}"` await game.scenes.documentClass.create(newDocuments);
//$.ajaxSetup({ game.scenes.find(i => i.name == "Accueil").activate();
//headers: { 'Access-Control-Allow-Origin': '*' }
//})
$.ajax(regURL)
} }
} }
@@ -105,6 +174,7 @@ function registerUsageCount( registerKey ) {
Hooks.once("ready", function () { Hooks.once("ready", function () {
WastelandUtility.ready(); WastelandUtility.ready();
// User warning // User warning
if (!game.user.isGM && game.user.character == undefined) { if (!game.user.isGM && game.user.character == undefined) {
ui.notifications.info("Attention ! Aucun personnage n'est relié au joueur !"); ui.notifications.info("Attention ! Aucun personnage n'est relié au joueur !");
@@ -113,9 +183,24 @@ Hooks.once("ready", function () {
user: game.user._id user: game.user._id
}); });
} }
if (!game.user.isGM && game.user.character && !game.user.character.prototypeToken.actorLink) {
registerUsageCount('fvtt-wasteland') ui.notifications.info("Le token de du joueur n'est pas connecté à l'acteur !");
ChatMessage.create({
content: "<b>ATTENTION</b> Le token du joueur " + game.user.name + " n'est pas connecté à l'acteur !",
user: game.user._id
});
}
import("https://www.uberwald.me/fvtt_appcount/count-class-ready.js").then(moduleCounter=>{
console.log("ClassCounter loaded", moduleCounter)
moduleCounter.ClassCounter.registerUsageCount()
}).catch(err=>
console.log("No stats available, giving up.")
)
welcomeMessage(); welcomeMessage();
importDefaultScene();
}); });
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -131,4 +216,3 @@ Hooks.on("chatMessage", (html, content, msg) => {
} }
return true; return true;
}); });

View File

@@ -1,79 +0,0 @@
import { WastelandUtility } from "./wasteland-utility.js";
export class WastelandRollDialog extends Dialog {
/* -------------------------------------------- */
static async create(actor, rollData ) {
let options = { classes: ["WastelandDialog"], width: 340, height: 'fit-content', 'z-index': 99999 };
let html = await renderTemplate('systems/fvtt-wasteland/templates/roll-dialog-generic.html', rollData);
return new WastelandRollDialog(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("1d10") }
},
rolld20: {
icon: '<i class="fas fa-check"></i>',
label: "Lancer 1d20",
callback: () => { this.roll("1d20") }
},
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
WastelandUtility.rollWasteland( 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(async (event) => {
this.rollData.difficulte = Number(event.currentTarget.value)
})
html.find('#attrKey').change(async (event) => {
this.rollData.attrKey = String(event.currentTarget.value)
})
html.find('#runemode').change(async (event) => {
this.rollData.runemode = String(event.currentTarget.value)
})
html.find('#runeame').change(async (event) => {
this.rollData.runeame = Number(event.currentTarget.value)
})
html.find('#doubleD20').change(async (event) => {
this.rollData.doubleD20 = event.currentTarget.checked
})
}
}

View File

@@ -2,6 +2,20 @@
import { WastelandCombat } from "./wasteland-combat.js"; import { WastelandCombat } from "./wasteland-combat.js";
import { WastelandCommands } from "./wasteland-commands.js"; import { WastelandCommands } from "./wasteland-commands.js";
/* -------------------------------------------- */
const __contrecouptCharme = {
1 : {name: "Effet chromatique", description: "le corps du kobold prend des teintes aussi étranges que voyantes. L'effet sestompe progressivement et 24 heures plus tard, le kobold retrouve ses couleurs dorigine." },
3 : {name: "Enivrement Kobold", description: "très excité par son premier tour, le kobold doit immédiatement faire un autre tour, pour lequel il emploiera un dé plus gros." },
5 : {name: "Mutisme superstitieux", description: "le kobold ne doit plus parler» pendant les prochaines 24 heures. S'il le fait malgré tout, les effets de son tour sarrêtent." },
7 : {name: "Agité!", description: "le kobold ne tient plus en place. Il ne peut se reposer pendant les prochaines 12 heures. Dès. que 12 heures se sont écoulées, il s'effondre comme une masse et dort 12 heures d'affilée dun sommeil enchanté dont rien ne pourra le réveiller." },
9 : {name: "Somnolence", description: "le kobold devient somnolent. Il narrive pas à se concentrer même sur une tâche simple, bäille sans arrêt, traîne les pieds et nagit plus que de mauvaise grâce. Cela dure jusquà ce qu'il ait dormi au moins 12 heures." },
11 : {name: "Manie incontrôlable", description: "le kobold est pris dune manie incontrôlable. Dès qu'il voit un chapeau rouge, il doit suivre son porteur. Il ne mangera que si son voisin de gauche mange aussi, etc. Cela dure pendant une jour- née puis leffet sinverse pendant une heure : il ne suivra jamais un chapeau rouge, ne mangera jamais si son voi- sin de gauche mange, etc. Le contrecoup prend alors fin." },
13 : {name: "Malédiction des Ternes", description: "le kobold perd cette qualité mystérieuse qui fait que les kobolds sont des kobolds et devient tout. Terne. Il perd 1d20 point(s) de Bonne Aventure (sil doit en perdre plus qu'il nen a, il tombe simplement à 0). Ces points perdus pourront cependant être regagnés normalement." },
15 : {name: "La petite Mort", description: "le kobold s'endort pour 1420 heures. Rien ni personne ne pourra le tirer de ce sommeil enchanté avant que ce contrecoup ne prenne fin." },
17 : {name: "Angoisse cauchemardesque", description: "le kobold a une brève vision de pure horreur. Il perd 1420 points de Psyché {s'il doit en perdre plus qu'il nen a, il tombe à 0)." },
19 : {name: "Anémie Kobold", description: "le kobold se met à saigner du nez, des oreilles et même dautres endroits. Il perd 1420 point(s) de Santé." }
}
/* -------------------------------------------- */ /* -------------------------------------------- */
export class WastelandUtility { export class WastelandUtility {
@@ -9,14 +23,12 @@ export class WastelandUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static async init() { static async init() {
Hooks.on('renderChatLog', (log, html, data) => WastelandUtility.chatListeners(html)) Hooks.on('renderChatLog', (log, html, data) => WastelandUtility.chatListeners(html))
Hooks.on("getChatLogEntryContext", (html, options) => WastelandUtility.chatRollMenu(html, options)) Hooks.on('renderChatMessageHTML', (message, html, data) => WastelandUtility.chatListeners(html))
Hooks.on("getChatMessageContextOptions", (html, options) => WastelandUtility.chatRollMenu(html, options))
Hooks.on("getCombatTrackerEntryContext", (html, options) => { Hooks.on("getCombatTrackerEntryContext", (html, options) => {
WastelandUtility.pushInitiativeOptions(html, options); WastelandUtility.pushInitiativeOptions(html, options);
}) })
Hooks.on("dropCanvasData", (canvas, data) => {
WastelandUtility.dropItemOnToken(canvas, data)
});
this.rollDataStore = {} this.rollDataStore = {}
this.defenderStore = {} this.defenderStore = {}
@@ -100,6 +112,25 @@ export class WastelandUtility {
static async ready() { static async ready() {
const skills = await WastelandUtility.loadCompendium("fvtt-wasteland.skills") const skills = await WastelandUtility.loadCompendium("fvtt-wasteland.skills")
this.skills = skills.map(i => i.toObject()) this.skills = skills.map(i => i.toObject())
// Note: listeNiveauSkill, listeNiveauCreature, etc. are now created in init hook
// to be available immediately when sheets are opened
}
/* -------------------------------------------- */
static createDirectOptionList(min, max) {
let options = {};
for (let i = min; i <= max; i++) {
options[`${i}`] = `${i}`;
}
return options;
}
static createArrayOptionList(min, max) {
let options = [];
for (let i = min; i <= max; i++) {
options.push({key:`${i}`, label:`${i}`});
}
return options;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -121,15 +152,41 @@ export class WastelandUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
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).data("predilection-index")
let messageId = WastelandUtility.findChatMessageId(event.currentTarget) let messageId = WastelandUtility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId) let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "wasteland-roll") let rollData = message.getFlag("world", "wasteland-roll")
let actor = WastelandUtility.getActorFromRollData(rollData) let actor = WastelandUtility.getActorFromRollData(rollData)
// Sauvegarder le résultat précédent
let previousResult = {
diceResult: rollData.diceResult,
finalResult: rollData.finalResult,
diceFormula: rollData.diceFormula
}
// Récupérer la prédilection utilisée
let predilectionUsed = foundry.utils.duplicate(rollData.competence.system.predilections[predIdx])
// Marquer la prédilection comme utilisée
await actor.setPredilectionUsed(rollData.competence._id, predIdx) await actor.setPredilectionUsed(rollData.competence._id, predIdx)
rollData.competence = duplicate(actor.getCompetence(rollData.competence._id))
await WastelandUtility.rollWasteland(rollData) // Mettre à jour la compétence dans rollData
rollData.competence = foundry.utils.duplicate(actor.getCompetence(rollData.competence._id))
// Lancer le reroll avec prédilection
await WastelandUtility.rollWastelandPredilection(rollData, predilectionUsed, previousResult)
})
$(html).on("click", '.arme-roll-degats', async event => {
let messageId = WastelandUtility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "wasteland-roll")
if (rollData && rollData.arme) {
let actor = WastelandUtility.getActorFromRollData(rollData)
await actor.rollArmeDegats(rollData.arme._id)
}
}) })
} }
@@ -137,11 +194,12 @@ export class WastelandUtility {
static async preloadHandlebarsTemplates() { static async preloadHandlebarsTemplates() {
const templatePaths = [ const templatePaths = [
'systems/fvtt-wasteland/templates/editor-notes-gm.html', 'systems/fvtt-wasteland/templates/editor-notes-gm.hbs',
'systems/fvtt-wasteland/templates/partial-item-description.html', 'systems/fvtt-wasteland/templates/partial-item-description.hbs',
'systems/fvtt-wasteland/templates/partial-list-niveau.html' 'systems/fvtt-wasteland/templates/partial-item-header.hbs',
'systems/fvtt-wasteland/templates/partial-item-nav.hbs'
] ]
return loadTemplates(templatePaths); return foundry.applications.handlebars.loadTemplates(templatePaths);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -173,15 +231,6 @@ export class WastelandUtility {
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 = ""
@@ -206,14 +255,14 @@ export class WastelandUtility {
let id = rollData.rollId; let id = rollData.rollId;
let oldRollData = this.rollDataStore[id] || {}; let oldRollData = this.rollDataStore[id] || {};
let newRollData = mergeObject(oldRollData, rollData); let newRollData = foundry.utils.mergeObject(oldRollData, rollData);
this.rollDataStore[id] = newRollData; this.rollDataStore[id] = newRollData;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static saveRollData(rollData) { static saveRollData(rollData) {
game.socket.emit("system.fvtt-wasteland", { game.socket.emit("system.fvtt-wasteland", {
name: "msg_update_roll", data: rollData name: "msg_update_roll", data: rollData
}); // Notify all other clients of the roll }); // Notify all other clients of the roll
this.updateRollData(rollData); this.updateRollData(rollData);
} }
@@ -278,26 +327,51 @@ export class WastelandUtility {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static computeResult(rollData) { static async computeResult(rollData, actor) {
if (rollData.mainDice == "1d20") { if (rollData.charme) {
let diceValue = rollData.roll.terms[0].results[0].result let resultIndex = false
diceValue *= (rollData.doubleD20) ? 2 : 1 let resTab = foundry.utils.duplicate(rollData.charme.system.resultats)
//console.log("PAIR/IMP", diceValue) for(let id in resTab) {
if (diceValue % 2 == 1) { let res = resTab[id]
//console.log("PAIR/IMP2", diceValue) if (!resultIndex && rollData.finalResult >= res.value) {
rollData.finalResult -= rollData.roll.terms[0].results[0].result // Substract value resultIndex = id;
if (diceValue == 1 || diceValue == 11) { }
rollData.isDramatique = true }
rollData.isSuccess = false if (resultIndex) {
rollData.charmeDuree = rollData.charme.system.resultats[resultIndex].description
}
let effectRoll = await new Roll(rollData.charmeDice).roll()
if (rollData.charme.system.charmetype == "tour") {
rollData.contrecoupResult = effectRoll.total
if (rollData.contrecoupResult % 2 == 1) {
rollData.contrecoup = __contrecouptCharme[rollData.contrecoupResult]
}
}
if (rollData.charme.system.charmetype == "charme") {
rollData.charmeSante = effectRoll.total
actor.incDecSante(rollData.charmeSante)
}
} else {
if (rollData.mainDice == "1d20") {
let diceValue = rollData.roll.terms[0].results[0].result
diceValue *= (rollData.doubleD20) ? 2 : 1
//console.log("PAIR/IMP", diceValue)
if (diceValue % 2 == 1) {
//console.log("PAIR/IMP2", diceValue)
rollData.finalResult -= rollData.roll.terms[0].results[0].result // Substract value
if (diceValue == 1 || diceValue == 11) {
rollData.isDramatique = true
rollData.isSuccess = false
}
} }
} }
}
//console.log("Result : ", rollData) //console.log("Result : ", rollData)
if (rollData.difficulte > 0 && !rollData.isDramatique) { if (rollData.difficulte > 0 && !rollData.isDramatique) {
rollData.isSuccess = (rollData.finalResult >= rollData.difficulte) rollData.isSuccess = (rollData.finalResult >= rollData.difficulte)
rollData.isHeroique = ((rollData.finalResult - rollData.difficulte) >= 10) rollData.isHeroique = ((rollData.finalResult - rollData.difficulte) >= 10)
rollData.isDramatique = ((rollData.finalResult - rollData.difficulte) <= -10) rollData.isDramatique = ((rollData.finalResult - rollData.difficulte) <= -10)
}
} }
} }
@@ -308,60 +382,90 @@ export class WastelandUtility {
if (rollData.attrKey == "tochoose") { // No attr selected, force address if (rollData.attrKey == "tochoose") { // No attr selected, force address
rollData.attrKey = "adr" rollData.attrKey = "adr"
} }
if (!rollData.attr) { if (!rollData.attr && actor && actor.system.attributs && actor.system.attributs[rollData.attrKey]) {
rollData.actionImg = "systems/fvtt-wasteland/assets/icons/" + actor.system.attributs[rollData.attrKey].labelnorm + ".webp" rollData.attr = foundry.utils.duplicate(actor.system.attributs[rollData.attrKey])
rollData.attr = duplicate(actor.system.attributs[rollData.attrKey]) if (rollData.attr.labelnorm) {
} rollData.actionImg = "systems/fvtt-wasteland/assets/icons/" + rollData.attr.labelnorm + ".webp"
rollData.diceFormula = rollData.mainDice
if (rollData.doubleD20) { // Multiply result !
rollData.diceFormula += "*2"
if (!rollData.isReroll) {
actor.changeEclat(-1)
} }
} }
//console.log("BEFORE COMP", rollData)
if (rollData.competence) { if (rollData.charme) {
rollData.predilections = duplicate(rollData.competence.system.predilections.filter(pred => !pred.used) || []) rollData.diceFormula = rollData.charmeDice
let compmod = (rollData.competence.system.niveau == 0) ? -3 : 0
rollData.diceFormula += `+${rollData.attr.value}+${rollData.competence.system.niveau}+${rollData.modificateur}+${compmod}`
} else { } else {
rollData.diceFormula += `+${rollData.attr.value}*2+${rollData.modificateur}` rollData.diceFormula = rollData.mainDice
// Double D20 ne s'applique que si on lance un D20, pas un D10
if (rollData.doubleD20 && rollData.mainDice === "1d20") {
rollData.diceFormula += "*2"
if (!rollData.isReroll) {
actor.changeEclat(-1)
}
}
} }
//console.log("BEFORE COMP", rollData)
if (rollData.competence) {
rollData.predilections = foundry.utils.duplicate(rollData.competence.system.predilections.filter(pred => !pred.used) || [])
let compmod = (rollData.competence.system.niveau == 0) ? -3 : 0
let attrValue = rollData.attr?.value || 0
rollData.diceFormula += `+${attrValue}+${rollData.competence.system.niveau}+${rollData.modificateur}+${compmod}`
} else {
let attrValue = rollData.attr?.value || 0
rollData.diceFormula += `+${attrValue}*2+${rollData.modificateur}`
}
if (rollData.arme && rollData.arme.type == "arme") { if (rollData.arme && rollData.arme.type == "arme") {
rollData.diceFormula += `+${rollData.arme.system.bonusmaniementoff}` rollData.diceFormula += `+${rollData.arme.system.bonusmaniementoff}`
} }
if (rollData.rune) { // Apply desavantages (tactical advantages)
rollData.runeduree = Math.ceil((rollData.runeame + 3) / 3) let desavantagesBonus = 0
if (rollData.runemode == "inscrire") { if (rollData.desavantages) {
rollData.runeduree *= 2 for (let desavantage in rollData.desavantages) {
if (rollData.desavantages[desavantage]) {
desavantagesBonus += 5
}
} }
if (rollData.runemode == "prononcer") { desavantagesBonus = Math.min(15, desavantagesBonus)
rollData.runeduree = 1 if (desavantagesBonus > 0) {
rollData.diceFormula += `+${desavantagesBonus}`
} }
} }
let myRoll = new Roll(rollData.diceFormula).roll({ async: false }) // Monté ?
if (rollData.isMonte) {
rollData.diceFormula += "+5"
}
// Specific modifiers for ranged combat
if (rollData.arme?.system?.isDistance) {
if (rollData.visee) {
rollData.diceFormula += "+5"
}
if (rollData.cibleconsciente && rollData.defender) {
rollData.diceFormula += `-${rollData.defender.system.attributs.adr.value}`
}
if (rollData.ciblecourt) {
if (rollData.difficulte <= 15) { // Portée courte ou moins
rollData.diceFormula += `-5`
} else {
rollData.diceFormula += `-10`
}
}
if (rollData.typeCouvert && rollData.typeCouvert != "aucun" && rollData.config.couverts[rollData.typeCouvert]) {
rollData.diceFormula += `+${rollData.config.couverts[rollData.typeCouvert].value}`
}
}
let myRoll = await new Roll(rollData.diceFormula).roll()
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode")) await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"))
rollData.roll = duplicate(myRoll) rollData.roll = foundry.utils.duplicate(myRoll)
rollData.diceResult = myRoll.terms[0].results[0].result rollData.diceResult = myRoll.terms[0].results[0].result
console.log(">>>> ", myRoll) console.log(">>>> ", myRoll)
rollData.finalResult = myRoll.total rollData.finalResult = myRoll.total
this.computeResult(rollData) await this.computeResult(rollData, actor)
if (rollData.rune) {
let subAme = rollData.runeame
if (rollData.isEchec && !rollData.isDramatique) {
subAme = Math.ceil((subAme + 1) / 2)
}
actor.subPointsAme(rollData.runemode, subAme)
}
this.createChatWithRollMode(rollData.alias, { this.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-wasteland/templates/chat-generic-result.html`, rollData) content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-wasteland/templates/chat-generic-result-v2.hbs`, rollData)
}, rollData) }, rollData)
} }
@@ -370,20 +474,146 @@ export class WastelandUtility {
static async bonusRollWasteland(rollData) { static async bonusRollWasteland(rollData) {
rollData.bonusFormula = rollData.addedBonus rollData.bonusFormula = rollData.addedBonus
let bonusRoll = new Roll(rollData.bonusFormula).roll({ async: false }) 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 = duplicate(bonusRoll) rollData.bonusRoll = foundry.utils.duplicate(bonusRoll)
rollData.finalResult += rollData.bonusRoll.total rollData.finalResult += rollData.bonusRoll.total
this.computeResult(rollData) await this.computeResult(rollData)
this.createChatWithRollMode(rollData.alias, { this.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-wasteland/templates/chat-generic-result.html`, rollData) content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-wasteland/templates/chat-generic-result-v2.hbs`, rollData)
}, rollData) }, rollData)
} }
/* -------------------------------------------- */
static async rollWastelandPredilection(rollData, predilectionUsed, previousResult) {
// Sauvegarder le premier résultat
rollData.firstRoll = {
diceResult: previousResult.diceResult,
finalResult: previousResult.finalResult,
diceFormula: previousResult.diceFormula
}
rollData.predilectionUsed = predilectionUsed
rollData.isPredilectionReroll = true
let actor = WastelandUtility.getActorFromRollData(rollData)
if (rollData.attrKey == "tochoose") {
rollData.attrKey = "adr"
}
if (!rollData.attr && actor && actor.system.attributs && actor.system.attributs[rollData.attrKey]) {
rollData.attr = foundry.utils.duplicate(actor.system.attributs[rollData.attrKey])
if (rollData.attr.labelnorm) {
rollData.actionImg = "systems/fvtt-wasteland/assets/icons/" + rollData.attr.labelnorm + ".webp"
}
}
if (rollData.charme) {
rollData.diceFormula = rollData.charmeDice
} else {
rollData.diceFormula = rollData.mainDice
// Double D20 ne s'applique que si on lance un D20, pas un D10
if (rollData.doubleD20 && rollData.mainDice === "1d20") {
rollData.diceFormula += "*2"
}
}
if (rollData.competence) {
rollData.predilections = foundry.utils.duplicate(rollData.competence.system.predilections.filter(pred => !pred.used) || [])
let compmod = (rollData.competence.system.niveau == 0) ? -3 : 0
let attrValue = rollData.attr?.value || 0
rollData.diceFormula += `+${attrValue}+${rollData.competence.system.niveau}+${rollData.modificateur}+${compmod}`
} else {
let attrValue = rollData.attr?.value || 0
rollData.diceFormula += `+${attrValue}*2+${rollData.modificateur}`
}
if (rollData.arme && rollData.arme.type == "arme") {
rollData.diceFormula += `+${rollData.arme.system.bonusmaniementoff}`
}
// Apply desavantages (tactical advantages)
if (rollData.desavantages) {
let totalDesavantage = 0
for (let key in rollData.desavantages) {
if (rollData.desavantages[key]) {
totalDesavantage += 5
}
}
totalDesavantage = Math.min(totalDesavantage, 15)
if (totalDesavantage > 0) {
rollData.diceFormula += `+${totalDesavantage}`
}
}
// Apply mounted bonus
if (rollData.isMonte) {
rollData.diceFormula += `+5`
}
// Apply ranged combat modifiers
if (rollData.arme && rollData.arme.system && rollData.arme.system.isDistance) {
if (rollData.visee) {
rollData.diceFormula += `+5`
}
if (rollData.cibleconsciente && rollData.defender) {
let adrMalus = -(rollData.defender.system?.attributs?.adr?.value || 0)
rollData.diceFormula += `${adrMalus}`
}
if (rollData.ciblecourt) {
if (rollData.portee === "portee-courte" || rollData.portee === "portee-moyenne") {
rollData.diceFormula += `-5`
} else {
rollData.diceFormula += `-10`
}
}
if (rollData.couvert && rollData.typeCouvertValue) {
rollData.diceFormula += `${rollData.typeCouvertValue}`
}
}
// Nouveau jet de dé
let myRoll = await new Roll(rollData.diceFormula).roll()
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"))
rollData.roll = foundry.utils.duplicate(myRoll)
rollData.diceResult = myRoll.total
rollData.bonusFormula = "0"
if (rollData.charme && rollData.charme.system.bonusmajeur > 0) {
rollData.bonusFormula = `${rollData.charme.system.bonusmajeur}d6`
rollData.textBonus = "Bonus de Charme"
}
rollData.finalResult = rollData.diceResult
let bonusRoll = await new Roll(rollData.bonusFormula).roll()
await this.showDiceSoNice(bonusRoll, game.settings.get("core", "rollMode"))
rollData.bonusRoll = foundry.utils.duplicate(bonusRoll)
rollData.finalResult += rollData.bonusRoll.total
// Comparer avec le premier jet et garder le meilleur
rollData.secondRoll = {
diceResult: rollData.diceResult,
finalResult: rollData.finalResult
}
if (rollData.firstRoll.finalResult > rollData.finalResult) {
// Garder le premier résultat
rollData.diceResult = rollData.firstRoll.diceResult
rollData.finalResult = rollData.firstRoll.finalResult
rollData.keptRoll = "first"
} else {
// Garder le second résultat
rollData.keptRoll = "second"
}
await this.computeResult(rollData)
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-wasteland/templates/chat-generic-result-v2.hbs`, rollData)
}, rollData)
}
/* -------------------------------------------- */ /* -------------------------------------------- */
static getUsers(filter) { static getUsers(filter) {
return game.users.filter(filter).map(user => user.data._id); return game.users.filter(filter).map(user => user.data._id);
@@ -406,7 +636,7 @@ export class WastelandUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static blindMessageToGM(chatOptions) { static blindMessageToGM(chatOptions) {
let chatGM = duplicate(chatOptions); let chatGM = foundry.utils.duplicate(chatOptions);
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);
@@ -468,12 +698,13 @@ export class WastelandUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static getBasicRollData() { static getBasicRollData() {
let rollData = { let rollData = {
rollId: randomID(16), rollId: foundry.utils.randomID(16),
rollMode: game.settings.get("core", "rollMode"), rollMode: game.settings.get("core", "rollMode"),
modificateursOptions: this.getModificateurOptions(), modificateursOptions: this.getModificateurOptions(),
pointAmeOptions: this.getPointAmeOptions(), pointAmeOptions: this.getPointAmeOptions(),
difficulte: 0, difficulte: 0,
modificateur: 0, modificateur: 0,
config: game.system.wasteland.config,
} }
WastelandUtility.updateWithTarget(rollData) WastelandUtility.updateWithTarget(rollData)
return rollData return rollData
@@ -485,6 +716,7 @@ export class WastelandUtility {
if (target) { if (target) {
rollData.defenderTokenId = target.id rollData.defenderTokenId = target.id
let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor
rollData.defender = defender.toObject() // For template access
rollData.armeDefense = defender.getBestDefenseValue() rollData.armeDefense = defender.getBestDefenseValue()
if (rollData.armeDefense) { if (rollData.armeDefense) {
rollData.difficulte = rollData.armeDefense.system.totalDefensif rollData.difficulte = rollData.armeDefense.system.totalDefensif
@@ -501,7 +733,7 @@ export class WastelandUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static applyBonneAventureRoll(li, changed, addedBonus) { static applyBonneAventureRoll(li, changed, addedBonus) {
let msgId = li.data("message-id") let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId) let msg = game.messages.get(msgId)
if (msg) { if (msg) {
let rollData = msg.getFlag("world", "wasteland-roll") let rollData = msg.getFlag("world", "wasteland-roll")
@@ -520,7 +752,7 @@ export class WastelandUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static applyEclatRoll(li, changed, addedBonus) { static applyEclatRoll(li, changed, addedBonus) {
let msgId = li.data("message-id") let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId) let msg = game.messages.get(msgId)
if (msg) { if (msg) {
let rollData = msg.getFlag("world", "wasteland-roll") let rollData = msg.getFlag("world", "wasteland-roll")
@@ -533,11 +765,90 @@ export class WastelandUtility {
} }
} }
/* -------------------------------------------- */
static cancelBlessure(li) {
let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId)
if (msg) {
let rollData = msg.getFlag("world", "wasteland-roll")
let actor = WastelandUtility.getActorFromRollData(rollData)
// Vérifier que l'acteur a au moins 1 point d'éclat
if (actor.getEclat() < 1) {
ui.notifications.warn("Pas assez de points d'Éclat")
return
}
// Déduire 1 point d'éclat
actor.changeEclat(-1)
// Annuler la dernière blessure (incrémenter la santé)
let currentSante = actor.system.sante.value
let maxSante = actor.system.sante.max
if (currentSante < maxSante) {
actor.update({ 'system.sante.value': currentSante + 1 })
ui.notifications.info("Blessure annulée - 1 Point d'Éclat dépensé")
} else {
ui.notifications.warn("Vous êtes déjà en pleine santé")
// Rembourser le point d'éclat
actor.changeEclat(1)
}
}
}
/* -------------------------------------------- */
static reloadBA(li) {
let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId)
if (msg) {
let rollData = msg.getFlag("world", "wasteland-roll")
let actor = WastelandUtility.getActorFromRollData(rollData)
// Vérifier que l'acteur a au moins 1 point d'éclat
if (actor.getEclat() < 1) {
ui.notifications.warn("Pas assez de points d'Éclat")
return
}
// Déduire 1 point d'éclat
actor.changeEclat(-1)
// Recharger les points de Bonne Aventure au maximum
let maxBA = actor.system.bonneaventure.base
actor.update({ 'system.bonneaventure.actuelle': maxBA })
ui.notifications.info(`Points de Bonne Aventure rechargés à ${maxBA} - 1 Point d'Éclat dépensé`)
}
}
/* -------------------------------------------- */
static incDecSante(li, value, costBA) {
let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId)
if (msg) {
let rollData = msg.getFlag("world", "wasteland-roll")
let actor = WastelandUtility.getActorFromRollData(rollData)
// Vérifier que l'acteur a assez de points de Bonne Aventure
if (actor.getBonneAventure() < costBA) {
ui.notifications.warn("Pas assez de points de Bonne Aventure")
return
}
// Déduire les points de Bonne Aventure
actor.changeBonneAventure(-costBA)
// Incrémenter la santé
actor.incDecSante(value)
ui.notifications.info(`+${value} Point(s) de Santé - ${costBA} Point(s) de Bonne Aventure dépensé(s)`)
}
}
/* -------------------------------------------- */ /* -------------------------------------------- */
static chatRollMenu(html, options) { static chatRollMenu(html, options) {
let canApply = li => canvas.tokens.controlled.length && li.find(".wasteland-roll").length let canApply = li => canvas.tokens.controlled.length && li.find(".wasteland-roll").length
let hasBA = function (li) { let hasBA = function (li) {
let message = game.messages.get(li.attr("data-message-id")) let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll") let rollData = message.getFlag("world", "wasteland-roll")
if (rollData?.actorId) { if (rollData?.actorId) {
let actor = game.actors.get(rollData.actorId) let actor = game.actors.get(rollData.actorId)
@@ -547,7 +858,7 @@ export class WastelandUtility {
return false return false
} }
let hasBA2 = function (li) { let hasBA2 = function (li) {
let message = game.messages.get(li.attr("data-message-id")) let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll") let rollData = message.getFlag("world", "wasteland-roll")
if (rollData?.actorId) { if (rollData?.actorId) {
let actor = game.actors.get(rollData.actorId) let actor = game.actors.get(rollData.actorId)
@@ -557,7 +868,7 @@ export class WastelandUtility {
return false return false
} }
let hasBA3 = function (li) { let hasBA3 = function (li) {
let message = game.messages.get(li.attr("data-message-id")) let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll") let rollData = message.getFlag("world", "wasteland-roll")
if (rollData?.actorId) { if (rollData?.actorId) {
let actor = game.actors.get(rollData.actorId) let actor = game.actors.get(rollData.actorId)
@@ -567,7 +878,7 @@ export class WastelandUtility {
return false return false
} }
let hasPE = function (li) { let hasPE = function (li) {
let message = game.messages.get(li.attr("data-message-id")) let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll") let rollData = message.getFlag("world", "wasteland-roll")
if (rollData?.actorId) { if (rollData?.actorId) {
let actor = game.actors.get(rollData.actorId) let actor = game.actors.get(rollData.actorId)
@@ -577,7 +888,7 @@ export class WastelandUtility {
return false return false
} }
let hasPredilection = function (li) { let hasPredilection = function (li) {
let message = game.messages.get(li.attr("data-message-id")) let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll") let rollData = message.getFlag("world", "wasteland-roll")
if (rollData.competence) { if (rollData.competence) {
let nbPred = rollData.competence.data.predilections.filter(pred => !pred.used).length let nbPred = rollData.competence.data.predilections.filter(pred => !pred.used).length
@@ -586,7 +897,7 @@ export class WastelandUtility {
return false return false
} }
let canCompetenceDouble = function (li) { let canCompetenceDouble = function (li) {
let message = game.messages.get(li.attr("data-message-id")) let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll") let rollData = message.getFlag("world", "wasteland-roll")
if (rollData.competence) { if (rollData.competence) {
return rollData.competence.data.doublebonus return rollData.competence.data.doublebonus
@@ -606,7 +917,7 @@ export class WastelandUtility {
name: "Gain de 1 Point de Santé / 24 heure (1 point de Bonne Aventure)", name: "Gain de 1 Point de Santé / 24 heure (1 point de Bonne Aventure)",
icon: "<i class='fas fa-user-plus'></i>", icon: "<i class='fas fa-user-plus'></i>",
condition: canApply && hasBA, condition: canApply && hasBA,
callback: li => WastelandUtility.incDecSante(1) callback: li => WastelandUtility.incDecSante(li, 1, 1)
} }
) )
options.push( options.push(
@@ -614,7 +925,7 @@ export class WastelandUtility {
name: "Gain de 2 Points de Santé / 24 heure (2 points de Bonne Aventure)", name: "Gain de 2 Points de Santé / 24 heure (2 points de Bonne Aventure)",
icon: "<i class='fas fa-user-plus'></i>", icon: "<i class='fas fa-user-plus'></i>",
condition: canApply && hasBA2, condition: canApply && hasBA2,
callback: li => WastelandUtility.incDecSante(2) callback: li => WastelandUtility.incDecSante(li, 2, 2)
} }
) )
options.push( options.push(
@@ -670,30 +981,24 @@ export class WastelandUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static async confirmDelete(actorSheet, li) { static async confirmDelete(actorSheet, li) {
let itemId = li.data("item-id"); const itemId = li.dataset.itemId
let msgTxt = "<p>Are you sure to remove this Item ?"; if (!itemId) return
let buttons = {
delete: { const item = actorSheet.document.items.get(itemId)
icon: '<i class="fas fa-check"></i>', if (!item) return
label: "Yes, remove it",
callback: () => { const confirmed = await foundry.applications.api.DialogV2.confirm({
actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]); window: { title: "Confirmer la suppression" },
li.slideUp(200, () => actorSheet.render(false)); content: `<p>Êtes-vous sûr de vouloir supprimer <strong>${item.name}</strong> ?</p>`,
} rejectClose: false,
}, modal: true,
cancel: { yes: { label: "Supprimer", icon: "fa-trash" },
icon: '<i class="fas fa-times"></i>', no: { label: "Annuler", icon: "fa-times" }
label: "Cancel" })
}
if (confirmed) {
await actorSheet.document.deleteEmbeddedDocuments("Item", [itemId])
} }
msgTxt += "</p>";
let d = new Dialog({
title: "Confirm removal",
content: msgTxt,
buttons: buttons,
default: "cancel"
});
d.render(true);
} }
} }

1
node_modules/.bin/acorn generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../acorn/bin/acorn

1
node_modules/.bin/atob generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../atob/bin/atob.js

1
node_modules/.bin/color-support generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../color-support/bin.js

1
node_modules/.bin/errno generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../errno/cli.js

1
node_modules/.bin/gulp generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../gulp/bin/gulp.js

1
node_modules/.bin/image-size generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../image-size/bin/image-size.js

1
node_modules/.bin/lessc generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../less/bin/lessc

1
node_modules/.bin/mime generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../mime/cli.js

1
node_modules/.bin/needle generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../needle/bin/needle

1
node_modules/.bin/resolve generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../resolve/bin/resolve

1
node_modules/.bin/semver generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../semver/bin/semver

1
node_modules/.bin/which generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../which/bin/which

4965
node_modules/.package-lock.json generated vendored Normal file

File diff suppressed because it is too large Load Diff

21
node_modules/@gulp-sourcemaps/identity-map/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Blaine Bublitz <blaine.bublitz@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

40
node_modules/@gulp-sourcemaps/identity-map/README.md generated vendored Normal file
View File

@@ -0,0 +1,40 @@
# @gulp-sourcemaps/identity-map
[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![AppVeyor Build Status][appveyor-image]][appveyor-url] [![Coveralls Status][coveralls-image]][coveralls-url]
Gulp plugin for generating an identity sourcemap for a file.
## Example
```js
var identityMap = require('@gulp-sourcemaps/identity-map');
gulp.src(...)
.pipe(sourcemaps.init())
.pipe(identityMap()) // .js and .css files will get a generated sourcemap
.pipe(sourcemaps.write())
.pipe(gulp.dest(...))
```
## API
### `identityMap()`
Returns an `objectMode` Transform stream that processes each file with a `.sourceMap` property and buffered contents. A sourcemap is generated and attached for each `.js` and `.css` file.
## License
MIT
[downloads-image]: http://img.shields.io/npm/dm/@gulp-sourcemaps/identity-map.svg
[npm-url]: https://npmjs.org/package/@gulp-sourcemaps/identity-map
[npm-image]: http://img.shields.io/npm/v/@gulp-sourcemaps/identity-map.svg
[travis-url]: https://travis-ci.org/gulp-sourcemaps/identity-map
[travis-image]: http://img.shields.io/travis/gulp-sourcemaps/identity-map.svg?label=travis-ci
[appveyor-url]: https://ci.appveyor.com/project/phated/identity-map
[appveyor-image]: https://img.shields.io/appveyor/ci/phated/identity-map.svg?label=appveyor
[coveralls-url]: https://coveralls.io/r/gulp-sourcemaps/identity-map
[coveralls-image]: http://img.shields.io/coveralls/gulp-sourcemaps/identity-map.svg

35
node_modules/@gulp-sourcemaps/identity-map/index.js generated vendored Normal file
View File

@@ -0,0 +1,35 @@
'use strict';
var through = require('through2');
var normalizePath = require('normalize-path');
var generate = require('./lib/generate');
function identityMap() {
function transform(file, _, cb) {
if (!file.sourceMap || !file.isBuffer()) {
return cb(null, file);
}
var sourcePath = normalizePath(file.relative);
var contents = file.contents.toString();
switch (file.extname) {
case '.js': {
file.sourceMap = generate.js(sourcePath, contents);
break;
}
case '.css': {
file.sourceMap = generate.css(sourcePath, contents);
break;
}
}
cb(null, file);
}
return through.obj(transform);
}
module.exports = identityMap;

View File

@@ -0,0 +1,53 @@
'use strict';
var acorn = require('acorn');
var postcss = require('postcss');
var SourceMapGenerator = require('source-map').SourceMapGenerator;
function generateJs(sourcePath, fileContent) {
var generator = new SourceMapGenerator({ file: sourcePath });
var tokenizer = acorn.tokenizer(fileContent, {
allowHashBang: true,
locations: true,
});
/* eslint no-constant-condition: 0 */
while (true) {
var token = tokenizer.getToken();
if (token.type.label === 'eof') {
break;
}
var mapping = {
original: token.loc.start,
generated: token.loc.start,
source: sourcePath,
};
if (token.type.label === 'name') {
mapping.name = token.value;
}
generator.addMapping(mapping);
}
generator.setSourceContent(sourcePath, fileContent);
return generator.toJSON();
}
var postcssSourceMapOptions = {
inline: false,
prev: false,
sourcesContent: true,
annotation: false,
};
function generateCss(sourcePath, fileContent) {
var root = postcss.parse(fileContent, { from: sourcePath });
var result = root.toResult({ to: sourcePath, map: postcssSourceMapOptions });
return result.map.toJSON();
}
module.exports = {
js: generateJs,
css: generateCss,
};

View File

@@ -0,0 +1,9 @@
# The MIT License (MIT)
**Copyright (c) Rod Vagg (the "Original Author") and additional contributors**
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,134 @@
# through2
[![NPM](https://nodei.co/npm/through2.png?downloads&downloadRank)](https://nodei.co/npm/through2/)
**A tiny wrapper around Node.js streams.Transform (Streams2/3) to avoid explicit subclassing noise**
Inspired by [Dominic Tarr](https://github.com/dominictarr)'s [through](https://github.com/dominictarr/through) in that it's so much easier to make a stream out of a function than it is to set up the prototype chain properly: `through(function (chunk) { ... })`.
***Note: Users of Node.js 0.10 and 0.12 should install `through2@2.x`. As of through2@3.x, readable-stream@3 is being used and is not compatible with older versions of Node.js.*** _v2.x support is being maintained on the [v2.x](https://github.com/rvagg/through2/tree/v2.x) branch._
```js
fs.createReadStream('ex.txt')
.pipe(through2(function (chunk, enc, callback) {
for (var i = 0; i < chunk.length; i++)
if (chunk[i] == 97)
chunk[i] = 122 // swap 'a' for 'z'
this.push(chunk)
callback()
}))
.pipe(fs.createWriteStream('out.txt'))
.on('finish', () => doSomethingSpecial())
```
Or object streams:
```js
var all = []
fs.createReadStream('data.csv')
.pipe(csv2())
.pipe(through2.obj(function (chunk, enc, callback) {
var data = {
name : chunk[0]
, address : chunk[3]
, phone : chunk[10]
}
this.push(data)
callback()
}))
.on('data', (data) => {
all.push(data)
})
.on('end', () => {
doSomethingSpecial(all)
})
```
Note that `through2.obj(fn)` is a convenience wrapper around `through2({ objectMode: true }, fn)`.
## API
<b><code>through2([ options, ] [ transformFunction ] [, flushFunction ])</code></b>
Consult the **[stream.Transform](https://nodejs.org/docs/latest/api/stream.html#stream_class_stream_transform)** documentation for the exact rules of the `transformFunction` (i.e. `this._transform`) and the optional `flushFunction` (i.e. `this._flush`).
### options
The options argument is optional and is passed straight through to `stream.Transform`. So you can use `objectMode:true` if you are processing non-binary streams (or just use `through2.obj()`).
The `options` argument is first, unlike standard convention, because if I'm passing in an anonymous function then I'd prefer for the options argument to not get lost at the end of the call:
```js
fs.createReadStream('/tmp/important.dat')
.pipe(through2({ objectMode: true, allowHalfOpen: false },
(chunk, enc, cb) => {
cb(null, 'wut?') // note we can use the second argument on the callback
// to provide data as an alternative to this.push('wut?')
}
))
.pipe(fs.createWriteStream('/tmp/wut.txt'))
```
### transformFunction
The `transformFunction` must have the following signature: `function (chunk, encoding, callback) {}`. A minimal implementation should call the `callback` function to indicate that the transformation is done, even if that transformation means discarding the chunk.
To queue a new chunk, call `this.push(chunk)`&mdash;this can be called as many times as required before the `callback()` if you have multiple pieces to send on.
Alternatively, you may use `callback(err, chunk)` as shorthand for emitting a single chunk or an error.
If you **do not provide a `transformFunction`** then you will get a simple pass-through stream.
### flushFunction
The optional `flushFunction` is provided as the last argument (2nd or 3rd, depending on whether you've supplied options) is called just prior to the stream ending. Can be used to finish up any processing that may be in progress.
```js
fs.createReadStream('/tmp/important.dat')
.pipe(through2(
(chunk, enc, cb) => cb(null, chunk), // transform is a noop
function (cb) { // flush function
this.push('tacking on an extra buffer to the end');
cb();
}
))
.pipe(fs.createWriteStream('/tmp/wut.txt'));
```
<b><code>through2.ctor([ options, ] transformFunction[, flushFunction ])</code></b>
Instead of returning a `stream.Transform` instance, `through2.ctor()` returns a **constructor** for a custom Transform. This is useful when you want to use the same transform logic in multiple instances.
```js
var FToC = through2.ctor({objectMode: true}, function (record, encoding, callback) {
if (record.temp != null && record.unit == "F") {
record.temp = ( ( record.temp - 32 ) * 5 ) / 9
record.unit = "C"
}
this.push(record)
callback()
})
// Create instances of FToC like so:
var converter = new FToC()
// Or:
var converter = FToC()
// Or specify/override options when you instantiate, if you prefer:
var converter = FToC({objectMode: true})
```
## See Also
- [through2-map](https://github.com/brycebaril/through2-map) - Array.prototype.map analog for streams.
- [through2-filter](https://github.com/brycebaril/through2-filter) - Array.prototype.filter analog for streams.
- [through2-reduce](https://github.com/brycebaril/through2-reduce) - Array.prototype.reduce analog for streams.
- [through2-spy](https://github.com/brycebaril/through2-spy) - Wrapper for simple stream.PassThrough spies.
- the [mississippi stream utility collection](https://github.com/maxogden/mississippi) includes `through2` as well as many more useful stream modules similar to this one
## License
**through2** is Copyright (c) Rod Vagg and additional contributors and licensed under the MIT license. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE file for more details.

View File

@@ -0,0 +1,32 @@
{
"name": "through2",
"version": "3.0.2",
"description": "A tiny wrapper around Node.js streams.Transform (Streams2/3) to avoid explicit subclassing noise",
"main": "through2.js",
"scripts": {
"test": "nyc node test/test.js | faucet && nyc report"
},
"repository": {
"type": "git",
"url": "https://github.com/rvagg/through2.git"
},
"keywords": [
"stream",
"streams2",
"through",
"transform"
],
"author": "Rod Vagg <r@va.gg> (https://github.com/rvagg)",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.4",
"readable-stream": "2 || 3"
},
"devDependencies": {
"bl": "~2.0.1",
"faucet": "0.0.1",
"nyc": "~13.1.0",
"stream-spigot": "~3.0.6",
"tape": "~4.9.1"
}
}

View File

@@ -0,0 +1,95 @@
var Transform = require('readable-stream').Transform
, inherits = require('inherits')
function DestroyableTransform(opts) {
Transform.call(this, opts)
this._destroyed = false
}
inherits(DestroyableTransform, Transform)
DestroyableTransform.prototype.destroy = function(err) {
if (this._destroyed) return
this._destroyed = true
var self = this
process.nextTick(function() {
if (err)
self.emit('error', err)
self.emit('close')
})
}
// a noop _transform function
function noop (chunk, enc, callback) {
callback(null, chunk)
}
// create a new export function, used by both the main export and
// the .ctor export, contains common logic for dealing with arguments
function through2 (construct) {
return function (options, transform, flush) {
if (typeof options == 'function') {
flush = transform
transform = options
options = {}
}
if (typeof transform != 'function')
transform = noop
if (typeof flush != 'function')
flush = null
return construct(options, transform, flush)
}
}
// main export, just make me a transform stream!
module.exports = through2(function (options, transform, flush) {
var t2 = new DestroyableTransform(options)
t2._transform = transform
if (flush)
t2._flush = flush
return t2
})
// make me a reusable prototype that I can `new`, or implicitly `new`
// with a constructor call
module.exports.ctor = through2(function (options, transform, flush) {
function Through2 (override) {
if (!(this instanceof Through2))
return new Through2(override)
this.options = Object.assign({}, options, override)
DestroyableTransform.call(this, this.options)
}
inherits(Through2, DestroyableTransform)
Through2.prototype._transform = transform
if (flush)
Through2.prototype._flush = flush
return Through2
})
module.exports.obj = through2(function (options, transform, flush) {
var t2 = new DestroyableTransform(Object.assign({ objectMode: true, highWaterMark: 16 }, options))
t2._transform = transform
if (flush)
t2._flush = flush
return t2
})

View File

@@ -0,0 +1,49 @@
{
"name": "@gulp-sourcemaps/identity-map",
"version": "2.0.1",
"description": "Gulp plugin for generating an identity sourcemap for a file.",
"author": "Gulp-sourcemaps Team",
"contributors": [
"Blaine Bublitz <blaine.bublitz@gmail.com>"
],
"repository": "gulp-sourcemaps/identity-map",
"license": "MIT",
"engines": {
"node": ">= 0.10"
},
"main": "index.js",
"files": [
"LICENSE",
"index.js",
"lib"
],
"scripts": {
"lint": "eslint .",
"pretest": "npm run lint",
"test": "nyc mocha --async-only",
"coveralls": "nyc report --reporter=text-lcov | coveralls"
},
"dependencies": {
"acorn": "^6.4.1",
"normalize-path": "^3.0.0",
"postcss": "^7.0.16",
"source-map": "^0.6.0",
"through2": "^3.0.1"
},
"devDependencies": {
"coveralls": "^3.0.3",
"eslint": "^5.16.0",
"eslint-config-gulp": "^3.0.1",
"expect": "^1.19.0",
"mississippi": "^4.0.0",
"mocha": "^6.1.4",
"nyc": "^14.1.0",
"vinyl": "^2.2.0"
},
"keywords": [
"sourcemap",
"identity",
"generate",
"stream"
]
}

21
node_modules/@gulp-sourcemaps/map-sources/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Blaine Bublitz <blaine.bublitz@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

52
node_modules/@gulp-sourcemaps/map-sources/README.md generated vendored Normal file
View File

@@ -0,0 +1,52 @@
# @gulp-sourcemaps/map-sources
[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![AppVeyor Build Status][appveyor-image]][appveyor-url] [![Coveralls Status][coveralls-image]][coveralls-url]
Gulp plugin for mapping sources of a sourcemap.
## Example
```js
var mapSources = require('@gulp-sourcemaps/map-sources');
gulp.src(...)
.pipe(sourcemaps.init())
.pipe(mapSources(function(sourcePath, file) {
return '../' + sourcePath;
}))
.pipe(sourcemaps.write())
.pipe(gulp.dest(...))
```
## API
### `mapSources(mapFn)`
Takes a map function as the only argument. Returns an `objectMode` Transform stream.
#### `mapFn(sourcePath, file)`
The map function is called once per value of the `sources` array of a `sourceMap` attached to each [`Vinyl`][vinyl-url] object passed through the stream. The map function is called with the `sourcePath` string from the `sources` array and the `file` object it originated from. The return value replaces the original value in the array.
If a `Vinyl` object doesn't have a `sourceMap` or `sourceMap.sources` property, the file is passed through the stream without having the `mapFn` called.
All `sources` are normalized to use `/` instead of `\\` as path separators.
## License
MIT
[vinyl-url]: https://github.com/gulpjs/vinyl
[downloads-image]: http://img.shields.io/npm/dm/@gulp-sourcemaps/map-sources.svg
[npm-url]: https://npmjs.org/package/@gulp-sourcemaps/map-sources
[npm-image]: http://img.shields.io/npm/v/@gulp-sourcemaps/map-sources.svg
[travis-url]: https://travis-ci.org/gulp-sourcemaps/map-sources
[travis-image]: http://img.shields.io/travis/gulp-sourcemaps/map-sources.svg?label=travis-ci
[appveyor-url]: https://ci.appveyor.com/project/phated/map-sources
[appveyor-image]: https://img.shields.io/appveyor/ci/phated/map-sources.svg?label=appveyor
[coveralls-url]: https://coveralls.io/r/gulp-sourcemaps/map-sources
[coveralls-image]: http://img.shields.io/coveralls/gulp-sourcemaps/map-sources.svg

30
node_modules/@gulp-sourcemaps/map-sources/index.js generated vendored Normal file
View File

@@ -0,0 +1,30 @@
'use strict';
var through = require('through2');
var normalize = require('normalize-path');
function mapSources(mapFn) {
function transform(file, _, cb) {
if (!file.sourceMap || !file.sourceMap.sources) {
return cb(null, file);
}
function mapper(sourcePath) {
var result = sourcePath;
if (typeof mapFn === 'function') {
result = mapFn(sourcePath, file);
}
return normalize(result);
}
file.sourceMap.sources = file.sourceMap.sources.map(mapper);
cb(null, file);
}
return through.obj(transform);
}
module.exports = mapSources;

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