Compare commits

...

77 Commits

Author SHA1 Message Date
038aa37838 Correction sur combat des PNJs
All checks were successful
Release Creation / build (release) Successful in 51s
2026-03-16 23:04:21 +01:00
25b6e30aa8 Correction sur calcul du rang
All checks were successful
Release Creation / build (release) Successful in 51s
2026-01-21 10:55:29 +01:00
f445741eda DIvers petites corrections pour les specialités et les competences
All checks were successful
Release Creation / build (release) Successful in 54s
2026-01-18 18:39:51 +01:00
d3c24e44d8 Correction sur la fortune
All checks were successful
Release Creation / build (release) Successful in 55s
2026-01-14 23:53:36 +01:00
d2cf0d80b1 Fix item Fee
All checks were successful
Release Creation / build (release) Successful in 50s
2026-01-12 20:17:53 +01:00
47350cb2f1 Fix item Fee
All checks were successful
Release Creation / build (release) Successful in 51s
2026-01-12 18:59:56 +01:00
26e46cf10a Fix item Fee
All checks were successful
Release Creation / build (release) Successful in 55s
2026-01-12 17:10:01 +01:00
4722fdf4d6 Migration vers DataModels et appv2 2026-01-10 22:55:38 +01:00
dc3040df26 Migration vers DataModels et appv2
All checks were successful
Release Creation / build (release) Successful in 53s
2026-01-10 22:51:45 +01:00
b1bce86604 Migration vers DataModels et appv2 2026-01-10 22:49:38 +01:00
6066091d8d Fix after testing 2026-01-10 22:36:38 +01:00
287b6d83a7 Fix after testing 2026-01-10 22:36:26 +01:00
d8efba89a1 Fix after testing 2026-01-10 22:36:01 +01:00
936d525503 Fix after testing 2026-01-10 22:35:39 +01:00
b113f630bf Migration vers DataModels et appv2 2026-01-10 15:08:28 +01:00
939cfb1e86 Update compendiums 2025-09-15 22:23:25 +02:00
5f5e0e2a8c Update magie 2025-09-15 21:10:46 +02:00
c993a9a5b1 Update magie 2025-09-15 21:09:09 +02:00
cc7de0e53c Amelioration diverses pour la magie 2025-09-14 20:12:02 +02:00
4d41986c12 Ajout pour la magie 2025-09-13 00:24:58 +02:00
d04731f475 Ajout pour la magie 2025-09-13 00:24:08 +02:00
44a641a0ca Update arts obscurs 2025-08-13 09:39:04 +02:00
1ad022d193 Update arts obscurs 2025-08-13 09:38:35 +02:00
1c7cf343b1 Update arts obscurs 2025-08-12 23:24:50 +02:00
d4b00e3508 Update arts obscurs 2025-08-12 23:20:51 +02:00
adc912e6cd Ajout/upgrade des arts obscurs 2025-08-11 22:53:23 +02:00
51a457ebf6 Foundry v13 migration 2025-05-02 08:34:22 +02:00
2e9c558027 Ajout arts obscurs 2024-11-17 22:46:52 +01:00
bcd0758328 Ajout arts obscurs 2024-11-17 22:45:48 +01:00
2b680a203f Fix jet avec prestance 2024-08-11 14:40:41 +02:00
e3d7874dce Fix masque/demaque + pouvoir passif avec point 2024-07-30 13:45:50 +02:00
ab6a5832c0 Various v12 fixes 2024-07-13 12:13:06 +02:00
d83a999974 Various v12 fixes 2024-07-13 12:12:38 +02:00
b83890a764 Add v12 support 2024-05-23 15:38:50 +02:00
5ad3c165e5 Add v12 support 2024-05-23 11:25:51 +02:00
2b3e774cbb Correction sur les pouvoirs des fiches de PNJ 2024-04-25 10:30:15 +02:00
96f8d2bceb Fix roll pouvoir + pouvoirs passifs 2024-04-11 12:36:50 +02:00
e288c90ee4 Bouton d'ajout d'items dans la fiche PNJ 2024-04-01 17:27:20 +02:00
8916de8613 Bouton d'ajout d'items dans la fiche 2024-03-31 17:53:56 +02:00
8598df5a57 Ajout de la gestion des Points d'usage 2024-03-23 11:37:15 +01:00
8781462c8d Correction sur competences à 0 + diverses ameliorations 2024-03-06 19:00:01 +01:00
8c38aead3e Correction sur competences à 0 + diverses ameliorations 2024-03-06 18:46:53 +01:00
67bf71e6c0 Fix competences 2024-03-01 13:39:32 +01:00
63d15e82bb Enhance stats 2024-02-08 12:56:25 +01:00
62c3787cea Affichage des specialisations 2023-12-19 22:13:26 +01:00
df61abac19 Gestion des heritages 2023-12-19 21:35:20 +01:00
a7d1a14c52 Fix css for compendiums 2023-05-29 17:00:57 +02:00
b0dc6f36e4 Fix css for compendiums 2023-05-29 16:33:08 +02:00
5109d2aa91 Modification CSS pour compendiums 2023-05-29 13:22:43 +02:00
51c162ecbb v10/v11 compat 2023-05-25 15:43:55 +02:00
44d02b0cd1 Fix sur pouvoirs, heritage et 2 pts de tricherie 2023-05-01 18:48:34 +02:00
9b1600304a Fix sur pouvoirs, heritage et 2 pts de tricherie 2023-04-30 20:08:29 +02:00
2dff59c829 Fix pv value pour PNJ 2023-04-24 15:59:07 +02:00
55a2a8e3c3 Fix malus 2023-04-23 23:13:31 +02:00
2da1f56a91 Ajout XP + rework fees 2023-04-23 17:13:08 +02:00
66bd9dd2c8 Ajout XP + rework fees 2023-04-23 17:12:43 +02:00
15427f3747 Gestion des attaques ciblées 2023-04-11 13:29:04 +02:00
577eccbbd3 Gestion des attaques ciblées 2023-04-11 13:26:51 +02:00
05026d454b Add attaque dos+plusieurs 2023-04-10 14:11:47 +02:00
6497369d7f Gestion assommer/charge 2023-04-09 21:45:46 +02:00
5e5ddd1c3b Rework combat 2023-04-08 20:21:09 +02:00
a72108db5b Rework combat 2023-04-08 18:50:30 +02:00
6a46faadc2 Rework combat 2023-04-08 18:49:54 +02:00
e95f7de0c5 Fix divers 2023-04-08 09:55:08 +02:00
9d3ef8cbeb CSS journal fix 2023-04-07 16:56:37 +02:00
c6ec1b74a2 Petits fixes 2023-03-13 20:39:38 +01:00
1b12dc44c9 Fiches de PNJ 2023-03-13 09:00:49 +01:00
f26cd7670c Various fixes 2023-03-11 12:11:27 +01:00
02f8207fb7 Tri alpha + ajout contacts 2023-03-10 13:21:06 +01:00
439797e71e Fix predilection 2023-03-10 09:30:43 +01:00
1d82a6aa60 Fix predilection 2023-03-10 09:08:10 +01:00
11b0f22aa7 Diverses ameliorations 2023-03-09 13:16:19 +01:00
b0a3cb08cb Fix sur armes et affichage 2023-03-09 00:26:52 +01:00
2f3a8e91bd Fix sur armes et affichage 2023-03-09 00:04:23 +01:00
f00825ea91 Fix sur armes et affichage 2023-03-08 23:44:59 +01:00
3fa80b6e57 Fix sur armes et affichage 2023-03-08 23:44:19 +01:00
fac6618b74 Divers ajouts 2023-03-08 16:58:11 +01:00
197 changed files with 21057 additions and 3683 deletions

View File

@@ -0,0 +1,63 @@
name: Release Creation
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "💡 The ${{ gitea.repository }} repository will cloned to the runner."
#- uses: actions/checkout@v3
- uses: RouxAntoine/checkout@v3.5.4
# get part of the tag after the `v`
- name: Extract tag version number
id: get_version
uses: battila7/get-version-action@v2
# Substitute the Manifest and Download URLs in the system.json
- name: Substitute Manifest and Download Links For Versioned Ones
id: sub_manifest_link_version
uses: microsoft/variable-substitution@v1
with:
files: 'system.json'
env:
version: ${{steps.get_version.outputs.version-without-v}}
url: https://www.uberwald.me/gitea/${{gitea.repository}}
manifest: https://www.uberwald.me/gitea/public/fvtt-les-heritiers/releases/download/latest/system.json
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-les-heritiers.zip
# Create a zip file with all files required by the module to add to the release
- run: |
apt update -y
apt install -y zip
- run: zip -r ./fvtt-les-heritiers.zip system.json README.md LICENCE.txt assets/ lang/ modules/ packs/ styles/ templates/
- name: setup go
uses: https://github.com/actions/setup-go@v4
with:
go-version: '>=1.20.1'
- name: Use Go Action
id: use-go-action
uses: https://gitea.com/actions/release-action@main
with:
files: |-
./fvtt-les-heritiers.zip
system.json
api_key: '${{secrets.ALLOW_PUSH_RELEASE}}'
- name: Publish to Foundry server
uses: https://github.com/djlechuck/foundryvtt-publish-package-action@v1
with:
token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
id: 'fvtt-les-heritiers'
version: ${{github.event.release.tag_name}}
manifest: 'https://www.uberwald.me/gitea/public/fvtt-les-heritiers/releases/download/latest/system.json'
notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-les-heritiers.zip'
compatibility-minimum: '13'
compatibility-verified: '13'

2
.gitignore vendored Normal file
View File

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

598
DEVELOPER_INSTRUCTIONS.md Normal file
View File

@@ -0,0 +1,598 @@
# FoundryVTT Les Héritiers System - Developer Instructions
## Executive Summary
**Les Héritiers** is an unofficial FoundryVTT system for the French RPG "Les Héritiers" (published by Titam France/Sombres Projets). The system implements a complete actor and item management framework with support for characters (personnage), NPCs (pnj), and 13 item types. Recent development includes a migration to Foundry v13+ with DataModels and AppV2 architecture.
**Repository**: https://www.uberwald.me/gitea/public/fvtt-les-heritiers
**Current Version**: 13.0.7
**Compatibility**: Foundry 13+
**Language**: French (single language localization)
---
## 1. BUILD & DEVELOPMENT COMMANDS
### Build System
- **Build Tool**: Gulp 4 with LESS compilation
- **Package Manager**: npm
### Scripts (package.json)
```json
"build": "gulp build" # Compile LESS to CSS (primary task)
"watch": "gulp watch" # Watch LESS files and rebuild on changes
```
### Gulp Pipeline (gulpfile.js)
The gulpfile defines three main tasks:
| Task | Source | Output | Features |
|------|--------|--------|----------|
| `styles` | `less/heritiers.less` | `styles/heritiers.css` | LESS→CSS compilation with source maps |
| `watchFiles` | `less/**/*.less` | Watches recursively | Auto-rebuild on LESS file changes |
| `build` | - | - | Runs `styles` (default export) |
| `watch` | - | - | Runs `build` then `watchFiles` |
**Development Workflow**:
```bash
npm install # Install dependencies (gulp, gulp-less, gulp-sourcemaps)
npm run build # One-time CSS compilation
npm run watch # Development: continuous LESS watching
```
**Key Files**:
- `/package.json` - Minimal deps (only gulp toolchain)
- `/gulpfile.js` - 36 lines defining LESS tasks
- `/less/heritiers.less` - Entry point (imports `simple-converted.less`)
- `/styles/heritiers.css` - Generated output + source maps
---
## 2. SYSTEM ARCHITECTURE
### 2.1 System Metadata (system.json)
| Field | Value |
|-------|-------|
| **id** | `fvtt-les-heritiers` |
| **title** | Les Héritiers |
| **version** | 13.0.7 |
| **description** | Les Héritiers pour FoundryVTT (French) |
| **authors** | Uberwald/LeRatierBretonnien, Prêtre |
| **compatibility** | min: "13", verified: "13" |
| **manifest** | Hosted on Uberwald Gitea |
| **languages** | French only (fr.json) |
| **license** | LICENCE.txt (proprietary) |
| **grid** | 5m squares |
**Key Configuration Flags**:
- `hotReload.extensions`: `css`, `html`, `hbs`, `json`
- `hotReload.paths`: `styles`, `./`, `templates`, `lang/fr.json`
- `primaryTokenAttribute`: `sante.vigueur` (health/vigor)
- `secondaryTokenAttribute`: `bonneaventure.actuelle` (current good fortune)
- **Socket Support**: Enabled (`socket: true`)
### 2.2 Data Model (template.json.backup)
The system defines a complex hierarchical data model with 2 Actor types and 14 Item types:
#### **Actor Types**
**1. `personnage` (Player Character)**
- Templates: `biodata`, `core`
- Key characteristics (8 attributes):
- Physical: Agilité (agi), Constitution (con), Force (for), Précision (prec)
- Mental: Esprit (esp), Perception (per), Prestance (pres), Sang-Froid (san)
- Fields include biodata (name, appearance variants masked/unmasked, age, personality traits), ranks (Tricherie, Féerie, Masque, Héritage with scenario tracking), PV (health), combat skills
- Profile-based competences (6 profiles + magic)
**2. `pnj` (NPC)**
- Templates: `biodata`, `core`
- Identical structure to `personnage` with simpler NPC-specific metadata
**Core Actor Data** (template.core):
```
caracteristiques: {
agi, con, for, prec, esp, per, pres, san
└─ label, labelnorm, abbrev, kind(physical|mental), value, rang, max
}
rang: {
tricherie, feerie, masque, heritage (with scenarios subfield)
}
pv: { value, max, mod }
competences: { aventurier, combattant, erudit, gentleman, roublard, savant }
magie: { pointsame (value, max) }
experience: { value, pourtricher }
combat: {
esquive, parade, resistancephysique, resistancepsychique, protection,
effetssecondaires, dissimulation, initiative, corpsacorps, tir
}
```
#### **Item Types** (14 total)
| Item Type | Key Fields | Use Case |
|-----------|-----------|----------|
| `arme` (Weapon) | degats, precision, cadence, portee, dissimulation, armetype, rarete, prix | Combat equipment |
| `protection` | points, malusagilite, protectiontype, effetsecondaire | Armor/shields |
| `equipement` | rarete, quantite, prix | General gear |
| `accessoire` | lieu (location code) | Small items |
| `competence` | categorie, profil, niveau, specialites[], ismagie, nomniveau | Skills (6 profiles) |
| `profil` | profiltype (majeur|mineur) | Character archetypes |
| `contact` | contacttype (contact, allie, ennemi, interet) | NPC contacts |
| `avantage` | description | Advantages (benefits) |
| `desavantage` | description | Disadvantages (flaws) |
| `capacitenaturelle` | pouvoirtype, activation, effet, portee, resistance, cibles, virulence | Natural abilities |
| `pouvoir` | pouvoirtype, masquetype, niveau, istest, feeriemasque, carac, zoneffet, pointsusagecourant | Powers/spells |
| `atoutfeerique` | description | Fairy/magical perks |
| `fee` | feetype, avantages, desavantages, pouvoirsfeeriques*, atoutsfeeriques, competences, capacitenaturelles | Fairy character sheet |
| `sort` (Spell) | niveau, rang, competence (Druidisme), carac1, carac2, duree, portee, ingredients, resistance | Magic spells |
**Item Templates**:
- `base`: description (HTMLField)
- `basequip`: rarete, quantite, prix, equipped (for equipable items)
### 2.3 Directory Structure
```
modules/ # ES6 source code (12,273 LOC total)
├── heritiers-main.js # Entry point: init, hook setup, document class config
├── heritiers-actor.js # HeritiersActor class with creation hooks
├── heritiers-item.js # HeritiersItem class
├── heritiers-config.js # HERITIERS_CONFIG object (enums, constants)
├── heritiers-commands.js # Chat command system
├── heritiers-utility.js # Static utility class (dice, rolls, templates, socket)
├── heritiers-combat.js # HeritiersCombat class
├── xregexp-all.js # RegExp library
├── models/
│ ├── index.mjs # Exports all DataModels
│ ├── personnage.mjs # PersonnageDataModel (complex, 12K+ lines)
│ ├── pnj.mjs # PnjDataModel
│ ├── arme.mjs # ArmeDataModel (updated Jan 21)
│ ├── competence.mjs # CompetenceDataModel (updated Jan 21)
│ ├── capacitenaturelle.mjs # Natural ability model (updated Jan 21)
│ ├── pouvoir.mjs # PouvoirDataModel (updated Jan 21)
│ ├── protection.mjs # ProtectionDataModel (updated Jan 21)
│ ├── [11 more models] # accessoire, atoutfeerique, avantage, contact,
│ │ # desavantage, equipement, fee, profil, sort
│ └── base-item.mjs # Base item template
├── applications/
│ ├── sheets/
│ │ ├── _module.mjs # Exports all sheet classes
│ │ ├── base-actor-sheet.mjs # HeritiersActorSheet (HandlebarsApplicationMixin, AppV2)
│ │ ├── base-item-sheet.mjs # HeritiersItemSheet base
│ │ ├── personnage-sheet.mjs # Character sheet (specific)
│ │ ├── pnj-sheet.mjs # NPC sheet (specific)
│ │ └── [11 item sheets] # Type-specific sheets: arme, protection, competence, etc.
│ └── heritiers-roll-dialog.mjs # Dialog for roll resolution
```
#### **Key Module Details**:
**heritiers-main.js** (100+ lines):
- Hooks.once("init") - Registers templates, combat, actor/item classes, DataModels
- Registers AppV2 sheets for all actor and item types
- Creates `game.system.lesheritiers` namespace with utilities, config, models, sheets
- Socket message handling
**heritiers-config.js**:
- `HERITIERS_CONFIG` object with ~40 enum dictionaries:
- `caracList`: 8 characteristics with labels
- `competenceProfil`: 6 profiles (aventurier, roublard, combattant, erudit, savant, gentleman) + magic
- `baseTestPouvoir`, `resistancePouvoir`, `typePouvoir`
- `seuilsDifficulte`: 30-level difficulty scale (5=Enfantine to 30=Divine)
- `attaqueCible`: target localization (body part damage tracking)
- Game-specific constants
**heritiers-utility.js** (60+ custom Handlebars helpers):
- `rollDataStore`, `defenderStore` for roll tracking
- Handlebars helpers: count, includes, upper, lower, upperFirst, notEmpty, mul, and, or, eq, isTest, etc.
- `preloadHandlebarsTemplates()` - loads 27 template files
- `chatListeners()` - click handlers for chat
- `loadCompendium()` - loads packs dynamically
- `onSocketMesssage()` - inter-client communication
**heritiers-actor.js**:
- Custom creation logic: auto-adds useful skills from compendium for personnage type
- Extends Foundry Actor class
**heritiers-item.js**:
- Custom Item class with item-type-specific business logic
### 2.4 Templates Directory (27 Handlebars files)
Organized by purpose:
**Actor Sheets** (2 files):
- `actor-sheet.hbs` - Personnage character sheet (complex form with tabs)
- `actor-pnj-sheet.hbs` - NPC sheet (simplified)
**Item Sheets** (14 files):
- `item-[type]-sheet.hbs` for each item type
- E.g., `item-arme-sheet.hbs`, `item-competence-sheet.hbs`, `item-sort-sheet.hbs`
**Partials** (5 files):
- `partial-actor-equipment.hbs` - Equipment list rendering
- `partial-item-header.hbs` - Item sheet header (common)
- `partial-item-nav.hbs` - Tabbed navigation
- `partial-item-description.hbs` - Description editor
- `partial-utile-skills.hbs` - Skill list filtering
- `post-item.hbs` - Chat card for item description
- `editor-notes-gm.hbs` - GM notes editor
**Chat Results** (3 files):
- `chat-generic-result.hbs` - Generic roll result
- `chat-cc-result.hbs` - Close combat result
- `chat-assommer-result.hbs` - Stun attack result
**Dialogs** (1 file):
- `roll-dialog-generic.hbs` - Roll dialog with modifiers
**Design Notes**:
- Heavy use of Handlebars conditionals ({{#if}}, {{#each}})
- Data-action attributes for action routing
- Flexbox-based layout (`flexrow`, `flexcol` CSS classes)
- Partial templates for reusability
### 2.5 Localization (lang/fr.json)
**Minimal localization** - only 24 lines:
```json
{
"TYPES": {
"Actor": {
"personnage": "Personnage",
"pnj": "PNJ"
},
"Item": {
"accessoire": "Accessoire",
"arme": "Arme",
[12 more item types...]
}
}
}
```
**Localization Strategy**:
- Labels hardcoded in code/templates in French
- No string keys for UI text outside TYPES
- Template labels defined in DataModel schema (initial values)
- Seeded localization keys in heritiers-config.js (no i18n lookup)
### 2.6 Styles (less/ and styles/)
**LESS Structure** (2 files):
- `heritiers.less` (4 lines) - Entry point, imports converted template
- `simple-converted.less` - Full stylesheet (converted from Bootstrap/template.css)
**Output**:
- Compiled to `/styles/heritiers.css` (referenced in system.json)
- Source maps generated (`.map` files)
**Styling Approach**:
- Utility classes: `flexrow`, `flexcol`, `padd-right`, `status-small-label`
- Color classes: `color-class-common`, etc.
- Section classes: `background-sheet-header`
- Responsive design via flexbox
### 2.7 Compendium Packs (packs/)
11 packs defined in system.json, stored as `.db` (LevelDB format):
| Pack Name | Type | Label | Folder | Contents |
|-----------|------|-------|--------|----------|
| `competences` | Item | Compétences | Creation | Skills |
| `avantages` | Item | Avantages | Creation | Advantages |
| `desavantages` | Item | Désavantages | Creation | Disadvantages |
| `capacites` | Item | Capacités Naturelles | Creation | Natural abilities |
| `atouts-feeriques` | Item | Atouts Féériques | Creation | Fairy perks |
| `magie-sorts` | Item | Sorts | Creation | Spells (5 spell schools) |
| `archetypes-fees` | Item | Fées | Creation | Fairy archetypes |
| `pouvoirs` | Item | Pouvoirs | Creation | Powers |
| `profils` | Item | Profils | Creation | Character profiles |
| `armes-et-protection` | Item | Armes et Protections | Equipment | Weapons & armor |
| `scenes` | Scene | Scènes | Root | Sample scenes |
| `journaux` | JournalEntry | Journaux | Root | Game journal entries |
**Ownership Model** (PLAYER: OBSERVER, ASSISTANT: OWNER):
- Players can observe (read) items in packs
- Assistants have full control
**Source Data** (`srcdata/`):
- 5 JSON files for spell normalization:
- `sort_druidisme.json`, `sort_faeomancie.json`, `sort_magieduclan.json`, `sort_necromancie.json`, `sort_theurgie.json`
- `normalize.py` - Python script to transform spell data to compendium format
---
## 3. KEY CONVENTIONS
### 3.1 Module/Class Structure
**Pattern**: ES6 Classes extending Foundry base classes
- **Naming**: `Heritiers[Type]` (e.g., `HeritiersActor`, `HeritiersItem`, `HeritiersCombat`)
- **Exports**: Named exports via `export class`/`export default`
**Example (heritiers-actor.js)**:
```javascript
export class HeritiersActor extends Actor {
static async create(data, options) { ... }
// Custom methods and hooks
}
```
**Example (models/personnage.mjs)**:
```javascript
export default class PersonnageDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
biodata: new fields.SchemaField({ ... }),
// nested SchemaFields with detailed field definitions
}
}
}
```
**Sheet Pattern (AppV2 - Foundry v13+)**:
```javascript
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class HeritiersPersonnageSheet extends HandlebarsApplicationMixin(
foundry.applications.sheets.ActorSheetV2
) {
static DEFAULT_OPTIONS = {
classes: ["fvtt-les-heritiers", "sheet", "actor"],
position: { width: 780, height: 840 },
form: { submitOnChange: true, closeOnSubmit: false },
actions: {
editImage, toggleSheet, editItem, deleteItem,
createItem, equipItem, rollCarac, ...
}
}
// Static action handlers (#onEditImage, etc.)
}
```
### 3.2 Data Model Conventions
**Field Types** (foundry.data.fields.*):
- `StringField` - Text values, default initial values
- `HTMLField` - Rich-text fields (description, notes, etc.)
- `NumberField` - Integers (integer: true), floats
- `BooleanField` - Boolean flags
- `ArrayField` - Lists (with element type)
- `SchemaField` - Nested objects
**Naming Conventions**:
- Snake_case for internal field names: `biodata`, `caracteristiques`, `pointsame`
- Abbreviations for characteristics: `agi`, `con`, `for`, `prec`, `esp`, `per`, `pres`, `san`
- French names for labels and UI: "Agilité", "Constitution", etc.
**Special Fields**:
- `labelnorm` - Normalized label for lookups (lowercase, no accents)
- `kind` - Classification (physical, mental, magical)
- `initial` - Default value in schema definition
### 3.3 Handlebars Template Conventions
**File Naming**:
- `actor-sheet.hbs` - Main sheet template (not item-specific)
- `actor-[type]-sheet.hbs` - Type-specific actor sheet
- `item-[type]-sheet.hbs` - Type-specific item sheet
- `partial-[purpose].hbs` - Reusable component
- `chat-[result-type].hbs` - Chat message template
- `editor-[context].hbs` - Editor component
**Data Binding**:
- Actor data: `{{system.field.subfield}}`, `{{actor.name}}`
- Item data: `{{system.field}}`
- Loops: `{{#each system.array as |item key|}}`
- Conditionals: `{{#if condition}}...{{/if}}`
**Custom Helpers** (60+):
- Comparison: `eq`, `ne`, `lt`, `gt`
- Array: `includes`, `count`, `notEmpty`
- String: `upper`, `lower`, `upperFirst`
- Math: `mul`, `add`, `sub`, `and`, `or`
- Game: `isTest` (checks test type)
**CSS Classes**:
- Layout: `flexrow`, `flexcol`, `item`, `item-list`
- Styling: `status-small-label`, `color-class-common`, `background-sheet-header`
- Action: `data-action="actionName"` for click routing
### 3.4 Localization Key Naming
**Pattern**: `TYPES.Actor.[type]`, `TYPES.Item.[type]`
Current localization (lang/fr.json):
```json
"TYPES": {
"Actor": {
"personnage": "Personnage",
"pnj": "PNJ"
},
"Item": {
"accessoire": "Accessoire",
"arme": "Arme",
"atoutfeerique": "Atout féerique",
...
}
}
```
**Strategy**:
- **Type labels**: Centralized in TYPES
- **Field labels**: Defined in DataModel schema as `initial` values
- **Config labels**: In heritiers-config.js (caracList, competenceProfil, etc.)
- **Template text**: Hardcoded in .hbs files (all French)
**No i18n System**: System is French-only; no game.i18n lookups used in code
### 3.5 Dice & Roll System
**Initiative Formula** (heritiers-config.js):
```javascript
CONFIG.Combat.initiative = {
formula: "1d10",
decimals: 1
};
```
**Roll Data Storage** (heritiers-utility.js):
- `rollDataStore = {}` - Tracks active rolls by ID
- `defenderStore = {}` - Tracks defenders for opposed rolls
- Socket-based roll updates for multi-client scenarios
**Bonus/Malus**:
- Range: -6 to +6 (signed integers)
- Generated as: `Array.from({ length: 7 }, (v, k) => toString(k - 6))`
**Dice Mechanics**:
- D8, D10, D12 support with face adjacency tables (unused in some contexts)
- Example: `__facesAdjacentes` object maps die faces to adjacent faces
### 3.6 Socket Message Handling
**Game Socket** (configured in system.json):
- Enabled via `"socket": true`
- Message channel: `"system.fvtt-les-heritiers"`
**Message Flow** (heritiers-utility.js):
```javascript
game.socket.on("system.fvtt-les-heritiers", data => {
HeritiersUtility.onSocketMesssage(data)
})
```
Used for:
- Inter-client roll notifications
- Multiplayer combat updates
### 3.7 Recent Migration Notes (v13.0.7)
**Latest Changes** (from git log):
- Migrated from legacy v1 sheets to **AppV2** (HandlebarsApplicationMixin + ActorSheetV2)
- Migrated to **DataModels** (foundry.abstract.TypeDataModel)
- All sheets converted to `.mjs` (ES6 modules)
- Specification & competence fixes (Jan 21)
- Armor (protection) and weapon (arme) model refinements (Jan 21)
**Key Commit**: `4722fdf - Migration vers DataModels et appv2` (major architecture update)
---
## 4. EXISTING AI & DEVELOPER CONFIGURATIONS
### No Pre-Existing Configs Found
The following configuration files **do NOT exist** in the repository:
- `.github/copilot-instructions.md`
- `CLAUDE.md`
- `.cursorrules`
- `AGENTS.md`
- `.windsurfrules`
- `CONVENTIONS.md`
- `CONTRIBUTING.md`
Only **README.md** exists (29 lines):
- Credits Titam France/Sombres Projets
- Lists contributors: LeRatierBretonnien (dev), Prêtre (testing/data entry)
- Notes official authorization
- Links to books at titam-france.fr
---
## 5. DEVELOPMENT WORKFLOW
### 5.1 Setup
```bash
cd /home/morr/work/foundryvtt/fvtt-les-heritiers
npm install
npm run watch
```
### 5.2 File Organization When Adding Features
| Purpose | Location | Pattern |
|---------|----------|---------|
| New item type logic | `modules/heritiers-item.js` | Extend HeritiersItem |
| New item data model | `modules/models/[type].mjs` | Extend TypeDataModel |
| New item sheet | `modules/applications/sheets/[type]-sheet.mjs` | Extend HeritiersItemSheet |
| New item template | `templates/item-[type]-sheet.hbs` | Handlebars + data-action |
| New actor type | `modules/models/[type].mjs` | Extend TypeDataModel |
| New actor sheet | `modules/applications/sheets/[type]-sheet.mjs` | Extend HeritiersActorSheet |
| New chat message | `templates/chat-[result]-result.hbs` | HTML template |
| Style updates | `less/heritiers.less` or new import | LESS → npm run build |
| New config constants | `modules/heritiers-config.js` | Add to HERITIERS_CONFIG |
| Game mechanics | `modules/heritiers-utility.js` or -commands.js | Static utilities or commands |
### 5.3 Registration Steps for New Item Type
1. **Update template.json.backup** - Add schema definition
2. **Create DataModel** - `modules/models/[type].mjs`
3. **Update heritiers-config.js** - Add enums if needed
4. **Create Sheet** - `modules/applications/sheets/[type]-sheet.mjs`
5. **Create Template** - `templates/item-[type]-sheet.hbs`
6. **Update heritiers-main.js** - Register sheet class
7. **Add to models/index.mjs** - Export DataModel
8. **Add to applications/sheets/_module.mjs** - Export Sheet class
9. **Update localization** - Add type to lang/fr.json TYPES.Item
10. **Update system.json** - If exposing in UI
### 5.4 Debugging
- Check browser console for JS errors
- Use Chrome DevTools on http://localhost:30000 (Foundry dev port)
- Hot reload enabled for: `.css`, `.html`, `.hbs`, `.json` files (from system.json flags)
- Git history available via `git log` or `git show <commit>`
---
## 6. KEY STATISTICS
| Metric | Value |
|--------|-------|
| **System ID** | fvtt-les-heritiers |
| **Version** | 13.0.7 |
| **Total Source LOC** | 12,273 lines |
| **Actor Types** | 2 (personnage, pnj) |
| **Item Types** | 14 (arme, protection, competence, etc.) |
| **Compendium Packs** | 11 |
| **Template Files** | 27 (.hbs) |
| **Module Files** | 28 (.js/.mjs) |
| **Model Files** | 16 (DataModels) |
| **Sheet Files** | 14 (+ 1 base) |
| **LESS Files** | 2 |
| **Localization Files** | 1 (fr.json, 24 lines) |
| **Dev Dependencies** | 3 (gulp, gulp-less, gulp-sourcemaps) |
| **Characteristics** | 8 (agi, con, for, prec, esp, per, pres, san) |
| **Competence Profiles** | 6 + magic (aventurier, roublard, combattant, erudit, savant, gentleman) |
| **Difficulty Scale** | 30 levels (-1 to 30) |
| **Last Commit** | "Correction sur calcul du rang" (recent) |
---
## 7. EXTERNAL RESOURCES
- **Official Publisher**: [Titam France / Sombres Projets](http://www.titam-france.fr)
- **System Repository**: https://www.uberwald.me/gitea/public/fvtt-les-heritiers
- **Foundry Documentation**: https://foundryvtt.com/articles/
- **Foundry v13 API**: DataModels, AppV2 sheets (major changes in this version)
---
## 8. RECOMMENDATIONS FOR DEVELOPERS
1. **Use AppV2 Sheet API** for all new sheets (do not use legacy v1)
2. **Define DataModels** for type data rather than inline templates
3. **Keep French localization** in code/templates (minimal i18n setup)
4. **Test with `npm run watch`** during development
5. **Use socket messages** for multiplayer features
6. **Follow naming conventions** (Heritiers[Type], snake_case for fields, French labels)
7. **Document dice mechanics** when adding new rolls
8. **Keep configuration constants** in heritiers-config.js
9. **Use Handlebars helpers** rather than inline logic in templates
10. **Register sheets in heritiers-main.js** and export from _module.mjs files

239
DOCUMENTATION_INDEX.md Normal file
View File

@@ -0,0 +1,239 @@
# Les Héritiers FoundryVTT System - Documentation Index
## 📖 Available Documentation
### 1. **QUICK_REFERENCE.md** ⭐ START HERE
- **Length**: 250 lines
- **Purpose**: Fast lookup and common task guide
- **Best for**: Developers new to the system
- **Contains**:
- Quick start setup commands
- System overview & statistics
- Project structure diagram
- Key classes and exports
- Step-by-step: Adding a new item type
- Common edits (characteristics, packs, rolls, UI)
- Testing and git workflow
- Useful commands
### 2. **DEVELOPER_INSTRUCTIONS.md** 📚 COMPREHENSIVE GUIDE
- **Length**: 598 lines
- **Purpose**: Complete architectural reference
- **Best for**: Deep understanding and long-term development
- **Contains**:
- Executive summary
- Build/dev commands (package.json, gulpfile.js)
- System architecture (metadata, data model, directories)
- Directory structure with file descriptions
- Template organization & conventions
- Localization strategy
- Styling (LESS, CSS)
- Compendium packs reference
- Detailed conventions:
- Module/class structure
- Data model conventions
- Handlebars template patterns
- Localization key naming
- Dice & roll system
- Socket message handling
- Recent migration notes (v13)
- Development workflow (setup, file organization, registration steps)
- Debugging tips
- Key statistics
- External resources
- Developer recommendations
### 3. **README.md** (ORIGINAL)
- **Length**: 29 lines
- **Purpose**: Project overview and credits
- **Contains**:
- Project description (English & French)
- Copyright & authorization notes
- Developer credits
---
## 🎯 How to Use This Documentation
### For First-Time Setup (5-10 minutes)
1. Read **QUICK_REFERENCE.md** → "Quick Start" section
2. Run `npm install` and `npm run watch`
3. Skim the "Project Structure" section
### For Adding a New Feature (30 minutes)
1. Open **QUICK_REFERENCE.md** → "Adding a New Item Type" section
2. Follow the numbered steps
3. Reference **DEVELOPER_INSTRUCTIONS.md** for deeper understanding of each step
4. Test with `npm run watch`
### For Understanding the System (1-2 hours)
1. Start with **DEVELOPER_INSTRUCTIONS.md** → "Executive Summary"
2. Read section 2: "System Architecture"
3. Read section 3: "Key Conventions"
4. Review section 5: "Development Workflow"
### For Debugging a Bug (varies)
1. Check **QUICK_REFERENCE.md** → "Useful Commands" for search/analysis tools
2. Reference **DEVELOPER_INSTRUCTIONS.md** → Section 3 for patterns
3. Look at git history: `git log --oneline | head -20`
4. Check browser console (DevTools) at `http://localhost:30000`
### For Making Common Edits (5-15 minutes each)
Reference **QUICK_REFERENCE.md** → "Common Edits" section:
- Adding a new characteristic
- Adding a new compendium pack
- Adding a new roll type
- Changing UI layout
---
## 📊 Quick System Overview
| Aspect | Detail |
|--------|--------|
| **System ID** | `fvtt-les-heritiers` |
| **Version** | 13.0.7 |
| **Foundry** | v13+ (AppV2, DataModels) |
| **Language** | French (monolingual) |
| **Build Tool** | Gulp 4 + LESS |
| **Code** | 12,273 LOC total |
| **Actor Types** | 2 (personnage, pnj) |
| **Item Types** | 14 types |
| **Compendium Packs** | 11 LevelDB packs |
| **Sheets** | 16 AppV2 sheets |
| **Architecture** | ES6 modules, TypeDataModel |
---
## 🗂️ Repository Structure
```
fvtt-les-heritiers/
├── README.md # Original project readme
├── QUICK_REFERENCE.md # ⭐ Start here (250 lines)
├── DEVELOPER_INSTRUCTIONS.md # 📚 Deep dive (598 lines)
├── DOCUMENTATION_INDEX.md # This file
├── package.json # npm config (build commands)
├── gulpfile.js # Gulp tasks (LESS → CSS)
├── system.json # FoundryVTT manifest
├── template.json.backup # Data schema reference
├── LICENCE.txt # License
├── modules/ # Source code (12,273 LOC)
│ ├── heritiers-main.js # Foundry init & registration
│ ├── heritiers-actor.js # HeritiersActor class
│ ├── heritiers-item.js # HeritiersItem class
│ ├── heritiers-config.js # Game constants (HERITIERS_CONFIG)
│ ├── heritiers-utility.js # Static utilities & helpers
│ ├── heritiers-commands.js # Chat commands
│ ├── heritiers-combat.js # Combat handling
│ ├── models/ # 16 TypeDataModels
│ │ └── personnage.mjs, arme.mjs, competence.mjs, ...
│ └── applications/sheets/ # 15 AppV2 Sheet classes
│ └── personnage-sheet.mjs, arme-sheet.mjs, ...
├── templates/ # 27 Handlebars templates
│ ├── actor-sheet.hbs # Character sheet
│ ├── actor-pnj-sheet.hbs # NPC sheet
│ ├── item-[type]-sheet.hbs # Item sheets (14)
│ ├── partial-*.hbs # Reusable components
│ ├── chat-*-result.hbs # Chat templates
│ └── editor-*.hbs # Editor components
├── lang/
│ └── fr.json # Localization (24 lines, minimal)
├── less/
│ ├── heritiers.less # Entry point
│ └── simple-converted.less # Imported styles
├── styles/
│ ├── heritiers.css # Compiled output (generated)
│ └── heritiers.css.map # Source map (generated)
├── packs/ # 11 Compendium packs (LevelDB)
├── srcdata/ # Source data for compilation
├── assets/ # UI assets (images, etc.)
└── .git/ # Git history available
```
---
## 🚀 Common Tasks Quick Links
| Task | Guide | Time |
|------|-------|------|
| Initial setup | QUICK_REFERENCE.md → Quick Start | 5 min |
| Add item type | QUICK_REFERENCE.md → Adding a New Item Type | 30 min |
| Fix a bug | DEVELOPER_INSTRUCTIONS.md → Section 5 | 15-60 min |
| Understand architecture | DEVELOPER_INSTRUCTIONS.md → Section 2 | 30 min |
| Change UI layout | QUICK_REFERENCE.md → Change UI layout | 10 min |
| Add characteristic | QUICK_REFERENCE.md → Add a new characteristic | 20 min |
| Add new roll type | QUICK_REFERENCE.md → Add a new roll type | 25 min |
| Debug an issue | QUICK_REFERENCE.md → Testing Changes | 10-30 min |
---
## 📝 Documentation Standards
All documentation follows these principles:
1. **Clarity**: Written for developers unfamiliar with the system
2. **Completeness**: All information needed to perform tasks included
3. **Accuracy**: Based on actual code analysis, not assumptions
4. **Brevity**: Concise sections with clear headings
5. **Examples**: Code snippets and patterns provided
6. **Cross-references**: Links between guides for deeper understanding
---
## 🔄 Keeping Documentation Updated
When making significant changes:
1. **Update QUICK_REFERENCE.md** if:
- Adding/removing item types
- Changing build commands
- Adding/removing features
- Changing conventions
2. **Update DEVELOPER_INSTRUCTIONS.md** if:
- Major architecture changes
- New development patterns established
- New statistics to report
- New sections needed
3. **Update git history** (automatic):
- Commit messages explain changes
- Reference these in DEVELOPER_INSTRUCTIONS.md
---
## 📞 Questions?
Refer to:
- **FoundryVTT Docs**: https://foundryvtt.com/articles/
- **Game Rules**: https://titam-france.fr
- **Git History**: `git log --oneline` (see recent commits)
- **Code Comments**: Most functions have JSDoc-style comments
---
**Last Updated**: March 16, 2025
**Documentation Version**: 1.0
**System Version**: 13.0.7
---
## 🎓 Learning Path
**Day 1**: Quick understanding
1. Read QUICK_REFERENCE.md (20 min)
2. Run setup commands (5 min)
3. Explore directory structure (10 min)
**Week 1**: Basic development
1. Read DEVELOPER_INSTRUCTIONS.md sections 1-3 (45 min)
2. Add a simple new item type (1-2 hours)
3. Fix a small bug (30 min - 1 hour)
**Week 2+**: Deep expertise
1. Read full DEVELOPER_INSTRUCTIONS.md (1-2 hours)
2. Add complex feature (3-6 hours)
3. Understand DataModels & AppV2 sheets intimately
4. Review git history for patterns (1-2 hours)

250
QUICK_REFERENCE.md Normal file
View File

@@ -0,0 +1,250 @@
# Les Héritiers FoundryVTT System - Quick Reference
## Quick Start
```bash
npm install # Install once
npm run watch # Development watch (LESS → CSS)
npm run build # Build once (production)
```
## System at a Glance
| Item | Details |
|------|---------|
| **ID** | `fvtt-les-heritiers` |
| **Version** | 13.0.7 |
| **Foundry** | v13+ (AppV2, DataModels) |
| **Language** | French only |
| **Architecture** | ES6 modules, Foundry TypeDataModel |
## Core Actors (2 types)
- **`personnage`** - Player Character (full biodata + core mechanics)
- **`pnj`** - NPC (same structure, simplified UI)
### Actor Data Structure
- **Characteristics** (8): agi, con, for, prec, esp, per, pres, san
- **Ranks** (4): tricherie, feerie, masque, heritage
- **Combat**: esquive, parade, initiative, corpsacorps, tir, resistances
- **Profiles** (6): aventurier, roublard, combattant, erudit, savant, gentleman + magic
## Core Items (14 types)
**Combat**: arme, protection
**Equipment**: equipement, accessoire
**Character Building**: competence, profil, contact, avantage, desavantage
**Powers**: pouvoir, capacitenaturelle, atoutfeerique, fee
**Magic**: sort
## Project Structure
```
modules/
├─ heritiers-main.js # Foundry init, hook setup
├─ heritiers-actor.js # Actor class
├─ heritiers-item.js # Item class
├─ heritiers-config.js # HERITIERS_CONFIG (enums)
├─ heritiers-utility.js # Static utilities, Handlebars helpers
├─ heritiers-commands.js # Chat commands
├─ heritiers-combat.js # Combat class
├─ models/ # DataModels for each type
│ └─ personnage.mjs, pnj.mjs, arme.mjs, ...
└─ applications/sheets/ # AppV2 Sheets
└─ personnage-sheet.mjs, arme-sheet.mjs, ...
templates/ # 27 Handlebars templates
lang/fr.json # Localization (minimal, type names only)
less/heritiers.less # LESS compilation entry
styles/heritiers.css # Compiled CSS (generated)
packs/ # 11 LevelDB compendium packs
system.json # Manifest
template.json.backup # Data schema reference
```
## Key Classes & Exports
### Actor Classes
- **`HeritiersActor`** extends `Actor`
- Custom creation: auto-adds utility skills for `personnage`
- **PersonnageDataModel**, **PnjDataModel** extend `foundry.abstract.TypeDataModel`
### Item Classes
- **`HeritiersItem`** extends `Item`
- **14 DataModels**: ArmeDataModel, CompetenceDataModel, etc.
### Sheets (AppV2 - Foundry v13+)
- **`HeritiersPersonnageSheet`**, **`HeritiersPnjSheet`** extend ActorSheetV2
- **`HeritiersArmeSheet`**, **`HeritiersCompetenceSheet`**, etc. (14 item sheets)
- All use `HandlebarsApplicationMixin`
- Registered in `heritiers-main.js` with `foundry.documents.collections.[Actors|Items].registerSheet()`
## Adding a New Item Type
1. Add schema to `template.json.backup`
2. Create `modules/models/[typename].mjs` (DataModel)
3. Create `modules/applications/sheets/[typename]-sheet.mjs` (AppV2 Sheet)
4. Create `templates/item-[typename]-sheet.hbs` (Handlebars template)
5. Export from `modules/models/index.mjs`
6. Export from `modules/applications/sheets/_module.mjs`
7. Register in `heritiers-main.js` (+ other registrations)
8. Add to `lang/fr.json` TYPES.Item
9. Optionally: add config constants to `heritiers-config.js`
## Key Constants (heritiers-config.js)
```javascript
HERITIERS_CONFIG = {
caracList, // 8 characteristics
competenceProfil, // 6 profiles + magic
seuilsDifficulte, // 30-level difficulty scale
baseTestPouvoir, // feerie, masque, autre
resistancePouvoir, // physical/psychic active/passive
typePouvoir, // actif, passif, metamorphose
// ... 30+ more enums
}
```
## Handlebars Template Patterns
**File Naming**:
- `actor-sheet.hbs` → Personnage sheet
- `actor-pnj-sheet.hbs` → NPC sheet
- `item-[type]-sheet.hbs` → Item sheet
- `partial-*.hbs` → Reusable components
- `chat-*-result.hbs` → Chat results
- `editor-*.hbs` → Editor components
**Data Access**:
```handlebars
{{actor.name}} <!-- Actor name -->
{{system.caracteristiques.agi.value}} <!-- Nested field -->
{{#each system.items as |item key|}} <!-- Loops -->
{{#if system.magie}} ... {{/if}} <!-- Conditionals -->
{{eq kind "physical"}} <!-- Custom helper -->
```
**Custom Helpers** (60+):
- Comparison: `eq`, `ne`, `lt`, `gt`, `gte`, `lte`
- String: `upper`, `lower`, `upperFirst`
- Array: `includes`, `count`, `notEmpty`
- Math: `mul`, `add`, `sub`, `and`, `or`
## Localization
**Single File**: `lang/fr.json`
```json
{
"TYPES": {
"Actor": { "personnage": "Personnage", "pnj": "PNJ" },
"Item": { "arme": "Arme", "competence": "Compétence", ... }
}
}
```
**Strategy**: Minimal i18n
- Type labels in TYPES
- Field labels in DataModel schema (French initial values)
- Config constants in heritiers-config.js (French)
- Template text hardcoded (French)
**No game.i18n calls** - all text is French-only
## Dice & Combat
- **Initiative**: 1d10 (decimal)
- **Bonus/Malus**: -6 to +6
- **Roll Storage**: `HeritiersUtility.rollDataStore` (socket-aware)
- **Defender Tracking**: `HeritiersUtility.defenderStore`
## Socket Communication
**Channel**: `"system.fvtt-les-heritiers"`
**Handler**: `HeritiersUtility.onSocketMesssage(data)`
**Use Cases**: Roll updates, multi-client sync
## Recent Changes (v13.0.7)
- ✅ AppV2 sheet migration (from legacy v1)
- ✅ DataModels adoption
- ✅ ES6 modules (.mjs) throughout
- ✅ Jan 21: arme, competence, capacitenaturelle, pouvoir, protection updates
- ✅ Rank calculation fixes
## Common Edits
### Add a new characteristic?
1. Update `template.json.backup``caracteristiques`
2. Update `PersonnageDataModel``caracteristiques` SchemaField
3. Update `HERITIERS_CONFIG.caracList`
4. Update templates that reference characteristics
### Add a new compendium pack?
1. Add to `system.json``packs` array
2. Create directory in `packs/[packname]/`
3. Add `.db` file via Foundry UI or scripts
### Add a new roll type?
1. Add dialog template: `templates/roll-dialog-*.hbs`
2. Add result template: `templates/chat-*-result.hbs`
3. Add handler in `heritiers-utility.js` or `heritiers-commands.js`
4. Emit via socket if multiplayer: `game.socket.emit("system.fvtt-les-heritiers", data)`
### Change UI layout?
1. Edit `templates/actor-sheet.hbs` or `item-*.hbs`
2. Update LESS: edit `less/heritiers.less`
3. Run `npm run build` or watch with `npm run watch`
4. CSS reloads automatically via hotReload
## Testing Changes
```bash
npm run watch
# Open FoundryVTT on http://localhost:30000
# Open system's world
# Make edits (files auto-reload)
# Check browser console for errors
# Test in UI
```
## Git Workflow (Development)
```bash
git status
git add -A
git commit -m "Description of change"
git push
# Check recent commits: git log --oneline
```
## Useful Commands
```bash
# Count lines of code
wc -l modules/*.js modules/**/*.mjs
# Search for string in code
grep -r "searchterm" modules/
# List all item types
grep '"type":' packs/*/[file].db | head -20
# Check template syntax
npm run build 2>&1 | grep error
```
## Documentation
- **DEVELOPER_INSTRUCTIONS.md** - Comprehensive guide (this repo)
- **README.md** - Project overview & credits
- **system.json** - Manifest (metadata, packs, compatibility)
- **template.json.backup** - Data schema reference
- [FoundryVTT Docs](https://foundryvtt.com/articles/)
- [Les Héritiers Books](https://titam-france.fr) - Game rules
## Notes
- All code is French (variable names in French, labels in French)
- No English strings in UI
- Single language system (French)
- Heavy use of camelCase following Foundry conventions
- AppV2 is the only supported sheet architecture (v13+)

View File

@@ -1,8 +1,8 @@
# Système Foundry pour Hawkmoon (French RPG, Titam France/Sombres Projets)
# Système Foundry pour Les Héritiers (French RPG, Titam France/Sombres Projets)
## EN
Unofficial system for Hawkmoon (French version from Titam France).
Unofficial system for Les Heritiers (from Titam France).
This system has been approved by Département des Sombres Projets ( http://www.titam-france.fr/ ), thanks !
@@ -10,7 +10,7 @@ Books are mandatory to play and are available at : http://www.titam-france.fr
## FR
Système non-officiel pour le JDR Hawkmoon (Titam France/Sombres Projets).
Système non-officiel pour le JDR Les Héritiers (Titam France/Sombres Projets).
Ce système a été autorisé par le Département des Sombres Projets ( http://www.titam-france.fr/ ), merci à eux !
@@ -18,7 +18,7 @@ Les livres du jeu sont nécessaires pour jouer, et sont disponibles ici : http:/
# Credits
Hawkmoon, le jeu de rôle du Troisième Millénaire, is a property of Titam France/Sombres Projets.
Les Héritiers, is a property of Titam France/Sombres Projets.
# Developmement
@@ -26,4 +26,4 @@ LeRatierBretonnien
# Tests, icones et saisie des données
Prêtre, Blondin, Zechrub/Chris, Kyllian, Lightbringer
Prêtre, Carter

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
assets/icons/erudit.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

BIN
assets/icons/gentleman.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
assets/icons/profil.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
assets/icons/roublard.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
assets/icons/sort.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

35
gulpfile.js Normal file
View File

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

View File

@@ -1,31 +1,24 @@
{
"ACTOR": {
"TypePersonnage": "Personnage",
"TypeCellule": "Cellule",
"TypeCreature": "Créature"
},
"ITEM": {
"TypeArtefact": "Artefact",
"TypeArme": "Arme",
"TypeTalent": "Talent",
"TypeHistorique": "Historique",
"TypeProfil": "Profil",
"TypeCompetence": "Compétence",
"TypeProtection": "Protection",
"TypeMonnaie": "Monnaie",
"TypeEquipement": "Equipement",
"TypeRessource": "Ressource",
"TypeContact": "Contact"
},
"HAWKMOON": {
"ui": {
"editContact": "Modifier le contact",
"deleteContact": "Supprimer le contact",
"editTrait": "Modifier le trait",
"deleteTrait": "Supprimer le trait"
"TYPES": {
"Actor": {
"personnage": "Personnage",
"pnj": "PNJ"
},
"Item": {
"accessoire": "Accessoire",
"arme": "Arme",
"atoutfeerique": "Atout féerique",
"avantage": "Avantage",
"capacitenaturelle": "Capacité naturelle",
"competence": "Compétence",
"contact": "Contact",
"desavantage": "Désavantage",
"equipement": "Equipement",
"fee": "Fée",
"pouvoir": "Pouvoir",
"profil": "Profil",
"protection": "Protection",
"sort": "Sort"
}
}
}

4
less/heritiers.less Normal file
View File

@@ -0,0 +1,4 @@
// Main LESS file for Les Héritiers system
// Temporarily importing the full converted simple.css while we refactor
@import "simple-converted";

2809
less/simple-converted.less Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,213 @@
import { HeritiersUtility } from "../heritiers-utility.js"
/**
* Dialogue de jet de dé pour Les Héritiers - Version AppV2
*/
export class HeritiersRollDialog {
/**
* Create and display the roll dialog
* @param {HeritiersActor} actor - The actor making the roll
* @param {Object} rollData - Data for the roll
* @returns {Promise<Object>} - Returns a dialog-like object for compatibility
*/
static async create(actor, rollData) {
// Préparer le contexte pour le template
const context = {
...rollData,
img: actor.img,
name: actor.name,
config: game.system.lesheritiers.config,
}
// Rendre le template en HTML
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-les-heritiers/templates/roll-dialog-generic.hbs",
context
)
// Préparer les boutons selon le mode et le niveau
const buttons = this._prepareButtons(rollData)
// Utiliser DialogV2.wait avec le HTML rendu
return foundry.applications.api.DialogV2.wait({
window: { title: "Test de Capacité", icon: "fa-solid fa-dice" },
classes: ["heritiers-roll-dialog"],
position: { width: 420 },
modal: false,
content,
buttons,
rejectClose: false
})
}
/**
* Préparer les boutons selon le mode et le niveau de compétence
* @param {Object} rollData - Data for the roll
* @returns {Array} - Array of button configurations
* @private
*/
static _prepareButtons(rollData) {
const buttons = []
// Bouton d8 toujours disponible
buttons.push({
action: "rolld8",
label: "1d8",
icon: "fa-solid fa-dice-d8",
default: true,
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements)
this._executeRoll(rollData, "d8")
}
})
// Bouton d10 si niveau > 0 ou pouvoir
const enableD10 = rollData.mode === "pouvoir" || rollData.competence?.system.niveau > 0
if (enableD10) {
buttons.push({
action: "rolld10",
label: "1d10",
icon: "fa-solid fa-dice-d10",
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements)
this._executeRoll(rollData, "d10")
}
})
}
// Bouton d12 si niveau > 1 ou pouvoir
const enableD12 = rollData.mode === "pouvoir" || rollData.competence?.system.niveau > 1
if (enableD12) {
buttons.push({
action: "rolld12",
label: "1d12",
icon: "fa-solid fa-dice-d12",
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements)
this._executeRoll(rollData, "d12")
}
})
}
// Bouton Tricherie si disponible
if (rollData.tricherie) {
buttons.push({
action: "rollTricherie",
label: "Lancer 1 Tricherie",
icon: "fa-solid fa-mask",
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements)
this._executeRoll(rollData, "tricherie")
}
})
}
// Bouton Héritage si disponible
if (rollData.heritage) {
buttons.push({
action: "rollHeritage",
label: "Lancer 1 Héritage",
icon: "fa-solid fa-crown",
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements)
this._executeRoll(rollData, "heritage")
}
})
}
// Si mode carac uniquement, on ne garde que d8
if (rollData.mode === "carac") {
return [
{
action: "rolld8",
label: "Lancer 1d8",
icon: "fa-solid fa-dice-d8",
default: true,
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements)
this._executeRoll(rollData, "d8")
}
}
]
}
return buttons
}
/**
* Mettre à jour rollData avec les valeurs du formulaire
* @param {Object} rollData - L'objet rollData à mettre à jour
* @param {HTMLFormControlsCollection} formElements - Les éléments du formulaire
* @private
*/
static _updateRollDataFromForm(rollData, formElements) {
// Seuil de Difficulté
if (formElements.sdValue) {
rollData.sdValue = Number(formElements.sdValue.value)
}
// Caractéristique
if (formElements.caracKey) {
rollData.caracKey = String(formElements.caracKey.value)
}
// Bonus/Malus contextuel
if (formElements['bonus-malus-context']) {
rollData.bonusMalusContext = Number(formElements['bonus-malus-context'].value)
}
// Attaque à plusieurs
if (formElements['bonus-attaque-plusieurs']) {
rollData.bonusAttaquePlusieurs = Number(formElements['bonus-attaque-plusieurs'].value)
}
// Spécialité
if (formElements.useSpecialite !== undefined) {
rollData.useSpecialite = formElements.useSpecialite.checked
}
// Points d'usage du pouvoir
if (formElements.pouvoirPointsUsage) {
rollData.pouvoirPointsUsage = Number(formElements.pouvoirPointsUsage.value)
}
// Attaque dans le dos
if (formElements.attaqueDos !== undefined) {
rollData.attaqueDos = formElements.attaqueDos.checked
}
// Seconde arme
if (formElements['bonus-attaque-seconde-arme']) {
rollData.secondeArme = String(formElements['bonus-attaque-seconde-arme'].value)
}
// Attaque ciblée
if (formElements['attaque-cible']) {
rollData.attaqueCible = String(formElements['attaque-cible'].value)
}
// Attaque à deux armes
if (formElements['bonus-attaque-deux-armes']) {
rollData.attaqueDeuxArmes = Number(formElements['bonus-attaque-deux-armes'].value)
}
}
/**
* Exécuter le jet de dés
* @param {Object} rollData - Data for the roll
* @param {String} dice - Type de dé (d8, d10, d12, tricherie, heritage)
* @private
*/
static _executeRoll(rollData, dice) {
if (dice === "heritage") {
rollData.useHeritage = true
} else if (dice === "tricherie") {
rollData.useTricherie = true
} else {
rollData.mainDice = dice
}
HeritiersUtility.rollHeritiers(rollData)
}
}

View File

@@ -0,0 +1,23 @@
/**
* Export all application sheets for Les Héritiers
*/
// Actor Sheets
export { default as HeritiersPersonnageSheet } from './personnage-sheet.mjs';
export { default as HeritiersPnjSheet } from './pnj-sheet.mjs';
// Item Sheets
export { default as HeritiersAccessoireSheet } from './accessoire-sheet.mjs';
export { default as HeritiersArmeSheet } from './arme-sheet.mjs';
export { default as HeritiersAtoutFeeriqueSheet } from './atoutfeerique-sheet.mjs';
export { default as HeritiersAvantageSheet } from './avantage-sheet.mjs';
export { default as HeritiersCapaciteNaturelleSheet } from './capacitenaturelle-sheet.mjs';
export { default as HeritiersCompetenceSheet } from './competence-sheet.mjs';
export { default as HeritiersContactSheet } from './contact-sheet.mjs';
export { default as HeritiersDesavantageSheet } from './desavantage-sheet.mjs';
export { default as HeritiersEquipementSheet } from './equipement-sheet.mjs';
export { default as HeritiersFeeSheet } from './fee-sheet.mjs';
export { default as HeritiersPouvoirSheet } from './pouvoir-sheet.mjs';
export { default as HeritiersProfilSheet } from './profil-sheet.mjs';
export { default as HeritiersProtectionSheet } from './protection-sheet.mjs';
export { default as HeritiersSortSheet } from './sort-sheet.mjs';

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersAccessoireSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.accessoire",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-accessoire-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersArmeSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.arme",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-arme-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersAtoutFeeriqueSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.atoutfeerique",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-atoutfeerique-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersAvantageSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.avantage",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-avantage-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,676 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
import { HeritiersUtility } from "../../heritiers-utility.js"
export default class HeritiersActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
/**
* Different sheet modes.
* @enum {number}
*/
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
this._sheetMode = this.constructor.SHEET_MODES.PLAY
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-les-heritiers", "sheet", "actor"],
position: {
width: 780,
height: 840,
},
window: {
resizable: true,
},
form: {
submitOnChange: true,
closeOnSubmit: false,
},
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: "form" }],
actions: {
editImage: HeritiersActorSheet.#onEditImage,
toggleSheet: HeritiersActorSheet.#onToggleSheet,
editItem: HeritiersActorSheet.#onEditItem,
deleteItem: HeritiersActorSheet.#onDeleteItem,
createItem: HeritiersActorSheet.#onCreateItem,
equipItem: HeritiersActorSheet.#onEquipItem,
modifyQuantity: HeritiersActorSheet.#onModifyQuantity,
quantityIncrease: HeritiersActorSheet.#onQuantityIncrease,
quantityDecrease: HeritiersActorSheet.#onQuantityDecrease,
pvIncrease: HeritiersActorSheet.#onPvIncrease,
pvDecrease: HeritiersActorSheet.#onPvDecrease,
rollInitiative: HeritiersActorSheet.#onRollInitiative,
rollCarac: HeritiersActorSheet.#onRollCarac,
rollRang: HeritiersActorSheet.#onRollRang,
rollRootCompetence: HeritiersActorSheet.#onRollRootCompetence,
rollCompetence: HeritiersActorSheet.#onRollCompetence,
rollSort: HeritiersActorSheet.#onRollSort,
rollAttaqueArme: HeritiersActorSheet.#onRollAttaqueArme,
rollAttaqueBrutaleArme: HeritiersActorSheet.#onRollAttaqueBrutaleArme,
rollAttaqueChargeArme: HeritiersActorSheet.#onRollAttaqueChargeArme,
rollAssomerArme: HeritiersActorSheet.#onRollAssomerArme,
rollPouvoir: HeritiersActorSheet.#onRollPouvoir,
toggleMasque: HeritiersActorSheet.#onToggleMasque,
dialogRecupUsage: HeritiersActorSheet.#onDialogRecupUsage,
},
}
/**
* Is the sheet currently in 'Play' mode?
* @type {boolean}
*/
get isPlayMode() {
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY
return this._sheetMode === this.constructor.SHEET_MODES.PLAY
}
/**
* Is the sheet currently in 'Edit' mode?
* @type {boolean}
*/
get isEditMode() {
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY
return this._sheetMode === this.constructor.SHEET_MODES.EDIT
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = { primary: "competences" }
/** @override */
async _prepareContext() {
const actor = this.document
const context = {
actor: actor,
system: actor.system,
source: actor.toObject(),
fields: actor.schema.fields,
systemFields: actor.system.schema.fields,
isEditable: this.isEditable,
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isGM: game.user.isGM,
config: CONFIG.HERITIERS,
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.description || "", { async: true }),
enrichedHabitat: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.habitat || "", { async: true }),
enrichedRevesetranges: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.revesetranges || "", { async: true }),
enrichedSecretsdecouverts: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.secretsdecouverts || "", { async: true }),
enrichedQuestions: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.questions || "", { async: true }),
enrichedPlayernotes: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.playernotes || "", { async: true }),
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
// Activate drag & drop handlers
this.#dragDrop.forEach(d => d.bind(this.element))
// Manual tab navigation
const html = this.element
const tabLinks = html.querySelectorAll('.sheet-tabs a.item[data-tab]')
const tabContents = html.querySelectorAll('.sheet-body .tab[data-group="primary"]')
// Hide all tabs initially
tabContents.forEach(tab => {
tab.classList.remove('active')
tab.style.display = 'none'
})
// Show active tab
const activeTab = this.tabGroups.primary
const activeTabContent = html.querySelector(`.tab[data-group="primary"][data-tab="${activeTab}"]`)
if (activeTabContent) {
activeTabContent.classList.add('active')
activeTabContent.style.display = 'block'
}
// Activate the corresponding nav link
tabLinks.forEach(link => {
if (link.dataset.tab === activeTab) {
link.classList.add('active')
} else {
link.classList.remove('active')
}
})
// Tab click handler
tabLinks.forEach(link => {
link.addEventListener('click', (event) => {
event.preventDefault()
const tab = link.dataset.tab
// Update state
this.tabGroups.primary = tab
// Hide all tabs
tabContents.forEach(t => {
t.classList.remove('active')
t.style.display = 'none'
})
// Show selected tab
const selectedTab = html.querySelector(`.tab[data-group="primary"][data-tab="${tab}"]`)
if (selectedTab) {
selectedTab.classList.add('active')
selectedTab.style.display = 'block'
}
// Update nav links
tabLinks.forEach(l => {
if (l.dataset.tab === tab) {
l.classList.add('active')
} else {
l.classList.remove('active')
}
})
})
})
// Inline item editing
html.querySelectorAll('.edit-item-data').forEach(input => {
input.addEventListener('change', (event) => {
const li = event.target.closest('.item')
const itemId = li?.dataset.itemId
const itemType = li?.dataset.itemType
const itemField = event.target.dataset.itemField
const dataType = event.target.dataset.dtype
// Pour les checkboxes, utiliser checked au lieu de value
const value = event.target.type === 'checkbox' ? event.target.checked : event.target.value
if (itemId && itemType && itemField) {
this.actor.editItemField(itemId, itemType, itemField, dataType, value)
}
})
})
}
// #region Drag & Drop
/**
* Create drag-and-drop workflow handlers for this Application
* @returns {DragDrop[]} An array of DragDrop handlers
* @private
*/
#createDragDropHandlers() {
return this.options.dragDrop.map((d) => {
d.permissions = {
dragstart: this._canDragStart.bind(this),
drop: this._canDragDrop.bind(this),
}
d.callbacks = {
dragstart: this._onDragStart.bind(this),
drop: this._onDrop.bind(this),
}
return new foundry.applications.ux.DragDrop(d)
})
}
/**
* Define whether a user is able to begin a dragstart workflow for a given drag selector
* @param {string} selector The candidate HTML selector for dragging
* @returns {boolean} Can the current user drag this selector?
* @protected
*/
_canDragStart(selector) {
return this.isEditable
}
/**
* Define whether a user is able to conclude a drag-and-drop workflow for a given drop selector
* @param {string} selector The candidate HTML selector for the drop target
* @returns {boolean} Can the current user drop on this selector?
* @protected
*/
_canDragDrop(selector) {
return this.isEditable
}
/**
* Callback actions which occur at the beginning of a drag start workflow.
* @param {DragEvent} event The originating DragEvent
* @protected
*/
_onDragStart(event) {
const li = event.currentTarget.closest(".item")
if (!li?.dataset.itemId) return
const item = this.actor.items.get(li.dataset.itemId)
if (!item) return
const dragData = item.toDragData()
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
}
/**
* Callback actions which occur when a dragged element is dropped on a target.
* @param {DragEvent} event The originating DragEvent
* @protected
*/
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
const actor = this.actor
// Handle different data types
switch (data.type) {
case "Item":
return this._onDropItem(event, data)
case "Actor":
return this._onDropActor(event, data)
case "ActiveEffect":
return this._onDropActiveEffect(event, data)
}
}
/**
* Handle dropping an Item on the actor sheet
* @param {DragEvent} event
* @param {object} data
* @private
*/
async _onDropItem(event, data) {
if (!this.actor.isOwner) return false
let item = await fromUuid(data.uuid)
if (item.pack) {
item = await HeritiersUtility.searchItem(item)
}
const itemData = item.toObject ? item.toObject() : item
return this.actor.createEmbeddedDocuments("Item", [itemData])
}
/**
* Handle dropping an Actor on the sheet
* @param {DragEvent} event
* @param {object} data
* @private
*/
async _onDropActor(event, data) {
return false
}
/**
* Handle dropping an ActiveEffect on the sheet
* @param {DragEvent} event
* @param {object} data
* @private
*/
async _onDropActiveEffect(event, data) {
return false
}
// #endregion
// #region Action Handlers
/**
* Toggle between edit and play mode
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static #onToggleSheet(event, target) {
const wasEditMode = this.isEditMode
this._sheetMode = wasEditMode ? this.constructor.SHEET_MODES.PLAY : this.constructor.SHEET_MODES.EDIT
this.render({ force: true })
}
/**
* Edit the actor image
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onEditImage(event, target) {
const fp = new FilePicker({
type: "image",
current: this.actor.img,
callback: (path) => {
this.actor.update({ img: path })
},
})
return fp.browse()
}
/**
* Edit an item
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onEditItem(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
const item = this.actor.items.get(itemId)
if (item) item.sheet.render(true)
}
/**
* Delete an item
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onDeleteItem(event, target) {
const li = target.closest(".item")
await HeritiersUtility.confirmDelete(this, li)
}
/**
* Create a new item
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onCreateItem(event, target) {
const itemType = target.dataset.type
// Cas spécial pour les sorts avec une compétence spécifique
if (itemType === "sort" && target.dataset.sortCompetence) {
const sortCompetence = target.dataset.sortCompetence
await this.actor.createEmbeddedDocuments('Item', [{
name: `Nouveau ${itemType} de ${sortCompetence}`,
type: itemType,
system: { competence: sortCompetence }
}], { renderSheet: true })
return
}
await this.actor.createEmbeddedDocuments("Item", [{ name: `Nouveau ${itemType}`, type: itemType }], { renderSheet: true })
}
/**
* Equip/unequip an item
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onEquipItem(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (itemId) {
await this.actor.equipItem(itemId)
this.render()
}
}
/**
* Modify item quantity
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onModifyQuantity(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
const value = Number(target.dataset.quantiteValue)
if (itemId) {
await this.actor.incDecQuantity(itemId, value)
}
}
/**
* Increase item quantity
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onQuantityIncrease(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (itemId) {
await this.actor.incDecQuantity(itemId, 1)
}
}
/**
* Decrease item quantity
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onQuantityDecrease(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (itemId) {
await this.actor.incDecQuantity(itemId, -1)
}
}
/**
* Increase PV
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onPvIncrease(event, target) {
const currentPv = this.actor.system.pv.value || 0
const maxPv = this.actor.system.pv.max || 0
const newPv = Math.min(currentPv + 1, maxPv)
await this.actor.update({ 'system.pv.value': newPv })
}
/**
* Decrease PV
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onPvDecrease(event, target) {
const currentPv = this.actor.system.pv.value || 0
const newPv = Math.max(currentPv - 1, 0)
await this.actor.update({ 'system.pv.value': newPv })
}
/**
* Roll initiative
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollInitiative(event, target) {
await this.actor.rollInitiative()
}
/**
* Roll caractéristique
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollCarac(event, target) {
const key = target.dataset.key
if (key) {
await this.actor.rollCarac(key, false)
}
}
/**
* Roll rang
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollRang(event, target) {
const key = target.dataset.rangKey
if (key) {
await this.actor.rollRang(key)
}
}
/**
* Roll root competence
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollRootCompetence(event, target) {
const compKey = target.dataset.attrKey
if (compKey) {
await this.actor.rollRootCompetence(compKey)
}
}
/**
* Roll competence
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollCompetence(event, target) {
const li = target.closest(".item")
const compId = li?.dataset.itemId
if (compId) {
await this.actor.rollCompetence(compId)
}
}
/**
* Roll sort
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollSort(event, target) {
const li = target.closest(".item")
const sortId = li?.dataset.itemId
if (sortId) {
await this.actor.rollSort(sortId)
}
}
/**
* Roll attaque arme
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollAttaqueArme(event, target) {
const li = target.closest(".item")
const armeId = li?.dataset.itemId
if (armeId) {
await this.actor.rollAttaqueArme(armeId)
}
}
/**
* Roll attaque brutale arme
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollAttaqueBrutaleArme(event, target) {
const li = target.closest(".item")
const armeId = li?.dataset.itemId
if (armeId) {
await this.actor.rollAttaqueBrutaleArme(armeId)
}
}
/**
* Roll attaque charge arme
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollAttaqueChargeArme(event, target) {
const li = target.closest(".item")
const armeId = li?.dataset.itemId
if (armeId) {
await this.actor.rollAttaqueChargeArme(armeId)
}
}
/**
* Roll assomer arme
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollAssomerArme(event, target) {
const li = target.closest(".item")
const armeId = li?.dataset.itemId
if (armeId) {
await this.actor.rollAssomerArme(armeId)
}
}
/**
* Roll pouvoir
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollPouvoir(event, target) {
const li = target.closest(".item")
const pouvoirId = li?.dataset.itemId
if (pouvoirId) {
await this.actor.rollPouvoir(pouvoirId)
}
}
/**
* Toggle masque
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onToggleMasque(event, target) {
await this.actor.toggleMasqueStatut()
this.render()
}
/**
* Dialog récupération usage
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onDialogRecupUsage(event, target) {
new Dialog({
title: "Récupération des Points d'Usage",
content: "<p>Combien de Points d'Usage souhaitez-vous récupérer ?</p>",
buttons: {
one: {
icon: '<i class="fas fa-check"></i>',
label: "1 Point",
callback: () => {
this.actor.recupUsage(1)
}
},
two: {
icon: '<i class="fas fa-check"></i>',
label: "2 Points",
callback: () => {
this.actor.recupUsage(2)
}
},
three: {
icon: '<i class="fas fa-check"></i>',
label: "3 Points",
callback: () => {
this.actor.recupUsage(3)
}
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Annuler"
}
},
default: "one"
}).render(true)
}
// #endregion
}

View File

@@ -0,0 +1,212 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class HeritiersItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-les-heritiers", "item"],
position: {
width: 620,
height: 600,
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
tabs: [
{
navSelector: 'nav[data-group="primary"]',
contentSelector: "section.sheet-body",
initial: "description",
},
],
dragDrop: [{ dragSelector: "[data-drag]", dropSelector: null }],
actions: {
editImage: HeritiersItemSheet.#onEditImage,
postItem: HeritiersItemSheet.#onPostItem,
},
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = { primary: "description" }
/** @override */
async _prepareContext() {
const context = {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
source: this.document.toObject(),
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true }),
isEditMode: true,
isEditable: this.isEditable,
isGM: game.user.isGM,
config: CONFIG.HERITIERS,
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
this.#dragDrop.forEach((d) => d.bind(this.element))
// Activate tab navigation manually
const nav = this.element.querySelector('nav.tabs[data-group]')
if (nav) {
const group = nav.dataset.group
// Activate the current tab
const activeTab = this.tabGroups[group] || "description"
nav.querySelectorAll('[data-tab]').forEach(link => {
const tab = link.dataset.tab
link.classList.toggle('active', tab === activeTab)
link.addEventListener('click', (event) => {
event.preventDefault()
this.tabGroups[group] = tab
this.render()
})
})
// Show/hide tab content
this.element.querySelectorAll('[data-group="' + group + '"][data-tab]').forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab)
})
}
}
// #region Drag-and-Drop Workflow
/**
* Create drag-and-drop workflow handlers for this Application
* @returns {DragDrop[]} An array of DragDrop handlers
* @private
*/
#createDragDropHandlers() {
return this.options.dragDrop.map((d) => {
d.permissions = {
dragstart: this._canDragStart.bind(this),
drop: this._canDragDrop.bind(this),
}
d.callbacks = {
dragstart: this._onDragStart.bind(this),
dragover: this._onDragOver.bind(this),
drop: this._onDrop.bind(this),
}
return new foundry.applications.ux.DragDrop(d)
})
}
/**
* Can the User start a drag workflow for a given drag selector?
* @param {string} selector The candidate HTML selector for the drag event
* @returns {boolean} Can the current user drag this selector?
* @protected
*/
_canDragStart(selector) {
return this.isEditable
}
/**
* Can the User drop an entry at a given drop selector?
* @param {string} selector The candidate HTML selector for the drop event
* @returns {boolean} Can the current user drop on this selector?
* @protected
*/
_canDragDrop(selector) {
return this.isEditable
}
/**
* Callback for dragstart events.
* @param {DragEvent} event The drag start event
* @protected
*/
_onDragStart(event) {
const target = event.currentTarget
const dragData = { type: "Item", uuid: this.document.uuid }
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
}
/**
* Callback for dragover events.
* @param {DragEvent} event The drag over event
* @protected
*/
_onDragOver(event) {
// Default behavior is fine
}
/**
* Callback for drop events.
* @param {DragEvent} event The drop event
* @protected
*/
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
const item = await fromUuid(data.uuid)
if (!item) return
console.log("Item dropped:", item)
}
// #endregion
// #region Action Handlers
/**
* Edit the item image
* @param {Event} event The triggering event
* @param {HTMLElement} target The target element
* @private
*/
static async #onEditImage(event, target) {
const fp = new FilePicker({
type: "image",
current: this.document.img,
callback: (path) => {
this.document.update({ img: path })
},
})
return fp.browse()
}
/**
* Post item to chat
* @param {Event} event The triggering event
* @param {HTMLElement} target The target element
* @private
*/
static async #onPostItem(event, target) {
let chatData = foundry.utils.duplicate(this.document)
if (this.document.actor) {
chatData.actor = { id: this.document.actor.id }
}
// Don't post any image for the item if the default image is used
if (chatData.img.includes("/blank.png") || chatData.img.includes("/mystery-man")) {
chatData.img = null
}
// JSON object for easy creation
chatData.jsondata = JSON.stringify({
compendium: "postedItem",
payload: chatData,
})
const html = await renderTemplate('systems/fvtt-les-heritiers/templates/post-item.html', chatData)
const chatOptions = {
user: game.user.id,
content: html,
}
ChatMessage.create(chatOptions)
}
// #endregion
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersCapaciteNaturelleSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.capacitenaturelle",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-capacitenaturelle-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,75 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersCompetenceSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.competence",
},
actions: {
addSpecialite: HeritiersCompetenceSheet.#onAddSpecialite,
deleteSpecialite: HeritiersCompetenceSheet.#onDeleteSpecialite,
}
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-competence-sheet.hbs",
},
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
// Attacher les écouteurs pour l'édition des spécialités
this.element.querySelectorAll('.edit-specialite').forEach(input => {
input.addEventListener('change', async (event) => {
const li = event.target.closest('.specialite-item')
const index = Number.parseInt(li?.dataset.specialiteIndex)
if (index !== undefined && !Number.isNaN(index)) {
const spec = foundry.utils.duplicate(this.item.system.specialites) || []
if (spec[index]) {
spec[index].name = event.target.value
await this.item.update({ 'system.specialites': spec })
}
}
})
})
this.element.querySelectorAll('.edit-specialite-description').forEach(textarea => {
textarea.addEventListener('change', async (event) => {
const li = event.target.closest('.specialite-item')
const index = Number.parseInt(li?.dataset.specialiteIndex)
if (index !== undefined && !Number.isNaN(index)) {
const spec = foundry.utils.duplicate(this.item.system.specialites) || []
if (spec[index]) {
spec[index].description = event.target.value
await this.item.update({ 'system.specialites': spec })
}
}
})
})
}
/* -------------------------------------------- */
/* Event Handlers */
/* -------------------------------------------- */
static async #onAddSpecialite(event, target) {
let spec = foundry.utils.duplicate(this.item.system.specialites) || []
spec.push({ name: "Nouvelle Spécialité", description: "", used: false })
await this.item.update({ 'system.specialites': spec })
}
static async #onDeleteSpecialite(event, target) {
const li = target.closest(".specialite-item")
let index = Number.parseInt(li.dataset.specialiteIndex)
let spec = foundry.utils.duplicate(this.item.system.specialites) || []
spec.splice(index, 1)
await this.item.update({ 'system.specialites': spec })
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersContactSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.contact",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-contact-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersDesavantageSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.desavantage",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-desavantage-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersEquipementSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.equipement",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-equipement-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersFeeSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.fee",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-fee-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,59 @@
import HeritiersActorSheet from "./base-actor-sheet.mjs"
export default class HeritiersPersonnageSheet extends HeritiersActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Actor.personnage",
},
actions: {
...super.DEFAULT_OPTIONS.actions,
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/actor-sheet.hbs",
},
}
/** @override */
tabGroups = { primary: "competences" }
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
// Add personnage-specific data
context.skills = actor.getSkills()
context.utileSkillsMental = actor.organizeUtileSkills("mental")
context.utileSkillsPhysical = actor.organizeUtileSkills("physical")
context.competencesMagie = game.system.lesheritiers.config.competencesMagie || []
context.futileSkills = actor.organizeFutileSkills()
context.contacts = actor.organizeContacts()
context.armes = foundry.utils.duplicate(actor.getWeapons())
context.monnaies = foundry.utils.duplicate(actor.getMonnaies())
context.pouvoirs = foundry.utils.duplicate(actor.getPouvoirs())
context.fee = foundry.utils.duplicate(actor.getFee() || {})
context.protections = foundry.utils.duplicate(actor.getArmors())
context.combat = actor.getCombatValues()
context.equipements = foundry.utils.duplicate(actor.getEquipments())
context.avantages = foundry.utils.duplicate(actor.getAvantages())
context.atouts = foundry.utils.duplicate(actor.getAtouts())
context.capacites = foundry.utils.duplicate(actor.getCapacites())
context.desavantages = foundry.utils.duplicate(actor.getDesavantages())
context.profils = foundry.utils.duplicate(actor.getProfils())
context.pvMalus = actor.getPvMalus()
context.heritage = game.settings.get("fvtt-les-heritiers", "heritiers-heritage")
context.initiative = actor.getFlag("world", "last-initiative") || -1
context.magieList = actor.prepareMagie()
context.isPNJ = false
return context
}
}

View File

@@ -0,0 +1,59 @@
import HeritiersActorSheet from "./base-actor-sheet.mjs"
export default class HeritiersPnjSheet extends HeritiersActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Actor.pnj",
},
actions: {
...super.DEFAULT_OPTIONS.actions,
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/actor-pnj-sheet.hbs",
},
}
/** @override */
tabGroups = { primary: "competences" }
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
// Add PNJ-specific data
context.skills = actor.getSkills()
context.utileSkillsMental = actor.organizeUtileSkills("mental")
context.utileSkillsPhysical = actor.organizeUtileSkills("physical")
context.competencesMagie = game.system.lesheritiers.config.competencesMagie || []
context.futileSkills = actor.organizeFutileSkills()
context.contacts = actor.organizeContacts()
context.armes = foundry.utils.duplicate(actor.getWeapons())
context.monnaies = foundry.utils.duplicate(actor.getMonnaies())
context.pouvoirs = foundry.utils.duplicate(actor.getPouvoirs())
context.fee = foundry.utils.duplicate(actor.getFee() || {})
context.protections = foundry.utils.duplicate(actor.getArmors())
context.combat = actor.getCombatValues()
context.equipements = foundry.utils.duplicate(actor.getEquipments())
context.avantages = foundry.utils.duplicate(actor.getAvantages())
context.atouts = foundry.utils.duplicate(actor.getAtouts())
context.capacites = foundry.utils.duplicate(actor.getCapacites())
context.desavantages = foundry.utils.duplicate(actor.getDesavantages())
context.profils = foundry.utils.duplicate(actor.getProfils())
context.pvMalus = actor.getPvMalus()
context.heritage = game.settings.get("fvtt-les-heritiers", "heritiers-heritage")
context.initiative = actor.getFlag("world", "last-initiative") || -1
context.magieList = actor.prepareMagie()
context.isPNJ = true
return context
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersPouvoirSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.pouvoir",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-pouvoir-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersProfilSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.profil",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-profil-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,19 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
export default class HeritiersProtectionSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.protection",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-protection-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,27 @@
import HeritiersItemSheet from "./base-item-sheet.mjs"
import { HeritiersUtility } from "../../heritiers-utility.js"
export default class HeritiersSortSheet extends HeritiersItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.sort",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-les-heritiers/templates/item-sort-sheet.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.competencesMagie = HeritiersUtility.getCompetencesMagie() || []
return context
}
}

View File

@@ -1,177 +0,0 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
import { HeritiersUtility } from "./heritiers-utility.js";
/* -------------------------------------------- */
export class HeritiersActorSheet extends ActorSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["fvtt-les-heritiers", "sheet", "actor"],
template: "systems/fvtt-les-heritiers/templates/actor-sheet.html",
width: 640,
height: 720,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
editScore: false
})
}
/* -------------------------------------------- */
async getData() {
const objectData = duplicate(this.object)
let formData = {
title: this.title,
id: objectData.id,
type: objectData.type,
img: objectData.img,
name: objectData.name,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
system: objectData.system,
effects: this.object.effects.map(e => foundry.utils.deepClone(e.data)),
limited: this.object.limited,
skills: this.actor.getSkills(),
utileSkills :this.actor.organizeUtileSkills(),
futileSkills :this.actor.organizeFutileSkills(),
armes: duplicate(this.actor.getWeapons()),
monnaies: duplicate(this.actor.getMonnaies()),
fee: duplicate(this.actor.getFee() || {} ),
protections: duplicate(this.actor.getArmors()),
combat: this.actor.getCombatValues(),
equipements: duplicate(this.actor.getEquipments()),
avantages: duplicate(this.actor.getAvantages()),
atouts: duplicate(this.actor.getAtouts()),
capacites: duplicate(this.actor.getCapacites()),
desavantages: duplicate(this.actor.getDesavantages()),
pvMalus: this.actor.getPvMalus(),
initiative: this.actor.getFlag("world", "last-initiative") || -1,
description: await TextEditor.enrichHTML(this.object.system.biodata.description, {async: true}),
habitat: await TextEditor.enrichHTML(this.object.system.biodata.habitat, {async: true}),
options: this.options,
owner: this.document.isOwner,
editScore: this.options.editScore,
isGM: game.user.isGM
}
this.formData = formData;
console.log("PC : ", formData, this.object);
return formData;
}
/* -------------------------------------------- */
getCelluleTalents( ) {
let talents = []
for(let cellule of game.actors) {
if (cellule.type == "cellule") {
let found = cellule.system.members.find( it => it.id == this.actor.id)
if (found) {
talents = talents.concat( cellule.getTalents() )
}
}
}
return talents
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item")
let itemId = li.data("item-id")
const item = this.actor.items.get( itemId )
item.sheet.render(true)
})
// Delete Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
HeritiersUtility.confirmDelete(this, li);
})
html.find('.edit-item-data').change(ev => {
const li = $(ev.currentTarget).parents(".item")
let itemId = li.data("item-id")
let itemType = li.data("item-type")
let itemField = $(ev.currentTarget).data("item-field")
let dataType = $(ev.currentTarget).data("dtype")
let value = ev.currentTarget.value
this.actor.editItemField(itemId, itemType, itemField, dataType, value)
})
html.find('.adversite-modify').click(event => {
const li = $(event.currentTarget).parents(".item")
let adv = li.data("adversite")
let value = Number($(event.currentTarget).data("adversite-value"))
this.actor.incDecAdversite(adv, value)
})
html.find('.quantity-modify').click(event => {
const li = $(event.currentTarget).parents(".item")
const value = Number($(event.currentTarget).data("quantite-value"))
this.actor.incDecQuantity( li.data("item-id"), value );
})
html.find('.roll-initiative').click((event) => {
this.actor.rollInitiative()
})
html.find('.roll-carac').click((event) => {
const key = $(event.currentTarget).data("key")
this.actor.rollCarac(key, false)
})
html.find('.roll-competence').click((event) => {
const li = $(event.currentTarget).parents(".item")
let compId = li.data("item-id")
this.actor.rollCompetence(compId)
})
html.find('.item-add').click((event) => {
const itemType = $(event.currentTarget).data("type")
this.actor.createEmbeddedDocuments('Item', [{ name: `Nouveau ${itemType}`, type: itemType }], { renderSheet: true })
})
html.find('.lock-unlock-sheet').click((event) => {
this.options.editScore = !this.options.editScore;
this.render(true);
});
html.find('.item-equip').click(ev => {
const li = $(ev.currentTarget).parents(".item");
this.actor.equipItem( li.data("item-id") );
this.render(true);
});
}
/* -------------------------------------------- */
/** @override */
setPosition(options = {}) {
const position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body");
const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
return position;
}
/* -------------------------------------------- */
/*async _onDropItem(event, dragData) {
let data = event.dataTransfer.getData('text/plain')
let dataItem = JSON.parse( data)
let item = fromUuidSync(dataItem.uuid)
if (item.pack) {
item = await HeritiersUtility.searchItem(item)
}
super._onDropItem(event, dragData)
}*/
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
/* -------------------------------------------- */
import { HeritiersUtility } from "./heritiers-utility.js";
import { HeritiersRollDialog } from "./heritiers-roll-dialog.js";
import { HeritiersRollDialog } from "./applications/heritiers-roll-dialog.mjs";
/* -------------------------------------------- */
export class HeritiersCommands {

View File

@@ -8,26 +8,34 @@ export const HERITIERS_CONFIG = {
"prec": "Précision",
"esp": "Esprit",
"per": "Perception",
"pres": "Présence",
"pres": "Prestance",
"san": "Sang-Froid"
},
competenceCategorie : {
competenceCategorie: {
"utile": "Utile",
"futile": "Futile"
},
competenceProfil : {
"aventurier": "Aventurier",
"roublard": "Roublard",
"combattant": "Combattant",
"erudit": "Erudit",
"savant": "Savant",
"gentleman": "Gentleman"
contactType: {
contact: "Contact",
allie: "Allié",
ennemi: "Ennemi",
interet: "Personne d'interêt"
},
competenceProfil: {
"aventurier": { kind: "physical", name: "Aventurier" },
"roublard": { kind: "physical", name: "Roublard" },
"combattant": { kind: "physical", name: "Combattant" },
"erudit": { kind: "mental", name: "Erudit" },
"savant": { kind: "mental", name: "Savant" },
"gentleman": { kind: "mental", name: "Gentleman" },
"magie": { kind: "magical", name: "Magie" },
},
baseTestPouvoir: {
"feerie": "Féerie",
"Masque": "Masque",
"masque": "Masque",
"autre": "Autre"
},
resistancePouvoir: {
@@ -43,6 +51,10 @@ export const HERITIERS_CONFIG = {
"passif": "Passif",
"metamorphose": "Métamorphose"
},
statutMasque: {
"masque": "Masqué",
"demasque": "Démasqué"
},
niveauPouvoir: {
"normal": "Normal",
"profond": "Profond",
@@ -53,24 +65,43 @@ export const HERITIERS_CONFIG = {
"demasque": "Démasqué"
},
seuilsDifficulte: {
"0": "Aucun/Non applicable",
"5": "Enfantine",
"6": "Triviale",
"8": "Aisée",
"10": "Normale",
"12": "Compliquée",
"14": "Difficile",
"16": "Très Difficile",
"18": "Critique",
"20": "Insurmontable",
"22": "Surhumaine",
"24": "Epique",
"26": "Légendaire",
"28": "Mythique",
"30": "Divine"
"-1": "Aucun/Non applicable",
"5": "Enfantine (5)",
"6": "Triviale (6)",
"7": "Moins Triviale (7)",
"8": "Aisée (8)",
"9": "Moins Aisée (9)",
"10": "Normale (10)",
"11": "Moins Normale (11)",
"12": "Compliquée (12)",
"13": "Plus Compliquée (13)",
"14": "Difficile (14)",
"15": "Plus Difficile (15)",
"16": "Très Difficile (16)",
"17": "Très Très Difficile (17)",
"18": "Critique (18)",
"19": "Plus Critique (19)",
"20": "Insurmontable (20)",
"21": "Très Insurmontable (21)",
"22": "Surhumaine (22)",
"23": "Très Surhumaine (23)",
"24": "Epique (24)",
"25": "Plus Epique (25)",
"26": "Légendaire (26)",
"27": "Très Légendaire (27)",
"28": "Mythique (28)",
"29": "Plus Mythique (29)",
"30": "Divine (30)"
},
categorieArme : {
attaqueCible: {
"none": "Aucune",
"membre": "Membre",
"main": "Main",
"tete": "Tête/Coeur"
},
categorieArme: {
"trait": "Arme de trait",
"poing": "Arme de poing",
"epaule": "Arme d'épaule",
@@ -78,7 +109,7 @@ export const HERITIERS_CONFIG = {
"blanche": "Arme blanche",
"improvise": "Arme improvisée",
"explosif": "Explosif"
},
},
typeArme: {
"naturelle": "Arme naturelle",
"trait": "Trait",
@@ -100,13 +131,13 @@ export const HERITIERS_CONFIG = {
"controlee": "Contrôlée (C)",
"prohibee": "Prohibée (P)"
},
armeDissimulation :{
armeDissimulation: {
"tresfacile": "Très facile (TF)",
"facile": "Facile (F)",
"difficile": "Difficile (D)",
"impossible": "Impossible (I)"
},
typeProtection : {
typeProtection: {
"balle": "Protège ds balles",
"melee": "Protège en mélée",
"tout": "Tout type de dégats"
@@ -115,7 +146,137 @@ export const HERITIERS_CONFIG = {
"traditionnelle": "Traditionnelle",
"moderne": "Moderne",
"orientale": "Orientale"
},
typeContact: {
"contact": "Contact",
"allie": "Allié",
"ennemi": "Ennemi",
"interet": "Personne d'interêt"
},
niveauContact: {
"1": "1",
"2": "2",
"3": "3",
},
pointsUsageList: {
"1": "1",
"2": "2",
"3": "3",
"4": "4",
},
attaquePlusieursList: {
"0": "0",
"1": "+1",
"2": "+2",
},
attaque2ArmesListe: [
{ value: "0", label: "Aucun" },
{ value: "-4", label: "Deux armes à 1 main" },
{ value: "-2", label: "Deux armes naturelles" },
{ value: "-2", label: "Avec spécialisation \"Mauvaise Main\"" }
],
typeProfil: {
"mineur": "Mineur",
"majeur": "Majeur",
},
bonusMalusContext: [
{ value: "-6", label: "-6" },
{ value: "-5", label: "-5" },
{ value: "-4", label: "-4" },
{ value: "-3", label: "-3" },
{ value: "-2", label: "-2" },
{ value: "-1", label: "-1" },
{ value: "0", label: "0" },
{ value: "1", label: "+1" },
{ value: "2", label: "+2" },
{ value: "3", label: "+3" },
{ value: "4", label: "+4" },
{ value: "5", label: "+5" },
{ value: "6", label: "+6" }
],
listNiveauSort: {
"1": "1",
"2": "2",
"3": "3",
"4": "4"
},
listRangSort: {
"1": "1",
"2": "2",
"3": "3",
"4": "4",
"5": "5",
"6": "6",
"7": "7"
},
listNiveau: {
"0": "0",
"1": "1",
"2": "2",
"3": "3",
"4": "4",
"5": "5",
"6": "6",
"7": "7",
"8": "8",
"9": "9",
"10": "10"
},
rangName: [
"Novice",
"Novice",
"Adepte",
"Maître",
"Grand Maître"
],
rangNameSpecific: {
"Druidisme": {
"Novice": "Eubage",
"Adepte": "Saronide",
"Maître": "Ovate",
"Grand Maître": "Archidruide"
},
"Faëomancie": {
"Novice": "Marmiton",
"Adepte": "Queux",
"Maître": "Chef",
"Grand Maître": "Maître-queux"
},
"Nécromancie": {
"Novice": "Inexpertus",
"Adepte": "Discipulus",
"Maître": "Dominus",
"Grand Maître": "Magister"
},
"Necromancie": {
"Novice": "Inexpertus",
"Adepte": "Discipulus",
"Maître": "Dominus",
"Grand Maître": "Magister"
},
"Magie du Clan": {
"Novice": "Apprenti",
"Adepte": "Disciple",
"Maître": "Maître",
"Grand Maître": "Éminence"
},
"Théurgie": {
"Novice": "Frère",
"Adepte": "Père",
"Maître": "Saint",
"Grand Maître": "Apôtre"
},
"Grand Langage": {
"Novice": "Éveillé",
"Adepte": "Initié",
"Maître": "Sage",
"Grand Maître": "Docteur"
}
},
soufflesMagieDuClan: {
"soufflecombat": "Souffle du Combat",
"soufflemouvement": "Souffle du Mouvement",
"souffleesprit": "Souffle de l'Esprit"
}
}

View File

@@ -1,25 +0,0 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
import { HeritiersActorSheet } from "./heritiers-actor-sheet.js";
import { HeritiersUtility } from "./heritiers-utility.js";
/* -------------------------------------------- */
export class HeritiersCreatureSheet extends HeritiersActorSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["fvtt-les-heritiers", "sheet", "actor"],
template: "systems/fvtt-les-heritiers/templates/creature-sheet.html",
width: 640,
height: 720,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
editScore: false
})
}
}

View File

@@ -1,200 +0,0 @@
import { HeritiersUtility } from "./heritiers-utility.js";
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
*/
export class HeritiersItemSheet extends ItemSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["fvtt-les-heritiers", "sheet", "item"],
template: "systems/fvtt-les-heritiers/templates/item-sheet.html",
dragDrop: [{ dragSelector: null, dropSelector: null }],
width: 620,
height: 550,
tabs: [{navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description"}]
});
}
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
// Add "Post to chat" button
// We previously restricted this to GM and editable items only. If you ever find this comment because it broke something: eh, sorry!
buttons.unshift(
{
class: "post",
icon: "fas fa-comment",
onclick: ev => { }
})
return buttons
}
/* -------------------------------------------- */
/** @override */
setPosition(options = {}) {
const position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body");
const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
if (this.item.type.includes('weapon')) {
position.width = 640;
}
return position;
}
/* -------------------------------------------- */
async getData() {
const objectData = duplicate(this.object)
let formData = {
title: this.title,
id: this.id,
type: objectData.type,
img: objectData.img,
name: objectData.name,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
system: objectData.system,
limited: this.object.limited,
options: this.options,
owner: this.document.isOwner,
config: game.system.lesheritiers.config,
description: await TextEditor.enrichHTML(this.object.system.description, {async: true}),
mr: (this.object.type == 'specialisation'),
isGM: game.user.isGM
}
//this.options.editable = !(this.object.origin == "embeddedItem");
console.log("ITEM DATA", formData, this);
return formData;
}
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
buttons.unshift({
class: "post",
icon: "fas fa-comment",
onclick: ev => this.postItem()
});
return buttons
}
/* -------------------------------------------- */
postItem() {
let chatData = duplicate(HeritiersUtility.data(this.item));
if (this.actor) {
chatData.actor = { id: this.actor.id };
}
// Don't post any image for the item (which would leave a large gap) if the default image is used
if (chatData.img.includes("/blank.png")) {
chatData.img = null;
}
// JSON object for easy creation
chatData.jsondata = JSON.stringify(
{
compendium: "postedItem",
payload: chatData,
});
renderTemplate('systems/fvtt-Heritiers-rpg/templates/post-item.html', chatData).then(html => {
let chatOptions = HeritiersUtility.chatDataSetup(html);
ChatMessage.create(chatOptions)
});
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item")
const item = this.object.options.actor.getOwnedItem(li.data("item-id"))
item.sheet.render(true);
});
html.find('.delete-subitem').click(ev => {
this.deleteSubitem(ev);
})
html.find('#add-specialite').click(ev => {
let spec = duplicate(this.object.system.specialites)
spec.push( { name: "Nouvelle Spécialité", id: randomID(16), used: false })
this.object.update( { 'system.specialites': spec })
})
html.find('.delete-specialite').click(ev => {
const li = $(ev.currentTarget).parents(".specialite-item")
let index = li.data("specialite-index")
let spec = duplicate(this.object.system.specialites)
spec.splice(index,1)
this.object.update( { 'system.specialites': spec })
})
html.find('.edit-specialite').change(ev => {
const li = $(ev.currentTarget).parents(".specialite-item")
let index = li.data("specialite-index")
let spec = duplicate(this.object.system.specialites)
spec[index].name = ev.currentTarget.value
spec[index].id = spec[index].id || randomID(16)
this.object.update( { 'system.specialites': spec })
})
html.find('.edit-specialite-description').change(ev => {
const li = $(ev.currentTarget).parents(".specialite-item")
let index = li.data("specialite-index")
let spec = duplicate(this.object.system.specialites)
spec[index].description = ev.currentTarget.value
spec[index].id = spec[index].id || randomID(16)
this.object.update( { 'system.specialites': spec })
})
html.find('#add-automation').click(ev => {
let autom = duplicate(this.object.system.automations)
autom.push( { eventtype: "on-drop", name: "Automatisation 1", competence: "", minLevel: 0, id: randomID(16) })
this.object.update( { 'system.automations': autom })
})
html.find('.delete-automation').click(ev => {
const li = $(ev.currentTarget).parents(".automation-item")
let index = li.data("automation-index")
let autom = duplicate(this.object.system.automations)
autom.splice(index,1)
this.object.update( { 'system.automations': autom })
})
html.find('.automation-edit-field').change(ev => {
let index = $(ev.currentTarget).data("automation-index")
let field = $(ev.currentTarget).data("automation-field")
let auto = duplicate(this.object.system.automations)
auto[index][field] = ev.currentTarget.value
auto[index].id = auto[index].id || randomID(16)
this.object.update( { 'system.automations': auto })
})
// Update Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
let itemId = li.data("item-id");
let itemType = li.data("item-type");
});
}
/* -------------------------------------------- */
get template() {
let type = this.item.type;
return `systems/fvtt-les-heritiers/templates/item-${type}-sheet.html`;
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
return this.object.update(formData);
}
}

View File

@@ -15,8 +15,10 @@ export const defaultItemImg = {
arme: "systems/fvtt-les-heritiers/assets/icons/weapon.webp",
accessoire: "systems/fvtt-les-heritiers/assets/icons/item.webp",
protection: "systems/fvtt-les-heritiers/assets/icons/armor.webp",
fee: "systems/fvtt-les-heritiers/assets/icons/faery_type.webp"
fee: "systems/fvtt-les-heritiers/assets/icons/faery_type.webp",
profil: "systems/fvtt-les-heritiers/assets/icons/profil.webp",
equipement: "systems/fvtt-les-heritiers/assets/icons/equipement.webp",
sort: "systems/fvtt-les-heritiers/assets/icons/sort.webp",
}
/**

View File

@@ -9,14 +9,17 @@
/* -------------------------------------------- */
// Import Modules
import { HeritiersActor } from "./heritiers-actor.js";
import { HeritiersItemSheet } from "./heritiers-item-sheet.js";
import { HeritiersActorSheet } from "./heritiers-actor-sheet.js";
import { HeritiersCreatureSheet } from "./heritiers-creature-sheet.js";
import { HeritiersItem } from "./heritiers-item.js";
import { HeritiersUtility } from "./heritiers-utility.js";
import { HeritiersCombat } from "./heritiers-combat.js";
import { HeritiersItem } from "./heritiers-item.js";
import { HERITIERS_CONFIG } from "./heritiers-config.js";
// Import DataModels
import * as models from "./models/index.mjs";
// Import AppV2 Sheets
import * as sheets from "./applications/sheets/_module.mjs";
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
@@ -30,7 +33,7 @@ Hooks.once("init", async function () {
HeritiersUtility.preloadHandlebarsTemplates()
/* -------------------------------------------- */
// Set an initiative formula for the system
// Set an initiative formula for the system
CONFIG.Combat.initiative = {
formula: "1d10",
decimals: 1
@@ -45,20 +48,62 @@ Hooks.once("init", async function () {
// Define custom Entity classes
CONFIG.Combat.documentClass = HeritiersCombat
CONFIG.Actor.documentClass = HeritiersActor
CONFIG.Actor.dataModels = {
personnage: models.PersonnageDataModel,
pnj: models.PnjDataModel
}
CONFIG.Item.documentClass = HeritiersItem
CONFIG.Item.dataModels = {
accessoire: models.AccessoireDataModel,
arme: models.ArmeDataModel,
atoutfeerique: models.AtoutFeeriqueDataModel,
avantage: models.AvantageDataModel,
capacitenaturelle: models.CapaciteNaturelleDataModel,
competence: models.CompetenceDataModel,
contact: models.ContactDataModel,
desavantage: models.DesavantageDataModel,
equipement: models.EquipementDataModel,
fee: models.FeeDataModel,
pouvoir: models.PouvoirDataModel,
profil: models.ProfilDataModel,
protection: models.ProtectionDataModel,
sort: models.SortDataModel
}
// Create an object of bonus/malus from -6 to +6 signed
HERITIERS_CONFIG.bonusMalus = Array.from({ length: 7 }, (v, k) => toString(k - 6))
CONFIG.HERITIERS = HERITIERS_CONFIG
game.system.lesheritiers = {
HeritiersUtility,
config: HERITIERS_CONFIG
config: HERITIERS_CONFIG,
models,
sheets
}
/* -------------------------------------------- */
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("fvtt-les-heritiers", HeritiersActorSheet, { types: ["personnage"], makeDefault: true })
Actors.registerSheet("fvtt-les-heritiers", HeritiersCreatureSheet, { types: ["creature"], makeDefault: true })
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
foundry.documents.collections.Actors.registerSheet("fvtt-les-heritiers", sheets.HeritiersPersonnageSheet, { types: ["personnage"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("fvtt-les-heritiers", sheets.HeritiersPnjSheet, { types: ["pnj"], makeDefault: true })
Items.unregisterSheet("core", ItemSheet);
Items.registerSheet("fvtt-les-heritiers", HeritiersItemSheet, { makeDefault: true })
// Register AppV2 Item Sheets
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersAccessoireSheet, { types: ["accessoire"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersArmeSheet, { types: ["arme"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersAtoutFeeriqueSheet, { types: ["atoutfeerique"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersAvantageSheet, { types: ["avantage"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersCapaciteNaturelleSheet, { types: ["capacitenaturelle"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersCompetenceSheet, { types: ["competence"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersContactSheet, { types: ["contact"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersDesavantageSheet, { types: ["desavantage"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersEquipementSheet, { types: ["equipement"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersFeeSheet, { types: ["fee"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersPouvoirSheet, { types: ["pouvoir"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersProfilSheet, { types: ["profil"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersProtectionSheet, { types: ["protection"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-heritiers", sheets.HeritiersSortSheet, { types: ["sort"], makeDefault: true })
HeritiersUtility.init()
@@ -69,40 +114,52 @@ function welcomeMessage() {
ChatMessage.create({
user: game.user.id,
whisper: [game.user.id],
content: `<div id="welcome-message-heritiers"><span class="rdd-roll-part">
<strong>Bienvenue dans Les Heritiers et la Belle Epoque !</strong>
<p>Les livres du JDR Les Heritiers sont nécessaires pour jouer : https://www.titam-france.fr</p>
<p>Les Heritiers est jeu de rôle publié par Titam France/Sombres projets, tout les droits leur appartiennent.</p>
<p>Système développé par LeRatierBretonnien, support sur le <a href="https://discord.gg/pPSDNJk">Discord FR de Foundry</a>.</p>
` });
content: `
<div class="heritiers-chat-card heritiers-welcome-card">
<div class="chat-card-header welcome-header">
<div class="welcome-icon-wrapper">
<i class="fas fa-book-open welcome-icon"></i>
</div>
<div class="chat-actor-info">
<h3 class="chat-actor-name">Bienvenue dans Les Héritiers !</h3>
<div class="chat-action-name">et la Belle Époque</div>
</div>
</div>
<div class="chat-card-content welcome-content">
<div class="welcome-section">
<i class="fas fa-info-circle"></i>
<p>Les livres du JDR <strong>Les Héritiers</strong> sont nécessaires pour jouer.</p>
</div>
<div class="welcome-section">
<i class="fas fa-copyright"></i>
<p><em>Les Héritiers</em> est un jeu de rôle publié par <strong>Titam France / Sombres Projets</strong>. Tous les droits leur appartiennent.</p>
</div>
<div class="welcome-section">
<i class="fas fa-code"></i>
<p>Système développé par <strong>LeRatierBretonnien</strong></p>
<p>Support sur le <a href="https://discord.gg/pPSDNJk" target="_blank"><i class="fab fa-discord"></i> Discord FR de Foundry</a></p>
</div>
</div>
</div>
`
});
}
/* -------------------------------------------- */
// Register world usage statistics
function registerUsageCount(registerKey) {
if (game.user.isGM) {
game.settings.register(registerKey, "world-key", {
name: "Unique world key",
scope: "world",
config: false,
default: "",
type: String
});
let worldKey = game.settings.get(registerKey, "world-key")
if (worldKey == undefined || worldKey == "") {
worldKey = randomID(32)
game.settings.set(registerKey, "world-key", worldKey)
}
// Simple API counter
let regURL = `https://www.uberwald.me/fvtt_appcount/count.php?name="${registerKey}"&worldKey="${worldKey}"&version="${game.release.generation}.${game.release.build}"&system="${game.system.id}"&systemversion="${game.system.version}"`
//$.ajaxSetup({
//headers: { 'Access-Control-Allow-Origin': '*' }
//})
$.ajax(regURL)
async function importDefaultScene() {
let exists = game.scenes.find(j => j.name == "Accueil");
if (!exists) {
const scenes = await HeritiersUtility.loadCompendium("fvtt-les-heritiers.scenes")
let newDocuments = scenes.filter(i => i.name == "Accueil");
await game.scenes.documentClass.create(newDocuments);
game.scenes.find(i => i.name == "Accueil").activate();
}
}
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
@@ -119,14 +176,15 @@ Hooks.once("ready", function () {
});
}
registerUsageCount('fvtt-les-heritiers')
welcomeMessage()
import("https://www.uberwald.me/fvtt_appcount/count-class-ready.js").then(moduleCounter => {
console.log("ClassCounter loaded", moduleCounter)
moduleCounter.ClassCounter.registerUsageCount()
}).catch(err =>
console.log("No stats available, giving up.")
)
welcomeMessage();
importDefaultScene();
// CSS patch for v9
if (game.version) {
let sidebar = document.getElementById("sidebar");
sidebar.style.width = "min-content";
}
});
/* -------------------------------------------- */
@@ -136,10 +194,9 @@ Hooks.on("chatMessage", (html, content, msg) => {
if (content[0] == '/') {
let regExp = /(\S+)/g;
let commands = content.match(regExp);
if (game.system.mournblade.commands.processChatCommand(commands, content, msg)) {
if (game.system.lesheritiers.commands.processChatCommand(commands, content, msg)) {
return false;
}
}
return true;
});

View File

@@ -1,97 +0,0 @@
import { HeritiersUtility } from "./heritiers-utility.js";
export class HeritiersRollDialog extends Dialog {
/* -------------------------------------------- */
static async create(actor, rollData) {
let options = { classes: ["HeritiersDialog"], width: 320, height: 'fit-content', 'z-index': 99999 };
let html = await renderTemplate('systems/fvtt-les-heritiers/templates/roll-dialog-generic.html', rollData);
return new HeritiersRollDialog(actor, rollData, html, options);
}
/* -------------------------------------------- */
constructor(actor, rollData, html, options, close = undefined) {
let conf = {
title: "Test de Capacité",
content: html,
buttons:
{
rolld8: {
icon: '<i class="fas fa-check"></i>',
label: "Lancer 1d8",
callback: () => { this.roll("d8") }
},
rolld10: {
icon: '<i class="fas fa-check"></i>',
label: "Lancer 1d10",
callback: () => { this.roll("d10") }
},
rolld12: {
icon: '<i class="fas fa-check"></i>',
label: "Lancer 1d12",
callback: () => { this.roll("d12") }
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Annuler",
callback: () => { this.close() }
}
},
close: close
}
// Overwrite in case of carac only -> 1d10
if (rollData.mode == "carac") {
conf.buttons = {
rolld8: {
icon: '<i class="fas fa-check"></i>',
label: "Lancer 1d8",
callback: () => { this.roll("d8") }
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Annuler",
callback: () => { this.close() }
}
}
}
super(conf, options);
this.actor = actor
this.rollData = rollData
}
/* -------------------------------------------- */
roll(dice) {
this.rollData.mainDice = dice
HeritiersUtility.rollHeritiers(this.rollData)
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
var dialog = this;
function onLoad() {
}
$(function () { onLoad(); });
html.find('#sdValue').change(async (event) => {
this.rollData.sdValue = Number(event.currentTarget.value)
})
html.find('#caracKey').change(async (event) => {
this.rollData.caracKey = String(event.currentTarget.value)
})
html.find('#bonus-malus-context').change((event) => {
this.rollData.bonusMalusContext = Number(event.currentTarget.value)
})
html.find('#useTricherie').change((event) => {
this.rollData.useTricherie = event.currentTarget.checked
})
html.find('#useHeritage').change((event) => {
this.rollData.useHeritage = event.currentTarget.checked
})
}
}

View File

@@ -2,6 +2,45 @@
import { HeritiersCombat } from "./heritiers-combat.js";
import { HeritiersCommands } from "./heritiers-commands.js";
const __facesAdjacentes = {
"d8": {
1: [4, 8, 6],
2: [7, 5, 3],
3: [2, 8, 6],
4: [1, 5, 7],
5: [2, 4, 8],
6: [1, 7, 3],
7: [2, 4, 6],
8: [1, 3, 5]
},
"d10": {
1: [4, 6, 9, 7],
2: [6, 8, 5, 9],
3: [7, 5, 8, 10],
4: [10, 6, 7, 1],
5: [3, 9, 2, 8],
6: [1, 4, 2, 9],
7: [1, 3, 4, 10],
8: [2, 10, 5, 3],
9: [1, 5, 6, 2],
10: [8, 4, 3, 7]
},
"d12": {
1: [2, 3, 4, 5, 6],
2: [1, 6, 8, 12, 3],
3: [1, 4, 11, 12, 2],
4: [1, 5, 10, 11, 3],
5: [1, 6, 9, 10, 4],
6: [1, 2, 8, 9, 5],
7: [8, 9, 10, 11, 12],
8: [2, 6, 9, 7, 12],
9: [5, 10, 7, 8, 6],
10: [4, 11, 7, 9, 5],
11: [7, 10, 4, 3, 12],
12: [2, 8, 7, 11, 3]
}
}
/* -------------------------------------------- */
export class HeritiersUtility {
@@ -9,7 +48,7 @@ export class HeritiersUtility {
/* -------------------------------------------- */
static async init() {
Hooks.on('renderChatLog', (log, html, data) => HeritiersUtility.chatListeners(html))
Hooks.on("getChatLogEntryContext", (html, options) => HeritiersUtility.chatRollMenu(html, options))
/* Unused for Heitiers : Hooks.on("getChatMessageContextOptions", (html, options) => HeritiersUtility.chatRollMenu(html, options))*/
this.rollDataStore = {}
this.defenderStore = {}
@@ -37,22 +76,25 @@ export class HeritiersUtility {
Handlebars.registerHelper('mul', function (a, b) {
return parseInt(a) * parseInt(b);
})
Handlebars.registerHelper('and', function (...args) {
// Last argument is Handlebars options object, ignore it
return args.slice(0, -1).every(Boolean);
})
}
/* -------------------------------------------- */
static sortByName(table) {
static sortByName(table) {
return table.sort(function (a, b) {
let fa = a.name.toLowerCase(),
fb = b.name.toLowerCase();
if (fa < fb) {
return -1;
}
if (fa > fb) {
return 1;
}
return 0;
return a.name.localeCompare(b.name);
})
}
/* -------------------------------------------- */
static sortArrayObjectsByName(myArray) {
myArray.sort((a, b) => {
return a.name.localeCompare(b.name);
})
}
@@ -66,6 +108,42 @@ export class HeritiersUtility {
const skills = await HeritiersUtility.loadCompendium("fvtt-les-heritiers.competences")
this.skills = skills.map(i => i.toObject())
this.competencesMagie = this.skills.filter(s => s.system.profil == "magie")
game.settings.register("fvtt-les-heritiers", "heritiers-heritage", {
name: "Points d'héritage",
hint: "Points d'héritage du groupe",
scope: "world",
config: true,
default: 0,
type: Number
})
}
/* -------------------------------------------- */
static getSDSortValue(niveau) {
if (niveau <= 1) return 12;
if (niveau == 2) return 14;
if (niveau == 3) return 16;
if (niveau > 3) return 18;
return 18;
}
/* -------------------------------------------- */
static getCompetencesMagie() {
return this.competencesMagie
}
/* -------------------------------------------- */
static buildCompetencesMagie() {
let competences = foundry.utils.duplicate(this.getCompetencesMagie())
for (let comp of competences) {
// Calcul du rang
let rang = Math.round(comp.system.niveau / 2);
competences.system.rang = rang;
competences.system.rangGenericName = game.system.lesheritiers.config.rangName[rang];
competences.system.rangSpecificName = game.system.lesheritiers.config.rangNameSpecific[comp.Name][competences.system.rangGenericName];
}
}
/* -------------------------------------------- */
@@ -87,18 +165,29 @@ export class HeritiersUtility {
/* -------------------------------------------- */
static async chatListeners(html) {
html.on("click", '.predilection-reroll', async event => {
$(html).on("click", '.predilection-reroll', async event => {
let predIdx = $(event.currentTarget).data("predilection-index")
let messageId = HeritiersUtility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "heritiers-roll")
let actor = this.getActorFromRollData(rollData)
await actor.setPredilectionUsed(rollData.competence._id, predIdx)
rollData.competence = duplicate(actor.getCompetence(rollData.competence._id))
rollData.competence = foundry.utils.duplicate(actor.getCompetence(rollData.competence._id))
HeritiersUtility.rollHeritiers(rollData)
})
html.on("click", '.roll-chat-degat', async event => {
$(html).on("click", '.roll-tricherie-2', async event => {
let messageId = HeritiersUtility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "heritiers-roll")
let actor = this.getActorFromRollData(rollData)
if (await actor.incDecTricherie(-2)) {
rollData.forcedValue = Number($(event.currentTarget).data("dice-value"))
HeritiersUtility.rollHeritiers(rollData)
}
})
$(html).on("click", '.roll-chat-degat', async event => {
let messageId = HeritiersUtility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "heritiers-roll")
@@ -111,13 +200,14 @@ export class HeritiersUtility {
static async preloadHandlebarsTemplates() {
const templatePaths = [
'systems/fvtt-les-heritiers/templates/editor-notes-gm.html',
'systems/fvtt-les-heritiers/templates/partial-item-header.html',
'systems/fvtt-les-heritiers/templates/partial-item-description.html',
'systems/fvtt-les-heritiers/templates/partial-item-nav.html',
'systems/fvtt-les-heritiers/templates/partial-list-niveau.html'
'systems/fvtt-les-heritiers/templates/editor-notes-gm.hbs',
'systems/fvtt-les-heritiers/templates/partial-item-header.hbs',
'systems/fvtt-les-heritiers/templates/partial-item-description.hbs',
'systems/fvtt-les-heritiers/templates/partial-item-nav.hbs',
'systems/fvtt-les-heritiers/templates/partial-utile-skills.hbs',
'systems/fvtt-les-heritiers/templates/partial-actor-equipment.hbs'
]
return loadTemplates(templatePaths);
return foundry.applications.handlebars.loadTemplates(templatePaths);
}
/* -------------------------------------------- */
@@ -194,14 +284,14 @@ export class HeritiersUtility {
let id = rollData.rollId;
let oldRollData = this.rollDataStore[id] || {};
let newRollData = mergeObject(oldRollData, rollData);
let newRollData = foundry.utils.mergeObject(oldRollData, rollData);
this.rollDataStore[id] = newRollData;
}
/* -------------------------------------------- */
static saveRollData(rollData) {
game.socket.emit("system.fvtt-les-heritiers", {
name: "msg_update_roll", data: rollData
}); // Notify all other clients of the roll
}); // Notify all other clients of the roll
this.updateRollData(rollData);
}
@@ -212,7 +302,6 @@ export class HeritiersUtility {
/* -------------------------------------------- */
static onSocketMesssage(msg) {
//console.log("SOCKET MESSAGE", msg.name, game.user.character.id, msg.data.defenderId);
if (msg.name == "msg_update_defense_state") {
this.updateDefenseState(msg.data.defenderId, msg.data.rollId);
}
@@ -269,123 +358,289 @@ export class HeritiersUtility {
/* -------------------------------------------- */
static computeMonnaieDetails(valueSC) {
let po = Math.floor(valueSC / 400)
let pa = Math.floor((valueSC - (po*400)) / 20)
let sc = valueSC - (po*400) - (pa*20)
let pa = Math.floor((valueSC - (po * 400)) / 20)
let sc = valueSC - (po * 400) - (pa * 20)
return {
po: po, pa: pa, sc: sc, valueSC: valueSC
po: po, pa: pa, sc: sc, valueSC: valueSC
}
}
/* -------------------------------------------- */
static incDecHeritage() {
}
/* -------------------------------------------- */
static computeResult(actor, rollData) {
rollData.diceResult = -1
let resTab = []
for ( let res of rollData.roll.terms[0].results) {
rollData.diceResult = Math.max(res.result, rollData.diceResult)
resTab.push(res.result)
}
let isFailure = false
if (rollData.mainDice.includes("d10")) {
if ( rollData.diceResult == 1) {
rollData.finalResult -= 3
isFailure = true
}
}
if (rollData.mainDice.includes("d12")) {
if ( rollData.diceResult == 1 || rollData.diceResult == 2) {
rollData.finalResult -= 5
isFailure = true
}
}
// Heritage/Tricherie management
let isTricherieHeritage = rollData.useHeritage || rollData.useTricherie
rollData.marge = 0
if (!isFailure && (rollData.useHeritage || rollData.useTricherie)) {
if (isTricherieHeritage) {
let resTab = [rollData.roll.terms[0].results[0].result, rollData.roll.terms[0].results[1].result, rollData.roll.terms[0].results[2].result]
rollData.diceResult = resTab[0] + "," + resTab[1] + "," + resTab[2]
let foundryTotal = resTab[0] + resTab[1] + resTab[2]
if (resTab[1] == 1) { resTab[1] -= 4 }
if (resTab[2] == 1) { resTab[2] -= 6 }
if (resTab[2] == 2) { resTab[2] -= 7 }
rollData.diceValue = Math.max(Math.max(resTab[0], resTab[1]), resTab[2])
rollData.finalResult = rollData.roll.total - foundryTotal + rollData.diceValue
// Gestion des résultats spéciaux
resTab = resTab.sort()
if ( (resTab[0] == resTab[1]) && (resTab[1] == resTab[2])) {
if ((resTab[0] == resTab[1]) && (resTab[1] == resTab[2])) {
rollData.marge = 7
rollData.isSuccess = true
rollData.isCriticalSuccess = true
rollData.isCriticalSuccess = true
rollData.isBrelan = true
}
if ((resTab[0]+1 == resTab[1]) && (resTab[1]+1 == resTab[2]) ) {
if ((resTab[0] + 1 == resTab[1]) && (resTab[1] + 1 == resTab[2])) {
rollData.marge = 7
rollData.isSuccess = true
rollData.isCriticalSuccess = true
}
if ( rollData.useTricherie) {
rollData.isCriticalSuccess = true
rollData.isSuite = true
}
if (rollData.useTricherie) {
actor.incDecTricherie(-1)
}
if ( rollData.useHeritage) {
if (rollData.useHeritage) {
this.incDecHeritage()
}
}
//rollData.finalResult = Math.max(rollData.finalResult, 0)
//console.log("Result : ", rollData)
if (rollData.marge == 0 && rollData.sdValue > 0 ) {
rollData.marge = rollData.finalResult - rollData.sdValue
rollData.isSuccess = (rollData.finalResult >= rollData.sdValue)
rollData.isCriticalSuccess = ((rollData.finalResult - rollData.sdValue) >= 7)
rollData.isCriticalFailure = ((rollData.finalResult - rollData.sdValue) <= -7)
} else {
rollData.finalResult = rollData.roll.total
let rollValue = rollData.forcedValue || rollData.roll.terms[0].results[0].result
rollData.diceResult = rollValue
rollData.diceValue = rollValue
if (rollData.mainDice.includes("d10")) {
if (rollValue == 1) {
rollData.finalResult -= 3 + rollValue // substract 3 and the 1 value that has been added
}
}
if (rollData.mainDice.includes("d12")) {
if (rollValue == 1 || rollValue == 2) {
rollData.finalResult -= 5 + rollValue // Remove also the dice result has it has been added already
}
}
if (!rollData.forcedValue) {
rollData.adjacentFaces = foundry.utils.duplicate(__facesAdjacentes[rollData.mainDice][rollData.diceValue])
}
}
}
/* -------------------------------------------- */
static computeArmeDegats(rollData, actor) {
rollData.degatsArme = rollData.arme.system.degats + rollData.marge
if (rollData.attaqueDeuxArmes != 0 && rollData.secondeArme) {
let secondeArme = actor.items.get(secondeArme)
if (secondeArme) {
rollData.degatsArme += secondeArme.system.degats
rollData.degatsArme += actor.system.caracteristiques.for.value
}
} else {
if (rollData.arme.system.categorie == "lourde") {
rollData.degatsArme += actor.system.caracteristiques.for.value
}
if (rollData.arme.system.categorie == "blanche" || rollData.arme.system.categorie == "improvise") {
rollData.degatsArme += Math.max(0, actor.system.caracteristiques.for.value - 2)
}
if (rollData.mode == "attaquecharge") {
rollData.degatsArme += 3
}
}
if (rollData.attaqueCible == "membre") {
rollData.degatsArme -= 2
}
if (rollData.attaqueCible == "main") {
rollData.degatsArme -= 3
}
if (rollData.attaqueCible == "tete") {
rollData.degatsArme *= 3
}
}
/* -------------------------------------------- */
static computeMarge(rollData, seuil) {
if (rollData.marge == 0 && seuil >= 0) {
rollData.marge = rollData.finalResult - seuil
rollData.isSuccess = (rollData.finalResult >= seuil)
rollData.isCriticalSuccess = ((rollData.finalResult - seuil) >= 7)
rollData.isCriticalFailure = ((rollData.finalResult - seuil) <= -7)
// Si compétence > 0 et d8 -> echec critique impossible
if (rollData?.competence?.system.niveau > 0 && rollData?.mainDice == "d8") {
rollData.isCriticalFailure = false
}
}
}
/* -------------------------------------------- */
static async displayUneDefense(rollData, actor, nomDefense, valeurDefense) {
rollData.defenderMode = nomDefense
rollData.defenderValue = valeurDefense
rollData.marge = 0
this.computeMarge(rollData, valeurDefense)
if (rollData.isSuccess) {
this.computeArmeDegats(rollData, actor)
}
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-les-heritiers/templates/chat-cc-result.hbs`, rollData)
}, rollData, "selfroll")
}
/* -------------------------------------------- */
static async displayAsssomer(rollData, actor, nomAttaque, etatAssomer, valeurDefense) {
rollData.defenderMode = nomAttaque
rollData.etatAssommer = etatAssomer
rollData.defenderValue = valeurDefense
rollData.marge = 0
this.computeMarge(rollData, valeurDefense)
rollData.dureeAssommer = (rollData.marge) ? rollData.marge * 2 : 1
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-les-heritiers/templates/chat-assommer-result.hbs`, rollData)
}, rollData, "selfroll")
}
/* -------------------------------------------- */
static async rollHeritiers(rollData) {
let actor = this.getActorFromRollData(rollData)
if (rollData.mode == "pouvoir" && actor.getPouvoirUsage(rollData.pouvoir._id) < rollData.pouvoirPointsUsage) {
ui.notifications.warn("Pas assez de points d'usage pour ce pouvoir.")
return
}
//rollData.actionImg = "systems/fvtt-les-heritiers/assets/icons/" + actor.system.attributs[rollData.attrKey].labelnorm + ".webp"
rollData.carac = duplicate(actor.system.caracteristiques[rollData.caracKey])
if (rollData.caracKey == "pre") rollData.caracKey = "pres"; // Patch tomanage wrong carac key
rollData.carac = foundry.utils.duplicate(actor.system.caracteristiques[rollData.caracKey])
rollData.nbDice = (rollData.useTricherie || rollData.useHeritage) ? 3 : 1
rollData.diceFormula = rollData.nbDice + rollData.mainDice + "kh1"
//console.log("BEFORE COMP", rollData)
if (rollData.competence) {
let compmod = (rollData.competence.system.niveau == 0) ? -3 : 0
rollData.diceFormula += `+${rollData.carac.value}+${rollData.competence.system.niveau}+${rollData.bonusMalusContext}+${compmod}`
if (rollData.forcedValue) {
rollData.diceFormula = rollData.forcedValue
} else {
rollData.diceFormula += `+${rollData.carac.value}+${rollData.bonusMalusContext}`
}
rollData.diceFormula += `+${rollData.pvMalus}`
if (rollData.arme && rollData.arme.type == "arme") {
rollData.diceFormula += `+${rollData.arme.system.bonusmaniementoff}`
if (rollData.useTricherie || rollData.useHeritage) {
rollData.diceFormula = "{1d8, 1d10, 1d12}"
} else {
rollData.diceFormula = "1" + rollData.mainDice + "kh1"
}
}
let myRoll = new Roll(rollData.diceFormula).roll({ async: false })
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"))
rollData.roll = myRoll
console.log(">>>> ", myRoll)
let rangValue = 0
if (rollData.rang) {
rangValue = rollData.rang.value
}
if (rollData.competence) {
let compmod = 0 // Bonus de compétence à 0 dans Les Heritiers
let specBonus = (rollData.useSpecialite) ? 1 : 0
rollData.diceFormula += `+${rollData.carac.value}+${rangValue}+${rollData.competence.system.niveau}+${specBonus}+${rollData.bonusMalusContext}+${compmod}`
} else if (rollData.pouvoirBase) {
rollData.diceFormula += `+${rollData.carac.value}+${rollData.pouvoirBase.value}+${rangValue}+${rollData.bonusMalusContext}`
} else {
rollData.diceFormula += `+${rollData.carac.value}+${rangValue}+${rollData.bonusMalusContext}`
}
let ruleMalus = 0
for (let malus of rollData.rulesMalus) {
ruleMalus += malus.value
}
rollData.diceFormula += `+${ruleMalus}`
// Gestion bonus attaque à plusieurs
let bonusAttaque = rollData.bonusAttaquePlusieurs
if (rollData.attaqueDos) {
bonusAttaque = 2
if (rollData.bonusAttaquePlusieurs) {
bonusAttaque = 3 // Valeur max, cf règle page 197
}
}
rollData.diceFormula += `+${bonusAttaque}`
// Gestion attaque avec 2 armes
if (rollData.attaqueDeuxArmes != 0) {
rollData.diceFormula += `+${rollData.attaqueDeuxArmes}`
}
// Gestion des attaques ciblées
if (rollData.attaqueCible != "none") {
if (rollData.attaqueCible == "membre") {
rollData.diceFormula += `-2`
}
if (rollData.attaqueCible == "main") {
rollData.diceFormula += `-3`
}
if (rollData.attaqueCible == "tete") {
rollData.diceFormula += `-6`
}
}
if (!rollData.noRoll) {
let myRoll = await new Roll(rollData.diceFormula).roll()
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"))
rollData.roll = foundry.utils.duplicate(myRoll)
console.log(">>>> ", myRoll)
this.computeResult(actor, rollData)
this.computeMarge(rollData, rollData.sdValue) // Calcul de la marge si seuil présent
// Compute weapon damage for successful attacks
if (rollData.arme && rollData.isSuccess) {
this.computeArmeDegats(rollData, actor)
}
}
rollData.finalResult = myRoll.total
this.computeResult(actor, rollData)
if (rollData.mode == "init") {
actor.setFlag("world", "last-initiative", rollData.finalResult)
}
// Gestion pouvoir et points d'usage
if (rollData.mode == "pouvoir" || rollData.mode == "pouvoirpassif") {
actor.incDecPointsUsage(rollData.pouvoir._id, -rollData.pouvoirPointsUsage)
}
// Gestion sort et points d'âme
if (rollData.mode == "sort") {
if (rollData.spendEsprit) {
actor.inDecCarac("esp", -rollData.totalEsprit)
} else {
actor.incDecPointsAme(-rollData.sortPointsAme)
if (rollData.sort.system.competence == "Magie du Clan") {
actor.incDecPV(-2)
}
}
}
this.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-les-heritiers/templates/chat-generic-result.html`, rollData)
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-les-heritiers/templates/chat-generic-result.hbs`, rollData)
}, rollData)
// Gestion attaque standard
if ((rollData.mode == "arme" || rollData.mode == "attaquebrutale" || rollData.mode == "attaquecharge") &&
rollData.defenderTokenId && rollData.arme) {
if (rollData.arme.system.categorie != "trait" && rollData.arme.system.categorie != "poing" && rollData.arme.system.categorie != "epaule") {
await this.displayUneDefense(rollData, actor, "Parade", rollData.defenderParade)
await this.displayUneDefense(rollData, actor, "Esquive", rollData.defenderEsquive)
} else if (rollData.sdValue) {
this.displayUneDefense(rollData, actor, "A Distance", rollData.sdValue)
} else {
ui.notifications.warn("Pas de difficulté positionnée pour l'attaque à distance.")
}
}
// Gestion assomer
if (rollData.mode == "assommer" && rollData.defenderTokenId && rollData.arme) {
await this.displayAsssomer(rollData, actor, "Assommer", "Surprise", rollData.defenderResistancePhysique)
await this.displayAsssomer(rollData, actor, "Assommer", "Conscient, Résistance+6", rollData.defenderResistancePhysique + 6)
await this.displayAsssomer(rollData, actor, "Assommer", "Conscient, Parade", rollData.defenderParade)
await this.displayAsssomer(rollData, actor, "Assommer", "Conscient, Esquive", rollData.defenderEsquive + 6)
}
}
/* -------------------------------------------- */
static async bonusRollHeritiers(rollData) {
rollData.bonusFormula = rollData.addedBonus
let bonusRoll = new Roll(rollData.bonusFormula).roll({ async: false })
let bonusRoll = await new Roll(rollData.bonusFormula).roll()
await this.showDiceSoNice(bonusRoll, game.settings.get("core", "rollMode"));
rollData.bonusRoll = bonusRoll
rollData.bonusRoll = foundry.utils.duplicate(bonusRoll)
rollData.finalResult += rollData.bonusRoll.total
this.computeResult(rollData)
this.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-les-heritiers/templates/chat-generic-result.html`, rollData)
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-les-heritiers/templates/chat-generic-result.hbs`, rollData)
}, rollData)
}
@@ -395,6 +650,10 @@ export class HeritiersUtility {
return game.users.filter(filter).map(user => user._id);
}
/* -------------------------------------------- */
static isArmeMelee(arme) {
return (arme.type == "arme" && (arme.system.categorie == "lourde" || arme.system.categorie == "blanche" || arme.system.categorie == "improvise"))
}
/* -------------------------------------------- */
static getWhisperRecipients(rollMode, name) {
switch (rollMode) {
@@ -412,7 +671,7 @@ export class HeritiersUtility {
/* -------------------------------------------- */
static blindMessageToGM(chatOptions) {
let chatGM = duplicate(chatOptions);
let chatGM = foundry.utils.duplicate(chatOptions);
chatGM.whisper = this.getUsers(user => user.isGM);
chatGM.content = "Blinde message of " + game.user.name + "<br>" + chatOptions.content;
console.log("blindMessageToGM", chatGM);
@@ -476,11 +735,17 @@ export class HeritiersUtility {
/* -------------------------------------------- */
static getBasicRollData() {
let rollData = {
rollId: randomID(16),
rollId: foundry.utils.randomID(16),
rollMode: game.settings.get("core", "rollMode"),
sdList: game.system.lesheritiers.config.seuilsDifficulte,
sdValue: 0,
bonusMalusContext: 0
sdValue: -1,
bonusAttaquePlusieurs: 0,
attaqueDeuxArmes: 0,
attaqueDos: false,
bonusMalusContext: 0,
attaqueCible: "none",
config: game.system.lesheritiers.config,
rulesMalus: []
}
return rollData
}
@@ -491,18 +756,18 @@ export class HeritiersUtility {
if (target) {
rollData.defenderTokenId = target.id
let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor
rollData.armeDefense = defender.getBestDefenseValue()
rollData.targetVigueur = defender.getVigueur()
if (rollData.armeDefense) {
rollData.difficulte = rollData.armeDefense.system.totalDefensif
} else {
ui.notifications.warn("Aucune arme de défense équipée, difficulté manuelle à positionner.")
}
rollData.defenderName = defender.name
rollData.defenderParade = defender.getCurrentParade()
rollData.defenderEsquive = defender.getCurrentEsquive()
rollData.defenderResistancePhysique = defender.getCurrentResistancePhysique()
}
}
/* -------------------------------------------- */
static createChatWithRollMode(name, chatOptions, rollData = undefined) {
static createChatWithRollMode(name, chatOptions, rollData = undefined, rollMode = undefined) {
if (rollMode == undefined) {
rollMode = game.settings.get("core", "rollMode")
}
this.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions, rollData)
}
@@ -548,13 +813,13 @@ export class HeritiersUtility {
static chatRollMenu(html, options) {
let canApply = li => canvas.tokens.controlled.length && li.find(".heritiers-roll").length
let canApplyBA = function (li) {
let message = game.messages.get(li.attr("data-message-id"))
let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "heritiers-roll")
let actor = this.getActorFromRollData(rollData)
return (!rollData.isReroll && actor.getBonneAventure() > 0)
}
let canApplyPE = function (li) {
let message = game.messages.get(li.attr("data-message-id"))
let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "heritiers-roll")
let actor = this.getActorFromRollData(rollData)
return (!rollData.isReroll && actor.getEclat() > 0)
@@ -588,25 +853,30 @@ export class HeritiersUtility {
/* -------------------------------------------- */
static async confirmDelete(actorSheet, li) {
let itemId = li.data("item-id");
let msgTxt = "<p>Are you sure to remove this Item ?";
// Support both jQuery and native elements
let itemId = li.dataset ? li.dataset.itemId : li.data("item-id");
let msgTxt = "<p>Certain de supprimer cet item ?";
let buttons = {
delete: {
icon: '<i class="fas fa-check"></i>',
label: "Yes, remove it",
label: "Oui !",
callback: () => {
actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]);
li.slideUp(200, () => actorSheet.render(false));
if (li.slideUp) {
li.slideUp(200, () => actorSheet.render(false));
} else {
actorSheet.render(false);
}
}
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Cancel"
label: "Non !"
}
}
msgTxt += "</p>";
let d = new Dialog({
title: "Confirm removal",
title: "Confirmer la suppression",
content: msgTxt,
buttons: buttons,
default: "cancel"
@@ -614,21 +884,56 @@ export class HeritiersUtility {
d.render(true);
}
/************************************************************************************/
static async __create_talents_table() {
let compName = "fvtt-les-heritiers.talents-cellule"
const compData = await HeritiersUtility.loadCompendium(compName)
let talents = compData.map(i => i.toObject())
let htmlTab = "<table border='1'><tbody>";
for (let entryData of talents) {
console.log(entryData)
htmlTab += `<tr><td>@UUID[Compendium.${compName}.${entryData._id}]{${entryData.name}}</td>`
htmlTab += `<td>${entryData.system.description}</td>`;
//htmlTab += `<td>${entryData.system.resumebonus}</td>`;
htmlTab += "</tr>\n";
static loadSort() {
// Create afolder in the item directory if it doesn't exist
if (!game.folders.getName("Magie du Clan")) {
Folder.create({
name: "Magie du Clan",
type: "Item",
color: "#3b1361"
});
}
htmlTab += "</table>";
await JournalEntry.create({ name: 'Liste des Talents de Cellule', content: htmlTab });
// Load the srcdata/sorts-druidisme.json file
return fetch("systems/fvtt-les-heritiers/srcdata/sort_magieduclan.json")
.then(response => response.json())
.then(data => {
console.log("Sorts Magie du Clan loaded:", data);
this.sortDruidisme = data;
// Loop through the spell and create the "sort "item based on the JSON content
data.forEach(spell => {
spell.name = spell.name;
spell.type = "sort";
spell.system = {
niveau: spell.niveau,
competence: spell.competence,
carac1: spell.carac1,
carac2: spell.carac2,
description: spell.description,
ingredients: spell.ingredients,
portee: spell.portee,
duree: spell.duree,
concentration: spell.concentration,
critique: spell.critique,
resistance: spell.resistance,
coutactivation: spell.coutactivation
};
spell.img = "systems/fvtt-les-heritiers/assets/icons/sort.webp";
spell.folder = game.folders.getName("Magie du Clan").id;
// Create the item in the world
Item.create(spell)
.then(item => {
console.log("Sort created:", item);
})
.catch(error => {
console.error("Error creating sort item:", error);
});
})
})
.catch(error => {
console.error("Error loading druidism spells:", error);
return [];
});
}
}

View File

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

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

@@ -0,0 +1,30 @@
/**
* Data model pour les armes
*/
export default class ArmeDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
quantite: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true }),
equipped: new fields.BooleanField({ initial: false }),
categorie: new fields.StringField({ initial: "trait" }),
armetype: new fields.StringField({ initial: "trait" }),
degats: new fields.NumberField({ initial: 0, integer: true }),
precision: new fields.NumberField({ initial: 0, integer: true }),
cadence: new fields.StringField({ initial: "" }),
enraiement: new fields.StringField({ initial: "" }),
magasin: new fields.NumberField({ initial: 0, integer: true }),
charge: new fields.NumberField({ initial: 0, integer: true }),
portee: new fields.StringField({ initial: "" }),
legalite: new fields.StringField({ initial: "libre" }),
dissimulation: new fields.StringField({ initial: "tresfacile" }),
zone: new fields.NumberField({ initial: 0, integer: true }),
temps: new fields.StringField({ initial: "" }),
allumage: new fields.StringField({ initial: "" }),
special: new fields.StringField({ initial: "" })
};
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
/**
* Data model pour les capacités naturelles
*/
export default class CapaciteNaturelleDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
pouvoirtype: new fields.StringField({ initial: "actif" }),
activation: new fields.StringField({ initial: "" }),
cibles: new fields.StringField({ initial: "" }),
effet: new fields.StringField({ initial: "" }),
duree: new fields.StringField({ initial: "" }),
portee: new fields.StringField({ initial: "" }),
resistance: new fields.StringField({ initial: "aucune" }),
resistanceautre: new fields.StringField({ initial: "" }),
isvirulence: new fields.BooleanField({ initial: false }),
virulence: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,43 @@
/**
* Data model pour les compétences
*/
export default class CompetenceDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "utile" }),
profil: new fields.StringField({ initial: "aventurier" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
nomniveau: new fields.SchemaField({
1: new fields.StringField({ initial: "" }),
2: new fields.StringField({ initial: "" }),
3: new fields.StringField({ initial: "" }),
4: new fields.StringField({ initial: "" })
}),
nomniveausouffle: new fields.SchemaField({
soufflecombat: new fields.SchemaField({
1: new fields.StringField({ initial: "" }),
2: new fields.StringField({ initial: "" }),
3: new fields.StringField({ initial: "" }),
4: new fields.StringField({ initial: "" })
}),
soufflemouvement: new fields.SchemaField({
1: new fields.StringField({ initial: "" }),
2: new fields.StringField({ initial: "" }),
3: new fields.StringField({ initial: "" }),
4: new fields.StringField({ initial: "" })
}),
souffleesprit: new fields.SchemaField({
1: new fields.StringField({ initial: "" }),
2: new fields.StringField({ initial: "" }),
3: new fields.StringField({ initial: "" }),
4: new fields.StringField({ initial: "" })
})
}),
predilection: new fields.BooleanField({ initial: false }),
specialites: new fields.ArrayField(new fields.ObjectField(), { initial: [] }),
ismagie: new fields.BooleanField({ initial: false }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

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

View File

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

View File

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

19
modules/models/fee.mjs Normal file
View File

@@ -0,0 +1,19 @@
/**
* Data model pour les fées
*/
export default class FeeDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
feetype: new fields.StringField({ initial: "traditionnelle" }),
avantages: new fields.StringField({ initial: "" }),
desavantages: new fields.StringField({ initial: "" }),
pouvoirsfeeriquesmasque: new fields.StringField({ initial: "" }),
pouvoirsfeeriquesdemasque: new fields.StringField({ initial: "" }),
atoutsfeeriques: new fields.StringField({ initial: "" }),
competences: new fields.StringField({ initial: "" }),
capacitenaturelles: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
};
}
}

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

@@ -0,0 +1,24 @@
/**
* Index des DataModels pour Les Héritiers
* Ce fichier centralise tous les exports des modèles de données
*/
// Modèles d'acteurs
export { default as PersonnageDataModel } from './personnage.mjs';
export { default as PnjDataModel } from './pnj.mjs';
// Modèles d'items
export { default as AccessoireDataModel } from './accessoire.mjs';
export { default as ArmeDataModel } from './arme.mjs';
export { default as AtoutFeeriqueDataModel } from './atoutfeerique.mjs';
export { default as AvantageDataModel } from './avantage.mjs';
export { default as CapaciteNaturelleDataModel } from './capacitenaturelle.mjs';
export { default as CompetenceDataModel } from './competence.mjs';
export { default as ContactDataModel } from './contact.mjs';
export { default as DesavantageDataModel } from './desavantage.mjs';
export { default as EquipementDataModel } from './equipement.mjs';
export { default as FeeDataModel } from './fee.mjs';
export { default as PouvoirDataModel } from './pouvoir.mjs';
export { default as ProfilDataModel } from './profil.mjs';
export { default as ProtectionDataModel } from './protection.mjs';
export { default as SortDataModel } from './sort.mjs';

View File

@@ -0,0 +1,234 @@
/**
* Data model pour les personnages
*/
export default class PersonnageDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
// Template biodata
biodata: new fields.SchemaField({
name: new fields.StringField({ initial: "" }),
activite: new fields.StringField({ initial: "" }),
nomhumain: new fields.StringField({ initial: "" }),
activites: new fields.StringField({ initial: "" }),
fortune: new fields.StringField({ initial: "0" }),
traitscaracteres: new fields.StringField({ initial: "" }),
tailledemasquee: new fields.StringField({ initial: "" }),
taillemasquee: new fields.StringField({ initial: "" }),
poidsmasquee: new fields.StringField({ initial: "" }),
poidsdemasquee: new fields.StringField({ initial: "" }),
apparencemasquee: new fields.StringField({ initial: "" }),
apparencedemasquee: new fields.StringField({ initial: "" }),
titrefamille: new fields.StringField({ initial: "" }),
langues: new fields.StringField({ initial: "" }),
factionfeerique: new fields.StringField({ initial: "" }),
typetaille: new fields.StringField({ initial: "" }),
age: new fields.StringField({ initial: "0" }),
poids: new fields.StringField({ initial: "" }),
taille: new fields.StringField({ initial: "" }),
cheveux: new fields.StringField({ initial: "" }),
sexe: new fields.StringField({ initial: "" }),
yeux: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
revesetranges: new fields.HTMLField({ initial: "" }),
secretsdecouverts: new fields.HTMLField({ initial: "" }),
questions: new fields.HTMLField({ initial: "" }),
habitat: new fields.HTMLField({ initial: "" }),
notes: new fields.HTMLField({ initial: "" }),
statut: new fields.StringField({ initial: "" }),
playernotes: new fields.HTMLField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" }),
magie: new fields.BooleanField({ initial: false })
}),
// Template core
subactors: new fields.ArrayField(new fields.StringField(), { initial: [] }),
caracteristiques: new fields.SchemaField({
agi: new fields.SchemaField({
label: new fields.StringField({ initial: "Agilité" }),
labelnorm: new fields.StringField({ initial: "agilite" }),
abbrev: new fields.StringField({ initial: "agi" }),
kind: new fields.StringField({ initial: "physical" }),
value: new fields.NumberField({ initial: 1, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true })
}),
con: new fields.SchemaField({
label: new fields.StringField({ initial: "Constitution" }),
labelnorm: new fields.StringField({ initial: "constitution" }),
abbrev: new fields.StringField({ initial: "con" }),
kind: new fields.StringField({ initial: "physical" }),
value: new fields.NumberField({ initial: 1, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true })
}),
for: new fields.SchemaField({
label: new fields.StringField({ initial: "Force" }),
labelnorm: new fields.StringField({ initial: "force" }),
abbrev: new fields.StringField({ initial: "for" }),
kind: new fields.StringField({ initial: "physical" }),
value: new fields.NumberField({ initial: 1, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true })
}),
prec: new fields.SchemaField({
label: new fields.StringField({ initial: "Précision" }),
labelnorm: new fields.StringField({ initial: "precision" }),
abbrev: new fields.StringField({ initial: "prec" }),
kind: new fields.StringField({ initial: "physical" }),
value: new fields.NumberField({ initial: 1, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true })
}),
esp: new fields.SchemaField({
label: new fields.StringField({ initial: "Esprit" }),
labelnorm: new fields.StringField({ initial: "esprit" }),
abbrev: new fields.StringField({ initial: "esp" }),
kind: new fields.StringField({ initial: "mental" }),
value: new fields.NumberField({ initial: 1, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true })
}),
per: new fields.SchemaField({
label: new fields.StringField({ initial: "Perception" }),
labelnorm: new fields.StringField({ initial: "perception" }),
abbrev: new fields.StringField({ initial: "per" }),
kind: new fields.StringField({ initial: "mental" }),
value: new fields.NumberField({ initial: 1, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true })
}),
pres: new fields.SchemaField({
label: new fields.StringField({ initial: "Prestance" }),
labelnorm: new fields.StringField({ initial: "pres" }),
abbrev: new fields.StringField({ initial: "pres" }),
kind: new fields.StringField({ initial: "mental" }),
value: new fields.NumberField({ initial: 1, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true })
}),
san: new fields.SchemaField({
label: new fields.StringField({ initial: "Sang-Froid" }),
labelnorm: new fields.StringField({ initial: "sangfroid" }),
abbrev: new fields.StringField({ initial: "san" }),
kind: new fields.StringField({ initial: "mental" }),
value: new fields.NumberField({ initial: 1, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true })
})
}),
statutmasque: new fields.StringField({ initial: "masque" }),
rang: new fields.SchemaField({
tricherie: new fields.SchemaField({
label: new fields.StringField({ initial: "Tricherie" }),
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
feerie: new fields.SchemaField({
label: new fields.StringField({ initial: "Féerie" }),
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
masque: new fields.SchemaField({
label: new fields.StringField({ initial: "Masque" }),
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
heritage: new fields.SchemaField({
label: new fields.StringField({ initial: "Héritage" }),
value: new fields.StringField({ initial: "0" }),
max: new fields.NumberField({ initial: 0, integer: true }),
scenarios: new fields.NumberField({ initial: 0, integer: true })
})
}),
pv: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
mod: new fields.NumberField({ initial: 0, integer: true })
}),
competences: new fields.SchemaField({
aventurier: new fields.SchemaField({
label: new fields.StringField({ initial: "Aventurier" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
pp: new fields.NumberField({ initial: 0, integer: true })
}),
combattant: new fields.SchemaField({
label: new fields.StringField({ initial: "Combattant" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
pp: new fields.NumberField({ initial: 0, integer: true })
}),
erudit: new fields.SchemaField({
label: new fields.StringField({ initial: "Erudit" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
pp: new fields.NumberField({ initial: 0, integer: true })
}),
gentleman: new fields.SchemaField({
label: new fields.StringField({ initial: "Gentleman" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
pp: new fields.NumberField({ initial: 0, integer: true })
}),
roublard: new fields.SchemaField({
label: new fields.StringField({ initial: "Roublard" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
pp: new fields.NumberField({ initial: 0, integer: true })
}),
savant: new fields.SchemaField({
label: new fields.StringField({ initial: "Savant" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
rang: new fields.NumberField({ initial: 0, integer: true }),
pp: new fields.NumberField({ initial: 0, integer: true })
})
}),
magie: new fields.SchemaField({
pointsame: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true })
})
}),
experience: new fields.SchemaField({
value: new fields.StringField({ initial: "0" }),
pourtricher: new fields.StringField({ initial: "0" })
}),
combat: new fields.SchemaField({
esquive: new fields.SchemaField({
masquee: new fields.NumberField({ initial: 0, integer: true }),
demasquee: new fields.NumberField({ initial: 0, integer: true })
}),
parade: new fields.SchemaField({
masquee: new fields.NumberField({ initial: 0, integer: true }),
demasquee: new fields.NumberField({ initial: 0, integer: true }),
value: new fields.NumberField({ initial: 0, integer: true })
}),
resistancephysique: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
resistancepsychique: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
protection: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
effetssecondaires: new fields.StringField({ initial: "" }),
dissimulation: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
initiative: new fields.SchemaField({
masquee: new fields.NumberField({ initial: 0, integer: true }),
demasquee: new fields.NumberField({ initial: 0, integer: true })
}),
corpsacorps: new fields.SchemaField({
masquee: new fields.NumberField({ initial: 0, integer: true }),
demasquee: new fields.NumberField({ initial: 0, integer: true })
}),
tir: new fields.SchemaField({
masquee: new fields.NumberField({ initial: 0, integer: true }),
demasquee: new fields.NumberField({ initial: 0, integer: true })
})
})
};
}
}

10
modules/models/pnj.mjs Normal file
View File

@@ -0,0 +1,10 @@
/**
* Data model pour les PNJ
* Utilise le même schéma que les personnages
*/
import PersonnageDataModel from './personnage.mjs';
export default class PnjDataModel extends PersonnageDataModel {
// Les PNJ utilisent exactement le même schéma que les personnages
// On hérite simplement de PersonnageDataModel
}

View File

@@ -0,0 +1,29 @@
/**
* Data model pour les pouvoirs
*/
export default class PouvoirDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
pouvoirtype: new fields.StringField({ initial: "actif" }),
masquetype: new fields.StringField({ initial: "masque" }),
niveau: new fields.StringField({ initial: "normal" }),
activation: new fields.StringField({ initial: "" }),
istest: new fields.BooleanField({ initial: false }),
feeriemasque: new fields.StringField({ initial: "feerie" }),
zoneffet: new fields.StringField({ initial: "" }),
testautre: new fields.StringField({ initial: "" }),
carac: new fields.StringField({ initial: "pre" }),
duree: new fields.StringField({ initial: "" }),
cibles: new fields.StringField({ initial: "" }),
effet: new fields.StringField({ initial: "" }),
portee: new fields.StringField({ initial: "" }),
resistance: new fields.StringField({ initial: "aucune" }),
resistanceautre: new fields.StringField({ initial: "" }),
pointsusagecourant: new fields.NumberField({ initial: -1, integer: true }),
isvirulence: new fields.BooleanField({ initial: false }),
virulence: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
};
}
}

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

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

View File

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

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

@@ -0,0 +1,27 @@
/**
* Data model pour les sorts
*/
export default class SortDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
niveau: new fields.StringField({ initial: "1" }),
rang: new fields.StringField({ initial: "1" }),
competence: new fields.StringField({ initial: "Druidisme" }),
carac1: new fields.StringField({ initial: "esp" }),
carac2: new fields.StringField({ initial: "none" }),
sdspecial: new fields.StringField({ initial: "" }),
duree: new fields.StringField({ initial: "" }),
portee: new fields.StringField({ initial: "" }),
concentration: new fields.StringField({ initial: "" }),
informatif: new fields.BooleanField({ initial: false }),
texteinformatif: new fields.StringField({ initial: "" }),
critique: new fields.StringField({ initial: "" }),
ingredients: new fields.StringField({ initial: "" }),
resistance: new fields.StringField({ initial: "" }),
coutactivation: new fields.StringField({ initial: "" }),
souffle: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
};
}
}

4971
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

16
package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "fvtt-les-heritiers",
"version": "13.0.7",
"description": "Les Héritiers RPG for FoundryVTT (French)",
"scripts": {
"build": "gulp build",
"watch": "gulp watch"
},
"author": "Uberwald/LeRatierBretonnien",
"license": "SEE LICENSE IN LICENCE.txt",
"devDependencies": {
"gulp": "^4.0.2",
"gulp-less": "^5.0.0",
"gulp-sourcemaps": "^3.0.0"
}
}

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

View File

@@ -0,0 +1 @@
MANIFEST-000336

View File

View File

@@ -0,0 +1,8 @@
2026/01/21-10:53:45.991086 7f152a3fc6c0 Recovering log #334
2026/01/21-10:53:46.000764 7f152a3fc6c0 Delete type=3 #332
2026/01/21-10:53:46.000821 7f152a3fc6c0 Delete type=0 #334
2026/01/21-10:54:50.937646 7f1529bfb6c0 Level-0 table #339: started
2026/01/21-10:54:50.937686 7f1529bfb6c0 Level-0 table #339: 0 bytes OK
2026/01/21-10:54:50.968467 7f1529bfb6c0 Delete type=0 #337
2026/01/21-10:54:51.044557 7f1529bfb6c0 Manual compaction at level-0 from '!items!1NhJH4IJpxsGmLB8' @ 72057594037927935 : 1 .. '!items!y1yOenfAJTsb3r6e' @ 0 : 0; will stop at (end)
2026/01/21-10:54:51.044593 7f1529bfb6c0 Manual compaction at level-1 from '!items!1NhJH4IJpxsGmLB8' @ 72057594037927935 : 1 .. '!items!y1yOenfAJTsb3r6e' @ 0 : 0; will stop at (end)

View File

@@ -0,0 +1,8 @@
2026/01/21-10:43:50.240377 7f152b3fe6c0 Recovering log #330
2026/01/21-10:43:50.250901 7f152b3fe6c0 Delete type=3 #328
2026/01/21-10:43:50.250970 7f152b3fe6c0 Delete type=0 #330
2026/01/21-10:46:58.350230 7f1529bfb6c0 Level-0 table #335: started
2026/01/21-10:46:58.350259 7f1529bfb6c0 Level-0 table #335: 0 bytes OK
2026/01/21-10:46:58.357573 7f1529bfb6c0 Delete type=0 #333
2026/01/21-10:46:58.364032 7f1529bfb6c0 Manual compaction at level-0 from '!items!1NhJH4IJpxsGmLB8' @ 72057594037927935 : 1 .. '!items!y1yOenfAJTsb3r6e' @ 0 : 0; will stop at (end)
2026/01/21-10:46:58.364070 7f1529bfb6c0 Manual compaction at level-1 from '!items!1NhJH4IJpxsGmLB8' @ 72057594037927935 : 1 .. '!items!y1yOenfAJTsb3r6e' @ 0 : 0; will stop at (end)

Binary file not shown.

View File

View File

@@ -0,0 +1 @@
MANIFEST-000336

View File

View File

@@ -0,0 +1,8 @@
2026/01/21-10:53:46.017018 7f152b3fe6c0 Recovering log #334
2026/01/21-10:53:46.027168 7f152b3fe6c0 Delete type=3 #332
2026/01/21-10:53:46.027239 7f152b3fe6c0 Delete type=0 #334
2026/01/21-10:54:51.075256 7f1529bfb6c0 Level-0 table #339: started
2026/01/21-10:54:51.075290 7f1529bfb6c0 Level-0 table #339: 0 bytes OK
2026/01/21-10:54:51.119572 7f1529bfb6c0 Delete type=0 #337
2026/01/21-10:54:51.190089 7f1529bfb6c0 Manual compaction at level-0 from '!items!1ETVaPBtjDtzelK1' @ 72057594037927935 : 1 .. '!items!zbsVCsWxRzkzzG1N' @ 0 : 0; will stop at (end)
2026/01/21-10:54:51.190275 7f1529bfb6c0 Manual compaction at level-1 from '!items!1ETVaPBtjDtzelK1' @ 72057594037927935 : 1 .. '!items!zbsVCsWxRzkzzG1N' @ 0 : 0; will stop at (end)

View File

@@ -0,0 +1,8 @@
2026/01/21-10:43:50.267294 7f152abfd6c0 Recovering log #330
2026/01/21-10:43:50.276983 7f152abfd6c0 Delete type=3 #328
2026/01/21-10:43:50.277035 7f152abfd6c0 Delete type=0 #330
2026/01/21-10:46:58.364131 7f1529bfb6c0 Level-0 table #335: started
2026/01/21-10:46:58.364205 7f1529bfb6c0 Level-0 table #335: 0 bytes OK
2026/01/21-10:46:58.370782 7f1529bfb6c0 Delete type=0 #333
2026/01/21-10:46:58.391376 7f1529bfb6c0 Manual compaction at level-0 from '!items!1ETVaPBtjDtzelK1' @ 72057594037927935 : 1 .. '!items!zbsVCsWxRzkzzG1N' @ 0 : 0; will stop at (end)
2026/01/21-10:46:58.391426 7f1529bfb6c0 Manual compaction at level-1 from '!items!1ETVaPBtjDtzelK1' @ 72057594037927935 : 1 .. '!items!zbsVCsWxRzkzzG1N' @ 0 : 0; will stop at (end)

Binary file not shown.

View File

View File

@@ -0,0 +1 @@
MANIFEST-000336

View File

View File

@@ -0,0 +1,8 @@
2026/01/21-10:53:45.963781 7f152abfd6c0 Recovering log #334
2026/01/21-10:53:45.974011 7f152abfd6c0 Delete type=3 #332
2026/01/21-10:53:45.974068 7f152abfd6c0 Delete type=0 #334
2026/01/21-10:54:50.901280 7f1529bfb6c0 Level-0 table #339: started
2026/01/21-10:54:50.901351 7f1529bfb6c0 Level-0 table #339: 0 bytes OK
2026/01/21-10:54:50.937470 7f1529bfb6c0 Delete type=0 #337
2026/01/21-10:54:51.044539 7f1529bfb6c0 Manual compaction at level-0 from '!items!0fPXtA5LkLgG8uDj' @ 72057594037927935 : 1 .. '!items!zvtBlG6KCIn0oCVk' @ 0 : 0; will stop at (end)
2026/01/21-10:54:51.044584 7f1529bfb6c0 Manual compaction at level-1 from '!items!0fPXtA5LkLgG8uDj' @ 72057594037927935 : 1 .. '!items!zvtBlG6KCIn0oCVk' @ 0 : 0; will stop at (end)

View File

@@ -0,0 +1,8 @@
2026/01/21-10:43:50.215403 7f152a3fc6c0 Recovering log #330
2026/01/21-10:43:50.225230 7f152a3fc6c0 Delete type=3 #328
2026/01/21-10:43:50.225283 7f152a3fc6c0 Delete type=0 #330
2026/01/21-10:46:58.336769 7f1529bfb6c0 Level-0 table #335: started
2026/01/21-10:46:58.336818 7f1529bfb6c0 Level-0 table #335: 0 bytes OK
2026/01/21-10:46:58.343699 7f1529bfb6c0 Delete type=0 #333
2026/01/21-10:46:58.364008 7f1529bfb6c0 Manual compaction at level-0 from '!items!0fPXtA5LkLgG8uDj' @ 72057594037927935 : 1 .. '!items!zvtBlG6KCIn0oCVk' @ 0 : 0; will stop at (end)
2026/01/21-10:46:58.364042 7f1529bfb6c0 Manual compaction at level-1 from '!items!0fPXtA5LkLgG8uDj' @ 72057594037927935 : 1 .. '!items!zvtBlG6KCIn0oCVk' @ 0 : 0; will stop at (end)

BIN
packs/avantages/000179.ldb Normal file

Binary file not shown.

View File

1
packs/avantages/CURRENT Normal file
View File

@@ -0,0 +1 @@
MANIFEST-000336

0
packs/avantages/LOCK Normal file
View File

8
packs/avantages/LOG Normal file
View File

@@ -0,0 +1,8 @@
2026/01/21-10:53:45.925373 7f152bbff6c0 Recovering log #334
2026/01/21-10:53:45.935373 7f152bbff6c0 Delete type=3 #332
2026/01/21-10:53:45.935424 7f152bbff6c0 Delete type=0 #334
2026/01/21-10:54:50.778002 7f1529bfb6c0 Level-0 table #339: started
2026/01/21-10:54:50.778030 7f1529bfb6c0 Level-0 table #339: 0 bytes OK
2026/01/21-10:54:50.828753 7f1529bfb6c0 Delete type=0 #337
2026/01/21-10:54:50.901010 7f1529bfb6c0 Manual compaction at level-0 from '!items!0EAAt0qSzcD9VRBH' @ 72057594037927935 : 1 .. '!items!zfpjROW9LDAlXUkN' @ 0 : 0; will stop at (end)
2026/01/21-10:54:50.901050 7f1529bfb6c0 Manual compaction at level-1 from '!items!0EAAt0qSzcD9VRBH' @ 72057594037927935 : 1 .. '!items!zfpjROW9LDAlXUkN' @ 0 : 0; will stop at (end)

8
packs/avantages/LOG.old Normal file
View File

@@ -0,0 +1,8 @@
2026/01/21-10:43:50.178287 7f152abfd6c0 Recovering log #330
2026/01/21-10:43:50.188475 7f152abfd6c0 Delete type=3 #328
2026/01/21-10:43:50.188562 7f152abfd6c0 Delete type=0 #330
2026/01/21-10:46:58.315382 7f1529bfb6c0 Level-0 table #335: started
2026/01/21-10:46:58.315409 7f1529bfb6c0 Level-0 table #335: 0 bytes OK
2026/01/21-10:46:58.323079 7f1529bfb6c0 Delete type=0 #333
2026/01/21-10:46:58.336644 7f1529bfb6c0 Manual compaction at level-0 from '!items!0EAAt0qSzcD9VRBH' @ 72057594037927935 : 1 .. '!items!zfpjROW9LDAlXUkN' @ 0 : 0; will stop at (end)
2026/01/21-10:46:58.336671 7f1529bfb6c0 Manual compaction at level-1 from '!items!0EAAt0qSzcD9VRBH' @ 72057594037927935 : 1 .. '!items!zfpjROW9LDAlXUkN' @ 0 : 0; will stop at (end)

BIN
packs/capacites/000179.ldb Normal file

Binary file not shown.

View File

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