13 Commits
Author SHA1 Message Date
uberwald 89d9d71395 style: ajuster la mise en page des fiches
Release Creation / build (release) Failing after 1m51s
Réduit la taille des polices d'en-tête et du logo, retravaille la
disposition des totems, et double la hauteur des zones Notes et
Relations dans l'onglet Histoire.
2026-08-03 15:59:57 +02:00
uberwald 1e298ed809 fix: éviter le double affichage des dés 3D
Le message de jet n'attache plus la liste des dés : Dice So Nice
réaffichait le jet via son hook updateChatMessage en plus de l'appel
explicite à showDiceSoNice, provoquant une double animation.
2026-08-03 15:59:51 +02:00
uberwald 4ffaebf52b feat: lancer les armes via la boîte de dialogue avec compétence
Ajoute un champ compétence au modèle arme, un sélecteur dans la fiche
d'arme et ouvre la boîte de dialogue de jet pré-remplie au clic sur
l'icône dé (réservé aux acteurs disposant d'aptitudes/compétences).
2026-08-03 15:59:47 +02:00
uberwald de7691c4dc fix: transmettre le handicap au message de jet (succès requis) 2026-08-01 21:35:48 +02:00
uberwald c87b25fbaf fix: aligner les jets et la boîte de dialogue avec les règles 2026-08-01 21:33:38 +02:00
uberwald 6ce51ed8cf fix: réparer les contrôles du combat tracker et la pause (v14) 2026-08-01 21:33:38 +02:00
uberwald df852f17d1 fix: aligner le JS avec l'API Foundry v14
- effects.mjs: remove e._getSourceName() (removed in v14, now a getter)
- fight.mjs: convert VermineCombatTracker from AppV1 (get template/getData)
  to AppV2 PARTS + _prepareContext/_prepareTurnContext
- fight.mjs: VermineCombat.rollInitiative v14 options-object signature
- hooks.mjs: renderPause -> renderGamePause; getSceneControlButtons now
  uses controls.tokens and tools record (v14 names/structure)
- roll.mjs: ChatMessage.create uses rolls array (v14 removed 'roll' key)
- item.mjs: getRollData returns system when no actor (v14 expects object)
- vermine2047.mjs: correct unregisterSheet API for ActorSheetV2
- base-item-sheet.mjs: call super._onRender
2026-08-01 21:33:38 +02:00
uberwald 8916600bf6 chore: ignorer le répertoire local .swival/ 2026-08-01 21:33:38 +02:00
uberwald 0189f62734 fix: importer foundry.applications.api et utiliser _onRender dans les pickers AppV2
- applications.mjs: import HandlebarsApplicationMixin/ApplicationV2 from
  foundry.applications.api (they are not globals in Foundry v14), fixing
  'ReferenceError: HandlebarsApplicationMixin is not defined'
- Replace activateListeners(html) with _onRender(context, options) and
  this.element — AppV2 does not call activateListeners
- TraitSelector: remove recursive render(true) in _validateTraits
- fight.mjs: VermineCombatTracker same _onRender conversion
2026-08-01 21:33:38 +02:00
uberwald 29b6a98c89 Fix dummy bug
Release Creation / build (release) Failing after 1m46s
2026-07-17 22:09:53 +02:00
uberwald b1d73e6b08 chore: remove CHANGELOG.md, add LICENSE.md (CC BY-NC-SA 4.0) 2026-07-12 22:32:48 +02:00
uberwald 509a5efd9d Actualiser README.md 2026-07-12 22:31:15 +02:00
uberwald fea7c1c271 docs: translate README to French with English summary 2026-07-12 22:30:16 +02:00
25 changed files with 328 additions and 362 deletions
+3
View File
@@ -19,6 +19,9 @@ foundry
# Ancien dossier SCSS (archivé)
scss_old/
# Outils d'audit local
.swival/
# Fichiers CSS générés
css/vermine2047.*.css
!css/vermine2047.css
-141
View File
@@ -1,141 +0,0 @@
# 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 `<details>`/`<summary>` 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
- ajout des rites
## 0.1.12
- passage des packs en LevelDB
## 0.1.11
- passage à une compatibilité v11
## 0.1.10
- révision du template évolution
- révision du template traumatisme
- ajout du type rite
- test import de pack
## 0.1.9
- versions condensées des méthodes (@kristov)
- renommage des templates
- image actors par défaut
- image des créatures
- ajout du totem et de l'apprentissage dans les capacités
## 0.1.8
- refactoring sheet vers actor
## 0.1.7
- sang-froid, effort, et santé
## 0.1.6
- finition de l'onglet totem
- ajout du logo de totem dans la sidebar
- ajout de traductions manquantes
## 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)
---
## 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*
+7
View File
@@ -0,0 +1,7 @@
Code Author :
- Developed by LeRatierBretonnien / www.uberwald.me
Code LICENCE :
C BY-NC-SA 4.0
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
This license requires that reusers give credit to the creator. It allows reusers to distribute, remix, adapt, and build upon the material in any medium or format, for noncommercial purposes only. If others modify or adapt the material, they must license the modified material under identical terms.
+87 -71
View File
@@ -1,51 +1,53 @@
# Vermine 2047 — Foundry VTT System
# Vermine 2047 — Système Foundry VTT
![Foundry v14](https://img.shields.io/badge/Foundry-v14-blue)
![Version](https://img.shields.io/badge/version-0.1.13-green)
Unofficial game system for **Vermine 2047**, a French post-apocalyptic tabletop RPG by **Éditions John Doe** (https://www.kickandgo.net/boutique-agate/category.php?cl=UyQcRrFtYcYrAnLbOfDj&idp=20).
Système non-officiel pour le jeu de rôle **Vermine 2047**, univers post-apo français par **Studio Agate**.
Bilingual **FR/EN** interface.
► Achetez le jeu : https://www.kickandgo.net/boutique-agate/category.php?cl=UyQcRrFtYcYrAnLbOfDj&idp=20
Interface bilingue **FR/EN**.
---
## Actor Types
## Types d'Acteurs
| Type | Sheet | Description |
| Type | Fiche | Description |
|------|-------|-------------|
| **character** | AppV2 | Player character with skills, abilities, totem, equipment |
| **npc** | AppV2 | Non-player character with role/threat, wound thresholds |
| **group** | AppV2 | Group with members, encounters, link sync |
| **creature** | AppV2 | Creature with template/size/role, custom attack stats |
| **character** | AppV2 | Personnage joueur — compétences, capacités, totem, équipement |
| **npc** | AppV2 | PNJ avec rôle/menace, seuils de blessure |
| **group** | AppV2 | Groupe avec membres, rencontres, synchro liée |
| **creature** | AppV2 | Créature avec gabarit/taille/rôle, attaques custom |
## Item Types
## Types d'Objets
| Type | Use |
|------|-----|
| **item** | Generic items (gear, resources) |
| **weapon** | Weapons with damage, range, reliability, rarity |
| **defense** | Armor/shields with protection, coverage |
| **vehicle** | Vehicles with speed, structure, weapons |
| **ability** | Special abilities (apprenticeship, domain) |
| **specialty** | Skill specializations (attached to skills) |
| **background** | Character backgrounds |
| **trauma** | Mental/physical trauma |
| **evolution** | Character evolutions |
| **rumor** | Rumors (group) |
| **target** | Targets (group) |
| **rite** | Rites (magic/rituals) |
| Type | Utilisation |
|------|-------------|
| **item** | Objets génériques (matériel, ressources) |
| **weapon** | Armes — dégâts, portée, fiabilité, rareté |
| **defense** | Armures/boucliers — protection, couverture |
| **vehicle** | Véhicules — vitesse, structure, armement |
| **ability** | Capacités spéciales (apprentissage, domaine) |
| **specialty** | Spécialités de compétence |
| **background** | Historiques de personnage |
| **trauma** | Traumatismes physiques/mentaux |
| **evolution** | Évolutions du personnage |
| **rumor** | Rumeurs (groupe) |
| **target** | Cibles (groupe) |
| **rite** | Rites (magie/rituels) |
## Dice System
## Système de Dés
- **d10-based success counting**: `Xd10cs>=N`
- Difficulty = number of successes required
- Totems give domain bonuses (human/adapted dice)
- Rerolls via Effort and Sang-froid (Composure)
- Automatic successes and failure thresholds
- **Dice So Nice!** 3D dice integration with custom d10 face images
- **Comptage de succès sur d10** : `Xd10cs>=N`
- Difficulté = nombre de succès requis
- Les totems donnent des bonus de domaine (dés humains/adaptés)
- Relances via l'Effort et le Sang-froid
- Succès automatiques et seuils d'échec
- Intégration **Dice So Nice!** avec faces de d10 personnalisées
### Initiative
Formula: `(@abilities.reflexes.value + @skills.alertness.value)d10cs>=@combatStatus.difficulty`
Formule : `(@abilities.reflexes.value + @skills.alertness.value)d10cs>=@combatStatus.difficulty`
---
@@ -53,68 +55,82 @@ Formula: `(@abilities.reflexes.value + @skills.alertness.value)d10cs>=@combatSta
```
module/
├── vermine2047.mjs # Entry point (Hooks.once init)
├── vermine2047.mjs # Point d'entrée (Hooks.once init)
├── models/ # DataModels (Foundry v2 TypeDataModel)
│ ├── actor/ # character.mjs, npc.mjs, group.mjs, creature.mjs
│ └── item/ # All 12 item types
│ └── item/ # Les 12 types d'objets
├── documents/ # VermineActor, VermineItem
├── applications/
│ └── sheets/ # AppV2 sheets per type
│ └── sheets/ # Fiches AppV2 par type
├── system/
│ ├── config.mjs # CONFIG.VERMINE (totems, traits, skills…)
│ ├── config.mjs # CONFIG.VERMINE (totems, traits, compétences…)
│ ├── roll.mjs # VermineUtils.roll()
│ ├── fight.mjs # Combat system, VermineCombat, VermineCombatant
│ ├── functions.mjs # Shared helpers (skill scores, etc.)
│ ├── hooks.mjs # Hook registrations (DiceSoNice, chat, pause…)
│ ├── dice3d.mjs # Dice So Nice colors, initUserDice
│ ├── fight.mjs # Système de combat, VermineCombat, VermineCombatant
│ ├── functions.mjs # Fonctions partagées
│ ├── hooks.mjs # Hooks (DiceSoNice, chat, pause…)
│ ├── dice3d.mjs # Couleurs Dice So Nice, initUserDice
│ ├── dialogs/rollDialog.mjs
│ ├── settings.mjs # System settings (game-mode, etc.)
│ ├── tour.mjs # Tours (stub)
│ └── group-link.mjs # GroupLink: auto-sync actors↔groups
│ ├── settings.mjs # Paramètres du système (mode de jeu…)
│ ├── tour.mjs # Visites guidées (stub)
│ └── group-link.mjs # GroupLink : synchro acteurs↔groupes
templates/
├── actor/appv2/ # AppV2 actor sheet templates
├── actor/ # Legacy templates (coexisting)
└── item/ # Item sheet templates
├── actor/appv2/ # Templates AppV2 des fiches acteurs
├── actor/ # Templates legacy (coexistence)
└── item/ # Templates des fiches objets
css/
└── vermine2047.min.css # Compiled from LESS
└── vermine2047.min.css # Compilé depuis LESS
less/
└── base.less # Main stylesheet (LESS source)
└── base.less # Source LESS principale
```
## Key Objects
## Objets Clés
- `game.vermine2047``{ VermineUtils, VermineCombat, GroupLink }`
- `CONFIG.VERMINE` → totems, traits, skills, domains, combat statuses
- `CONFIG.ui.combat` custom `VermineCombatTracker`
- `CONFIG.VERMINE` → totems, traits, compétences, domaines, états de combat
- `CONFIG.ui.combat``VermineCombatTracker` personnalisé
## Commands
## Commandes
```sh
npm run build:less # LESS → css/vermine2047.min.css
npm run build:less:dev # LESS → css/vermine2047.dev.css (unminified)
npm run build:all-css # Both
npm run build:less:dev # LESS → css/vermine2047.dev.css (non minifié)
npm run build:all-css # Les deux
npm run watch # Gulp watch + browser-sync → localhost:30000
npm run lint:less # LESS lint
npm run pushLDBtoYAML # Extract packs → src/packs/*.yml
npm run pullYAMLtoLDB # Compile packs → packs/* (LevelDB)
make build|watch|lint # Makefile wrappers
npm run lint:less # Vérification LESS
npm run pushLDBtoYAML # Extraire les packs → src/packs/*.yml
npm run pullYAMLtoLDB # Compiler les packs → packs/* (LevelDB)
make build|watch|lint # Raccourcis Makefile
```
### Requirements
- Node.js `v16.15.1` (see `.nvmrc`)
### Prérequis
- Node.js `v16.15.1` (cf `.nvmrc`)
- Foundry VTT **v14**
## Development Notes
## Notes de Développement
- **DataModels before Document classes**: In `init` hook, register `CONFIG.Actor.dataModels.*` and `CONFIG.Item.dataModels.*` **before** setting `CONFIG.Actor.documentClass` / `CONFIG.Item.documentClass`.
- **Two template systems**: AppV2 (`templates/actor/appv2/`) and legacy (`templates/actor/*.hbs`) coexist — don't confuse them.
- **Packs**: Edit YAML source in `src/packs/*.yml`, then run `pullYAMLtoLDB` to compile to LevelDB.
- **CSS**: Declared in `system.json` styles — do **not** inject `<link>` manually.
- **ESLint**: ~350 rules. Run `npx eslint module/` before committing. No `console.log` (use `console.error` sparingly).
- **editMode**: Not auto-set on actor creation (causes ActiveEffect errors). Enable manually via sheet checkbox.
- **GroupLink**: Auto-syncs on actor update. Changes propagate bidirectionally.
- **HotReload**: Enabled for css, scss, hbs, json in `system.json`.
- **DataModels avant Document classes** : Dans le hook `init`, enregistrer `CONFIG.Actor.dataModels.*` et `CONFIG.Item.dataModels.*` **avant** de définir `CONFIG.Actor.documentClass` / `CONFIG.Item.documentClass`.
- **Deux systèmes de templates** : AppV2 (`templates/actor/appv2/`) et legacy (`templates/actor/*.hbs`) coexistent.
- **Packs** : Éditer les sources YAML dans `src/packs/*.yml`, puis lancer `pullYAMLtoLDB`.
- **CSS** : Déclaré dans `system.json` — ne **pas** injecter de `<link>` manuellement.
- **ESLint** : ~350 règles. Lancer `npx eslint module/` avant chaque commit. Pas de `console.log`.
- **editMode** : Ne se met plus automatiquement à la création d'acteur (cause des erreurs ActiveEffect). Activer manuellement via la case à cocher.
- **GroupLink** : Synchronisation automatique bidirectionnelle acteurs↔groupes.
- **HotReload** : Activé pour css, scss, hbs, json dans `system.json`.
## License
---
This system is an unofficial fan project. The Vermine 2047 setting and trademarks belong to Éditions John Doe.
## English Summary
**Vermine 2047** is a French post-apocalyptic TTRPG. This is an unofficial Foundry VTT system (v14).
- **4 actor types**: character, npc, group, creature
- **12 item types**: item, weapon, defense, vehicle, ability, specialty, background, trauma, evolution, rumor, target, rite
- **Dice**: d10 success-counting with totem domains, Effort rerolls, Dice So Nice! 3D integration
- **Sheets**: AppV2 with tab navigation, edit/play mode toggle
- **Bilingual**: FR/EN interface
---
## Licence
Projet fan non-officiel. L'univers et les marques Vermine 2047 appartiennent aux Éditions John Doe.
+34
View File
@@ -0,0 +1,34 @@
# vermine2047 Audit Findings
Security audit of `vermine2047` at commit `b1d73e6b08bf`. Each finding links to a detailed write-up (`NNN-*.md`) and a proposed patch (`NNN-*.patch`).
## Summary
**Total findings: 0**
## Findings
## Dropped in adjudication
These findings reproduced in Phase 4 but the Phase 4.5 gate judged them false positives or out of a realistic threat model.
- **Unpinned mutable CI action enabling supply chain hijacking** (high, .gitea/workflows/release.yaml): Malformed URL in `uses` breaks workflow parsing; step is unreachable and prevents execution entirely.; Relies on improbable upstream compromise; standard configuration gap rather than immediate vulnerability.
## Run details
- run id: `aa4595a1`
- commit: `b1d73e6b08bf7ed0157a03137cc3bb50c8d3a26c`
- branch: `main`
- scope: 214 file(s)
- files triaged: 214
- files escalated: 14
- files deep-reviewed: 14
- findings proposed: 1
- findings verified: 0
- findings discarded: 0
- findings dropped in adjudication: 1
- artifacts: 0 written, 0 failed, 0 pending
## How to read this directory
Each finding has a paired `NNN-<slug>.md` (the human report) and `NNN-<slug>.patch` (the proposed fix as a unified diff). The report carries the narrative; the patch is the suggested change. Regenerate a single finding's artifacts with `/audit --regen --finding N`.
+10 -3
View File
@@ -31,7 +31,7 @@
font-size: 0.9rem;
}
.char-name-value {
font-size: 1.5rem;
font-size: 1.1rem;
}
input:not([type="radio"]):not([type="checkbox"]) {
font-size: 1rem;
@@ -291,7 +291,7 @@
.chooseTotem {
font-family: "DistressBlack", sans-serif;
font-size: 0.9rem;
font-size: 0.8rem;
cursor: pointer;
&:hover {
@@ -377,7 +377,7 @@
}
.char-vermine2047 {
font-size: 1.5rem;
font-size: 1.1rem;
}
}
@@ -409,6 +409,13 @@
}
}
// ── Onglet Histoire : doubler la hauteur des zones Notes et Relations ──
.tab.stories {
prose-mirror.editor {
min-height: 150px;
}
}
nav.tabs[data-group="sheet"] {
display: inline-flex;
justify-content: space-around;
+18 -3
View File
@@ -20,16 +20,25 @@
div.minor-totems {
position: relative;
height: 8.5rem;
background-color: rgba(146, 156, 111, 0.5215686275);
h3 {
position: relative;
z-index: 1;
}
h5 {
position: absolute;
top: 0;
top: 2.6rem;
margin: 0;
z-index: 1;
img {
max-width: 2rem;
position: absolute;
bottom: -2rem;
top: 1.4rem;
bottom: auto;
}
&.human, &.adapted {
@@ -53,6 +62,12 @@
}
.totem-dice {
position: absolute;
left: 0;
right: 0;
bottom: 0;
z-index: 2;
.human-dice,
.adapted-dice {
display: flex;
@@ -90,7 +105,7 @@
.adapted {
right: 0;
img { right: 0; }
img { right: 0; left: auto; }
}
}
}
@@ -230,6 +230,17 @@ export default class VermineBaseActorSheet extends HandlebarsApplicationMixin(fo
const id = target.closest("[data-item-id]")?.dataset?.itemId
if (!id) return
const item = this.document.items.get(id)
if (!item) return
if (item.type === "weapon" && this.document.system.abilities && this.document.system.skills) {
const { default: RollDialog } = await import("../../system/dialogs/rollDialog.mjs")
const dialog = await RollDialog.create({
actorId: this.document.id,
rolltype: "skill",
label: item.system.skill
})
if (dialog) dialog.render(true)
return
}
item?.roll()
}
@@ -76,6 +76,7 @@ export class VermineBaseItemSheet extends HandlebarsApplicationMixin(foundry.app
// ── Rendu ───────────────────────────────────────────────────────────
_onRender(context, options) {
super._onRender(context, options);
this.#dragDrop.forEach(d => d.bind(this.element))
}
+1 -1
View File
@@ -54,7 +54,7 @@ export default class VermineItem extends Item {
*/
getRollData() {
// If present, return the actor's roll data.
if (!this.actor) return null;
if (!this.actor) return this.system;
const rollData = this.actor.getRollData();
// Grab the item's system data as well.
rollData.item = foundry.utils.deepClone(this.system);
+1
View File
@@ -14,6 +14,7 @@ export default class VermineWeaponData extends foundry.abstract.TypeDataModel {
const reqInt = { required: true, nullable: false, integer: true }
return {
...baseItemSchema(),
skill: new fields.StringField({ required: true, nullable: false, initial: "" }),
min_range: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
max_range: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
damage: new fields.SchemaField({
+13 -12
View File
@@ -1,3 +1,5 @@
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api
export class TotemPicker extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(linkEl, actor) {
@@ -24,9 +26,9 @@ export class TotemPicker extends HandlebarsApplicationMixin(ApplicationV2) {
return { config: CONFIG.VERMINE };
}
activateListeners(html) {
super.activateListeners(html);
html.querySelectorAll('.totem').forEach(el => {
_onRender(context, options) {
super._onRender(context, options);
this.element.querySelectorAll('.totem').forEach(el => {
el.addEventListener('click', event => {
const link = event.target.closest('a');
if (!link?.dataset.totem) return;
@@ -80,9 +82,9 @@ export class ActorPicker extends HandlebarsApplicationMixin(ApplicationV2) {
};
}
activateListeners(html) {
super.activateListeners(html);
html.querySelectorAll('.actor').forEach(el => {
_onRender(context, options) {
super._onRender(context, options);
this.element.querySelectorAll('.actor').forEach(el => {
el.addEventListener('click', async event => {
const div = event.target.closest('div');
const actorId = div?.dataset.actorId;
@@ -148,15 +150,15 @@ export class TraitSelector extends HandlebarsApplicationMixin(ApplicationV2) {
};
}
activateListeners(html) {
super.activateListeners(html);
this._validateTraits(html);
html.querySelectorAll('input').forEach(el => {
_onRender(context, options) {
super._onRender(context, options);
this._validateTraits(this.element);
this.element.querySelectorAll('input').forEach(el => {
el.addEventListener('change', ev => this._onChangeInput(ev));
});
}
async _validateTraits(html) {
_validateTraits(html) {
const checks = html.querySelectorAll("input.trait-selector");
for (const inp of checks) {
if (this.targetItem.system.traits[inp.dataset.trait]) {
@@ -165,7 +167,6 @@ export class TraitSelector extends HandlebarsApplicationMixin(ApplicationV2) {
}
}
}
await this.render(true);
}
async _onChangeInput(ev) {
+2 -5
View File
@@ -121,7 +121,8 @@ export default class RollDialog extends HandlebarsApplicationMixin(foundry.appli
const specChecked = this.#el.querySelector("#usingSpecialization")?.checked;
const helped = this.#el.querySelector("#helped")?.checked;
const tools = this.#el.querySelector("input[name='usingTools']:checked")?.value !== "0";
const bonuses = (specChecked ? 1 : 0) + (helped ? 1 : 0) + (tools ? 1 : 0);
const group = parseInt(this.#el.querySelector("#group")?.value, 10) || 0;
const bonuses = (specChecked ? 1 : 0) + (helped ? 1 : 0) + (tools ? 1 : 0) + group;
return (abilVal + sc + skillPool + bonuses) || 0;
}
@@ -212,10 +213,6 @@ export default class RollDialog extends HandlebarsApplicationMixin(foundry.appli
b += parseInt(this.#getSelfCtrl()?.value, 10) || 0;
const tools = this.#el.querySelector("input[name='usingTools']:checked");
if (tools && tools.value !== "0") b += 1;
const human = this.#el.querySelector("#human-totem");
if (human?.checked) b += parseInt(this.#actor?.system?.adaptation?.totems?.human?.value, 10) || 0;
const adapted = this.#el.querySelector("#adapted-totem");
if (adapted?.checked) b += parseInt(this.#actor?.system?.adaptation?.totems?.adapted?.value, 10) || 0;
if (this.hasSpecialtySelected()) b += 1;
return b;
}
-1
View File
@@ -54,7 +54,6 @@ export function prepareActiveEffectCategories(effects) {
// Iterate over active effects, classifying them into categories
for ( let e of effects ) {
e._getSourceName(); // Trigger a lookup for the source name
if ( e.disabled ) categories.inactive.effects.push(e);
else if ( e.isTemporary ) categories.temporary.effects.push(e);
else categories.passive.effects.push(e);
+64 -15
View File
@@ -184,10 +184,9 @@ export class VermineCombat extends Combat {
// encounter check
}
async rollInitiative(ids, formula = undefined, messageOptions = {}) {
async rollInitiative(ids, {formula=null, updateTurn=true, messageMode, messageOptions={}}={}) {
// roll initiative
return super.rollInitiative(ids, formula, messageOptions)
return super.rollInitiative(ids, {formula, updateTurn, messageMode, messageOptions});
}
nextRound() {
@@ -224,24 +223,74 @@ export class VermineCombat extends Combat {
export class VermineCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
get template() {
return "systems/vermine2047/templates/combat-tracker.hbs";
}
async getData(options) {
const context = await super.getData(options);
if (!context.hasCombat) {
return context;
/** @override */
static PARTS = {
main: {
template: "systems/vermine2047/templates/combat-tracker.hbs",
root: true
}
};
/** @override */
async _prepareContext(options) {
const context = await super._prepareContext(options);
context.user = game.user;
const combat = this.viewed;
const hasCombat = combat !== null;
const combats = this.combats;
const currentIdx = combats.indexOf(combat);
const isPlayerTurn = combat?.combatant?.players?.includes(game.user);
const canControl = combat?.turn && combat.turn.between(1, combat.turns.length - 2)
? combat.canUserModify(game.user, "update", { turn: 0 })
: combat?.canUserModify(game.user, "update", { round: 0 });
Object.assign(context, {
combat,
hasCombat,
combatCount: combats.length,
currentIndex: currentIdx + 1,
previousId: combats[currentIdx - 1]?.id,
nextId: combats[currentIdx + 1]?.id,
round: combat?.round,
control: isPlayerTurn && canControl,
linked: combat?.scene !== null,
cssClass: combats.length > 7 ? "cycle" : combats.length ? "tabbed" : "",
cssId: "combat",
tabName: "combat",
labels: {
scope: game.i18n.localize(`COMBAT.${combat?.scene ? "Linked" : "Unlinked"}`)
},
turns: []
});
if ( combat ) {
for ( const [i, combatant] of combat.turns.entries() ) {
if ( !combatant.visible ) continue;
context.turns.push(await this._prepareTurnContext(combat, combatant, i));
}
}
return context;
}
activateListeners(html) {
super.activateListeners(html);
html.querySelectorAll("[data-attitude]").forEach(el => {
/** @override */
async _prepareTurnContext(combat, combatant, index) {
const turn = await super._prepareTurnContext(combat, combatant, index);
const actor = combatant.actor;
turn.attitude = combatant.getFlag("world", "attitude") || "active";
turn.isPlayer = actor?.hasPlayerOwner ?? false;
turn.isNpc = !!actor && !actor.hasPlayerOwner;
turn.owner = combatant.isOwner;
turn.defeated = combatant.isDefeated;
turn.hasRolled = combatant.initiative !== null;
turn.hasResource = combatant.resource !== null && combatant.resource !== undefined;
turn.effects = (turn.effects?.icons ?? []).map(e => e.img);
return turn;
}
_onRender(context, options) {
super._onRender(context, options);
this.element.querySelectorAll("[data-attitude]").forEach(el => {
el.addEventListener("click", this._setStatut.bind(this));
});
}
+11
View File
@@ -388,4 +388,15 @@ export const registerHandlebarsHelpers = function () {
Handlebars.registerHelper("setVar", function (varName, varValue, options) {
options.data.root[varName] = varValue;
});
// assign local variables in the current context (used by roll-message.hbs)
Handlebars.registerHelper("set", function (options) {
const ctx = (this && typeof this === "object") ? this : options.contexts?.[0];
if ( ctx && typeof ctx === "object" ) {
for ( const [key, value] of Object.entries(options.hash) ) {
ctx[key] = value;
}
}
return "";
});
}
+6 -6
View File
@@ -69,8 +69,8 @@ export const registerHooks = function () {
});
// changement de la pause
Hooks.on("renderPause", async function () {
const pauseEl = document.getElementById("pause");
Hooks.on("renderGamePause", async function (app, element) {
const pauseEl = element ?? document.getElementById("pause");
if (!pauseEl || pauseEl.className !== "paused") return;
pauseEl.querySelectorAll("img").forEach(img => {
img.src = 'systems/vermine2047/assets/images/ui/vermine_pause.webp';
@@ -94,17 +94,17 @@ export const registerHooks = function () {
});
Hooks.on('getSceneControlButtons', async (controls) => {
const tokenControls = controls.token;
const tokenControls = controls.tokens;
if (tokenControls && tokenControls.tools) {
tokenControls.tools.push({
tokenControls.tools['dice-roller'] = {
name: 'Dice Roller',
title: game.i18n.localize("VERMINE.RollTool"),
icon: 'fas fa-dice-d10',
button: true,
onClick() {
onChange() {
RollDialog.create().then(d => d.render(true));
}
});
};
}
});
+8 -76
View File
@@ -49,22 +49,13 @@ export class VermineUtils {
totemBonus = this._calculateTotemDomainBonuses(skillCategory, actor);
}
// Apply auto-successes and auto-thresholds
let autoSuccesses = 0;
// Apply automatic successes from skill mastery
let adjustedDifficulty = difficulty;
if (skillLevel !== null && skillLevel !== undefined) {
autoSuccesses = this._calculateAutoSuccesses(skillLevel, hasSpecialty);
const autoThreshold = this._getAutoThreshold(skillLevel);
if (autoThreshold !== null) {
adjustedDifficulty = autoThreshold;
}
}
// Handle human totem
if (totems.human) {
NoD--;
const humanDifficulty = skillLevel !== null ? Math.max(adjustedDifficulty, difficulty) : adjustedDifficulty;
const humanDifficulty = adjustedDifficulty;
const humanFormula = `(1D10cs>=${humanDifficulty}[human_${safeUserName}]*2)`;
// Apply domain bonus/malus
@@ -78,7 +69,7 @@ export class VermineUtils {
// Handle adapted totem
if (totems.adapted) {
NoD--;
const adaptedDifficulty = skillLevel !== null ? Math.max(adjustedDifficulty, difficulty) : adjustedDifficulty;
const adaptedDifficulty = adjustedDifficulty;
const adaptedFormula = `(1D10cs>=${adaptedDifficulty}[adapted_${safeUserName}]*2)`;
// Apply domain bonus/malus
@@ -105,8 +96,8 @@ export class VermineUtils {
}
}
// Apply handicap (wounds/maluses reduce dice pool)
NoD = Math.max(1, NoD - handicap)
// Ensure the dice pool cannot go below zero
NoD = Math.max(0, NoD)
// Build base formula
const baseFormula = `${NoD}d10cs>=${adjustedDifficulty}[regular_${safeUserName}]`;
@@ -126,7 +117,6 @@ export class VermineUtils {
skillCategory: skillCategory,
skillLevel: skillLevel,
hasSpecialty: hasSpecialty,
autoSuccesses: autoSuccesses,
totemBonuses: { ...totemBonus },
baseNoD: NoD,
rerolls: Reroll,
@@ -152,7 +142,8 @@ export class VermineUtils {
skillCategory,
keepTotem,
skillLevel,
hasSpecialty
hasSpecialty,
handicap
});
return roll;
@@ -205,63 +196,6 @@ export class VermineUtils {
return bonuses;
}
/**
* Calculates automatic successes based on skill mastery level.
* @param {number} skillLevel - Skill level (0-5)
* @param {boolean} [hasSpecialty=false] - Whether a specialty is used
* @returns {number} Number of automatic successes
*/
static _calculateAutoSuccesses(skillLevel, hasSpecialty = false) {
// According to Vermine2047 rules, automatic successes are based on mastery level:
// Level 0 (Incompetent): 0 automatic successes
// Level 1 (Beginner): 0 automatic successes
// Level 2 (Proficient): 1 automatic success if specialty is used
// Level 3 (Expert): 1 automatic success
// Level 4 (Master): 1 automatic success + 1 if specialty is used
// Level 5 (Legend): 2 automatic successes
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;
}
/**
* Determines the automatic threshold if the skill is not mastered.
* @param {number} skillLevel - Skill level
* @returns {number|null} Automatic threshold or null if skill is mastered
*/
static _getAutoThreshold(skillLevel) {
// If the skill is not mastered (level 0 or 1), use a default threshold
// Level 0 (Incompetent): threshold = 9 (very hard)
// Level 1 (Beginner): threshold = 7 (hard)
// Level >= 2: null (use normal threshold)
if (skillLevel === 0) return 9; // Very hard
if (skillLevel === 1) return 7; // Hard
return null; // Utiliser le seuil normal
}
/**
* Handles reroll events on dice in chat messages.
* @param {Object} message - The chat message containing the reroll event
@@ -471,11 +405,9 @@ export class VermineUtils {
const chatData = {
user: game.user?._id,
speaker: ChatMessage.getSpeaker(),
content: content,
roll: roll
content: content
};
const msg = await ChatMessage.create(chatData);
await msg.setFlag('world', 'roll', roll);
return msg;
}
+1 -1
View File
@@ -71,7 +71,7 @@ Hooks.once('init', async function () {
// Register sheet application classes (ApplicationV2)
// Unregister core sheets
foundry.applications.sheets.ActorSheetV2?.unregisterSheet?.("core", "Actor", {
foundry.documents.collections.Actors.unregisterSheet("core", foundry.applications.sheets.ActorSheetV2, {
types: ["character", "npc", "group", "creature"]
})
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1?.sheets?.ItemSheet)
+1 -1
View File
@@ -126,7 +126,7 @@
}
},
{
"name": "matériel",
"name": "materiel",
"label": "Matériel",
"system": "vermine2047",
"path": "packs/materiel",
+1 -1
View File
@@ -11,7 +11,7 @@
{{!-- Logo --}}
<img class="logo mx-auto" src="systems/vermine2047/assets/images/ui/logo.webp"
width="200" alt="logo Vermine" />
width="150" alt="logo Vermine" />
{{!-- Sidebar: Profile Image + Totem Dice --}}
<div class="image-wrapper">
+19 -19
View File
@@ -23,7 +23,7 @@
</a>
{{/if}}
<a class="combat-button combat-control" data-tooltip="COMBAT.Delete"
data-control="endCombat" {{#unless
data-action="endCombat" {{#unless
combatCount}}disabled{{/unless}}>
<i class="fas fa-trash"></i>
</a>
@@ -33,12 +33,12 @@
<div class="encounter-controls flexrow {{#if hasCombat}}combat{{/if}}">
{{#if user.isGM}}
<a class="combat-button combat-control"
data-tooltip="COMBAT.RollAll" data-control="rollAll" {{#unless
data-tooltip="COMBAT.RollAll" data-action="rollAll" {{#unless
turns}}disabled{{/unless}}>
<i class="fas fa-users"></i>
</a>
<a class="combat-button combat-control"
data-tooltip="COMBAT.RollNPC" data-control="rollNPC" {{#unless
data-tooltip="COMBAT.RollNPC" data-action="rollNPC" {{#unless
turns}}disabled{{/unless}}>
<i class="fas fa-users-cog"></i>
</a>
@@ -58,18 +58,18 @@
{{#if user.isGM}}
<a class="combat-button combat-control"
data-tooltip="COMBAT.InitiativeReset" data-control="resetAll"
data-tooltip="COMBAT.InitiativeReset" data-action="resetAll"
{{#unless hasCombat}}disabled{{/unless}}>
<i class="fas fa-undo"></i>
</a>
<a class="combat-button combat-control"
data-tooltip="{{labels.scope}}"
data-control="toggleSceneLink" {{#unless
data-action="toggleSceneLink" {{#unless
hasCombat}}disabled{{/unless}}>
<i class="fas fa-{{#unless linked}}un{{/unless}}link"></i>
</a>
<a class="combat-button combat-settings"
data-tooltip="COMBAT.Settings" data-control="trackerSettings">
data-tooltip="COMBAT.Settings" data-action="trackerSettings">
<i class="fas fa-cog"></i>
</a>
{{/if}}
@@ -90,20 +90,20 @@
<a
class="combatant-control {{#if turn.hidden}}active{{/if}}"
data-tooltip="COMBAT.ToggleVis"
data-control="toggleHidden">
data-action="toggleHidden">
<i class="fas fa-eye-slash"></i>
</a>
<a
class="combatant-control {{#if turn.defeated}}active{{/if}}"
data-tooltip="COMBAT.ToggleDead"
data-control="toggleDefeated">
data-action="toggleDefeated">
<i class="fas fa-skull"></i>
</a>
{{#if turn.canPing}}
<a class="combatant-control"
data-tooltip="COMBAT.PingCombatant"
data-control="alertPingCombatant">
data-action="pingCombatant">
<i class="fa-solid fa-bullseye-arrow"></i>
</a>
{{/if}}
@@ -158,7 +158,7 @@
{{else if this.owner}}
<a class="combatant-control roll"
data-tooltip="COMBAT.InitiativeRoll"
data-control="rollInitiative"></a>
data-action="rollInitiative"></a>
{{/if}}
</div>
</li>
@@ -171,30 +171,30 @@
{{#if user.isGM}}
{{#if round}}
<a class="combat-control" data-tooltip="COMBAT.RoundPrev"
data-control="previousRound"><i
data-action="previousRound"><i
class="fas fa-step-backward"></i></a>
<a class="combat-control" data-tooltip="COMBAT.TurnPrev"
data-control="previousTurn"><i
data-action="previousTurn"><i
class="fas fa-arrow-left"></i></a>
<a class="combat-control center" data-control="endCombat">{{localize
<a class="combat-control center" data-action="endCombat">{{localize
'COMBAT.End'}}</a>
<a class="combat-control" data-tooltip="COMBAT.TurnNext"
data-control="nextTurn"><i class="fas fa-arrow-right"></i></a>
data-action="nextTurn"><i class="fas fa-arrow-right"></i></a>
<a class="combat-control" data-tooltip="COMBAT.RoundNext"
data-control="nextRound"><i class="fas fa-step-forward"></i></a>
data-action="nextRound"><i class="fas fa-step-forward"></i></a>
{{else}}
<a class="combat-control center"
data-control="startCombat">{{localize
data-action="startCombat">{{localize
'COMBAT.Begin'}}</a>
{{/if}}
{{else if control}}
<a class="combat-control" data-tooltip="COMBAT.TurnPrev"
data-control="previousTurn"><i
data-action="previousTurn"><i
class="fas fa-arrow-left"></i></a>
<a class="combat-control center" data-control="nextTurn">{{localize
<a class="combat-control center" data-action="nextTurn">{{localize
'COMBAT.TurnEnd'}}</a>
<a class="combat-control" data-tooltip="COMBAT.TurnNext"
data-control="nextTurn"><i class="fas fa-arrow-right"></i></a>
data-action="nextTurn"><i class="fas fa-arrow-right"></i></a>
{{/if}}
{{/if}}
</nav>
+4 -4
View File
@@ -280,7 +280,7 @@
class="totem-checkbox"
/>
<span class="totem-label">{{localize 'TOTEMS.human.name'}}</span>
<small>(+{{@root.actor.system.adaptation.totems.human.value}}D)</small>
<small>({{@root.actor.system.adaptation.totems.human.value}}D)</small>
</label>
<label class="totem-option">
<input
@@ -292,7 +292,7 @@
class="totem-checkbox"
/>
<span class="totem-label">{{localize 'TOTEMS.adapted.name'}}</span>
<small>(+{{@root.actor.system.adaptation.totems.adapted.value}}D)</small>
<small>({{@root.actor.system.adaptation.totems.adapted.value}}D)</small>
</label>
</div>
<small class="totem-hint">{{localize 'VERMINE.totem_hint'}}</small>
@@ -309,7 +309,7 @@
value="1"
/>
<span class="totem-label">{{localize 'TOTEMS.human.name'}}</span>
<small>(+{{@root.actor.system.adaptation.totems.human.value}}D)</small>
<small>({{@root.actor.system.adaptation.totems.human.value}}D)</small>
</label>
</div>
{{/ifgt}}
@@ -326,7 +326,7 @@
value="1"
/>
<span class="totem-label">{{localize 'TOTEMS.adapted.name'}}</span>
<small>(+{{@root.actor.system.adaptation.totems.adapted.value}}D)</small>
<small>({{@root.actor.system.adaptation.totems.adapted.value}}D)</small>
</label>
</div>
{{/ifgt}}
+23
View File
@@ -3,6 +3,29 @@
<section class="sheet-body">
<div class="flexrow">
<label class="resource-label" for="system.skill">{{ localize 'VERMINE.skill_title'}}</label>
<select
name="system.skill"
class="skill-select"
>
{{#each @root.config.skillCategories as |skillCategory sckey|}}
<optgroup label="{{ smarttlk 'SKILLS_CATEGORIES' sckey 'name' }}">
{{#each @root.config.skills as |skill key|}}
{{#ife skill.category sckey}}
<option
value="{{key}}"
{{#ife key @root.system.skill}}
selected
{{/ife}}
>{{ smarttlk 'SKILLS' key 'name' }}</option>
{{/ife}}
{{/each}}
</optgroup>
{{/each}}
</select>
</div>
{{> "systems/vermine2047/templates/item/partials/traits.hbs"}}
+1 -1
View File
@@ -56,7 +56,7 @@
</div>
<div class="flexcol">
<h4>{{localize "VERMINE.success_required"}}:</h4>
<span id="required">{{param.handicap}}</span>
<span id="required">{{math_add 1 param.handicap}}</span>
</div>
</div>
</div>