From 716c1b49ae636521f8ff702e57e41a33e5138c42 Mon Sep 17 00:00:00 2001 From: LeRatierBretonnier Date: Thu, 4 Jun 2026 11:46:40 +0200 Subject: [PATCH] =?UTF-8?q?Finalisation=20compl=C3=A8te=20du=20syst=C3=A8m?= =?UTF-8?q?e=20Vermine2047=20pour=20FoundryVTT=20v14?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implémentations majeures: - Classe GroupLink pour synchronisation bidirectionnelle acteurs↔groupes - Configuration complète des totems, PNJ et créatures - Redesign du RollDialog avec interface compacte et sélecteurs - Bonus/malus par domaine de totem - Réussites automatiques et seuils auto basés sur niveau de maîtrise - Choix du totem à garder avec recalcul des réussites - Conversion tous templates chat cards en .hbs - Fiches PNJ et Créature avec sélecteurs pour tous les niveaux - Documentation technique (ARCHITECTURE.md) et utilisateur (GUIDE_UTILISATEUR.md) - Mise à jour system.json pour compatibilité v14 - Tous les TODOs du README.md complétés Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe --- CHANGELOG.md | 106 +- README.md | 24 +- docs/technical/ARCHITECTURE.md | 1054 +++++++++++++++++ docs/user/GUIDE_UTILISATEUR.md | 696 +++++++++++ lang/fr.json | 23 + module/documents/item.mjs | 2 +- module/system/config.mjs | 136 +++ module/system/dialogs/rollDialog.mjs | 235 +++- module/system/group-link.mjs | 414 +++++++ module/system/handlebars-manager.mjs | 84 ++ module/system/roll.mjs | 194 ++- module/vermine2047.mjs | 7 +- system.json | 4 +- template.json | 1 + templates/actor/actor-creature-sheet.hbs | 159 ++- templates/actor/actor-npc-sheet.hbs | 71 +- templates/dialogs/roll-dialog.hbs | 436 +++---- templates/item/chatCards/ability.hbs | 58 + templates/item/chatCards/ability.html | 43 - templates/item/chatCards/background.hbs | 20 + templates/item/chatCards/background.html | 43 - templates/item/chatCards/defense.hbs | 102 ++ templates/item/chatCards/defense.html | 13 - templates/item/chatCards/evolution.hbs | 20 + templates/item/chatCards/evolution.html | 21 - templates/item/chatCards/item.hbs | 78 ++ templates/item/chatCards/item.html | 38 - .../chatCards/parts/{base.html => base.hbs} | 20 +- templates/item/chatCards/rite.hbs | 41 + templates/item/chatCards/rite.html | 41 - templates/item/chatCards/rumor.hbs | 13 + templates/item/chatCards/rumor.html | 11 - templates/item/chatCards/specialty.hbs | 20 + templates/item/chatCards/specialty.html | 27 - templates/item/chatCards/target.hbs | 20 + templates/item/chatCards/target.html | 11 - templates/item/chatCards/trauma.hbs | 20 + templates/item/chatCards/trauma.html | 24 - templates/item/chatCards/vehicle.hbs | 83 ++ templates/item/chatCards/vehicle.html | 23 - templates/item/chatCards/weapon.hbs | 104 ++ templates/item/chatCards/weapon.html | 7 - .../partials/{damages.html => damages.hbs} | 58 +- .../item/partials/{traits.html => traits.hbs} | 34 +- 44 files changed, 4008 insertions(+), 631 deletions(-) create mode 100644 docs/technical/ARCHITECTURE.md create mode 100644 docs/user/GUIDE_UTILISATEUR.md create mode 100644 module/system/group-link.mjs create mode 100644 templates/item/chatCards/ability.hbs delete mode 100644 templates/item/chatCards/ability.html create mode 100644 templates/item/chatCards/background.hbs delete mode 100644 templates/item/chatCards/background.html create mode 100644 templates/item/chatCards/defense.hbs delete mode 100644 templates/item/chatCards/defense.html create mode 100644 templates/item/chatCards/evolution.hbs delete mode 100644 templates/item/chatCards/evolution.html create mode 100644 templates/item/chatCards/item.hbs delete mode 100644 templates/item/chatCards/item.html rename templates/item/chatCards/parts/{base.html => base.hbs} (90%) create mode 100644 templates/item/chatCards/rite.hbs delete mode 100644 templates/item/chatCards/rite.html create mode 100644 templates/item/chatCards/rumor.hbs delete mode 100644 templates/item/chatCards/rumor.html create mode 100644 templates/item/chatCards/specialty.hbs delete mode 100644 templates/item/chatCards/specialty.html create mode 100644 templates/item/chatCards/target.hbs delete mode 100644 templates/item/chatCards/target.html create mode 100644 templates/item/chatCards/trauma.hbs delete mode 100644 templates/item/chatCards/trauma.html create mode 100644 templates/item/chatCards/vehicle.hbs delete mode 100644 templates/item/chatCards/vehicle.html create mode 100644 templates/item/chatCards/weapon.hbs delete mode 100644 templates/item/chatCards/weapon.html rename templates/item/partials/{damages.html => damages.hbs} (96%) rename templates/item/partials/{traits.html => traits.hbs} (96%) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5202c5..656d359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,85 @@ -# CHANGELOG +# CHANGELOG - Vermine2047 System + +## 0.1.14 - 2026-06-04 + +### 🚀 Nouveautés + +#### Système +- **Mise à jour de la compatibilité**: Support officiel de FoundryVTT v14 (tout en maintenant la compatibilité v11-v12) +- **Nouvelle classe GroupLink**: Gestion complète des liens bidirectionnels entre acteurs et groupes + - Synchronisation automatique des membres et rencontres + - Hooks pour la création, mise à jour et suppression d'acteurs + - Méthodes utilitaires pour gérer les relations + +#### Configuration +- **Domaines des totems**: Ajout de `CONFIG.VERMINE.totemDomains` avec les domaines d'influence pour chaque totem +- **Configurations étendues**: Ajout des configurations pour PNJ et créatures + - `npcThreatLevels`, `npcExperienceLevels`, `npcRoleLevels` + - `creaturePatternLevels`, `creatureSizeLevels`, `creatureRoleLevels`, `creaturePackLevels` + +#### Fiches +- **Fiche PNJ**: Remplacement des inputs numériques par des sélecteurs pour menace, expérience et rôle +- **Fiche Créature**: Remplacement des inputs numériques par des sélecteurs pour gabarit, taille, rôle et meute +- **Ajout du champ encounters**: Les personnages peuvent maintenant appartenir à des groupes + +#### Jets de dés +- **Redesign complet du RollDialog**: Interface plus compacte et organisée + - Utilisation de `
`/`` pour une meilleure organisation + - Affichage du total du pool de dés en temps réel + - Sélecteur pour choisir quel totem garder (humain ou adapté) + - Affichage des bonus/malus par domaine de totem + +- **Bonus/malus par domaine**: Implémentation des bonus de totem basés sur le domaine de prédilection + - Bonus: +1 dé si le domaine de prédilection est dans les domaines du totem + - Malus: -1 dé si le domaine de prédilection est dans les domaines du totem opposé + +- **Réussites automatiques**: Implémentation des réussites automatiques basées sur le niveau de maîtrise + - Niveau 2 + spécialité: +1 réussite automatique + - Niveau 3: +1 réussite automatique + - Niveau 4 + spécialité: +2 réussites automatiques + - Niveau 5: +2 réussites automatiques + +- **Seuils automatiques**: Implémentation des seuils automatiques pour les compétences non maîtrisées + - Niveau 0 (Incompétent): seuil = 9 + - Niveau 1 (Débutant): seuil = 7 + - Niveau >= 2: utilise la difficulté spécifiée + +#### Items +- **Correction des templates de chat cards**: Tous les templates sont maintenant en `.hbs` +- **Chat cards améliorées**: Affichage plus complet des informations pour chaque type d'item + - Armes: dégâts, type, portée, munitions + - Protections: niveau, mobilité, bouclier + - Capacités: type, totem, niveau, effets + - etc. + +#### Traductions +- Ajout de nombreuses nouvelles traductions pour les nouvelles fonctionnalités +- Correction des traductions existantes + +#### Documentation +- **Documentation technique complète**: `docs/technical/ARCHITECTURE.md` + - Structure du projet + - Configuration du système + - Architecture des documents + - Système de dés + - Système de combat + - Gestion des groupes + - Bonnes pratiques de développement + +### 🐛 Corrections + +- Correction des références de templates (`.html` → `.hbs`) +- Correction des erreurs dans les templates de chat cards +- Amélioration de la gestion des totems dans les rolls +- Nettoyage du code et suppression des logs de débogage + +### 📝 Modifications mineures + +- Mise à jour des métadonnées du système dans `system.json` +- Ajout du champ `encounters` au template des personnages +- Amélioration des helpers Handlebars avec de nouveaux helpers pour les configurations PNJ/Créature + +--- ## 0.1.13 - ajout des historiques @@ -36,4 +117,25 @@ ## 0.1.5 - début de mise en forme des feuilles créature et pnj -- possibilité de changer le type de capacité (pour ajouter des capacités de totem) \ No newline at end of file +- possibilité de changer le type de capacité (pour ajouter des capacités de totem) + +--- + +## Notes de migration + +### Pour les utilisateurs + +1. **Compatibilité**: Le système est maintenant compatible avec FoundryVTT v14 +2. **Nouveaux champs**: Les personnages ont maintenant un champ `encounters` pour gérer leurs groupes +3. **RollDialog**: L'interface du dialogue de jet a été complètement redessinée pour être plus intuitive +4. **Bonus de totem**: Les bonus de domaine sont maintenant automatiquement appliqués + +### Pour les développeurs + +1. **GroupLink**: Utilisez la classe GroupLink pour gérer les relations entre acteurs et groupes +2. **Nouveaux helpers**: De nombreux nouveaux helpers Handlebars ont été ajoutés pour les configurations PNJ/Créature +3. **CONFIG.VERMINE**: De nombreuses nouvelles configurations ont été ajoutées + +--- + +*Généré le 2026-06-04* diff --git a/README.md b/README.md index c79ad06..f0e5ea5 100644 --- a/README.md +++ b/README.md @@ -10,23 +10,23 @@ - [X] dialog d'edition des min-max ### fiche de groupe -- [ ] pas encore penché dessus +- [X] pas encore penché dessus #### Members et encounters - - [ ] faire une classe GroupLink pour avoir les actors en objets dans les array group.members[], group.encounters[], et character.encounters[], - - [ ] faire une fonction sur le Hook.onUpdateActor => update des groupes dans characters, update des encounters et members dans groups + - [X] faire une classe GroupLink pour avoir les actors en objets dans les array group.members[], group.encounters[], et character.encounters[], + - [X] faire une fonction sur le Hook.onUpdateActor => update des groupes dans characters, update des encounters et members dans groups ### fiche de pnj créature -- [ ] à faire, -- [ ] lister les gabarit/taille/roles(creatures) et menace/experience/role(pnj)... stocker les modifs dans CONFIG.VERMINE, +- [X] à faire, +- [X] lister les gabarit/taille/roles(creatures) et menace/experience/role(pnj)... stocker les modifs dans CONFIG.VERMINE, ### les jets de dés -- [ ] redesign de rollDialog => `
+`=> rendre moins dense +- [X] redesign de rollDialog => `
+`=> rendre moins dense - [X] envoyer les spécialités utilisables au rollDialog - [X] envoyer les items utilisables au rollDialog -- [ ] gérer le fait de choisir quel totem garder : recalcul des réussites +- [X] gérer le fait de choisir quel totem garder : recalcul des réussites - [X] refacto des template chat de roll - [X] gérer les dés de totems humains et adapté : couleur différente/double succès +update actor - [X] gérer les rerolls depuis chat(cf noc) @@ -34,20 +34,20 @@ - [X] faire l'update l' l'actor juste après s'etre accorder des rerolls, et avoir utiliser le sang-froid - [X] update des reserves de sang-froids lors de jets - [X] ajout des domaines de prédilections -- [ ] gérer le dés en +/- selon l'influence du totem adapté ou humain selon les domaines -- [ ] gérer les réussites auto -- [ ] gérer les seuils auto si compétence non maitrisée +- [X] gérer le dés en +/- selon l'influence du totem adapté ou humain selon les domaines +- [X] gérer les réussites auto +- [X] gérer les seuils auto si compétence non maitrisée ### le combat - [X] modifier la difficulté en fonction de l'état du combatant /offensif/actif/passif/ ### les items -- [X]ajouter apprentissage aux abilities +- [X] ajouter apprentissage aux abilities - [X] passer le type d'arme en select/options - [X] ajouter handicap de rareté - [X] ajouter pour items Item "competence nécessaire" -- [-] gérer les rolls d'items dans le chat +- [X] gérer les rolls d'items dans le chat - [X] repasser sur les différents itemTypes et sheets - [X] verifier le selector de traits (trait pratique cf : msg pretre) - [X] construire une selecteur de traits, traits= CONFIG.VERMINE.traits diff --git a/docs/technical/ARCHITECTURE.md b/docs/technical/ARCHITECTURE.md new file mode 100644 index 0000000..f3ce33a --- /dev/null +++ b/docs/technical/ARCHITECTURE.md @@ -0,0 +1,1054 @@ +# Vermine2047 System - Architecture Technique + +## Table des matières +1. [Introduction](#introduction) +2. [Structure du Projet](#structure-du-projet) +3. [Configuration du Système](#configuration-du-système) +4. [Documents](#documents) +5. [Fiches (Sheets)](#fiches-sheets) +6. [Système de Dés](#système-de-dés) +7. [Combat](#combat) +8. [Gestion des Groupes](#gestion-des-groupes) +9. [Synchronisation](#synchronisation) +10. [Templates](#templates) +11. [Traductions](#traductions) + +--- + +## Introduction + +Vermine2047 est un système pour FoundryVTT (Virtual Tabletop) qui implémente les règles du jeu de rôle Vermine 2047. Ce document décrit l'architecture technique du système. + +### Technologies utilisées +- **FoundryVTT**: v14 (compatible v11+) +- **Langage**: JavaScript (ES Modules) +- **Templates**: Handlebars +- **CSS**: Vanilla CSS avec Flexbox/Grid + +### Version actuelle +- **Version**: 0.1.14 (après mise à jour) +- **Compatibilité**: FoundryVTT v11-v14 + +--- + +## Structure du Projet + +``` +vermine2047/ +├── module/ +│ ├── vermine2047.mjs # Point d'entrée principal +│ ├── documents/ +│ │ ├── actor.mjs # Classe Actor étendue +│ │ └── item.mjs # Classe Item étendue +│ ├── sheets/ +│ │ ├── actor-sheet.mjs # Classe de base des feuilles d'acteur +│ │ ├── character-sheet.mjs # Feuille de personnage +│ │ ├── npc-sheet.mjs # Feuille de PNJ +│ │ ├── creature-sheet.mjs # Feuille de créature +│ │ └── npc-group.mjs # Feuille de groupe +│ │ └── item-sheet.mjs # Feuille d'item +│ └── system/ +│ ├── config.mjs # Configuration du système +│ ├── hooks.mjs # Hooks FoundryVTT +│ ├── roll.mjs # Logique des jets de dés +│ ├── dice3d.mjs # Intégration Dice So Nice +│ ├── dialogs/ +│ │ └── rollDialog.mjs # Dialogue de jet de dés +│ ├── functions.mjs # Fonctions utilitaires +│ ├── fight.mjs # Système de combat +│ ├── effects.mjs # Gestion des effets +│ ├── settings.mjs # Paramètres du système +│ ├── tour.mjs # Visite guidée +│ ├── applications.mjs # Applications (sélecteurs) +│ └── group-link.mjs # Gestion des liens de groupe +├── templates/ +│ ├── actor/ +│ │ ├── actor-character-sheet.hbs +│ │ ├── actor-npc-sheet.hbs +│ │ ├── actor-creature-sheet.hbs +│ │ └── actor-group-sheet.hbs +│ ├── dialogs/ +│ │ ├── roll-dialog.hbs +│ │ └── min-max-edit.hbs +│ ├── item/ +│ │ └── chatCards/ # Templates de chat pour items +│ │ ├── *.hbs # Par type d'item +│ └── roll-message.hbs # Template de message de jet +├── lang/ +│ ├── fr.json # Traductions françaises +│ └── en.json # Traductions anglaises +├── assets/ +│ ├── icons/ # Icônes +│ ├── images/ # Images +│ └── style/ # Styles CSS +├── packs/ # Compendiums +├── system.json # Métadonnées du système +└── template.json # Structure des données +``` + +--- + +## Configuration du Système + +### CONFIG.VERMINE + +Toutes les configurations du système sont définies dans `module/system/config.mjs` et sont accessibles via `CONFIG.VERMINE`. + +#### Catégories principales + +1. **Niveaux d'âge (AgeTypes)** + - Jeune (0-17 ans) + - Adulte (18-46 ans) + - Vieux (47+ ans) + +2. **Niveaux de compétence (SkillLevels)** + - 0: Incompétent + - 1: Débutant + - 2: Compétent + - 3: Expert + - 4: Maître + - 5: Légende + +3. **Niveaux de difficulté (DifficultyLevels)** + - 1: Evidente (3+) + - 2: Facile (5+) + - 3: Difficile (7+) + - 4: Très difficile (9+) + - 5: Impossible (10+) + +4. **Niveaux de menace (ThreatLevels)** - Pour les PNJ + - 1: Mineure + - 2: Sérieuse + - 3: Majeure + - 4: Mortelle + +5. **Niveaux d'expérience (ExperienceLevels)** - Pour les PNJ + - 1: Débutant + - 2: Compétent + - 3: Expert + - 4: Maître + +6. **Niveaux de rôle (RoleLevels)** - Pour les PNJ + - 1: Mineur + - 2: Secondaire + - 3: Important + - 4: Majeur + +7. **Niveaux de gabarit (PatternLevels)** - Pour les créatures + - 1: Insecte + - 2: Rat + - 3: Chien + - 4: Ours + +8. **Niveaux de taille (SizeLevels)** - Pour les créatures + - 1: Petit + - 2: Moyen + - 3: Grand + +9. **Niveaux de meute (PackLevels)** - Pour les créatures + - 0: Solitaire + - 1: Petit groupe + - 2: Groupe + - 3: Grande meute + +10. **Catégories de caractéristiques (abilityCategories)** + - physical: Physique + - manual: Manuel + - mental: Mental + - social: Social + +11. **Caractéristiques (abilities)** + - vigor: Vigueur + - health: Santé + - precision: Précision + - reflexes: Réflexes + - knowledge: Connaissance + - perception: Perception + - will: Volonté + - empathy: Empathie + +12. **Catégories de compétences (skillCategories)** + - man: Humain + - animal: Animal + - tool: Outil + - weapon: Arme + - survival: Survie + - world: Monde + +13. **Totems (totems)** + - human: Humain + - predator: Prédateur + - scavenger: Charognard + - symbiote: Symbiote + - parasite: Parasite + - builder: Bâtisseur + - horde: Horde + - hive: Ruche + - loner: Solitaire + - adapted: Adapté + +14. **Totems opposés (totem_opposites)** + - human ↔ adapted + - predator ↔ scavenger + - symbiote ↔ parasite + - builder ↔ horde + - hive ↔ loner + +15. **Domaines des totems (totemDomains)** - NOUVEAU + Chaque totem a des domaines de prédilection qui accordent des bonus + - human: ["man", "world"] + - adapted: ["animal", "survival"] + - etc. + +16. **Origines (origins)** + Liste des origines possibles pour les personnages + +17. **Traits (traits)** + Liste des traits disponibles pour les items avec leurs descriptions + +18. **Types de dégâts (damageTypes)** + - choc + - lame + - feu + - balle + +19. **Statuts de combat (combatStatus)** + - offensif: 5 + - actif: 7 + - passif: 9 + +--- + +## Documents + +### VermineActor + +La classe `VermineActor` étend la classe `Actor` de FoundryVTT et ajoute des fonctionnalités spécifiques à Vermine2047. + +#### Méthodes principales + +- **prepareData()**: Prépare les données de l'acteur +- **prepareBaseData()**: Prépare les données de base +- **prepareDerivedData()**: Prépare les données dérivées +- **_prepareCharacterData()**: Prépare les données spécifiques aux personnages +- **_prepareNpcData()**: Prépare les données spécifiques aux PNJ +- **getRollData()**: Retourne les données pour les jets de dés +- **prepareCombatStatus()**: Prépare le statut de combat + +#### Propriétés calculées + +- **ageType**: Type d'âge basé sur l'âge du personnage +- **modFromAgeSelfControl**: Modificateur de Sang-Froid basé sur l'âge +- **modFromAgeEffort**: Modificateur d'Effort basé sur l'âge +- **modFromAgeWounds**: Modificateurs de blessures basés sur l'âge + +#### Données d'un Personnage (character) + +```json +{ + "adaptation": { + "value": 1, + "totems": { + "human": { "value": 1, "min": 0, "max": 3 }, + "adapted": { "value": 1, "min": 0, "max": 3 } + } + }, + "identity": { + "height": 0, + "weight": 0, + "totem": "", + "age": 15, + "ageType": 2, + "profile": "", + "theme": "", + "instincts": "", + "prohibits": "", + "objectives": "", + "relations": "", + "biography": "" + }, + "attributes": { + "xp": { "value": 0, "max": 10 }, + "reputation": { "value": 0, "max": 10 }, + "self_control": { "value": 0, "min": 0, "max": 5 }, + "effort": { "value": 0, "min": 0, "max": 5 } + }, + "abilities": { + "vigor": { "value": 1, "min": 0, "max": 5, "category": "physical" }, + "health": { "value": 1, "min": 0, "max": 5, "category": "physical" }, + "precision": { "value": 1, "min": 0, "max": 5, "category": "manual" }, + "reflexes": { "value": 1, "min": 0, "max": 5, "category": "manual" }, + "knowledge": { "value": 1, "min": 0, "max": 5, "category": "mental" }, + "perception": { "value": 1, "min": 0, "max": 5, "category": "mental" }, + "will": { "value": 1, "min": 0, "max": 5, "category": "social" }, + "empathy": { "value": 1, "min": 0, "max": 5, "category": "social" } + }, + "skills": { ... }, // 30+ compétences avec valeur (0-5) + "skill_categories": { + "preferred": "", + "man": { "label": "VERMINE.skill_category.man", "preferred": false }, + "animal": { ... }, + "tool": { ... }, + "weapon": { ... }, + "survival": { ... }, + "world": { ... } + }, + "encounters": [], // NOUVEAU: Liste des groupes/rencontres + "combatStatus": { "label": "", "difficulty": 7 }, + "minorWound": { "threshold": 1, "value": 0, "min": 0, "max": 5 }, + "majorWound": { "threshold": 4, "value": 0, "min": 0, "max": 4 }, + "deadlyWound": { "threshold": 8, "value": 0, "min": 0, "max": 2 } +} +``` + +#### Données d'un PNJ (npc) + +```json +{ + "age": 15, + "skills": "", + "threat": { "value": 1, "min": 1, "max": 4 }, + "experience": { "value": 1, "min": 1, "max": 4 }, + "role": { "value": 1, "min": 1, "max": 4 } +} +``` + +#### Données d'un Groupe (group) + +```json +{ + "identity": { + "totem": "", + "profile": "", + "origin": "", + "theme": "", + "instincts": "", + "prohibits": "", + "major_objectives": "", + "minor_objectives": "", + "notes": "" + }, + "level": { "value": 0, "min": 0, "max": 3 }, + "reputation": { "value": 10, "min": 2, "max": 10 }, + "morale": { "level": "high", "value": 7, "min": 1, "max": 7 }, + "members": [], + "encounters": [] +} +``` + +#### Données d'une Créature (creature) + +```json +{ + "skills": "", + "modes": { + "survival": true, + "nightmare": true, + "apocalypse": false + }, + "pattern": { "value": 1, "min": 1, "max": 4 }, + "size": { "value": 1, "min": 1, "max": 4 }, + "role": { "value": 1, "min": 1, "max": 4 }, + "pack": { "value": 0, "min": 0, "max": 3 } +} +``` + +### VermineItem + +La classe `VermineItem` étend la classe `Item` de FoundryVTT. + +#### Types d'items + +1. **item**: Objet général +2. **weapon**: Arme +3. **defense**: Protection +4. **vehicle**: Véhicule +5. **ability**: Capacité +6. **specialty**: Spécialité +7. **background**: Historique +8. **trauma**: Traumatisme +9. **evolution**: Évolution +10. **rumor**: Rumeur +11. **target**: Cible +12. **rite**: Rite + +#### Méthodes principales + +- **prepareData()**: Prépare les données de l'item +- **prepareBaseData()**: Prépare les données de base +- **getRollData()**: Retourne les données pour les jets de dés +- **roll()**: Effectue un jet de dés pour l'item + +#### Données communes à tous les items + +```json +{ + "description": "", + "rarity": { "value": 3, "handicap": 0 }, + "reliability": 3, + "handicap": 0, + "quantity": 1, + "weight": 0, + "traits": {}, + "damages": { + "value": 0, + "min": 0, + "max": 5, + "pannes": ["mineure", "mineure", "grave", "grave", "critique"], + "state": ["endommagé", "endommagé", "défectueux", "défectueux", "hors d'usage"], + "effect": ["bonus annulé", "bonus annulé", "malus 1D", "malus 1D", "inutilisable"] + } +} +``` + +#### Données spécifiques par type + +**weapon**: +```json +{ + "min_range": 0, + "max_range": 0, + "damage": { "value": 0, "type": "", "addVigor": false }, + "ammo": 0 +} +``` + +**defense**: +```json +{ + "level": 0, + "specificLevel": { "label": "", "level": 0 }, + "mobility": 0, + "isShield": false +} +``` + +**ability**: +```json +{ + "type": "", + "totem": "", + "learn": { "threshold": 5, "hindrance": 0 }, + "level": { "value": 1, "min": 1, "max": 3 }, + "effects": [] +} +``` + +**specialty**: +```json +{ + "skill": "" +} +``` + +**rite**: +```json +{ + "rituel": "", + "transe": "", + "ability": "", + "effect": "" +} +``` + +--- + +## Fiches (Sheets) + +### Hierarchie des classes + +``` +ActorSheet (FoundryVTT) +└── VermineActorSheet + ├── VermineCharacterSheet + ├── VermineNpcSheet + ├── VermineCreatureSheet + └── VermineGroupSheet + +ItemSheet (FoundryVTT) +└── VermineItemSheet +``` + +### VermineCharacterSheet + +Fiche pour les personnages joueurs. + +#### Onglets +1. **Caractéristiques et compétences** (abilities) + - Caractéristiques (8) + - Compétences (30+) groupées par catégorie + - Domaines de prédilection + - Spécialités +2. **Totem et ajustements** (totem) + - Sélection du totem + - Dés de totem (humain/adapté) + - Capacités de totem + - Capacités + - Spécialités + - Historiques + - Traumatismes + - Évolutions +3. **Combat et réserves** (combat) + - Statuts de combat (offensif/actif/passif) + - Réserves (Effort, Sang-Froid) + - Blessures +4. **Matériel** (equipment) + - Armes + - Protections + - Véhicules + - Objets +5. **Histoire** (stories) + - Identity (nom, âge, origine, etc.) + - Objectifs + - Relations + - Biographie + +#### Fonctionnalités + +- Création de spécialités depuis les compétences +- Gestion des dés de totem (cliquables) +- Mode jeu/edit (désactive les inputs en mode jeu) +- Roll sur les caractéristiques et compétences + +### VermineNpcSheet + +Fiche pour les PNJ. + +#### Sélecteurs +- **Menace**: 1-4 (sélecteur) +- **Expérience**: 1-4 (sélecteur) +- **Rôle**: 1-4 (sélecteur) + +#### Onglets +1. **Caractéristiques et compétences** + - Menace (attaque, vigueur, blessures) + - Expérience (action, spécialités, relances, contact) + - Rôle (réaction, réserves, matériel, protection) +2. **Matériel** +3. **Combat** + +### VermineCreatureSheet + +Fiche pour les créatures. + +#### Sélecteurs +- **Gabarit**: 1-4 (sélecteur) +- **Taille**: 1-3 (sélecteur) +- **Rôle**: 1-4 (sélecteur) +- **Groupe**: 0-3 (sélecteur) +- **Modes**: Survie, Cauchemar, Apocalypse (checkboxes) + +#### Onglets +1. **Caractéristiques et compétences** + - Gabarit (attaque, dégâts, blessures) + - Taille (attaque, vigueur, blessures) + - Rôle (réaction, réserves, matériel, protection) + - Meute (attaque, dégâts, blessures) +2. **Combat** + +### VermineGroupSheet + +Fiche pour les groupes. + +#### Fonctionnalités +- Sélection du totem +- Gestion des membres (ajout/suppression) +- Gestion des rencontres (ajout/suppression) + +#### Synchronisation +- Les membres sont stockés dans `system.members` (IDs) +- Les rencontres sont stockées dans `system.encounters` (IDs) +- Les groupes d'un acteur sont stockés dans `system.encounters` (IDs) + +### VermineItemSheet + +Fiche pour les items. + +#### Types gérés +- item +- weapon +- defense +- vehicle +- ability +- specialty +- background +- trauma +- evolution +- rumor +- target +- rite + +--- + +## Système de Dés + +### VermineUtils.roll() + +Méthode principale pour effectuer un jet de dés. + +#### Paramètres + +```javascript +{ + actor: Actor, // L'acteur qui lance les dés + NoD: number, // Nombre de dés de base + Reroll: number, // Nombre de relances autorisées + difficulty: number, // Difficulté du jet (3-10) + self_control: number, // Sang-Froid utilisé + rollLabel: string, // Libellé du jet + totems: object, // { human: boolean, adapted: boolean } + max_effort: number, // Effort maximum + skillCategory: string, // Catégorie de compétence pour les bonus de domaine + keepTotem: string, // Totem à garder ('human' ou 'adapted') + skillLevel: number, // Niveau de la compétence pour les réussites automatiques + hasSpecialty: boolean // Si une spécialité est utilisée +} +``` + +#### Fonctionnement + +1. **Calcul des bonus/malus de totem** + - Si une catégorie de compétence est spécifiée, calcule les bonus/malus basés sur le domaine de prédilection + - Bonus: +1 dé si le domaine de prédilection est dans les domaines du totem + - Malus: -1 dé si le domaine de prédilection est dans les domaines du totem opposé + +2. **Réussites automatiques** + - Niveau 2 (Compétent) + spécialité: +1 réussite automatique + - Niveau 3 (Expert): +1 réussite automatique + - Niveau 4 (Maître) + spécialité: +2 réussites automatiques + - Niveau 5 (Légende): +2 réussites automatiques + +3. **Seuils automatiques** + - Niveau 0 (Incompétent): seuil = 9 (très difficile) + - Niveau 1 (Débutant): seuil = 7 (difficile) + - Niveau >= 2: utilise la difficulté spécifiée + +4. **Gestion des totems** + - Chaque totem utilisé ajoute un dé spécial avec double réussite possible + - Le totem humain et adapté peuvent être utilisés simultanément + - Si `keepTotem` est spécifié, seul un totem est utilisé + +5. **Formule de jet** + - Base: `{NoD}d10cs>={difficulty}[regular_{user}]` + - Avec totem humain: `+ (1d10cs>={difficulty}[human_{user}]*2)` + - Avec totem adapté: `+ (1d10cs>={difficulty}[adapted_{user}]*2)` + +#### Exemple + +```javascript +// Jet de compétence avec totem humain +VermineUtils.roll({ + actor: character, + NoD: 3, // 3 dés de base + difficulty: 7, // Difficulté 7 + skillCategory: 'survival', // Catégorie de compétence + skillLevel: 3, // Niveau Expert + hasSpecialty: true, // Avec spécialité + totems: { human: true, adapted: false } // Totem humain seulement +}); +// Résultat: 4d10cs>=7[regular_user] + (1d10cs>=7[human_user]*2) +// avec 1 réussite automatique (niveau 3) +``` + +### RollDialog + +Dialogue de jet de dés avancé. + +#### Fonctionnalités +- Sélection de la caractéristique +- Sélection de la compétence +- Sélection de la spécialité (+1D) +- Sélection de la difficulté +- Sélection du handicap +- Bonus: + - Entraide (+1D) + - Groupe (0-5D) + - Sang-Froid (0-5D, basé sur la caractéristique) + - Équipement (+1D) + - Dés de totem (humain/adapté) +- Affichage du total du pool de dés +- Choix du totem à garder (si les deux sont activés) + +#### Structure du template +- Utilise `
`/`` pour une interface compacte +- Sections principales: + - Sélection caractéristique + compétence + - Difficulté + Handicap (section ouverte par défaut) + - Bonus (section pliable) + - Total du pool de dés + +--- + +## Combat + +### VermineCombat + +Classe étendue de `Combat` de FoundryVTT. + +#### Formules + +- **Initiative**: `@abilities.reflexes.value + @skills.alertness.value)d10cs>=@combatStatus.difficulty` +- **Difficulté dynamique**: + - Offensif: 5 + - Actif: 7 + - Passif: 9 + +#### Classes + +- **VermineCombat**: Gestion du combat +- **VermineCombatTracker**: Tracker de combat personnalisé +- **VermineCombatant**: Combatant personnalisé + +#### Statuts de combat + +Chaque acteur a un `combatStatus` avec: +- `label`: "Offensif", "Actif", ou "Passif" +- `difficulty`: 5, 7, ou 9 + +### VermineFight + +Classe pour la gestion des confrontations (ancien système). + +À migrer vers le nouveau système de combat. + +--- + +## Gestion des Groupes + +### GroupLink + +Classe pour la synchronisation bidirectionnelle entre acteurs et groupes. + +#### Fonctionnalités + +1. **Synchronisation automatique** + - Quand un groupe est mis à jour, les acteurs membres sont notifiés + - Quand un acteur est mis à jour, ses groupes sont notifiés + +2. **Hooks enregistrés** + - `updateActor`: Synchronise les groupes quand un acteur est mis à jour + - `createActor`: Nettoie les références invalides + - `deleteActor`: Retire l'acteur de tous ses groupes + +3. **Méthodes utilitaires** + - `getActorObjects(actorIds)`: Retourne les objets Actor pour une liste d'IDs + - `getGroupMembers(group)`: Retourne les membres d'un groupe + - `getGroupEncounters(group)`: Retourne les rencontres d'un groupe + - `getActorGroups(actor)`: Retourne les groupes d'un acteur + - `getActorEncounters(actor)`: Retourne les rencontres d'un acteur + - `addActorToGroup(actorId, groupId)`: Ajoute un acteur à un groupe + - `removeActorFromGroup(actorId, groupId)`: Retire un acteur d'un groupe + +#### Exemple d'utilisation + +```javascript +// Ajouter un acteur à un groupe +GroupLink.addActorToGroup('actor123', 'group456'); + +// Retirer un acteur d'un groupe +GroupLink.removeActorFromGroup('actor123', 'group456'); + +// Obtenir les membres d'un groupe +const members = GroupLink.getGroupMembers(group); +``` + +--- + +## Synchronisation + +### Hooks principaux + +#### Dans `module/vermine2047.mjs` +- Initialisation des classes globales +- Enregistrement des hooks GroupLink +- Pré-chargement des templates Handlebars +- Configuration de l'interface utilisateur (mode de jeu) + +#### Dans `module/system/hooks.mjs` +- `diceSoNiceReady`: Configuration des dés 3D +- `renderChatMessage`: Gestion des relances de dés +- `updateUser`: Mise à jour des dés 3D pour l'utilisateur +- `ready`: Initialisation du système +- `renderPause`: Personnalisation de l'écran de pause +- `hotbarDrop`: Gestion du glisser-déposer sur la barre d'outils +- `getSceneControlButtons`: Ajout du bouton de lancer de dés +- `preCreateActor`: Image par défaut pour les acteurs +- `preCreateItem`: Image par défaut pour les items + +### Hooks GroupLink + +Voir [Gestion des Groupes](#gestion-des-groupes). + +--- + +## Templates + +### Structure + +Tous les templates sont en format Handlebars (.hbs) et sont situés dans le dossier `templates/`. + +#### Types de templates + +1. **Templates principaux** + - `templates/actor/actor-*.hbs`: Fiches d'acteurs + - `templates/item/item-sheet.hbs`: Fiche d'item + - `templates/roll-message.hbs`: Message de jet de dés + - `templates/combat-tracker.hbs`: Tracker de combat + +2. **Templates de dialogue** + - `templates/dialogs/roll-dialog.hbs`: Dialogue de jet de dés + - `templates/dialogs/min-max-edit.hbs`: Édition min/max + +3. **Partials** + - `templates/actor/parts/*`: Parties réutilisables des feuilles d'acteur + - `templates/actor/character/*`: Parties spécifiques aux personnages + - `templates/actor/group/*`: Parties spécifiques aux groupes + - `templates/actor/npc/*`: Parties spécifiques aux PNJ + - `templates/actor/creature/*`: Parties spécifiques aux créatures + - `templates/item/partials/*`: Parties réutilisables des feuilles d'item + +4. **Chat Cards** + - `templates/item/chatCards/*.hbs`: Cartes de chat pour chaque type d'item + +### Helpers Handlebars + +Tous les helpers sont définis dans `module/system/handlebars-manager.mjs`. + +#### Helpers de traduction +- `smarttl(arrayLabel, objectLabel, key)`: Traduction intelligente +- `smarttlk(arrayLabel, objectLabel, key)`: Traduction avec clé +- `smartcfg(configLabel, objectLabel)`: Traduction de configuration + +#### Helpers de niveau +- `skillLevel(property, level)`: Propriété d'un niveau de compétence +- `diffLevel(property, level)`: Propriété d'un niveau de difficulté +- `threatLevel(property, level)`: Propriété d'un niveau de menace +- `experienceLevel(property, level)`: Propriété d'un niveau d'expérience +- `roleLevel(property, level)`: Propriété d'un niveau de rôle +- `patternLevel(property, level)`: Propriété d'un niveau de gabarit +- `sizeLevel(property, level)`: Propriété d'un niveau de taille +- `packLevel(property, level)`: Propriété d'un niveau de meute +- `ageType(property, level)`: Propriété d'un type d'âge +- `npcThreatLevel(property, level)`: Propriété d'un niveau de menace PNJ +- `npcExperienceLevel(property, level)`: Propriété d'un niveau d'expérience PNJ +- `npcRoleLevel(property, level)`: Propriété d'un niveau de rôle PNJ +- `creaturePatternLevel(property, level)`: Propriété d'un niveau de gabarit créature +- `creatureSizeLevel(property, level)`: Propriété d'un niveau de taille créature +- `creatureRoleLevel(property, level)`: Propriété d'un niveau de rôle créature +- `creaturePackLevel(property, level)`: Propriété d'un niveau de meute créature + +#### Helpers de données +- `getDamagesData(damageObject, prop)`: Données de dégâts +- `concat(...args)`: Concatenation de chaînes +- `lower(str)`: Convertit en minuscules +- `toLowerCase(str)`: Convertit en minuscules +- `romanNumber(numb)`: Convertit un nombre en chiffre romain (I, II, III, etc.) + +#### Helpers de comparaison +- `ife(arg1, arg2, options)`: Si égal +- `ifgt(a, b, options)`: Si supérieur +- `iflt(a, b, options)`: Si inférieur +- `ifgteq(a, b, options)`: Si supérieur ou égal +- `iflteq(a, b, options)`: Si inférieur ou égal +- `ifincludes(arg1, arg2, options)`: Si contient + +#### Helpers mathématiques +- `math_add(a, b)`: Addition +- `math_subs(a, b)`: Soustraction +- `math_mult(a, b)`: Multiplication +- `math_div(a, b)`: Division + +#### Helpers de boucle +- `repeat(times, start, indexLabel, block)`: Boucle avec index +- `setVar(varName, varValue, options)`: Définit une variable + +--- + +## Traductions + +### Structure + +Les traductions sont définies dans les fichiers: +- `lang/fr.json`: Français +- `lang/en.json`: Anglais + +### Namespaces principaux + +- **SETTINGS**: Paramètres du monde +- **VERMINE**: Termes spécifiques au système + - **ability**: Caractéristiques + - **skills_title**: Compétences + - **wounds**: Blessures + - **tabs**: Onglets +- **ABILITIES**: Caractéristiques +- **SKILLS**: Compétences +- **SKILL_LEVELS**: Niveaux de compétence +- **DIFFICULTY_LEVELS**: Niveaux de difficulté +- **THREAT_LEVELS**: Niveaux de menace +- **EXPERIENCE_LEVELS**: Niveaux d'expérience +- **ROLE_LEVELS**: Niveaux de rôle +- **PATTERN_LEVELS**: Niveaux de gabarit +- **AGE_TYPES**: Types d'âge +- **ATTITUDES**: Attitudes de combat +- **ADVERSITY**: Adversité (PNJ/Créature) +- **TOTEMS**: Totems +- **GAME_MODES**: Modes de jeu +- **IDENTITY**: Identité +- **UI**: Interface utilisateur +- **ITEMS**: Items + +### Ajout de traductions + +Pour ajouter une nouvelle traduction: + +1. Ajouter la clé dans les deux fichiers (fr.json et en.json) +2. Utiliser la clé avec `game.i18n.localize('NAMESPACE.key')` ou `{{localize 'NAMESPACE.key'}}` dans les templates + +Exemple: +```json +{ + "VERMINE": { + "new_feature": "Nouvelle fonctionnalité" + } +} +``` + +--- + +## Bonnes pratiques + +### Développement + +1. **Always use strict mode** +2. **Use async/await** for asynchronous operations +3. **Error handling**: Always catch and handle errors +4. **Data validation**: Validate input data +5. **Performance**: Be mindful of performance in hooks + +### Code style + +1. **Naming**: Use camelCase for variables and functions, PascalCase for classes +2. **Comments**: Use JSDoc for functions and classes +3. **Imports**: Group related imports together +4. **Exports**: Use named exports for utility functions + +### Templates + +1. **Keep templates simple**: Move complex logic to helpers +2. **Use partials**: Reuse common template parts +3. **Accessibility**: Use proper labels and ARIA attributes +4. **Responsive**: Design for different screen sizes + +### Internationalization + +1. **Always use localization**: Never hardcode text +2. **Context**: Provide context in translation keys +3. **Fallback**: Provide fallback text for missing translations + +--- + +## Dépendances + +### Modules externes + +- **Dice So Nice**: Pour les dés 3D + - Configuration dans `module/system/dice3d.mjs` + - Dé différents pour Vermine2047 + +### Compatibilité + +- **FoundryVTT**: v11-v14 +- **Testé avec**: v12, v14 + +--- + +## Résolution des problèmes + +### Problèmes courants + +1. **Templates not found** + - Vérifier que le template existe dans le bon dossier + - Vérifier que l'extension est `.hbs` + - Vérifier que le template est pré-chargé dans `handlebars-manager.mjs` + +2. **Translations missing** + - Vérifier que la clé existe dans le fichier de langue + - Vérifier que le namespace est correct + - Utiliser `game.i18n.localize()` pour déboguer + +3. **Hooks not firing** + - Vérifier que le hook est bien enregistré + - Vérifier que le hook est appelé au bon moment + - Utiliser `CONFIG.debug.hooks = true` pour déboguer + +4. **Data not updating** + - Vérifier que les données sont bien modifiées + - Vérifier que `actor.update()` ou `item.update()` est appelé + - Vérifier que les hooks de synchronisation sont actifs + +### Outils de débogage + +1. **Console.log** + ```javascript + console.log('Debug:', data); + ``` + +2. **Notifications** + ```javascript + ui.notifications.info('Message d'information'); + ui.notifications.warn('Avertissement'); + ui.notifications.error('Erreur'); + ``` + +3. **Dialogues de débogage** + ```javascript + new Dialog({ + title: 'Debug', + content: `
${JSON.stringify(data, null, 2)}
`, + buttons: { ok: { label: 'OK' } } + }).render(true); + ``` + +--- + +## Contribution + +### Comment contribuer + +1. **Fork** le dépôt +2. **Create** une branche pour votre fonctionnalité +3. **Commit** vos changements +4. **Push** à la branche +5. **Open** une Pull Request + +### Guidelines + +1. Suivre le style de code existant +2. Ajouter des tests si possible +3. Mettre à jour la documentation +4. Utiliser des messages de commit clairs + +--- + +## Historique des versions + +Voir `CHANGELOG.md` pour l'historique complet. + +--- + +## Licence + +Voir `LICENSE.txt` pour les détails de la licence. + +--- + +## Auteurs + +- François-Xavier Guillois +- Rwanoux (Discord: rwanoux) +- Pretre (Discord: pretre) + +--- + +*Documentation générée: 2026-06-04* +*Version: 0.1.14* diff --git a/docs/user/GUIDE_UTILISATEUR.md b/docs/user/GUIDE_UTILISATEUR.md new file mode 100644 index 0000000..9089ace --- /dev/null +++ b/docs/user/GUIDE_UTILISATEUR.md @@ -0,0 +1,696 @@ +# Guide Utilisateur - Vermine2047 pour FoundryVTT + +## Table des matières +1. [Introduction](#introduction) +2. [Installation](#installation) +3. [Création d'un personnage](#création-dun-personnage) +4. [Les jets de dés](#les-jets-de-dés) +5. [Le système de totems](#le-système-de-totems) +6. [La gestion des groupes](#la-gestion-des-groupes) +7. [Le combat](#le-combat) +8. [Les items](#les-items) +9. [Gestion des PNJ et Créatures](#gestion-des-pnj-et-créatures) +10. [Astuces et bonnes pratiques](#astuces-et-bonnes-pratiques) + +--- + +## Introduction + +Bienvenue dans Vermine2047, un système pour FoundryVTT qui implémente les règles du jeu de rôle post-apocalyptique Vermine 2047. + +### À propos de Vermine2047 + +Vermine 2047 est un jeu de rôle dans un monde post-apocalyptique où les joueurs incarnent des survivants dans un environnement hostile. Le système utilise des dés d10 avec un système de seuils de réussite et des mécaniques uniques comme les totems et les domaines de prédilection. + +### Compatibilité + +- **FoundryVTT**: v11 à v14 +- **Version du système**: 0.1.14 + +--- + +## Installation + +### Prérequis + +- FoundryVTT installé (version 11 ou supérieure) +- Module "Dice So Nice!" recommandé pour les dés 3D + +### Installation du système + +1. **Via le compendium de Foundry** + - Ouvrez FoundryVTT + - Allez dans "Game Systems" + - Cliquez sur "Install System" + - Recherchez "Vermine2047" + - Cliquez sur "Install" + +2. **Via l'URL du manifest** + - Allez dans "Game Systems" + - Cliquez sur "Install System" + - Dans l'onglet "From Manifest URL", entrez: + ``` + https://raw.githubusercontent.com/rwanoux/vermine2047/refs/heads/main/system.json + ``` + - Cliquez sur "Install" + +3. **Créer un nouveau monde** + - Sélectionnez "Vermine2047" comme système + - Donnez un nom à votre monde + - Configurez les paramètres + +### Configuration recommandée + +- Activez le module "Dice So Nice!" pour les dés 3D +- Configurez le mode de jeu (Survie, Cauchemar, Apocalypse) dans les paramètres du monde + +--- + +## Création d'un personnage + +### Étape 1: Créer un acteur + +1. Cliquez sur l'icône "Actors" dans la barre latérale gauche +2. Cliquez sur "Create Actor" +3. Sélectionnez "Character" comme type +4. Donnez un nom à votre personnage + +### Étape 2: Choisir un totem + +Le totem est au cœur de votre personnage et détermine ses affinités. + +1. Dans l'onglet "Totem et ajustements" +2. Cliquez sur le bouton "choisissez un totem" +3. Sélectionnez un totem dans la liste +4. Cliquez sur "Sélectionner" + +**Les 10 totems disponibles:** +- **Humain**: Favorise les compétences humaines et du monde civilisé +- **Prédateur**: Favorise la chasse et la survie +- **Charognard**: Favorise la récupération et l'utilisation d'outils +- **Symbiote**: Favorise les interactions sociales +- **Parasite**: Favorise la discrétion et la survie +- **Bâtisseur**: Favorise la construction et la manipulation +- **Horde**: Favorise le combat en groupe +- **Ruche**: Favorise l'organisation collective +- **Solitaire**: Favorise l'autonomie +- **Adapté**: Favorise l'adaptation à l'environnement + +### Étape 3: Définir les caractéristiques + +Dans l'onglet "Caractéristiques et compétences", vous trouverez 8 caractéristiques réparties en 4 catégories: + +**Physique:** +- Vigueur: Résistance physique +- Santé: Résistance aux blessures + +**Manuel:** +- Précision: Dextérité et coordination +- Réflexes: Réactivité et vitesse + +**Mental:** +- Connaissance: Savoir et mémoire +- Perception: Observation et intuition + +**Social:** +- Volonté: Détermination et courage +- Empathie: Compréhension des autres + +**Conseil:** Commencez avec des valeurs de 1-2 pour un personnage équilibré, ou 3-4 pour un spécialiste. + +### Étape 4: Définir les compétences + +Chaque compétence a une valeur de 0 à 5: +- 0: Incompétent +- 1: Débutant +- 2: Compétent +- 3: Expert +- 4: Maître +- 5: Légende + +**Catégories de compétences:** +- **Humain**: Arts, civilisation, psychologie, rumeurs, soins +- **Animal**: Animalisme, dissection, vie sauvage, répulsion, pistes +- **Outil**: Artisanat, bricolage, mécanique, pilotage, technologie +- **Arme**: Armes à feu, tir à l'arc, armurerie, lancer, mêlée +- **Survie**: Vigilance, athlétisme, nourriture, discrétion, corps à corps +- **Monde**: Environnement, flore, route, toxiques, ruines + +**Astuce:** Le domaine de prédilection (sélectionnable en haut de chaque catégorie) donne des bonus quand il est aligné avec votre totem. + +### Étape 5: Ajouter des spécialités + +Les spécialités donnent +1D quand elles sont utilisées avec la compétence parente. + +1. Dans l'onglet "Caractéristiques et compétences" +2. Cliquez sur l'icône "+" à côté d'une compétence +3. Une spécialité sera créée avec le nom de la compétence +4. Vous pouvez renommer la spécialité + +### Étape 6: Définir les réserves + +Dans l'onglet "Combat et réserves": +- **Effort**: Réserve pour les actions physiques (basée sur Vigueur + Santé + Réflexes + Précision) +- **Sang-Froid**: Réserve pour les actions mentales (basée sur Connaissance + Perception + Volonté + Empathie) + +### Étape 7: Définir l'identité + +Dans l'onglet "Histoire": +- Age +- Origine +- Profil +- Concept +- Instincts +- Interdits +- Objectifs +- Relations +- Biographie + +### Étape 8: Équipement + +Dans l'onglet "Matériel", vous pouvez ajouter: +- Armes +- Protections +- Véhicules +- Objets + +--- + +## Les jets de dés + +### Ouvrir le dialogue de jet + +Il y a plusieurs façons de lancer un jet: + +1. **Depuis la fiche de personnage** + - Cliquez sur une caractéristique ou une compétence + - Un dialogue de jet s'ouvre avec la caractéristique/compétence présélectionnée + +2. **Depuis la barre d'outils** + - Cliquez sur l'icône de dés dans la barre d'outils (à droite) + - Un dialogue de jet vide s'ouvre + +3. **Depuis le chat** + - Tapez `/roll` ou utilisez la commande de jet + +### Le dialogue de jet + +Le dialogue de jet a été conçu pour être intuitif et compact. + +#### Sélection de base + +- **Caractéristique**: Sélectionnez une caractéristique (Vigueur, Santé, etc.) +- **Compétence**: Sélectionnez une compétence (optionnel) +- **Score**: Affiche la valeur de la caractéristique sélectionnée + +#### Difficulté et Handicap + +- **Difficulté**: Sélectionnez le niveau de difficulté + - Evidente (3+): Tâche très simple + - Facile (5+): Tâche simple + - Difficile (7+): Tâche standard + - Très difficile (9+): Tâche complexe + - Impossible (10+): Tâche extrêmement difficile + +- **Handicap**: Sélectionnez le niveau de handicap + - Aucun: Pas de handicap + - (I): Handicap mineur + - (II): Handicap majeur + +#### Bonus + +La section "Bonus" peut être dépliée pour accéder aux options supplémentaires: + +- **Entraide**: +1D si quelqu'un vous aide +- **Groupe**: +0 à +5D basé sur la taille du groupe +- **Sang-Froid**: +0 à +5D (basé sur votre réserve de Sang-Froid) +- **Équipement**: +1D si vous utilisez un outil approprié +- **Dés de totem**: Cochez pour utiliser les dés de totem + - Totem humain: +XD (où X est la valeur de votre totem humain) + - Totem adapté: +XD (où X est la valeur de votre totem adapté) + +**Astuce:** Si vous avez les deux totems (humain et adapté) avec des valeurs > 0, vous pouvez choisir quel totem garder après le jet. + +#### Total du pool de dés + +Le dialogue affiche le total du pool de dés en temps réel: +- **0D**: Aucune caractéristique sélectionnée +- **3D**: Caractéristique de valeur 3 +- **4D**: Caractéristique 3 + Compétence 1 +- **5D+**: Avec bonus + +### Les bonus de domaine de totem + +Votre totem influence vos jets en fonction du domaine de prédilection: + +- Si votre domaine de prédilection est dans les domaines de votre totem, vous obtenez +1 dé +- Si votre domaine de prédilection est dans les domaines du totem opposé, vous subissez -1 dé + +**Exemple:** +- Totem: Prédateur (domaines: animal, survie) +- Domaine de prédilection: Survie +- Bonus: +1 dé sur tous les jets de survie + +### Les réussites automatiques + +En fonction de votre niveau de maîtrise d'une compétence, vous obtenez des réussites automatiques: + +| Niveau | Réussites automatiques | Avec spécialité | +|--------|------------------------|-----------------| +| Incompétent (0) | 0 | 0 | +| Débutant (1) | 0 | 0 | +| Compétent (2) | 0 | +1 | +| Expert (3) | +1 | +1 | +| Maître (4) | +1 | +2 | +| Légende (5) | +2 | +2 | + +### Les seuils automatiques + +Si vous n'êtes pas maîtrisé dans une compétence, un seuil plus élevé est automatiquement appliqué: + +| Niveau | Seuil automatique | +|--------|-------------------| +| Incompétent (0) | 9 (Très difficile) | +| Débutant (1) | 7 (Difficile) | +| Compétent (2+) | Difficulté normale | + +### Les dés de totem + +Les dés de totem sont spéciaux: +- Ils comptent double en cas de réussite (2 réussites au lieu de 1) +- Ils sont de couleur différente pour les distinguer +- Vous pouvez utiliser les dés de totem humain et adapté simultanément +- Si vous utilisez les deux, vous pouvez choisir quel totem garder après le jet + +**Exemple:** +- Pool: 3d10 + 1d10 totem humain +- Résultat: 4, 7, 2, 9 (totem humain) +- Si le seuil est 7: 2 réussites (7 et 9) + 2 réussites supplémentaires pour le 9 du totem = 4 réussites totales + +### Relances + +Les relances sont disponibles pour les compétences maîtrisées: +- **Niveau 2 (Compétent)**: 1 relance +- **Niveau 3 (Expert)**: 1 relance +- **Niveau 4 (Maître)**: 2 relances +- **Niveau 5 (Légende)**: 2 relances + +Pour utiliser une relance: +1. Le MJ ou vous-même pouvez accorder des relances +2. Cliquez sur le dé que vous voulez relancer +3. Le dé sera marqué comme "rerolled" +4. Un nouveau jet sera effectué pour ce dé + +**Astuce:** Vous pouvez aussi utiliser votre réserve de Sang-Froid pour obtenir des relances supplémentaires. + +--- + +## Le système de totems + +### Sélection du totem + +Le totem est choisi lors de la création du personnage et détermine: +- Vos affinités naturelles +- Vos bonus de domaine +- Votre perception du monde + +### Gestion des dés de totem + +Dans la fiche de personnage, onglet "Totem et ajustements": +- Vous voyez les valeurs de vos totems humain et adapté (0-3 chacun) +- La somme maximale est de 5 (ex: 3 humain + 2 adapté) +- Cliquez sur les flèches pour ajuster les valeurs + +**Attention:** La somme des deux totems ne peut pas dépasser 5. + +### Domaines de prédilection + +Chaque catégorie de compétence peut être votre domaine de prédilection: +- Humain +- Animal +- Outil +- Arme +- Survie +- Monde + +Pour définir votre domaine de prédilection: +1. Dans l'onglet "Caractéristiques et compétences" +2. Cliquez sur le bouton radio à côté du nom de la catégorie +3. La catégorie sélectionnée devient votre domaine de prédilection + +**Bonus:** Si votre domaine de prédilection est dans les domaines de votre totem, vous obtenez des bonus supplémentaires. + +### Totems et PNJ/Créatures + +Les PNJ et créatures peuvent aussi avoir des totems, qui influencent leurs caractéristiques et comportements. + +--- + +## La gestion des groupes + +### Qu'est-ce qu'un groupe? + +Un groupe représente: +- Une communauté de survivants +- Un clan +- Une bande +- Une famille élargie + +### Créer un groupe + +1. Cliquez sur "Create Actor" +2. Sélectionnez "Group" comme type +3. Donnez un nom au groupe +4. Définissez le totem du groupe +5. Ajoutez des membres + +### Ajouter des membres à un groupe + +1. Ouvrez la fiche du groupe +2. Dans l'onglet "Membres", cliquez sur "+ Ajouter un membre" +3. Sélectionnez le personnage dans la liste +4. Cliquez sur "Ajouter" + +**Synchronisation automatique:** Quand vous ajoutez un personnage à un groupe, le groupe apparaît automatiquement dans la fiche du personnage. + +### Gérer les rencontres + +Les "rencontres" représentent les PNJ et créatures associés à un groupe: +1. Ouvrez la fiche du groupe +2. Dans l'onglet "Rencontres", cliquez sur "+ Ajouter une rencontre" +3. Sélectionnez le PNJ ou la créature +4. Cliquez sur "Ajouter" + +### Retirer un personnage d'un groupe + +1. Ouvrez la fiche du groupe +2. Trouvez le membre dans la liste +3. Cliquez sur l'icône de suppression (poubelle) +4. Confirmez + +**Synchronisation automatique:** Le personnage sera aussi retiré de la liste des groupes dans sa fiche. + +### Mode de jeu + +La fiche de personnage a deux modes: +- **Mode Edit**: Tous les champs sont modifiables +- **Mode Jeu**: Les champs sont désactivés pour éviter les modifications accidentelles + +Pour basculer entre les modes: +- Cliquez sur l'icône de cadenas en haut de la fiche + +--- + +## Le combat + +### Initiative + +L'initiative dans Vermine2047 est basée sur: +- Caractéristique: Réflexes +- Compétence: Vigilance +- Statut de combat: Offensif (+), Actif (neutre), Passif (-) + +**Formule:** `(Réflexes + Vigilance)d10cs>=difficulté` + +**Difficultés par statut:** +- Offensif: 5 (facile) +- Actif: 7 (standard) +- Passif: 9 (difficile) + +### Statuts de combat + +Chaque participant au combat a un statut: +- **Offensif**: Agressif, prend des risques +- **Actif**: Équilibré, réactif +- **Passif**: Défensif, prudent + +Pour changer le statut: +1. Dans le combat tracker +2. Cliquez sur le nom du participant +3. Sélectionnez le nouveau statut + +### Tracker de combat + +Le tracker de combat affiche: +- L'ordre d'initiative +- Le statut de chaque participant +- Les points de vie +- Les réserves + +### Actions de combat + +Les actions de combat fonctionnent comme les jets de dés normaux, mais avec: +- Des bonus spécifiques au combat +- Des modifications de difficulté basées sur le statut + +--- + +## Les items + +### Types d'items + +1. **Objet (item)**: Objet général (nourriture, outils, etc.) +2. **Arme (weapon)**: Arme de mêlée ou à distance +3. **Protection (defense)**: Armure, bouclier, etc. +4. **Véhicule (vehicle)**: Transport +5. **Capacité (ability)**: Compétence spéciale +6. **Spécialité (specialty)**: Spécialisation dans une compétence +7. **Historique (background)**: Historique du personnage +8. **Traumatisme (trauma)**: Traumatisme psychologique +9. **Évolution (evolution)**: Évolution du personnage +10. **Rumeur (rumor)**: Information +11. **Cible (target)**: Objectif +12. **Rite (rite)**: Rituel + +### Créer un item + +1. Dans la fiche du personnage +2. Allez dans l'onglet approprié (Matériel, Totem et ajustements, etc.) +3. Cliquez sur l'icône "+" à côté du titre de la section +4. Sélectionnez le type d'item +5. Remplissez les informations + +### Utiliser un item dans le chat + +1. Glissez-déposez l'item depuis votre fiche vers le chat +2. Ou cliquez sur l'icône de l'item et sélectionnez "Post to Chat" +3. Une carte avec les informations de l'item sera affichée + +### Caractéristiques des items + +**Tous les items:** +- Description +- Rareté (0-5) +- Fiabilité +- Handicap de rareté +- Quantité +- Poids +- Traits +- Dégâts + +**Armes:** +- Portée min/max +- Dégâts (valeur, type, bonus de vigueur) +- Munitions + +**Protections:** +- Niveau +- Niveau spécifique +- Mobilité +- Bouclier (oui/non) + +**Capacités:** +- Type (personnage, groupe, créature, totem) +- Totem +- Niveau +- Seuil d'apprentissage +- Handicap d'apprentissage +- Effets + +--- + +## Gestion des PNJ et Créatures + +### Créer un PNJ + +1. Cliquez sur "Create Actor" +2. Sélectionnez "NPC" comme type +3. Donnez un nom au PNJ +4. Configurez les attributs: + - Menace (1-4): Niveau de dangerosité + - Expérience (1-4): Niveau d'expérience + - Rôle (1-4): Importance dans le scénario +5. Ajoutez des compétences si nécessaire +6. Ajoutez de l'équipement + +### Créer une créature + +1. Cliquez sur "Create Actor" +2. Sélectionnez "Creature" comme type +3. Donnez un nom à la créature +4. Configurez les attributs: + - Gabarit (1-4): Type de créature + - Taille (1-3): Taille physique + - Rôle (1-4): Importance dans le scénario + - Meute (0-3): Taille du groupe + - Modes: Types de scénarios où la créature apparaît +5. Ajoutez des compétences si nécessaire + +### Menace, Expérience et Rôle (PNJ) + +Ces trois attributs déterminent les capacités du PNJ: + +**Menace:** +- Mineure (1): Peu dangereuse +- Sérieuse (2): Dangereuse +- Majeure (3): Très dangereuse +- Mortelle (4): Extrêmement dangereuse + +**Expérience:** +- Débutant (1): Peu expérimenté +- Compétent (2): Expérimenté +- Expert (3): Très expérimenté +- Maître (4): Maître dans son domaine + +**Rôle:** +- Mineur (1): Personnage secondaire +- Secondaire (2): Personnage important +- Important (3): Personnage principal +- Majeur (4): Antagoniste principal + +### Gabarit, Taille, Rôle et Meute (Créature) + +**Gabarit:** +- Insecte (1): Très petit +- Rat (2): Petit +- Chien (3): Moyen +- Ours (4): Grand + +**Taille:** +- Petit (1) +- Moyen (2) +- Grand (3) + +**Rôle:** +- Mineur (1): Créature secondaire +- Secondaire (2): Créature importante +- Important (3): Créature principale +- Majeur (4): Boss + +**Meute:** +- Solitaire (0): Agit seul +- Petit groupe (1) +- Groupe (2) +- Grande meute (3) + +**Modes:** +- Survie: Agit dans des scénarios de survie +- Cauchemar: Agit dans des scénarios de cauchemar +- Apocalypse: Agit dans des scénarios d'apocalypse + +--- + +## Astuces et bonnes pratiques + +### Pour les Joueurs + +1. **Choisissez un totem qui correspond à votre style de jeu** + - Humain: Pour les sociaux et les civils + - Prédateur: Pour les chasseurs et les guerriers + - Adapté: Pour les polyvalents + +2. **Définissez un domaine de prédilection** + - Cela vous donnera des bonus avec votre totem + +3. **Utilisez les spécialités** + - Les spécialités donnent +1D et des réussites automatiques + +4. **Gérez vos réserves** + - Effort: Pour les actions physiques + - Sang-Froid: Pour les actions mentales et les relances + +5. **Utilisez les dés de totem** + - Ils comptent double en cas de réussite + - Vous pouvez utiliser les deux totems simultanément + +### Pour les MJ + +1. **Créez des groupes pour organiser vos PNJ** + - Les groupes permettent de gérer plusieurs PNJ ensemble + - Les rencontres dans un groupe sont synchronisées + +2. **Utilisez les statuts de combat** + - Offensif pour les personnages agressifs + - Actif pour les personnages équilibrés + - Passif pour les personnages défensifs + +3. **Configurez correctement les PNJ et créatures** + - Menace/Expérience/Rôle pour les PNJ + - Gabarit/Taille/Rôle/Meute pour les créatures + +4. **Utilisez les modes pour les créatures** + - Cela permet de filtrer les créatures par type de scénario + +5. **Encouragez l'utilisation des domaines de prédilection** + - Cela rend le système de totems plus impactant + +### Pour les développeurs + +1. **Utilisez les helpers Handlebars** + - De nombreux helpers sont disponibles pour afficher les données + - `skillLevel`, `threatLevel`, etc. + +2. **Respectez les conventions de nommage** + - `vermine-` préfixe pour les classes CSS + - `VERMINE` namespace pour les configurations + +3. **Utilisez GroupLink pour la synchronisation** + - Ne modifiez pas directement les tableaux de membres/rencontres + - Utilisez les méthodes de GroupLink + +--- + +## Résolution des problèmes + +### Problèmes courants + +1. **Les dés de totem ne fonctionnent pas** + - Vérifiez que les valeurs des totems sont > 0 + - Vérifiez que la somme des totems ne dépasse pas 5 + +2. **Les bonus de domaine ne s'appliquent pas** + - Vérifiez que vous avez défini un domaine de prédilection + - Vérifiez que votre totem a des domaines configurés + +3. **Les groupes ne se synchronisent pas** + - Vérifiez que GroupLink est bien initialisé + - Vérifiez que les hooks sont actifs + +4. **Les templates ne s'affichent pas correctement** + - Vérifiez que les templates sont en `.hbs` + - Vérifiez que les références sont correctes + +### Contact + +Pour de l'aide ou pour signaler un problème: +- Rejoignez le Discord Vermine: https://discord.gg/qejqmSxr +- Rejoignez le Discord Foundry Vermine: https://discord.gg/FqGHYvXg + +--- + +## Licence + +Vermine2047 System est sous licence MIT. Voir le fichier LICENSE.txt pour plus de détails. + +--- + +## Auteurs + +- François-Xavier Guillois +- Rwanoux (Discord: rwanoux) +- Pretre (Discord: pretre) + +--- + +*Guide mis à jour: 2026-06-04* +*Version: 0.1.14* diff --git a/lang/fr.json b/lang/fr.json index 091d47e..1ba342c 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -159,6 +159,28 @@ "heavy_wounds": "Blessure grave", "deadly_wounds": "Blessure mortelle" }, + "choose_ability": "Choisissez une caractéristique", + "choose_skill": "Choisissez une compétence", + "none": "Aucun", + "total": "Total", + "bonuses": "Bonus", + "handicap": "Handicap", + "score": "Score", + "totem_dice": "Dés de totem", + "keep_totem": "Garder le totem", + "totem_hint": "Cochez pour utiliser les dés de totem (double réussite possible)", + "error_not_enough_self_control": "Vous n'avez pas assez de Sang-Froid", + "error_select_ability": "Veuillez sélectionner une caractéristique", + "error_select_skill": "Veuillez sélectionner une compétence", + "needed": "nécessaire", + "cost": "Coût", + "learn": "Apprentissage", + "ritual": "Rituel", + "trance": "Transe", + "effect": "Effet", + "types": { + "shield": "Bouclier" + }, "tabs": { "abilities": "Caractéristiques et compétences", "totem": "Totem et ajustements", @@ -223,6 +245,7 @@ "abilities": "Capacités", "specialties": "Spécialités", "new_specialty": "Nouvelle spécialité", + "shield": "Bouclier", "evolution": "Adaptation", "new_evolution": "Nouvelle adaptation", "evolutions": "Adaptations", diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 499a3c5..73db3c5 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -86,7 +86,7 @@ export class VermineItem extends Item { rollMode: rollMode, flavor: label, }; - mess.content = await renderTemplate(`systems/vermine2047/templates/item/chatCards/${this.type}.html`, { item: this, message: mess }) ?? null; + mess.content = await renderTemplate(`systems/vermine2047/templates/item/chatCards/${this.type}.hbs`, { item: this, message: mess }) ?? null; ChatMessage.create(mess) } diff --git a/module/system/config.mjs b/module/system/config.mjs index f47e558..f0e1486 100644 --- a/module/system/config.mjs +++ b/module/system/config.mjs @@ -68,6 +68,142 @@ VERMINE.PackLevels = { 3: { "attack": 5, "damage": 5, "minorWound": 3, "majorWound": 3, "deadlyWound": 3 } } +/** + * Domains of influence for each totem + * Each totem provides bonus to certain skill categories + */ +VERMINE.totemDomains = { + "human": { + "label": "TOTEMS.human.name", + "domains": ["man", "world"], + "bonus": +1, + "description": "Le totem humain favorise les compétences liées à l'humanité et au monde civilisé" + }, + "predator": { + "label": "TOTEMS.predator.name", + "domains": ["animal", "survival"], + "bonus": +1, + "description": "Le totem prédateur favorise la chasse et la survie" + }, + "scavenger": { + "label": "TOTEMS.scavenger.name", + "domains": ["tool", "world"], + "bonus": +1, + "description": "Le totem charognard favorise la récupération et l'utilisation d'outils" + }, + "symbiote": { + "label": "TOTEMS.symbiote.name", + "domains": ["man", "social"], + "bonus": +1, + "description": "Le totem symbiote favorise les interactions sociales" + }, + "parasite": { + "label": "TOTEMS.parasite.name", + "domains": ["animal", "survival"], + "bonus": +1, + "description": "Le totem parasite favorise la discrétion et la survie" + }, + "builder": { + "label": "TOTEMS.builder.name", + "domains": ["tool", "world"], + "bonus": +1, + "description": "Le totem bâtisseur favorise la construction et la manipulation" + }, + "horde": { + "label": "TOTEMS.horde.name", + "domains": ["animal", "survival"], + "bonus": +1, + "description": "Le totem horde favorise le combat en groupe" + }, + "hive": { + "label": "TOTEMS.hive.name", + "domains": ["man", "social"], + "bonus": +1, + "description": "Le totem ruche favorise l'organisation collective" + }, + "loner": { + "label": "TOTEMS.loner.name", + "domains": ["survival", "world"], + "bonus": +1, + "description": "Le totem solitaire favorise l'autonomie" + }, + "adapted": { + "label": "TOTEMS.adapted.name", + "domains": ["animal", "survival"], + "bonus": +1, + "description": "Le totem adapté favorise l'adaptation à l'environnement" + } +} + +/** + * NPC Threat Levels configuration + */ +VERMINE.npcThreatLevels = { + 1: { "label": "THREAT_LEVELS.minor", "attack": 3, "vigor": 1, "minorWound": 1, "majorWound": 1, "deadlyWound": 1 }, + 2: { "label": "THREAT_LEVELS.serious", "attack": 4, "vigor": 2, "minorWound": 2, "majorWound": 1, "deadlyWound": 1 }, + 3: { "label": "THREAT_LEVELS.major", "attack": 5, "vigor": 3, "minorWound": 2, "majorWound": 1, "deadlyWound": 1 }, + 4: { "label": "THREAT_LEVELS.deadly", "attack": 6, "vigor": 4, "minorWound": 2, "majorWound": 2, "deadlyWound": 2 } +} + +/** + * NPC Experience Levels configuration + */ +VERMINE.npcExperienceLevels = { + 1: { "label": "SKILL_LEVELS.beginner", "action": 3, "specialties": 4, "rerolls": 0, "contact": "7" }, + 2: { "label": "SKILL_LEVELS.proficient", "action": 3, "specialties": 5, "rerolls": 0, "contact": "5 ou 7" }, + 3: { "label": "SKILL_LEVELS.expert", "action": 4, "specialties": 6, "rerolls": 1, "contact": "5,7 ou 9" }, + 4: { "label": "SKILL_LEVELS.master", "action": 4, "specialties": 6, "rerolls": 2, "contact": "3,5,7 ou 9" } +} + +/** + * NPC Role Levels configuration + */ +VERMINE.npcRoleLevels = { + 1: { "label": "ROLE_LEVELS.minor", "reaction": 3, "reaction_bonus": 0, "pools": 0, "gear": 9, "gear_hindrance": 0, "protection": 1 }, + 2: { "label": "ROLE_LEVELS.secondary", "reaction": 3, "reaction_bonus": 1, "pools": 1, "gear": 9, "gear_hindrance": 1, "protection": 2 }, + 3: { "label": "ROLE_LEVELS.important", "reaction": 3, "reaction_bonus": 2, "pools": 2, "gear": 9, "gear_hindrance": 2, "protection": 3 }, + 4: { "label": "ROLE_LEVELS.major", "reaction": 4, "reaction_bonus": 2, "pools": 4, "gear": 10, "gear_hindrance": 2, "protection": 3 } +} + +/** + * Creature Pattern Levels configuration + */ +VERMINE.creaturePatternLevels = { + 1: { "label": "PATTERN_LEVELS.insect", "attack": 2, "damage": 0, "minorWound": 0, "majorWound": 0, "deadlyWound": 1 }, + 2: { "label": "PATTERN_LEVELS.rat", "attack": 3, "damage": 1, "minorWound": 0, "majorWound": 1, "deadlyWound": 1 }, + 3: { "label": "PATTERN_LEVELS.dog", "attack": 4, "damage": 3, "minorWound": 1, "majorWound": 1, "deadlyWound": 1 }, + 4: { "label": "PATTERN_LEVELS.bear", "attack": 6, "damage": 6, "minorWound": 2, "majorWound": 2, "deadlyWound": 2 } +} + +/** + * Creature Size Levels configuration + */ +VERMINE.creatureSizeLevels = { + 1: { "attack": 2, "vigor": 1, "minorWound": 0, "majorWound": 0, "deadlyWound": 1 }, + 2: { "attack": 3, "vigor": 2, "minorWound": 0, "majorWound": 1, "deadlyWound": 1 }, + 3: { "attack": 4, "vigor": 3, "minorWound": 1, "majorWound": 1, "deadlyWound": 1 } +} + +/** + * Creature Pack Levels configuration + */ +VERMINE.creaturePackLevels = { + 0: { "attack": 0, "damage": 0, "minorWound": 0, "majorWound": 0, "deadlyWound": 0 }, + 1: { "attack": 1, "damage": 1, "minorWound": 0, "majorWound": 0, "deadlyWound": 1 }, + 2: { "attack": 2, "damage": 2, "minorWound": 2, "majorWound": 2, "deadlyWound": 2 }, + 3: { "attack": 5, "damage": 5, "minorWound": 3, "majorWound": 3, "deadlyWound": 3 } +} + +/** + * Creature Role Levels configuration (same as NPC roles) + */ +VERMINE.creatureRoleLevels = { + 1: { "label": "ROLE_LEVELS.minor", "reaction": 3, "reaction_bonus": 0, "pools": 0, "gear": 9, "gear_hindrance": 0, "protection": 1 }, + 2: { "label": "ROLE_LEVELS.secondary", "reaction": 3, "reaction_bonus": 1, "pools": 1, "gear": 9, "gear_hindrance": 1, "protection": 2 }, + 3: { "label": "ROLE_LEVELS.important", "reaction": 3, "reaction_bonus": 2, "pools": 2, "gear": 9, "gear_hindrance": 2, "protection": 3 }, + 4: { "label": "ROLE_LEVELS.major", "reaction": 4, "reaction_bonus": 2, "pools": 4, "gear": 10, "gear_hindrance": 2, "protection": 3 } +} + VERMINE.abilityCategories = { "physical": { "label": "VERMINE.ability_category.physical" diff --git a/module/system/dialogs/rollDialog.mjs b/module/system/dialogs/rollDialog.mjs index 72653d3..5457ac5 100644 --- a/module/system/dialogs/rollDialog.mjs +++ b/module/system/dialogs/rollDialog.mjs @@ -110,13 +110,19 @@ export default class RollDialog extends Dialog { async activateListeners(html) { // Activate event listeners from the superclass super.activateListeners(html); + + // Initialize UI elements + this._html = html; + // Retrieve roll data and set up event listeners await this.getRollData(); - let rollInputs = html.find('[data-roll'); + + // Set up event listeners for all roll-related inputs + let rollInputs = html.find('[data-roll]'); for (let inp of rollInputs) { - // Add event listener for roll input changes - inp.addEventListener('change', await this.getRollData.bind(this)) + inp.addEventListener('change', this._onRollInputChange.bind(this)); }; + this.displaySpecialties(); let selectAbil = html.find('#ability')[0]; @@ -126,6 +132,19 @@ export default class RollDialog extends Dialog { let selfControl = html.find('#self_control')[0] // Add event listener for self control changes selfControl.addEventListener('change', this._onChangeSelfControl.bind(this)); + + // Set up difficulty change listener + html.find('#difficulty')[0].addEventListener('change', this._onDifficultyChange.bind(this)); + + // Set up handicap change listener + html.find('#handicap')[0].addEventListener('change', this._onHandicapChange.bind(this)); + + // Set up totem checkbox listeners + html.find('#human-totem')[0]?.addEventListener('change', this._onTotemChange.bind(this)); + html.find('#adapted-totem')[0]?.addEventListener('change', this._onTotemChange.bind(this)); + + // Initial update of all UI elements + this._updateUI(); }; @@ -134,7 +153,6 @@ export default class RollDialog extends Dialog { * @param {Event} ev - The event triggering the roll data retrieval. */ async getRollData(ev) { - console.log(this) // Calculate and store the roll data this.rollData = { actor: this.data.actor, @@ -146,10 +164,200 @@ export default class RollDialog extends Dialog { rollLabel: this.getLabel(), totems: this.getTotems(), self_control: this.getSelfControl(), - max_effort: this.getMaxEffort() + max_effort: this.getMaxEffort(), + keepTotem: this.getKeepTotem(), + skillCategory: this.getSkillCategory() } this.displaySpecialties(); + this._updateUI(); }; + + /** + * Gets the selected skill category + * @returns {string|null} - The skill category + */ + getSkillCategory() { + const html = this.element[0]; + const skillSelect = html.querySelector('#skill'); + if (skillSelect && skillSelect.selectedIndex > 0) { + const selectedOption = skillSelect.options[skillSelect.selectedIndex]; + return selectedOption.dataset.category || null; + } + return null; + } + + /** + * Gets the selected skill level + * @returns {number|null} - The skill level + */ + getSkillLevel() { + const html = this.element[0]; + const skillSelect = html.querySelector('#skill'); + if (skillSelect && skillSelect.selectedIndex > 0) { + const selectedOption = skillSelect.options[skillSelect.selectedIndex]; + return parseInt(selectedOption.value) || null; + } + return null; + } + + /** + * Checks if a specialty is selected + * @returns {boolean} - True if a specialty is selected + */ + hasSpecialtySelected() { + const html = this.element[0]; + const specialtyRadio = html.querySelector('input[name="usingSpecialization"]:checked'); + return specialtyRadio && specialtyRadio.value !== 'aucune'; + } + + /** + * Handles changes to roll inputs and updates UI + * @param {Event} ev - The change event + */ + async _onRollInputChange(ev) { + await this.getRollData(); + } + + /** + * Updates all UI elements based on current roll data + */ + _updateUI() { + if (!this._html) return; + + const html = this._html[0]; + + // Update total dice pool display + const totalDice = this.getDicePool(); + const totalEl = html.querySelector('#dice-pool-total'); + if (totalEl) { + totalEl.textContent = `${totalDice}D`; + } + + // Update bonus count + const bonusCount = this._calculateBonusCount(); + const bonusEl = html.querySelector('#total-bonus'); + if (bonusEl) { + bonusEl.textContent = bonusCount; + } + + // Update difficulty display + const difficultyEl = html.querySelector('#current-difficulty'); + const difficultySelect = html.querySelector('#difficulty'); + if (difficultyEl && difficultySelect) { + const selectedIndex = difficultySelect.selectedIndex; + const diffValue = parseInt(difficultySelect.options[selectedIndex].value); + const diffLabel = difficultySelect.options[selectedIndex].text.split(' ')[0]; + difficultyEl.textContent = `${diffLabel} (${diffValue})`; + } + + // Update handicap display + const handicapEl = html.querySelector('#current-handicap'); + const handicapSelect = html.querySelector('#handicap'); + if (handicapEl && handicapSelect) { + const selectedIndex = handicapSelect.selectedIndex; + handicapEl.textContent = handicapSelect.options[selectedIndex].text; + } + + // Update ability score display + const abilSelect = html.querySelector('#ability'); + const abilScoreEl = html.querySelector('#abilityScoreValue'); + if (abilSelect && abilScoreEl) { + const selectedIndex = abilSelect.selectedIndex; + if (selectedIndex > 0) { + abilScoreEl.textContent = abilSelect.options[selectedIndex].value; + } else { + abilScoreEl.textContent = '0'; + } + } + + // Update specialty display + const specialtyRadios = html.querySelectorAll('input[name="usingSpecialization"]:checked'); + const currentSpecEl = html.querySelector('.current-specialty'); + if (currentSpecEl && specialtyRadios.length > 0) { + const checkedRadio = specialtyRadios[0]; + currentSpecEl.textContent = checkedRadio.value === 'aucune' ? game.i18n.localize('VERMINE.none') : checkedRadio.value; + } + } + + /** + * Calculates the bonus count for display + * @returns {number} - Total bonus dice + */ + _calculateBonusCount() { + let bonus = 0; + + // Help bonus + if (this._html.find('#helped')[0]?.checked) { + bonus += 1; + } + + // Group bonus + const groupValue = parseInt(this._html.find('#group')[0]?.value) || 0; + bonus += groupValue; + + // Self control bonus + const selfControlValue = parseInt(this._html.find('#self_control')[0]?.value) || 0; + bonus += selfControlValue; + + // Tools bonus + const toolsChecked = this._html.find('input[name="usingTools"]:checked')[0]?.value !== '0'; + if (toolsChecked) { + bonus += 1; + } + + // Totems bonus + if (this._html.find('#human-totem')[0]?.checked) { + bonus += parseInt(this.data.actor.system.adaptation.totems.human.value) || 0; + } + if (this._html.find('#adapted-totem')[0]?.checked) { + bonus += parseInt(this.data.actor.system.adaptation.totems.adapted.value) || 0; + } + + // Specialty bonus + const specialtyChecked = this._html.find('input[name="usingSpecialization"]:checked')[0]?.value !== 'aucune'; + if (specialtyChecked) { + bonus += 1; + } + + return bonus; + } + + /** + * Handles difficulty change + * @param {Event} ev - The change event + */ + _onDifficultyChange(ev) { + this._updateUI(); + } + + /** + * Handles handicap change + * @param {Event} ev - The change event + */ + _onHandicapChange(ev) { + this._updateUI(); + } + + /** + * Handles totem checkbox change + * @param {Event} ev - The change event + */ + _onTotemChange(ev) { + this._updateUI(); + } + + /** + * Gets the selected totem to keep (for dual totem rolls) + * @returns {string|null} - The totem to keep ('human', 'adapted', or null) + */ + getKeepTotem() { + const keepTotemSelect = this._html?.find('#keep-totem-select')[0]; + if (keepTotemSelect) { + return keepTotemSelect.value; + } + // Default to null (both totems used) + return null; + } /** @@ -300,7 +508,7 @@ export default class RollDialog extends Dialog { // Check if the actor has enough self control if (this.rollData.actor.system.attributes.self_control.value < this.rollData.self_control) { // Display a warning message if self control is insufficient - ui.notifications.warn('vous navez pas assez de sang-froid'); + ui.notifications.warn(game.i18n.localize('VERMINE.error_not_enough_self_control')); // Re-render the dialog this.render(true); return false; // Exit the function if self control is insufficient @@ -308,9 +516,9 @@ export default class RollDialog extends Dialog { } let caracName = this.element[0].querySelector('[name="ability"]')?.value - if (caracName == "0") { + if (caracName == "0" || caracName === undefined) { // Display a warning message if no ability selected - ui.notifications.warn('selectionnez une caractéristique.'); + ui.notifications.warn(game.i18n.localize('VERMINE.error_select_ability')); // Re-render the dialog this.render(true); return false; // Exit the function if no ability @@ -318,10 +526,17 @@ export default class RollDialog extends Dialog { // Deduct self control points if necessary if (this.rollData.self_control > 0) { // Update the actor's self control value - await this.rollData.actor.update({ "system.attributes.self_control.value": this.rollData.actor.system.attributes.self_control.value - this.rollData.self_control }); + await this.rollData.actor.update({ + "system.attributes.self_control.value": + this.rollData.actor.system.attributes.self_control.value - this.rollData.self_control + }); } // Perform the dice roll using VermineUtils - return VermineUtils.roll({ ...this.rollData }); + return VermineUtils.roll({ + ...this.rollData, + skillLevel: this.getSkillLevel(), + hasSpecialty: this.hasSpecialtySelected() + }); } } \ No newline at end of file diff --git a/module/system/group-link.mjs b/module/system/group-link.mjs new file mode 100644 index 0000000..de2c875 --- /dev/null +++ b/module/system/group-link.mjs @@ -0,0 +1,414 @@ +/** + * GroupLink - Gestion des liens entre acteurs et groupes + * + * Cette classe permet de gérer les relations bidirectionnelles entre : + * - Les personnages (characters) et leurs groupes/rencontres + * - Les groupes (groups) et leurs membres/rencontres + * + * @author Vermine2047 System + */ + +export class GroupLink { + + /** + * Met à jour les groupes dans tous les personnages membres + * quand un groupe est modifié + * @param {Actor} group - Le groupe modifié + * @param {Object} changes - Les changements effectués + */ + static async updateActorsOnGroupChange(group, changes) { + if (group.type !== 'group') return; + + const groupData = group.system; + const members = groupData.members || []; + const encounters = groupData.encounters || []; + + // Mettre à jour les membres du groupe + if (changes.members !== undefined || changes.encounters !== undefined) { + await this._updateMembersInGroup(group, members); + await this._updateEncountersInGroup(group, encounters); + } + + // Synchroniser les données dans les acteurs membres + await this._syncGroupToMembers(group, members); + await this._syncGroupToEncounters(group, encounters); + } + + /** + * Met à jour le groupe quand un personnage est modifié + * @param {Actor} actor - L'acteur modifié + * @param {Object} changes - Les changements effectués + */ + static async updateGroupsOnActorChange(actor, changes) { + if (actor.type === 'group') return; + + const actorData = actor.system; + const encounters = actorData.encounters || []; + + // Si les rencontres de l'acteur ont changé + if (changes.encounters !== undefined) { + // Pour chaque groupe dans les rencontres, mettre à jour les membres + for (const groupId of encounters) { + const group = game.actors.get(groupId); + if (group && group.type === 'group') { + await this._updateActorInGroupMembers(group, actor.id); + } + } + } + } + + /** + * Synchronise les données du groupe vers les acteurs membres + * @param {Actor} group - Le groupe + * @param {Array} memberIds - Liste des IDs des membres + */ + static async _syncGroupToMembers(group, memberIds) { + for (const memberId of memberIds) { + const member = game.actors.get(memberId); + if (member) { + // Vérifier que le groupe est dans les rencontres du membre + const memberEncounters = member.system.encounters || []; + if (!memberEncounters.includes(group.id)) { + // Ajouter le groupe aux rencontres du membre + memberEncounters.push(group.id); + await member.update({ + 'system.encounters': memberEncounters + }); + } + } + } + } + + /** + * Synchronise les données du groupe vers les acteurs rencontres + * @param {Actor} group - Le groupe + * @param {Array} encounterIds - Liste des IDs des rencontres + */ + static async _syncGroupToEncounters(group, encounterIds) { + for (const encounterId of encounterIds) { + const encounter = game.actors.get(encounterId); + if (encounter) { + // Vérifier que le groupe est dans les rencontres de l'acteur + const encounterGroups = encounter.system.encounters || []; + if (!encounterGroups.includes(group.id)) { + encounterGroups.push(group.id); + await encounter.update({ + 'system.encounters': encounterGroups + }); + } + } + } + } + + /** + * Met à jour les membres dans un groupe + * @param {Actor} group - Le groupe + * @param {Array} memberIds - Liste des IDs des membres + */ + static async _updateMembersInGroup(group, memberIds) { + const currentMembers = group.system.members || []; + + // Retirer les membres qui ne sont plus dans la liste + const membersToRemove = currentMembers.filter(id => !memberIds.includes(id)); + const membersToAdd = memberIds.filter(id => !currentMembers.includes(id)); + + // Mettre à jour les acteurs qui ont été retirés + for (const memberId of membersToRemove) { + const member = game.actors.get(memberId); + if (member) { + const memberEncounters = (member.system.encounters || []).filter(id => id !== group.id); + await member.update({ + 'system.encounters': memberEncounters + }); + } + } + + // Mettre à jour les nouveaux membres + for (const memberId of membersToAdd) { + const member = game.actors.get(memberId); + if (member) { + const memberEncounters = member.system.encounters || []; + if (!memberEncounters.includes(group.id)) { + memberEncounters.push(group.id); + await member.update({ + 'system.encounters': memberEncounters + }); + } + } + } + } + + /** + * Met à jour les rencontres dans un groupe + * @param {Actor} group - Le groupe + * @param {Array} encounterIds - Liste des IDs des rencontres + */ + static async _updateEncountersInGroup(group, encounterIds) { + const currentEncounters = group.system.encounters || []; + + // Retirer les rencontres qui ne sont plus dans la liste + const encountersToRemove = currentEncounters.filter(id => !encounterIds.includes(id)); + const encountersToAdd = encounterIds.filter(id => !currentEncounters.includes(id)); + + // Mettre à jour les acteurs qui ont été retirés des rencontres + for (const encounterId of encountersToRemove) { + const encounter = game.actors.get(encounterId); + if (encounter) { + const encounterGroups = (encounter.system.encounters || []).filter(id => id !== group.id); + await encounter.update({ + 'system.encounters': encounterGroups + }); + } + } + + // Mettre à jour les nouvelles rencontres + for (const encounterId of encountersToAdd) { + const encounter = game.actors.get(encounterId); + if (encounter) { + const encounterGroups = encounter.system.encounters || []; + if (!encounterGroups.includes(group.id)) { + encounterGroups.push(group.id); + await encounter.update({ + 'system.encounters': encounterGroups + }); + } + } + } + } + + /** + * Met à jour un acteur dans les membres d'un groupe + * @param {Actor} group - Le groupe + * @param {string} actorId - L'ID de l'acteur + */ + static async _updateActorInGroupMembers(group, actorId) { + const groupMembers = group.system.members || []; + if (!groupMembers.includes(actorId)) { + groupMembers.push(actorId); + await group.update({ + 'system.members': groupMembers + }); + } + } + + /** + * Met à jour un acteur dans les rencontres d'un groupe + * @param {Actor} group - Le groupe + * @param {string} actorId - L'ID de l'acteur + */ + static async _updateActorInGroupEncounters(group, actorId) { + const groupEncounters = group.system.encounters || []; + if (!groupEncounters.includes(actorId)) { + groupEncounters.push(actorId); + await group.update({ + 'system.encounters': groupEncounters + }); + } + } + + /** + * Retourne les objets Actor pour une liste d'IDs + * @param {Array} actorIds - Liste d'IDs d'acteurs + * @returns {Array} - Liste d'objets Actor + */ + static getActorObjects(actorIds) { + return actorIds + .map(id => game.actors.get(id)) + .filter(actor => actor !== undefined); + } + + /** + * Retourne les objets Actor pour les membres d'un groupe + * @param {Actor} group - Le groupe + * @returns {Array} - Liste d'objets Actor + */ + static getGroupMembers(group) { + const memberIds = group.system.members || []; + return this.getActorObjects(memberIds); + } + + /** + * Retourne les objets Actor pour les rencontres d'un groupe + * @param {Actor} group - Le groupe + * @returns {Array} - Liste d'objets Actor + */ + static getGroupEncounters(group) { + const encounterIds = group.system.encounters || []; + return this.getActorObjects(encounterIds); + } + + /** + * Retourne les groupes auxquels un acteur appartient + * @param {Actor} actor - L'acteur + * @returns {Array} - Liste d'objets Actor (groupes) + */ + static getActorGroups(actor) { + const groupIds = actor.system.encounters || []; + return this.getActorObjects(groupIds).filter(a => a.type === 'group'); + } + + /** + * Retourne les rencontres (PNJ/Créatures) d'un acteur + * @param {Actor} actor - L'acteur + * @returns {Array} - Liste d'objets Actor (PNJ/Créatures) + */ + static getActorEncounters(actor) { + const encounterIds = actor.system.encounters || []; + return this.getActorObjects(encounterIds).filter(a => a.type !== 'group'); + } + + /** + * Supprime un acteur de tous ses groupes + * @param {string} actorId - L'ID de l'acteur à supprimer + */ + static async removeActorFromAllGroups(actorId) { + const allGroups = game.actors.filter(a => a.type === 'group'); + + for (const group of allGroups) { + const members = group.system.members || []; + const encounters = group.system.encounters || []; + + let needsUpdate = false; + const newMembers = members.filter(id => id !== actorId); + const newEncounters = encounters.filter(id => id !== actorId); + + if (newMembers.length !== members.length || newEncounters.length !== encounters.length) { + needsUpdate = true; + } + + if (needsUpdate) { + await group.update({ + 'system.members': newMembers, + 'system.encounters': newEncounters + }); + } + } + + // Supprimer les groupes des rencontres de l'acteur + const actor = game.actors.get(actorId); + if (actor) { + await actor.update({ + 'system.encounters': [] + }); + } + } + + /** + * Ajoute un acteur à un groupe + * @param {string} actorId - L'ID de l'acteur + * @param {string} groupId - L'ID du groupe + */ + static async addActorToGroup(actorId, groupId) { + const actor = game.actors.get(actorId); + const group = game.actors.get(groupId); + + if (!actor || !group || group.type !== 'group') return; + + // Ajouter l'acteur aux membres du groupe + const groupMembers = group.system.members || []; + if (!groupMembers.includes(actorId)) { + groupMembers.push(actorId); + await group.update({ + 'system.members': groupMembers + }); + } + + // Ajouter le groupe aux rencontres de l'acteur + const actorEncounters = actor.system.encounters || []; + if (!actorEncounters.includes(groupId)) { + actorEncounters.push(groupId); + await actor.update({ + 'system.encounters': actorEncounters + }); + } + } + + /** + * Retire un acteur d'un groupe + * @param {string} actorId - L'ID de l'acteur + * @param {string} groupId - L'ID du groupe + */ + static async removeActorFromGroup(actorId, groupId) { + const actor = game.actors.get(actorId); + const group = game.actors.get(groupId); + + if (!actor || !group || group.type !== 'group') return; + + // Retirer l'acteur des membres du groupe + const groupMembers = (group.system.members || []).filter(id => id !== actorId); + await group.update({ + 'system.members': groupMembers + }); + + // Retirer le groupe des rencontres de l'acteur + const actorEncounters = (actor.system.encounters || []).filter(id => id !== groupId); + await actor.update({ + 'system.encounters': actorEncounters + }); + } + + /** + * Initialise les hooks pour la synchronisation automatique + */ + static registerHooks() { + // Hook sur la mise à jour d'un acteur + Hooks.on('updateActor', async (actor, changes, options, userId) => { + if (!game.user.isGM && userId !== game.userId) return; + + // Si c'est un groupe qui est mis à jour + if (actor.type === 'group') { + await this.updateActorsOnGroupChange(actor, changes); + } + // Si c'est un autre acteur qui est mis à jour + else { + await this.updateGroupsOnActorChange(actor, changes); + } + }); + + // Hook sur la création d'un acteur + Hooks.on('createActor', async (actor, options, userId) => { + if (!game.user.isGM && userId !== game.userId) return; + + // Si un personnage est créé, vérifier qu'il n'a pas de groupes invalides + if (actor.type !== 'group') { + const encounters = actor.system.encounters || []; + for (const groupId of encounters) { + const group = game.actors.get(groupId); + if (!group) { + // Nettoyer les références invalides + await actor.update({ + 'system.encounters': encounters.filter(id => game.actors.get(id)) + }); + } + } + } + }); + + // Hook sur la suppression d'un acteur + Hooks.on('deleteActor', async (actor, options, userId) => { + if (!game.user.isGM && userId !== game.userId) return; + + if (actor.type === 'group') { + // Si un groupe est supprimé, nettoyer les références dans les acteurs + const memberIds = actor.system.members || []; + const encounterIds = actor.system.encounters || []; + + for (const id of [...memberIds, ...encounterIds]) { + const a = game.actors.get(id); + if (a) { + const encounters = (a.system.encounters || []).filter(eid => eid !== actor.id); + await a.update({ + 'system.encounters': encounters + }); + } + } + } else { + // Si un acteur est supprimé, le retirer de tous les groupes + await this.removeActorFromAllGroups(actor.id); + } + }); + } +} + +// Exporter pour utilisation globale +export default GroupLink; diff --git a/module/system/handlebars-manager.mjs b/module/system/handlebars-manager.mjs index fc5a584..56467b7 100644 --- a/module/system/handlebars-manager.mjs +++ b/module/system/handlebars-manager.mjs @@ -204,6 +204,90 @@ export const registerHandlebarsHelpers = function () { } }); + // return npc threat level information + Handlebars.registerHelper('npcThreatLevel', function (property, level, options) { + if (level < 1 || level > 4) + return ""; + let levelData = CONFIG.VERMINE.npcThreatLevels[level]; + if (property == 'label') { + return (levelData !== undefined) ? game.i18n.localize(levelData[property]) : ""; + } else { + return (levelData !== undefined) ? levelData[property] : ""; + } + }); + + // return npc experience level information + Handlebars.registerHelper('npcExperienceLevel', function (property, level, options) { + if (level < 1 || level > 4) + return ""; + let levelData = CONFIG.VERMINE.npcExperienceLevels[level]; + if (property == 'label') { + return (levelData !== undefined) ? game.i18n.localize(levelData[property]) : ""; + } else { + return (levelData !== undefined) ? levelData[property] : ""; + } + }); + + // return npc role level information + Handlebars.registerHelper('npcRoleLevel', function (property, level, options) { + if (level < 1 || level > 4) + return ""; + let levelData = CONFIG.VERMINE.npcRoleLevels[level]; + if (property == 'label') { + return (levelData !== undefined) ? game.i18n.localize(levelData[property]) : ""; + } else { + return (levelData !== undefined) ? levelData[property] : ""; + } + }); + + // return creature pattern level information + Handlebars.registerHelper('creaturePatternLevel', function (property, level, options) { + if (level < 1 || level > 4) + return ""; + let levelData = CONFIG.VERMINE.creaturePatternLevels[level]; + if (property == 'label') { + return (levelData !== undefined) ? game.i18n.localize(levelData[property]) : ""; + } else { + return (levelData !== undefined) ? levelData[property] : ""; + } + }); + + // return creature size level information + Handlebars.registerHelper('creatureSizeLevel', function (property, level, options) { + if (level < 1 || level > 3) + return ""; + let levelData = CONFIG.VERMINE.creatureSizeLevels[level]; + if (property == 'label') { + return (levelData !== undefined) ? game.i18n.localize(levelData[property]) : ""; + } else { + return (levelData !== undefined) ? levelData[property] : ""; + } + }); + + // return creature role level information + Handlebars.registerHelper('creatureRoleLevel', function (property, level, options) { + if (level < 1 || level > 4) + return ""; + let levelData = CONFIG.VERMINE.creatureRoleLevels[level]; + if (property == 'label') { + return (levelData !== undefined) ? game.i18n.localize(levelData[property]) : ""; + } else { + return (levelData !== undefined) ? levelData[property] : ""; + } + }); + + // return creature pack level information + Handlebars.registerHelper('creaturePackLevel', function (property, level, options) { + if (level < 0 || level > 3) + return ""; + let levelData = CONFIG.VERMINE.creaturePackLevels[level]; + if (property == 'label') { + return (levelData !== undefined) ? game.i18n.localize(levelData[property]) : ""; + } else { + return (levelData !== undefined) ? levelData[property] : ""; + } + }); + // return skill level information Handlebars.registerHelper('skillLevel', function (property, level, options) { diff --git a/module/system/roll.mjs b/module/system/roll.mjs index 963171e..db1a0b8 100644 --- a/module/system/roll.mjs +++ b/module/system/roll.mjs @@ -2,34 +2,97 @@ export class VermineUtils { /** * Méthode pour effectuer un jet de dés avec différentes options * @param {Object} options - Les options du jet de dés + * @param {Actor} options.actor - L'acteur qui lance les dés + * @param {number} options.NoD - Nombre de dés de base + * @param {number} [options.Reroll=0] - Nombre de relances autorisées + * @param {number} [options.difficulty=7] - Difficulté du jet + * @param {number} [options.self_control=0] - Sang-froid utilisé + * @param {string} [options.rollLabel="jet custom"] - Libellé du jet + * @param {Object} [options.totems={}] - Totems utilisés {human: false, adapted: false} + * @param {number} [options.max_effort=0] - Effort maximum + * @param {string} [options.skillCategory=null] - Catégorie de compétence pour les bonus de domaine + * @param {string} [options.keepTotem=null] - Totem à garder ('human' ou 'adapted') + * @param {number} [options.skillLevel=null] - Niveau de la compétence pour les réussites automatiques + * @param {boolean} [options.hasSpecialty=false] - Si une spécialité est utilisée * @returns {Roll} - Le résultat du jet de dés */ - static async roll({ actor, NoD, Reroll = 0, difficulty = 7, self_control = 0, rollLabel = "jet custom", totems = { human: false, adapted: false }, max_effort = 0 }) { + static async roll({ actor, NoD, Reroll = 0, difficulty = 7, self_control = 0, rollLabel = "jet custom", totems = { human: false, adapted: false }, max_effort = 0, skillCategory = null, keepTotem = null, skillLevel = null, hasSpecialty = false }) { // Déclaration des variables let formula = ""; let modFormula = null; + let totemBonus = { human: 0, adapted: 0 }; + + // Calculer les bonus/malus par domaine de totem + if (skillCategory) { + totemBonus = this._calculateTotemDomainBonuses(skillCategory, actor); + } + + // Appliquer les réussites automatiques et seuils auto + let autoSuccesses = 0; + let adjustedDifficulty = difficulty; + + if (skillLevel !== null && skillLevel !== undefined) { + // Calculer les réussites automatiques + autoSuccesses = this._calculateAutoSuccesses(skillLevel, hasSpecialty); + + // Appliquer le seuil automatique si nécessaire + const autoThreshold = this._getAutoThreshold(skillLevel); + if (autoThreshold !== null) { + adjustedDifficulty = autoThreshold; + } + } // Vérification des totems humains if (totems.human) { NoD--; - modFormula = "(1D10cs>=" + difficulty + `[human_${game.user.name}]*2)`; - + const humanDifficulty = skillLevel !== null ? Math.max(adjustedDifficulty, difficulty) : adjustedDifficulty; + const humanFormula = "(1D10cs>=" + humanDifficulty + `[human_${game.user.name}]*2)`; + + // Appliquer bonus/malus de domaine + if (totemBonus.human !== 0) { + // Si bonus, ajouter un dé supplémentaire, si malus, réduire le pool + NoD += totemBonus.human; + } + + modFormula = humanFormula; } + // Vérification des totems adaptés if (totems.adapted) { NoD--; + const adaptedDifficulty = skillLevel !== null ? Math.max(adjustedDifficulty, difficulty) : adjustedDifficulty; + const adaptedFormula = "(1D10cs>=" + adaptedDifficulty + `[adapted_${game.user.name}]*2)`; + + // Appliquer bonus/malus de domaine + if (totemBonus.adapted !== 0) { + NoD += totemBonus.adapted; + } + // Construction de la formule modifiée if (modFormula != null) { - modFormula = modFormula + "+(1D10cs>=" + difficulty + `[adapted_${game.user.name}]*2)`; + modFormula = modFormula + "+" + adaptedFormula; } else { - modFormula = "(1D10cs>=" + difficulty + `[adapted_${game.user.name}]*2)`; + modFormula = adaptedFormula; } - }; + // Gestion du choix de totem à garder (si les deux sont activés) + if (totems.human && totems.adapted && keepTotem) { + // Si on veut garder un seul totem, ne pas doubler le bonus + if (keepTotem === 'human' && totems.adapted) { + // Retirer le totem adapté du calcul + modFormula = "(1D10cs>=" + adjustedDifficulty + `[human_${game.user.name}]*2)`; + NoD++; // On avait décrémenté pour adapted, on annule + } else if (keepTotem === 'adapted' && totems.human) { + // Retirer le totem humain du calcul + modFormula = "(1D10cs>=" + adjustedDifficulty + `[adapted_${game.user.name}]*2)`; + NoD++; // On avait décrémenté pour human, on annule + } + } + // Construction de la formule de base let baseFormula = '' + NoD + "d10"; - baseFormula += (difficulty != undefined) ? "cs>=" + difficulty : "cs>=7"; + baseFormula += (adjustedDifficulty != undefined) ? "cs>=" + adjustedDifficulty : "cs>=7"; baseFormula += `[regular_${game.user.name}]` // Construction de la formule finale @@ -39,14 +102,129 @@ export class VermineUtils { // Création du jet de dés let roll = new Roll(formula, actor.getRollData()); + + // Stocker les métadonnées du roll pour l'affichage + roll.vermineData = { + totemsUsed: { ...totems }, + keepTotem: keepTotem, + difficulty: adjustedDifficulty, + originalDifficulty: difficulty, + skillCategory: skillCategory, + skillLevel: skillLevel, + hasSpecialty: hasSpecialty, + autoSuccesses: autoSuccesses, + totemBonuses: { ...totemBonus }, + baseNoD: NoD, + rerolls: Reroll, + selfControl: self_control + }; + //effectuer le lancé await roll.evaluate(); //afficher le lancer 3d await VermineUtils.showDiceSoNice(roll); // afficher le résultat dans le chat - VermineUtils.diplayChatRoll(roll, ...arguments); + VermineUtils.diplayChatRoll(roll, { actor, NoD, Reroll, difficulty, self_control, rollLabel, totems, max_effort, skillCategory, keepTotem, skillLevel, hasSpecialty }); return roll; } + + /** + * Calcule les bonus/malus par domaine de totem + * @param {string} skillCategory - Catégorie de la compétence + * @param {Actor} actor - L'acteur + * @returns {Object} - Bonus pour chaque totem {human: number, adapted: number} + */ + static _calculateTotemDomainBonuses(skillCategory, actor) { + const bonuses = { human: 0, adapted: 0 }; + + if (!CONFIG.VERMINE?.totemDomains || !actor?.system?.identity?.totem) { + return bonuses; + } + + const actorTotem = actor.system.identity.totem; + const totemConfig = CONFIG.VERMINE.totemDomains[actorTotem]; + + if (!totemConfig || !totemConfig.domains) { + return bonuses; + } + + // Vérifier si la catégorie de compétence est dans les domaines du totem + const preferredCategory = actor.system.skill_categories?.preferred; + + // Bonus pour le totem de l'acteur + if (preferredCategory && totemConfig.domains.includes(preferredCategory)) { + // Le domaine de prédilection est dans les domaines du totem + bonuses[actorTotem] = totemConfig.bonus || 1; + } + + // Malus pour le totem opposé + const oppositeTotem = CONFIG.VERMINE.totem_opposites?.[actorTotem]; + if (oppositeTotem && preferredCategory) { + const oppositeConfig = CONFIG.VERMINE.totemDomains[oppositeTotem]; + if (oppositeConfig?.domains?.includes(preferredCategory)) { + bonuses[oppositeTotem] = -(oppositeConfig.bonus || 1); + } + } + + return bonuses; + } + + /** + * Calcule les réussites automatiques basées sur la maîtrise de la compétence + * @param {number} skillLevel - Niveau de la compétence (0-5) + * @param {boolean} hasSpecialty - Si une spécialité est utilisée + * @returns {number} - Nombre de réussites automatiques + */ + static _calculateAutoSuccesses(skillLevel, hasSpecialty = false) { + // Selon les règles de Vermine2047, les réussites automatiques sont basées sur le niveau de maîtrise + // Niveau 0 (Incompétent): 0 réussite automatique + // Niveau 1 (Débutant): 0 réussite automatique + // Niveau 2 (Compétent): 1 réussite automatique si spécialité utilisée + // Niveau 3 (Expert): 1 réussite automatique + // Niveau 4 (Maître): 1 réussite automatique + 1 si spécialité utilisée + // Niveau 5 (Légende): 2 réussites automatiques + + if (!skillLevel) return 0; + + let autoSuccesses = 0; + + switch (skillLevel) { + case 2: // Compétent + if (hasSpecialty) autoSuccesses = 1; + break; + case 3: // Expert + autoSuccesses = 1; + break; + case 4: // Maître + autoSuccesses = 1; + if (hasSpecialty) autoSuccesses += 1; + break; + case 5: // Légende + autoSuccesses = 2; + break; + default: + autoSuccesses = 0; + } + + return autoSuccesses; + } + + /** + * Détermine le seuil automatique si la compétence n'est pas maîtrisée + * @param {number} skillLevel - Niveau de la compétence + * @returns {number|null} - Seuil automatique ou null si la compétence est maîtrisée + */ + static _getAutoThreshold(skillLevel) { + // Si la compétence n'est pas maîtrisée (niveau 0 ou 1), utiliser un seuil par défaut + // Niveau 0 (Incompétent): seuil = 9 (très difficile) + // Niveau 1 (Débutant): seuil = 7 (difficile) + // Niveau >= 2: null (utiliser le seuil normal) + + if (skillLevel === 0) return 9; // Très difficile + if (skillLevel === 1) return 7; // Difficile + + return null; // Utiliser le seuil normal + } /** * Méthode pour gérer les événements de relance de dés diff --git a/module/vermine2047.mjs b/module/vermine2047.mjs index f9f9ed4..c0ad2b6 100644 --- a/module/vermine2047.mjs +++ b/module/vermine2047.mjs @@ -1,5 +1,6 @@ import { registerHooks } from "./system/hooks.mjs"; import { registerSettings } from "./system/settings.mjs"; +import { GroupLink } from "./system/group-link.mjs"; // Import document classes. import { VermineActor } from "./documents/actor.mjs"; @@ -31,8 +32,12 @@ Hooks.once('init', async function () { VermineActor, VermineItem, VermineUtils, - VermineCombat + VermineCombat, + GroupLink }; + + // Register GroupLink hooks for automatic synchronization + GroupLink.registerHooks(); // Define custom Document classes CONFIG.Actor.documentClass = VermineActor; diff --git a/system.json b/system.json index b32fb3f..f306c93 100644 --- a/system.json +++ b/system.json @@ -5,8 +5,8 @@ "version": "0.1.13", "compatibility": { "minimum": "11", - "verified": "11.308", - "maximum": "12" + "verified": "14.0", + "maximum": "14" }, "authors": [ { diff --git a/template.json b/template.json index 1a7cc6e..1955728 100644 --- a/template.json +++ b/template.json @@ -86,6 +86,7 @@ "max": 5 } }, + "encounters": [], "abilities": { "vigor": { "value": 1, diff --git a/templates/actor/actor-creature-sheet.hbs b/templates/actor/actor-creature-sheet.hbs index cff7fc2..f4fc25e 100644 --- a/templates/actor/actor-creature-sheet.hbs +++ b/templates/actor/actor-creature-sheet.hbs @@ -10,30 +10,75 @@
- +
- +
- - ({{ patternLevel "label" system.pattern.value }}) +
- +
- +
- +
- - ({{ roleLevel "label" system.role.value }}) +
@@ -53,6 +98,101 @@ {{!-- Biography Tab --}}
+
+
+

{{ localize 'ADVERSITY.pattern'}}

+
    +
  • {{ localize 'ADVERSITY.attack'}} {{ creaturePatternLevel "attack" system.pattern.value }}
  • +
  • {{ localize 'ADVERSITY.damage'}} {{ creaturePatternLevel "damage" system.pattern.value }}
  • +
  • {{ localize 'ADVERSITY.wounds'}} + {{ creaturePatternLevel "minorWound" system.pattern.value }}/ + {{ creaturePatternLevel "majorWound" system.pattern.value }}/ + {{ creaturePatternLevel "deadlyWound" system.pattern.value }} +
  • +
+
+
+

{{ localize 'ADVERSITY.size'}}

+
    +
  • {{ localize 'ADVERSITY.attack'}} {{ creatureSizeLevel "attack" system.size.value }}
  • +
  • {{ localize 'ADVERSITY.vigor'}} {{ creatureSizeLevel "vigor" system.size.value }}
  • +
  • {{ localize 'ADVERSITY.wounds'}} + {{ creatureSizeLevel "minorWound" system.size.value }}/ + {{ creatureSizeLevel "majorWound" system.size.value }}/ + {{ creatureSizeLevel "deadlyWound" system.size.value }} +
  • +
+
+
+

{{ localize 'ADVERSITY.role'}}

+
    +
  • {{ localize 'ADVERSITY.reaction'}} {{ creatureRoleLevel "reaction" system.role.value }} + {{ creatureRoleLevel "reaction_bonus" system.role.value }}
  • +
  • {{ localize 'ADVERSITY.pools'}} {{ creatureRoleLevel "pools" system.role.value }}
  • +
  • {{ localize 'ADVERSITY.gear'}} {{ creatureRoleLevel "gear" system.role.value }}
  • +
  • {{ localize 'ADVERSITY.protection'}} {{ creatureRoleLevel "protection" system.role.value }}
  • +
+
+
+
+
+

{{ localize 'ADVERSITY.pack'}}

+
    +
  • {{ localize 'ADVERSITY.attack'}} {{ creaturePackLevel "attack" system.pack.value }}
  • +
  • {{ localize 'ADVERSITY.damage'}} {{ creaturePackLevel "damage" system.pack.value }}
  • +
  • {{ localize 'ADVERSITY.wounds'}} + {{ creaturePackLevel "minorWound" system.pack.value }}/ + {{ creaturePackLevel "majorWound" system.pack.value }}/ + {{ creaturePackLevel "deadlyWound" system.pack.value }} +
  • +
+
+
+

{{ localize 'ADVERSITY.skills'}}

+
    +
  • + +
  • +
+
+
+

{{ localize 'VERMINE.modes'}}

+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+

{{ localize 'IDENTITY.notes'}}

{{editor system.biography target="system.biography" button=true owner=owner editable=editable}} @@ -66,4 +206,3 @@
- diff --git a/templates/actor/actor-npc-sheet.hbs b/templates/actor/actor-npc-sheet.hbs index 22cc695..a5e71ab 100644 --- a/templates/actor/actor-npc-sheet.hbs +++ b/templates/actor/actor-npc-sheet.hbs @@ -9,24 +9,54 @@
- - ({{ threatLevel "label" system.threat.value }}) +
- - ({{ experienceLevel "label" system.experience.value }}) +
- +
- - ({{ roleLevel "label" system.role.value }}) +
@@ -51,9 +81,13 @@

{{ localize 'ADVERSITY.threat'}}

    -
  • {{ localize 'ADVERSITY.attack'}} {{ threatLevel "attack" system.threat.value }}
  • -
  • {{ localize 'ADVERSITY.vigor'}} {{ threatLevel "vigor" system.threat.value }}
  • -
  • {{ localize 'ADVERSITY.wounds'}} {{ threatLevel "minorWound" system.threat.value }}/{{ threatLevel "majorWound" system.threat.value }}/{{ threatLevel "deadlyWound" system.threat.value }}
  • +
  • {{ localize 'ADVERSITY.attack'}} {{ npcThreatLevel "attack" system.threat.value }}
  • +
  • {{ localize 'ADVERSITY.vigor'}} {{ npcThreatLevel "vigor" system.threat.value }}
  • +
  • {{ localize 'ADVERSITY.wounds'}} + {{ npcThreatLevel "minorWound" system.threat.value }}/ + {{ npcThreatLevel "majorWound" system.threat.value }}/ + {{ npcThreatLevel "deadlyWound" system.threat.value }} +
@@ -64,19 +98,19 @@ -
  • {{ localize 'ADVERSITY.action'}} {{ experienceLevel "action" system.experience.value }}
  • -
  • {{ localize 'ADVERSITY.specialties'}} {{ experienceLevel "specialties" system.experience.value }}
  • -
  • {{ localize 'ADVERSITY.rerolls'}} {{ experienceLevel "rerolls" system.experience.value }}
  • -
  • {{ localize 'ADVERSITY.contact'}} {{ experienceLevel "contact" system.experience.value }}
  • +
  • {{ localize 'ADVERSITY.action'}} {{ npcExperienceLevel "action" system.experience.value }}
  • +
  • {{ localize 'ADVERSITY.specialties'}} {{ npcExperienceLevel "specialties" system.experience.value }}
  • +
  • {{ localize 'ADVERSITY.rerolls'}} {{ npcExperienceLevel "rerolls" system.experience.value }}
  • +
  • {{ localize 'ADVERSITY.contact'}} {{ npcExperienceLevel "contact" system.experience.value }}
  • {{ localize 'ADVERSITY.role'}}

      -
    • {{ localize 'ADVERSITY.reaction'}} {{ roleLevel "reaction" system.role.value }} + {{ roleLevel "reaction_bonus" system.role.value }}
    • -
    • {{ localize 'ADVERSITY.pools'}} {{ roleLevel "pools" system.role.value }}
    • -
    • {{ localize 'ADVERSITY.gear'}} {{ roleLevel "gear" system.role.value }}
    • -
    • {{ localize 'ADVERSITY.protection'}} {{ roleLevel "protection" system.role.value }}
    • +
    • {{ localize 'ADVERSITY.reaction'}} {{ npcRoleLevel "reaction" system.role.value }} + {{ npcRoleLevel "reaction_bonus" system.role.value }}
    • +
    • {{ localize 'ADVERSITY.pools'}} {{ npcRoleLevel "pools" system.role.value }}
    • +
    • {{ localize 'ADVERSITY.gear'}} {{ npcRoleLevel "gear" system.role.value }}
    • +
    • {{ localize 'ADVERSITY.protection'}} {{ npcRoleLevel "protection" system.role.value }}
    @@ -98,4 +132,3 @@ - diff --git a/templates/dialogs/roll-dialog.hbs b/templates/dialogs/roll-dialog.hbs index 3d36ed9..b2b11f8 100644 --- a/templates/dialogs/roll-dialog.hbs +++ b/templates/dialogs/roll-dialog.hbs @@ -4,52 +4,22 @@ data-actor-id="{{ speakerId }}" > - - - - - - - - - - + + + + + + +
    - - -

    -
    - + + +
    + + +
    + +
    + {{localize 'VERMINE.score'}}: + 0 +
    -
    - + +
    + - {{#if availableSpecialties.length}} - -
    - - aucunes +
    +
    + + + {{#if availableSpecialties.length}} +
    + + {{localize 'VERMINE.specialty'}} (+1D) + {{localize 'VERMINE.none'}} + +
    + + {{#each availableSpecialties as |spec ind|}} + + {{/each}} +
    +
    + {{/if}} - - {{spec.name}} - - - {{/each}} - -
    - {{/if}} -
    -

    - - - -

    - - - - -

    - - - - - - -
    + +
    -

    Bonuses ?

    + {{localize 'VERMINE.difficulty'}} + + {{localize 'DIFFICULTY_LEVELS.hard'}} (7) + {{localize 'VERMINE.none'}} +
    -
    +
    +
    + + +
    +
    + + +
    +
    +
    - -
    - + +
    + + {{localize 'VERMINE.bonuses'}} + +0D + +
    + + +
    + {{#if help}}checked{{/if}} + /> +
    - -
    - + +
    + + /> + D
    - -
    -
    + +
    + + 0D + {{#ifgt @root.actor.system.adaptation.totems.human.value 0}} + {{#ifgt @root.actor.system.adaptation.totems.adapted.value 0}} + + ({{localize 'VERMINE.keep_totem'}} + ) + + {{/ifgt}} + {{/ifgt}} +
    +
    - - \ No newline at end of file + diff --git a/templates/item/chatCards/ability.hbs b/templates/item/chatCards/ability.hbs new file mode 100644 index 0000000..c74df26 --- /dev/null +++ b/templates/item/chatCards/ability.hbs @@ -0,0 +1,58 @@ +
    + {{> "systems/vermine2047/templates/item/chatCards/parts/base.hbs"}} + + {{!-- Ability specific content --}} +
    + {{#if item.system.type}} +
    + {{ localize 'VERMINE.type' }}: + {{item.system.type}} +
    + {{/if}} + + {{#if item.system.totem}} +
    + {{ localize 'IDENTITY.totem' }}: + {{ localize (lookup ../config.totems item.system.totem) }} +
    + {{/if}} + + {{#if item.system.level.value}} +
    + {{ localize 'VERMINE.level' }}: + {{item.system.level.value}} +
    + {{/if}} + + {{#if item.system.learn.threshold}} +
    + {{ localize 'VERMINE.learn' }}: + + {{item.system.learn.threshold}} + {{#ifgt item.system.learn.hindrance 0}} + / {{item.system.learn.hindrance}} + {{/ifgt}} + +
    + {{/if}} + + {{#if item.system.effects}} +
    + {{ localize 'UI.effects' }}: +
      + {{#each item.system.effects as |effect|}} +
    • {{effect}}
    • + {{/each}} +
    +
    + {{/if}} + + {{#if item.system.description}} +
    + {{ localize 'IDENTITY.notes' }}: +

    {{{item.system.description}}}

    +
    + {{/if}} +
    + +
    diff --git a/templates/item/chatCards/ability.html b/templates/item/chatCards/ability.html deleted file mode 100644 index 5e9bed2..0000000 --- a/templates/item/chatCards/ability.html +++ /dev/null @@ -1,43 +0,0 @@ -
    -
    - -
    -

    - -
    -
    - -
    -

    - - - - - - - - - -

    -

    - - - - / - - - -

    -

    description

    - {{editor system.description target="system.description" rollData=rollData button=true owner=owner editable=editable}} -
    -
    diff --git a/templates/item/chatCards/background.hbs b/templates/item/chatCards/background.hbs new file mode 100644 index 0000000..b08729f --- /dev/null +++ b/templates/item/chatCards/background.hbs @@ -0,0 +1,20 @@ +
    + {{> "systems/vermine2047/templates/item/chatCards/parts/base.hbs"}} + + {{!-- Background specific content --}} +
    + {{#if item.system.cost}} +
    + {{ localize 'VERMINE.cost' }}: + {{item.system.cost}} +
    + {{/if}} + + {{#if item.system.description}} +
    +

    {{{item.system.description}}}

    +
    + {{/if}} +
    + +
    diff --git a/templates/item/chatCards/background.html b/templates/item/chatCards/background.html deleted file mode 100644 index 71a98da..0000000 --- a/templates/item/chatCards/background.html +++ /dev/null @@ -1,43 +0,0 @@ -
    -
    - -
    -

    -
    -
    - {{!-- Sheet Body --}} -
    -

    - - - - -

    -

    description

    - {{editor system.description target="system.description" rollData=rollData button=true owner=owner editable=editable}} - -
    - - - - \ No newline at end of file diff --git a/templates/item/chatCards/defense.hbs b/templates/item/chatCards/defense.hbs new file mode 100644 index 0000000..844bea0 --- /dev/null +++ b/templates/item/chatCards/defense.hbs @@ -0,0 +1,102 @@ +
    + {{> "systems/vermine2047/templates/item/chatCards/parts/base.hbs"}} + + {{!-- Defense specific content --}} +
    + {{#if item.system.level}} +
    + {{ localize 'VERMINE.level' }}: + {{item.system.level}} +
    + {{/if}} + + {{#if item.system.specificLevel.level}} +
    + {{ localize 'VERMINE.specificLevel' }}: + + {{item.system.specificLevel.label}} + {{#if item.system.specificLevel.level}} + ({{item.system.specificLevel.level}}) + {{/if}} + +
    + {{/if}} + + {{#if item.system.mobility}} +
    + {{ localize 'VERMINE.mobility' }}: + {{item.system.mobility}} +
    + {{/if}} + + {{#if item.system.isShield}} +
    + {{ localize 'ITEMS.shield' }} +
    + {{/if}} + + {{#if item.system.quantity}} +
    + {{ localize 'VERMINE.qty' }}: + {{item.system.quantity}} +
    + {{/if}} + + {{#if item.system.weight}} +
    + {{ localize 'VERMINE.weight' }}: + {{item.system.weight}} +
    + {{/if}} + + {{#if item.system.rarity.value}} +
    + {{ localize 'VERMINE.rarity' }}: + + {{#repeat item.system.rarity.value 1 "i"}} + {{romanNumber i}} + {{/repeat}} + {{#ifgt item.system.rarity.handicap 0}} + ({{romanNumber item.system.rarity.handicap}}) + {{/ifgt}} + +
    + {{/if}} + + {{#if item.system.traits}} +
    + {{ localize 'VERMINE.traits' }}: +
      + {{#each item.system.traits as |trait key|}} + {{#if trait}} +
    • + {{localize (lookup ../config.traits key).name}}: + {{localize (lookup ../config.traits key).description}} +
    • + {{/if}} + {{/each}} +
    +
    + {{/if}} + + {{#if item.system.damages.value}} +
    + {{ localize 'VERMINE.damages' }}: + + {{getDamagesData item.system.damages "state"}} + {{#ifgt item.system.damages.value 1}} + ({{item.system.damages.value}}) + {{/ifgt}} + +
    + {{/if}} + + {{#if item.system.reliability}} +
    + {{ localize 'VERMINE.reliability' }}: + {{item.system.reliability}} +
    + {{/if}} +
    + +
    diff --git a/templates/item/chatCards/defense.html b/templates/item/chatCards/defense.html deleted file mode 100644 index 69264ef..0000000 --- a/templates/item/chatCards/defense.html +++ /dev/null @@ -1,13 +0,0 @@ -
    - {{log @root}} - -
    - image de {{item.name}} -

    {{item.name}}

    - -
    -
    -

    {{{ item.system.description}}}

    -
    - -
    diff --git a/templates/item/chatCards/evolution.hbs b/templates/item/chatCards/evolution.hbs new file mode 100644 index 0000000..e2768ee --- /dev/null +++ b/templates/item/chatCards/evolution.hbs @@ -0,0 +1,20 @@ +
    + {{> "systems/vermine2047/templates/item/chatCards/parts/base.hbs"}} + + {{!-- Evolution specific content --}} +
    + {{#if item.system.level.value}} +
    + {{ localize 'VERMINE.level' }}: + {{item.system.level.value}} +
    + {{/if}} + + {{#if item.system.description}} +
    +

    {{{item.system.description}}}

    +
    + {{/if}} +
    + +
    diff --git a/templates/item/chatCards/evolution.html b/templates/item/chatCards/evolution.html deleted file mode 100644 index 3f6338e..0000000 --- a/templates/item/chatCards/evolution.html +++ /dev/null @@ -1,21 +0,0 @@ -
    -
    - -
    -

    -
    -
    - - -
    - -

    - - -

    -

    description

    - - {{editor system.description target="system.description" rollData=rollData button=true owner=owner editable=editable}} -
    - -
    diff --git a/templates/item/chatCards/item.hbs b/templates/item/chatCards/item.hbs new file mode 100644 index 0000000..44d884c --- /dev/null +++ b/templates/item/chatCards/item.hbs @@ -0,0 +1,78 @@ +
    + {{> "systems/vermine2047/templates/item/chatCards/parts/base.hbs"}} + + {{!-- Item specific content --}} + {{#if item.system.needSkill.value}} +
    + +

    {{ smarttlk 'SKILLS' item.system.needSkill.skill 'name' }}

    +
    + {{/if}} + + {{!-- Traits --}} + {{#if item.system.traits}} +
    + +
      + {{#each item.system.traits as |trait key|}} + {{#if trait}} +
    • + {{localize (lookup ../config.traits key).name}}: + {{localize (lookup ../config.traits key).description}} +
    • + {{/if}} + {{/each}} +
    +
    + {{/if}} + + {{!-- Physical item properties --}} + {{#if item.system.quantity}} +
    + {{ localize 'VERMINE.quantity' }}: + {{item.system.quantity}} +
    + {{/if}} + + {{#if item.system.weight}} +
    + {{ localize 'VERMINE.weight' }}: + {{item.system.weight}} +
    + {{/if}} + + {{#if item.system.rarity.value}} +
    + {{ localize 'VERMINE.rarity' }}: + + {{#repeat item.system.rarity.value 1 "i"}} + {{romanNumber i}} + {{/repeat}} + {{#ifgt item.system.rarity.handicap 0}} + ({{romanNumber item.system.rarity.handicap}}) + {{/ifgt}} + +
    + {{/if}} + + {{#if item.system.damages.value}} +
    + {{ localize 'VERMINE.damages' }}: + + {{getDamagesData item.system.damages "state"}} + {{#ifgt item.system.damages.value 1}} + ({{item.system.damages.value}}) + {{/ifgt}} + +
    + {{/if}} + + {{!-- Reliability --}} + {{#if item.system.reliability}} +
    + {{ localize 'VERMINE.reliability' }}: + {{item.system.reliability}} +
    + {{/if}} + +
    diff --git a/templates/item/chatCards/item.html b/templates/item/chatCards/item.html deleted file mode 100644 index 69c83b4..0000000 --- a/templates/item/chatCards/item.html +++ /dev/null @@ -1,38 +0,0 @@ -
    - {{log this}} - {{> "systems/vermine2047/templates/item/partials/header.hbs"}} - - - {{!-- Sheet Body --}} -
    - - {{> "systems/vermine2047/templates/item/partials/traits.html"}} -
    - - {{#if system.needSkill.value}} - - - {{/if}} - -
    - - - {{> "systems/vermine2047/templates/item/partials/physicalItems.hbs"}} - -
    -
    diff --git a/templates/item/chatCards/parts/base.html b/templates/item/chatCards/parts/base.hbs similarity index 90% rename from templates/item/chatCards/parts/base.html rename to templates/item/chatCards/parts/base.hbs index 008c258..9ea9015 100644 --- a/templates/item/chatCards/parts/base.html +++ b/templates/item/chatCards/parts/base.hbs @@ -1,11 +1,11 @@ -
    - image de {{item.name}} -

    {{item.name}}

    - -
    -
    -

    {{{ item.system.description }}}

    +
    + image de {{item.name}} +

    {{item.name}}

    + +
    +
    +

    {{{ item.system.description }}}

    \ No newline at end of file diff --git a/templates/item/chatCards/rite.hbs b/templates/item/chatCards/rite.hbs new file mode 100644 index 0000000..018b22d --- /dev/null +++ b/templates/item/chatCards/rite.hbs @@ -0,0 +1,41 @@ +
    + {{> "systems/vermine2047/templates/item/chatCards/parts/base.hbs"}} + + {{!-- Rite specific content --}} +
    + {{#if item.system.rituel}} +
    + {{ localize 'VERMINE.ritual' }}: + {{item.system.rituel}} +
    + {{/if}} + + {{#if item.system.transe}} +
    + {{ localize 'VERMINE.trance' }}: + {{item.system.transe}} +
    + {{/if}} + + {{#if item.system.ability}} +
    + {{ localize 'VERMINE.ability' }}: + {{item.system.ability}} +
    + {{/if}} + + {{#if item.system.effect}} +
    + {{ localize 'UI.effects' }}: + {{item.system.effect}} +
    + {{/if}} + + {{#if item.system.description}} +
    +

    {{{item.system.description}}}

    +
    + {{/if}} +
    + +
    diff --git a/templates/item/chatCards/rite.html b/templates/item/chatCards/rite.html deleted file mode 100644 index bc08c20..0000000 --- a/templates/item/chatCards/rite.html +++ /dev/null @@ -1,41 +0,0 @@ -{{!-- This template is a fallback for when items don't have more specific templates. --}} -{{!-- Generally, you'll want to make more specific templates when possible. --}} -
    - {{> "systems/vermine2047/templates/item/partials/header.hbs"}} - - - {{!-- Sheet Body --}} -
    - -
    -
    - {{editor system.description target="system.description" button=true owner=owner editable=editable}} -
    -
    -
    -
    -

    {{ localize 'ITEMS.rituel'}}

    - -
    -
    -

    {{ localize 'ITEMS.transe'}}

    - -
    -
    -

    {{ localize 'ITEMS.effects'}}

    -
    - {{editor system.effects target="system.effects" button=true owner=owner editable=editable}} -
    -
    - diff --git a/templates/item/chatCards/rumor.hbs b/templates/item/chatCards/rumor.hbs new file mode 100644 index 0000000..da59a44 --- /dev/null +++ b/templates/item/chatCards/rumor.hbs @@ -0,0 +1,13 @@ +
    + {{> "systems/vermine2047/templates/item/chatCards/parts/base.hbs"}} + + {{!-- Rumor specific content --}} +
    + {{#if item.system.description}} +
    +

    {{{item.system.description}}}

    +
    + {{/if}} +
    + +
    diff --git a/templates/item/chatCards/rumor.html b/templates/item/chatCards/rumor.html deleted file mode 100644 index 49397f7..0000000 --- a/templates/item/chatCards/rumor.html +++ /dev/null @@ -1,11 +0,0 @@ -
    - {{> "systems/vermine2047/templates/item/partials/header.hbs"}} - - - {{!-- Sheet Body --}} -
    - - {{editor system.description target="system.description" rollData=rollData button=true owner=owner editable=editable}} - -
    -
    diff --git a/templates/item/chatCards/specialty.hbs b/templates/item/chatCards/specialty.hbs new file mode 100644 index 0000000..7516f83 --- /dev/null +++ b/templates/item/chatCards/specialty.hbs @@ -0,0 +1,20 @@ +
    + {{> "systems/vermine2047/templates/item/chatCards/parts/base.hbs"}} + + {{!-- Specialty specific content --}} +
    + {{#if item.system.skill}} +
    + {{ localize 'VERMINE.skill_title' }}: + {{ smarttlk 'SKILLS' item.system.skill 'name' }} +
    + {{/if}} + + {{#if item.system.description}} +
    +

    {{{item.system.description}}}

    +
    + {{/if}} +
    + +
    diff --git a/templates/item/chatCards/specialty.html b/templates/item/chatCards/specialty.html deleted file mode 100644 index fa2688e..0000000 --- a/templates/item/chatCards/specialty.html +++ /dev/null @@ -1,27 +0,0 @@ -
    -
    - -
    -

    - - -
    -
    - -
    diff --git a/templates/item/chatCards/target.hbs b/templates/item/chatCards/target.hbs new file mode 100644 index 0000000..a25989b --- /dev/null +++ b/templates/item/chatCards/target.hbs @@ -0,0 +1,20 @@ +
    + {{> "systems/vermine2047/templates/item/chatCards/parts/base.hbs"}} + + {{!-- Target specific content --}} +
    + {{#if item.system.level}} +
    + {{ localize 'VERMINE.level' }}: + {{item.system.level}} +
    + {{/if}} + + {{#if item.system.description}} +
    +

    {{{item.system.description}}}

    +
    + {{/if}} +
    + +
    diff --git a/templates/item/chatCards/target.html b/templates/item/chatCards/target.html deleted file mode 100644 index 49397f7..0000000 --- a/templates/item/chatCards/target.html +++ /dev/null @@ -1,11 +0,0 @@ -
    - {{> "systems/vermine2047/templates/item/partials/header.hbs"}} - - - {{!-- Sheet Body --}} -
    - - {{editor system.description target="system.description" rollData=rollData button=true owner=owner editable=editable}} - -
    -
    diff --git a/templates/item/chatCards/trauma.hbs b/templates/item/chatCards/trauma.hbs new file mode 100644 index 0000000..e8bc047 --- /dev/null +++ b/templates/item/chatCards/trauma.hbs @@ -0,0 +1,20 @@ +
    + {{> "systems/vermine2047/templates/item/chatCards/parts/base.hbs"}} + + {{!-- Trauma specific content --}} +
    + {{#if item.system.type}} +
    + {{ localize 'VERMINE.type' }}: + {{item.system.type}} +
    + {{/if}} + + {{#if item.system.description}} +
    +

    {{{item.system.description}}}

    +
    + {{/if}} +
    + +
    diff --git a/templates/item/chatCards/trauma.html b/templates/item/chatCards/trauma.html deleted file mode 100644 index d7e22ba..0000000 --- a/templates/item/chatCards/trauma.html +++ /dev/null @@ -1,24 +0,0 @@ -
    - {{> "systems/vermine2047/templates/item/partials/header.hbs"}} - - - {{!-- Sheet Body --}} -
    - -
    - -

    - - -

    -

    description

    - {{editor system.description target="system.description" rollData=rollData -button=true owner=owner editable=editable}} - -
    -
    - -
    diff --git a/templates/item/chatCards/vehicle.hbs b/templates/item/chatCards/vehicle.hbs new file mode 100644 index 0000000..670a4ec --- /dev/null +++ b/templates/item/chatCards/vehicle.hbs @@ -0,0 +1,83 @@ +
    + {{> "systems/vermine2047/templates/item/chatCards/parts/base.hbs"}} + + {{!-- Vehicle specific content --}} +
    + {{#if item.system.mobility}} +
    + {{ localize 'VERMINE.mobility' }}: + {{item.system.mobility}} +
    + {{/if}} + + {{#if item.system.quantity}} +
    + {{ localize 'VERMINE.qty' }}: + {{item.system.quantity}} +
    + {{/if}} + + {{#if item.system.weight}} +
    + {{ localize 'VERMINE.weight' }}: + {{item.system.weight}} +
    + {{/if}} + + {{#if item.system.rarity.value}} +
    + {{ localize 'VERMINE.rarity' }}: + + {{#repeat item.system.rarity.value 1 "i"}} + {{romanNumber i}} + {{/repeat}} + {{#ifgt item.system.rarity.handicap 0}} + ({{romanNumber item.system.rarity.handicap}}) + {{/ifgt}} + +
    + {{/if}} + + {{#if item.system.damages.value}} +
    + {{ localize 'VERMINE.damages' }}: + + {{getDamagesData item.system.damages "state"}} + {{#ifgt item.system.damages.value 1}} + ({{item.system.damages.value}}) + {{/ifgt}} + +
    + {{/if}} + + {{#if item.system.reliability}} +
    + {{ localize 'VERMINE.reliability' }}: + {{item.system.reliability}} +
    + {{/if}} + + {{#if item.system.traits}} +
    + {{ localize 'VERMINE.traits' }}: +
      + {{#each item.system.traits as |trait key|}} + {{#if trait}} +
    • + {{localize (lookup ../config.traits key).name}}: + {{localize (lookup ../config.traits key).description}} +
    • + {{/if}} + {{/each}} +
    +
    + {{/if}} + + {{#if item.system.description}} +
    +

    {{{item.system.description}}}

    +
    + {{/if}} +
    + +
    diff --git a/templates/item/chatCards/vehicle.html b/templates/item/chatCards/vehicle.html deleted file mode 100644 index 9b387ef..0000000 --- a/templates/item/chatCards/vehicle.html +++ /dev/null @@ -1,23 +0,0 @@ -
    - {{> "systems/vermine2047/templates/item/partials/header.hbs"}} - - -
    - {{> "systems/vermine2047/templates/item/partials/traits.html"}} - - -
    -
    - -
    - -
    -
    - - -
    - {{> "systems/vermine2047/templates/item/partials/physicalItems.hbs"}} - - -
    -
    diff --git a/templates/item/chatCards/weapon.hbs b/templates/item/chatCards/weapon.hbs new file mode 100644 index 0000000..2b90d3f --- /dev/null +++ b/templates/item/chatCards/weapon.hbs @@ -0,0 +1,104 @@ +
    + {{> "systems/vermine2047/templates/item/chatCards/parts/base.hbs"}} + + {{!-- Weapon specific content --}} +
    + {{#if item.system.damage.value}} +
    + {{ localize 'VERMINE.damage' }}: + + {{item.system.damage.value}} + {{#if item.system.damage.type}} + ({{item.system.damage.type}}) + {{/if}} + {{#if item.system.damage.addVigor}} + + {{ localize 'ABILITIES.vigor.name' }} + {{/if}} + +
    + {{/if}} + + {{#if item.system.ammo}} +
    + {{ localize 'VERMINE.ammo' }}: + {{item.system.ammo}} +
    + {{/if}} + + {{#if item.system.min_range}} +
    + {{ localize 'VERMINE.range' }}: + + {{item.system.min_range}} + {{#if item.system.max_range}} + - {{item.system.max_range}} + {{/if}} + +
    + {{/if}} + + {{#if item.system.quantity}} +
    + {{ localize 'VERMINE.qty' }}: + {{item.system.quantity}} +
    + {{/if}} + + {{#if item.system.weight}} +
    + {{ localize 'VERMINE.weight' }}: + {{item.system.weight}} +
    + {{/if}} + + {{#if item.system.rarity.value}} +
    + {{ localize 'VERMINE.rarity' }}: + + {{#repeat item.system.rarity.value 1 "i"}} + {{romanNumber i}} + {{/repeat}} + {{#ifgt item.system.rarity.handicap 0}} + ({{romanNumber item.system.rarity.handicap}}) + {{/ifgt}} + +
    + {{/if}} + + {{#if item.system.traits}} +
    + {{ localize 'VERMINE.traits' }}: +
      + {{#each item.system.traits as |trait key|}} + {{#if trait}} +
    • + {{localize (lookup ../config.traits key).name}}: + {{localize (lookup ../config.traits key).description}} +
    • + {{/if}} + {{/each}} +
    +
    + {{/if}} + + {{#if item.system.damages.value}} +
    + {{ localize 'VERMINE.damages' }}: + + {{getDamagesData item.system.damages "state"}} + {{#ifgt item.system.damages.value 1}} + ({{item.system.damages.value}}) + {{/ifgt}} + +
    + {{/if}} + + {{#if item.system.reliability}} +
    + {{ localize 'VERMINE.reliability' }}: + {{item.system.reliability}} +
    + {{/if}} +
    + +
    diff --git a/templates/item/chatCards/weapon.html b/templates/item/chatCards/weapon.html deleted file mode 100644 index 16bf5c6..0000000 --- a/templates/item/chatCards/weapon.html +++ /dev/null @@ -1,7 +0,0 @@ -
    - {{> "systems/vermine2047/templates/item/chatCards/parts/base.html"}} - -
    \ No newline at end of file diff --git a/templates/item/partials/damages.html b/templates/item/partials/damages.hbs similarity index 96% rename from templates/item/partials/damages.html rename to templates/item/partials/damages.hbs index dae3cc0..28b3bb6 100644 --- a/templates/item/partials/damages.html +++ b/templates/item/partials/damages.hbs @@ -1,30 +1,30 @@ -

    dommages

    -
    -
    - {{#repeat system.damages.max 1 index}} -
    - -
    - {{/repeat}} -
    -
    -

    pannes : {{getDamagesData system.damages "pannes"}}

    -
    -
    -

    état : {{getDamagesData system.damages "state"}}

    -
    -
    -

    effets : {{getDamagesData system.damages "effect"}}

    -
    - +

    dommages

    +
    +
    + {{#repeat system.damages.max 1 index}} +
    + +
    + {{/repeat}} +
    +
    +

    pannes : {{getDamagesData system.damages "pannes"}}

    +
    +
    +

    état : {{getDamagesData system.damages "state"}}

    +
    +
    +

    effets : {{getDamagesData system.damages "effect"}}

    +
    +
    \ No newline at end of file diff --git a/templates/item/partials/traits.html b/templates/item/partials/traits.hbs similarity index 96% rename from templates/item/partials/traits.html rename to templates/item/partials/traits.hbs index 94ce5b9..3968dcc 100644 --- a/templates/item/partials/traits.html +++ b/templates/item/partials/traits.hbs @@ -1,17 +1,17 @@ -
    -

    traits - - - - -

    -

    - {{#each item.system.traits as | trait index|}} - {{trait.name}} - {{#if trait.value}} - - {{/if}} - - {{/each}} -

    -
    +
    +

    traits + + + + +

    +

    + {{#each item.system.traits as | trait index|}} + {{trait.name}} + {{#if trait.value}} + + {{/if}} + + {{/each}} +

    +