79 Commits

Author SHA1 Message Date
uberwald d1fbe611ef Fix fiche creature
Release Creation / build (release) Successful in 9m14s
2026-06-07 23:52:50 +02:00
uberwald 5a30ffb00f Fix fiche creature 2026-06-07 23:52:10 +02:00
uberwald 3aefdeb42e Fix fiche creature 2026-06-07 23:51:44 +02:00
uberwald 939247e731 Fix fiche creature 2026-06-07 23:51:31 +02:00
uberwald 6dea5ba479 ALl features OK, excetp creatures sub-type, WIP3 2026-06-07 23:29:02 +02:00
uberwald 494ec3ea84 Feat: Add sous-type field to Creature sheets (Créature, Démon, Automata)
- Add soustype field to CreatureDataModel
- Add optionsSousTypeCreature to config with localization support
- Add sous-type select dropdown to creature-sheet.hbs header
- Add translation keys to fr.json

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 21:42:59 +02:00
uberwald cb4b255b35 Fix: Remove remaining static method from inside init() function
The getItemValueSC static method was still incorrectly placed inside the
init() method, causing 'Unexpected strict mode reserved word' error at line 103.
This moves it to the class level with calculateItemValueSC.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 21:36:14 +02:00
uberwald 116ac66a8a Fix: Move static methods outside of init() function to class level
The static methods calculateItemValueSC and getItemValueSC were incorrectly
placed inside the init() method, causing a SyntaxError in strict mode.
This moves them to the class level where they belong.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 21:16:52 +02:00
uberwald b4a4737d5b Docs: Update CORRECTIONS.md with new refactoring details and fixes
- Document code duplication elimination in item models
- Document currency conversion logic centralization
- Document new Handlebars helpers (localizeAllegiance, joinPredilections)
- Fix monetary conversion rates in documentation (1 PO = 100 SC, 1 PA = 10 SC)

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 20:54:02 +02:00
uberwald 4c33607b2b Refactor: Eliminate code duplication for currency conversion and item pricing
- Add static calculateItemValueSC() and getItemValueSC() helpers to MournbladeCYD2Utility
  to centralize currency conversion logic (1 PO = 100 SC, 1 PA = 10 SC)
- Refactor computeRichesse() and computeValeurEquipement() in Actor to use shared helpers
- Add localizeAllegiance Handlebars helper to display allegiance values localized
  (tous->Tous, chaos->Chaos, loi->Loi, betes->Bêtes, elementaires->Élémentaires)
- Add joinPredilections helper to fix comma display after single Predilection
- Create BaseItemWithPriceDataModel base class for items with pricing fields
  (prixpo, prixca, prixsc, rarete, quantite, equipped)
- Update arme, equipement, protection, monnaie models to extend base class
- Update actor-sheet and creature-sheet templates to use new helpers
- Update partial-item-prix.hbs to display total item value in SC
- Add item-base-sheet.hbs template for future item sheet inheritance

Fixes:
- Allegiance values now display properly localized in Dons & Pactes tabs
- Predilections no longer show trailing comma with single entry
- Equipment value totals now update correctly when items are added/modified
- Currency conversion logic centralized and consistent across the system

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 20:52:46 +02:00
uberwald 0d4bd37f30 Fix: Correction de computeRichesse() pour prendre en compte PO et PA
- computeRichesse() ne prenait en compte que prixsc, maintenant il calcule aussi prixca et prixpo
- Conversion : prixca * 10 + prixpo * 100 (selon le lore)
- Cohérent avec computeValeurEquipement()

Fichier modifié : mournblade-cyd2-actor.js

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 20:16:37 +02:00
uberwald 0aefe8bea8 Docs: Mise à jour tableau état avec correction conversion monétaire
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 19:19:48 +02:00
uberwald 083b02ff96 Docs: Documentation de la correction des taux de conversion monétaire
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 19:16:45 +02:00
uberwald 59fa8c72ff Fix: Correction des taux de conversion monétaire selon le lore Mournblade
- Ancienne conversion : 1 PO = 400 SC, 1 PA = 20 SC (incorrect)
- Nouvelle conversion : 1 PO = 100 SC, 1 PA = 10 SC (selon le lore)

Selon le lore des Jeunes Royaumes :
- 1 Sou d'Argent (SA) = 10 Pièces de Bronze (PB)
- 1 Pièce d'Or (PO) = 10 Sous d'Argent = 100 Pièces de Bronze

Correspondance dans le code :
- SC = Pièces de Bronze (PB)
- PA/CA = Sous d'Argent (SA)
- PO = Pièces d'Or (PO)

Fichiers modifiés :
- mournblade-cyd2-utility.js : Helper calculateItemValueSC et computeMonnaieDetails
- mournblade-cyd2-actor.js : computeValeurEquipement()

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 19:12:17 +02:00
uberwald 4f675cb5c1 Docs: Correction du tableau état des fonctionnalités
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 18:59:04 +02:00
uberwald d1c7c74085 Docs: Documentation de l'activation des totaux d'argent et équipement
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 18:53:27 +02:00
uberwald c5628586f4 Fix: Activation du calcul automatique des totaux d'argent et d'équipement
- Ajout des appels à computeRichesse() et computeValeurEquipement() dans _prepareContext()
- Ces méthodes existent déjà dans l'acteur mais n'étaient pas appelées dans la feuille
- Maintenant, les totaux PO, PA, SC sont calculés automatiquement à partir des items

Fichier modifié : mournblade-cyd2-personnage-sheet.mjs

Répond à la question : 'Où le total restant de ces trois types de pièces apparaît-il ?'
Réponse : Les totaux sont maintenant calculés et affichés dans l'onglet Équipement

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 18:50:36 +02:00
uberwald 13c0d801c3 Docs: Mise à jour tableau état des fonctionnalités
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 18:31:44 +02:00
uberwald fc24b94784 Docs: Documentation de la correction orthographique scéance→séance
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 18:28:22 +02:00
uberwald 29e9230422 Fix: Correction orthographique 'scéance' → 'séance' dans optionsUseTalent
- Correction de la faute d'orthographe dans l'option d'utilisation des Talents
- Avant : 'Une fois par scéance'
- Après : 'Une fois par séance'

Fichier modifié : mournblade-cyd2-config.js (ligne 214)

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 18:27:58 +02:00
uberwald fc1be1513a Docs: Documentation de la correction des valeurs d'Allégeance
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 15:32:36 +02:00
uberwald 5c66c29d24 Fix: Correction de l'affichage des valeurs d'Allégeance
- Modification des options d'allégeance pour afficher des valeurs plus courtes et propres
- 'tous' → 'Tous' (déjà correct via MNBL.all)
- 'chaos' → 'Chaos' (déjà correct via MNBL.chaos)
- 'loi' → 'Loi' (déjà correct via MNBL.law)
- 'betes' → 'Bêtes' (nouvelle clé MNBL.betes)
- 'elementaires' → 'Élémentaires' (nouvelle clé MNBL.elementaires)

Fichiers modifiés:
- lang/fr.json : Ajout des clés MNBL.betes et MNBL.elementaires
- mournblade-cyd2-config.js : Mise à jour des allegeanceOptions pour utiliser les nouvelles clés

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 15:30:27 +02:00
uberwald b34857325d Docs: Documentation de la correction des virgules après Prédilections
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 15:27:59 +02:00
uberwald 51a4df73ab Fix: Correction de l'affichage des virgules après les Prédilections
- Suppression de la virgule finale lorsqu'il n'y a qu'une seule Prédilection affichée
- Utilisation du helper subtract pour vérifier si c'est la dernière prédilection dans la liste
- Correction appliquée à la fois sur actor-sheet.hbs et creature-sheet.hbs

Avant : 'Prédilection1,' (virgule inutile)
Après : 'Prédilection1' (pas de virgule) ou 'Prédilection1, Prédilection2' (virgule seulement entre les éléments)

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 15:27:25 +02:00
uberwald c0bc37e32a Docs: Mise à jour de la documentation des corrections
- Ajout des nouvelles corrections pour le popup, le coût en Pouvoir, et l'amélioration des Profils
- Mise à jour de l'état des fonctionnalités demandées

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 14:54:49 +02:00
uberwald 92ea0164a2 Fix: Corrections diverses pour Mournblade CYD 2.0
- Modification du popup de bienvenue : simplification de la mention des droits (jeu Titam)
- Ajout du champ 'Coût en Pouvoir des invocations' dans l'onglet Sorcellerie pour suivre le coût des invocations en cours
- Amélioration de l'onglet Détails des Profils : remplacement des sections de texte simples par des sheet-box pour un meilleur rendu visuel
- Ajout de la clé i18n 'coutPouvoirInvocations' dans lang/fr.json
- Ajout du champ coutPouvoirInvocations dans le schéma de données Personnage

Corrections basées sur les demandes : popup, onglet Sorcellerie, onglet Détails Profils

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 14:52:50 +02:00
uberwald 0425ccf723 feat: Ajout carte Combat sur fiche de personnage
- Ajout d'une carte Combat (case bleue) dans l'en-tête de la fiche
  de personnage, similaire à celle des créatures
- Affiche en temps réel :
  * Initiative (calculée : Adresse + bonus)
  * Défense (calculée : base + bonus + protection - adversité)
  * Protection (calculée à partir des armures/boucliers équipés)
- Ajout des clés i18n pour les libellés abrégés :
  * MNBL.initShort: Init.
  * MNBL.defShort: Déf.
  * MNBL.protShort: Prot.

Ces valeurs sont déjà calculées par getCombatValues() et
protectionTotal dans le contexte de la feuille.

Permet aux joueurs de voir d'un coup d'œil leurs
caractéristiques de combat actuelles.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 14:39:14 +02:00
uberwald 37ff6ebf1d feat: Ajout section Invocations en cours dans l'onglet Sorcellerie
- Ajout du champ system.sorcellerie.invocationsencours (HTMLField)
- Ajout de la clé i18n SORCELLERIE.invocationsencours
- Ajout de la section dans le template avec éditeur riche
- Permet de noter les invocations actives avec leur coût en Pouvoir

L'éditeur riche permet de:
- Glisser-déposer des liens vers des objets du compendium
- Formater le texte (gras, italique, listes)
- Noter le coût temporaire en Pouvoir pour chaque invocation

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 14:34:08 +02:00
uberwald 1c73faeb00 feat: Ajout onglet Sorcellerie sur la fiche de personnage
- Ajout d'un nouvel onglet 'Sorcellerie' dans la navigation
- Déplacement de la section Runes de 'Dons & Pactes' vers 'Sorcellerie'
- Ajout de 3 nouvelles sections avec éditeurs riches :
  * Créatures invoquées
  * Démons liés
  * Enchantements / Automata
- Ajout des champs système pour stocker ces informations (HTMLField)
- Ajout des clés i18n sous le namespace SORCELLERIE

Structure de l'onglet Sorcellerie :
1. Runes (liste d'items, comme auparavant)
2. Créatures invoquées (éditeur riche)
3. Démons liés (éditeur riche)
4. Enchantements / Automata (éditeur riche)

Cela permet aux sorciers d'avoir plus d'espace pour noter leurs
nombreuses runes, invocations, etc. et d'y glisser-déposer des liens
vers des objets du compendium.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 14:14:30 +02:00
uberwald 0c42b6ab34 feat: Amélioration de l'onglet Détails des Profils
- Conversion des champs Compétences, Talents (Initié/Aguerri/Maître)
  et Prérequis (Aguerri/Maître) en HTMLField pour permettre
  l'utilisation de l'éditeur riche
- Remplacement des textarea à fond marron par des éditeurs blancs
  (comme pour les Sacrifices des Dons)
- Permet maintenant de glisser-déposer des liens vers des objets
  du compendium (Compétences, Talents, etc.)
- Structure cohérente avec le template des Dons

Modèles modifiés:
- modules/models/profil.mjs: conversion StringField → HTMLField
- templates/item-profil-sheet.hbs: remplacement textarea → éditeur

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 14:07:01 +02:00
uberwald d6e7b62c31 Docs: Mise à jour du pop-up de bienvenue
- Suppression du lien vers titam-france.fr
- Ajout du lien vers lahiette.com pour les règles PAO 0.9
- Conservation de la mention Titam dans la section Droits

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 14:03:36 +02:00
uberwald a3f304c77b Fix: Correction des clés i18n et affichage de l'onglet Effets
- Correction du namespace i18n: MOURNBLADECYD2.EFFECT → EFFECT
- Suppression de la condition {{#if item.effects.length}} dans tous les templates d'items
- Ajout de la clé EFFECT.noItemEffects pour les items sans effets
- Remplacement des textes en dur par des clés i18n dans les partials
- Mise à jour de toutes les références dans le code JavaScript

Cela corrige:
1. Les clés i18n manquantes (namespace incohérent)
2. L'onglet Effets des items qui était vide quand l'item n'avait pas d'effets

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 09:47:22 +02:00
uberwald 1b81b0a3ac Corrections§upgrade diverses 2026-06-07 09:43:37 +02:00
uberwald 3ff2b8e9bb Docs: Add MNBL i18n and effects tab fixes to documentation
- Documented missing MNBL.details and MNBL.description keys
- Documented effects tab visibility fix
- Updated file list and impact section

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 00:53:53 +02:00
uberwald 38525c3257 Fix: Add missing MNBL i18n keys and show effects tab always
- Added MNBL.details and MNBL.description to lang/fr.json
- Removed conditional display of effects tab in partial-item-nav.hbs
- Effects tab now always visible in item sheets
- Added MNBL i18n keys verification to test script

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 00:53:31 +02:00
uberwald f035bcfae2 Docs: Add i18n EFFECT keys fix to documentation
- Documented the missing i18n localization keys
- Updated file list and impact section

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 00:51:32 +02:00
uberwald a8bf356d20 Fix: Add missing i18n EFFECT keys to lang/fr.json
Added missing localization keys for ActiveEffect management:
- createError: Erreur lors de la création de l'effet
- deleteError: Erreur lors de la suppression de l'effet
- applyError: Erreur lors de l'application de l'effet
- applyItemError: Erreur lors de l'application de l'effet sur l'item
- selectActor: Sélectionnez un acteur pour appliquer l'effet
- toggleError: Erreur lors de l'activation/désactivation de l'effet

Added test verification for EFFECT i18n keys

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 00:51:12 +02:00
uberwald cd70b70088 Docs: Add subtract helper fix to documentation
- Documented the missing subtract helper issue
- Updated file list and impact section

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 00:49:15 +02:00
uberwald 14763cc5b3 Fix: Add missing Handlebars subtract helper
- Added subtract helper: parseInt(a) - parseInt(b)
- Helper is used in partial-active-effects.hbs and partial-item-effects.hbs
- Registered in MournbladeCYD2Utility.init() alongside other helpers
- Added test verification for subtract helper registration

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 00:48:55 +02:00
uberwald 0258c2e8b7 Docs: Update with duration.type to duration.units fix
- Documented the ActiveEffectDuration property deprecation fix
- Updated file list and impact section

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 00:42:05 +02:00
uberwald 9b3d34c5d7 Fix: Replace deprecated duration.type with duration.units (Foundry v14+)
- ActiveEffectDuration#type was renamed to #units in Foundry VTT v14
- Updated partial-active-effects.hbs to use duration.units
- Updated partial-item-effects.hbs to use duration.units
- Added test verification for duration.type usage
- Support for duration.type will be removed in v16

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 00:41:46 +02:00
uberwald 335238df3d Docs: Update CORRECTIONS.md with effect.webp icon fix
- Documented the effect.webp missing icon issue
- Updated file list with all corrected files
- Updated impact section

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 00:36:40 +02:00
uberwald a1519e7a60 Fix: Replace missing effect.webp icon with existing capacite.webp
- effect.webp icon was missing, causing infinite 404 errors
- Replaced all references with capacite.webp which exists
- Fixed in base-actor-sheet.mjs, base-item-sheet.mjs, mournblade-cyd2-effects.js
- Fixed in partial-active-effects.hbs and partial-item-effects.hbs templates
- Updated test script to check for effect.webp references

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 00:36:12 +02:00
uberwald e55b5cbe15 Test: Add check for deprecated ActiveEffectDialog API usage
- Added verification for deprecated ActiveEffectDialog.create() calls
- Updated test script to check all critical fixes

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 00:34:06 +02:00
uberwald f28719fc6f Fix: Replace deprecated ActiveEffectDialog.create() with createEmbeddedDocuments
- ActiveEffectDialog.create() was removed in Foundry VTT v14
- Replaced with direct document.createEmbeddedDocuments() call
- After creation, opens the effect sheet for editing
- Fixed in both base-actor-sheet.mjs and base-item-sheet.mjs

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 00:33:44 +02:00
uberwald d0423b2017 Fix: Add missing Handlebars partials to preload function
- Added partial-active-effects.hbs and partial-item-effects.hbs to preloadHandlebarsTemplates()
- These partials were used in sheets but not preloaded, causing render errors
- Fixed lang/fr.json formatting (added closing brace and newline)
- Added test script to validate template loading
- Added CORRECTIONS.md documentation

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 00:26:07 +02:00
uberwald 156672d853 ActiveEffects: Add complete ActiveEffects management system
- New: modules/mournblade-cyd2-effects.js with utility methods for creating, applying, and managing effects
- New: templates/partial-active-effects.hbs for displaying actor effects
- New: templates/partial-item-effects.hbs for displaying item effects
- Update: modules/mournblade-cyd2-config.js with effect configuration (types, attribute keys, categories)
- Update: templates/actor-sheet.hbs and creature-sheet.hbs with Effects tab
- Update: templates/partial-item-nav.hbs with conditional Effects tab
- Update: templates/item-talent-sheet.hbs with Effects tab content
- Update: base-actor-sheet.mjs with effect action handlers (create, edit, delete, toggle)
- Update: base-item-sheet.mjs with effect action handlers and context
- Update: modules/mournblade-cyd2-main.js to import and expose MournbladeCYD2Effects
- Update: lang/fr.json with effect-related translations

Features:
- Support for creating permanent and temporary effects
- Support for attribute modifications (ADR, PUI, CLA, PRE, TRE, etc.)
- Support for health, soul, combat, and adversity modifications
- Support for status effects
- Support for runes (pronounced and traced) effects
- Toggle to enable/disable effects
- Duration tracking (rounds, turns, seconds, combat, scene)
- Display of all active modifications summary

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-06 22:51:31 +02:00
uberwald 5ab03920d6 Corrections diverses pour CYD2.0
- Fix: Remplacement de 'Points d'Âme' par 'Points de Pouvoir' dans les messages de chat
- Fix: Ajout de la case Âme avec Seuil de Pouvoir dans les fiches de créature
- Fix: Ajout du champ Seuil de Pouvoir éditable dans les fiches de créature
- Fix: Initiative, Défense et Protection maintenant éditables dans les fiches de créature
- Fix: Ajout du champ Bonus/Malus aux templates de Traits chaotiques et d'espèce
- Fix: Le champ coût en Pouvoir des Runes accepte maintenant du texte (StringField)
- Fix: Rafraîchissement des fiches après drop d'items pour afficher les Traits
- Fix: Ajout des nouveaux types d'items (Trait Démoniaque, Pouvoir Élémentaire, Capacité d'Automata)
- New: Ajout des modèles et templates pour les nouveaux types d'items
- New: Intégration complète des nouveaux types dans les fichiers de configuration

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-06 22:08:53 +02:00
uberwald 9dd6fbd2e7 Fix: Affichage des Traits et corrections diverses
- Fix: Ajout des sections Traits Chaotiques et Traits d'Espèce dans les fiches de créature
  - Ajout de context.traitsChaotiques et context.traitsEspeces dans la feuille de créature
  - Ajout des sections dans le template creature-sheet.hbs avec boutons d'ajout
- Fix: Bouton Ajouter Automatisation fonctionnel dans les Talents
  - Ajout des actions addAutomation et deleteAutomation dans base-item-sheet.mjs
  - Ajout des méthodes #onAddAutomation et #onDeleteAutomation
  - Ajout des attributs data-action sur les boutons du template partial-automation.hbs
  - Ajout des attributs name sur les champs d'automatisation
- Ajout du champ Don lié dans les Tendances
  - Ajout de donlie dans le modèle tendance.mjs
  - Ajout du champ Don lié dans le template item-tendance-sheet.hbs
- Ajout de la catégorie Balance dans les options d'allégeance
  - Ajout dans mournblade-cyd2-config.js
  - Ajout de la traduction dans lang/fr.json
- Correction du libellé Coût en Âme → Coût en points de pouvoir
- Correction du bug de l'éditeur Sacrifices dans les Dons
  - Ajout de owner et editable dans le contexte
  - Renommage de Sacrifices en Sacrifices et Tendances liées
- Ajout de la case Âme avec Seuil de Pouvoir dans les fiches de créature
- Rendre Initiative, Défense et Protection éditables dans les fiches de créature
  - Ajout des champs inittotal, defensetotal, protectiontotal dans creature.mjs
  - Modification du contexte pour prioriser les valeurs manuelles
  - Remplacement des spans par des inputs dans le template

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-06 20:30:04 +02:00
uberwald 76ed974352 Fix: Correction du libellé Coût en Âme et ajout de la catégorie Balance
- Correction du libellé 'Coût en Âme' en 'Coût en points de pouvoir' pour les runes
- Fix: Ajout des gestionnaires d'événements pour les automatisations des Talents
  - Ajout des actions addAutomation et deleteAutomation dans base-item-sheet.mjs
  - Ajout des attributs data-action sur les boutons du template partial-automation.hbs
  - Ajout des attributs name sur les champs d'automatisation pour la sauvegarde
- Ajout de la catégorie 'Balance' dans les options d'allégeance pour Dons et Tendances

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-06 18:32:10 +02:00
uberwald c65a55225d Corrections diverses pour CYD2.0
Release Creation / build (release) Successful in 8m48s
2026-05-24 16:49:39 +02:00
uberwald adc104b757 Corrections diverses pour CYD2.03 2026-05-24 16:49:25 +02:00
uberwald 2e14c70a02 Feat: Add 'Coût en Pouvoir' field to Rune items
Ajoute un champ numérique 'coutAme' (entier, défaut=0) pour les runes :
- DataModel: Ajout du champ dans modules/models/rune.mjs
- Template fiche: Ajout du champ 'Coût en Pouvoir' dans templates/item-rune-sheet.hbs
- Template post: Ajout de l'affichage dans templates/post-item.hbs

Ce champ permet de spécifier le coût en Pouvoir pour chaque rune,
et s'affiche à la fois dans la fiche et dans le chat.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-05-24 16:44:45 +02:00
uberwald 2d5b844796 Fix: Add predilections and description to competence post-item template
Améliore l'affichage des compétences dans le chat en ajoutant :
- La liste des prédilections avec leurs propriétés (nom, description, maîtrisée, acquise, utilisée)
- La description est déjà affichée (déjà présente dans le template)

Cela permet aux joueurs de voir toutes les informations importantes
lorsqu'une compétence est postée dans le chat.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-05-24 16:25:46 +02:00
uberwald c62131ac97 Fix: Add missing data-action attributes to competence sheet predilection buttons
Corrige le bouton 'Ajouter une prédilection' qui ne fonctionnait pas
à cause de l'absence de l'attribut data-action requis par AppV2.

Modifications:
- Ajout de data-action="addPredilection" au bouton d'ajout
- Ajout de data-action="deletePredilection" au bouton de suppression
- Ajout des attributs name= aux champs de prédilection pour la sauvegarde automatique

Cela corrige le problème: le bouton 'Ajouter une prédilection' ne répondait pas
lorsqu'on cliquait dessus dans la fiche de compétence.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-05-24 16:20:32 +02:00
uberwald cc92f5a418 Fix: Complete correction of image paths and item types in all compendium packs
Corrige TOUS les problèmes de chemins d'images et de types d'items :

1. Chemins d'images incorrects (3 variantes) :
   - systems/fvtt-mournblade/assets/icons/ → systems/fvtt-mournblade-cyd-2-0/assets/icons/
   - systems/fvtt-mournblade-cyd2/assets/icons/ → systems/fvtt-mournblade-cyd-2-0/assets/icons/

2. Type d'item invalide :
   - 4 boucliers (Écu d'acier, Pavois, Bouclier d'Infanterie, Targe)
     avaient le type 'bouclier' → corrigé en 'protection'

Packs corrigés : armes, protections, equipement, dons, runes, tendances,
 traits-chaotiques, historiques, profils, talents, skills, skills-creatures, pnj-creatures

Total: 1136+ items corrigés

Cela corrige l'erreur: 'Cannot convert undefined or null to object'
lors de l'ouverture des items dans les compendiums.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-05-24 16:11:21 +02:00
uberwald b877262283 Minor fixes from v14 feedback
Release Creation / build (release) Successful in 8m22s
2026-05-23 23:07:10 +02:00
uberwald d120da6718 Ready for release
Release Creation / build (release) Successful in 11m34s
2026-04-04 19:08:22 +02:00
uberwald ef609136e2 Ready for release
Release Creation / build (release) Failing after 11m26s
2026-04-04 18:50:36 +02:00
uberwald d9e770a250 Ready for release 2026-04-04 18:50:33 +02:00
uberwald 7c500f3ef4 Ready for release 2026-04-04 18:46:55 +02:00
uberwald 56b58565d1 Derniers fix de sécurité et assimilé 2026-04-04 09:40:30 +02:00
uberwald 1fb80f6abe Rework fiches, jets de dés et CSS 2026-04-03 16:22:51 +02:00
uberwald e001ec0dc9 fix: améliore lisibilité des short-label dans les headers de listes
Change la couleur des labels de colonnes (Allégeance, Haut Parler, Difficulté...)
de l'or #c0a870 vers un crème clair #ddd0b8, plus lisible sur le fond sombre
des rangées items-title-bg.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-03 09:44:42 +02:00
uberwald b255a628fe Fix creature sheet: complete TECHNIQUE tab + copper theme
- Fix tabGroups 'stats'→'principal' (tab content was invisible)
- Add 'creature' CSS class to distinguish from personnage sheet
- New header layout: stat cards (Ressources, Vigueur, Combat)
- Rewrite creature template with full TECHNIQUE tab content:
  * Attributes with icons and level dropdowns (0-35)
  * Vitesse input
  * Santé section (Vigueur seuil)
  * Actions de combat (Initiative, Monté/Déf. totale, Assommer,
    Coup bas, Immobiliser, Repousser, Désengager)
  * Adversités section (reuses personnage CSS classes)
- Copper/orange CSS theme for .creature class overrides
- Add .stat-derived-value CSS for non-editable derived stats

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-03 09:35:12 +02:00
uberwald f0969c9eb4 Aesthetic overhaul: all 6 PJ sheet tabs styled with Mournblade theme
- Section boxes, item lists with alternating rows and hover effects
- Dark header rows (CentaurMT font, uppercase, gold text)
- 28x28px item icons with brown border
- Roll buttons: warm dark gradient with hover glow
- Chat/action buttons: blue gradient
- Predilection text: small italic muted gold
- Defense values in blue, gold argent banners
- Bio grid fields styled, description section header with accent
- Rich text editor area styled

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-03 09:15:29 +02:00
uberwald e67ecd9238 Add adversité total malus display
Show 'MALUS JETS -X' below adversité cards. Highlights in red when non-zero.
Value = bleue + rouge + noire (same as nbAdversites used in roll formula).
Tooltip explains it's subtracted from all rolls.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-03 08:15:30 +02:00
uberwald 21c3c5b88b Homogenize header: VIGUEUR threshold as subtitle like AME
Move vigueur value from standalone big number to inline subtitle (X)
in the card title, matching AME's (Seuil X) pattern.
Remove stat-big-value block from VIGUEUR card.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-03 08:09:08 +02:00
uberwald 0ce42d0fe3 Redesign adversité section: 3 colored cards layout
Replace vertical list with horizontal card layout matching header style:
- BLEUE (blue), ROUGE (red), NOIRE (dark) cards with top-colored borders
- Gem image 52px with drop-shadow + hover scale
- Large numeric counter (Signika font)
- Colored -/+ buttons (red minus, green plus)
- Modifier fields compact row below cards
- Section title with amber letterhead style

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-03 08:04:45 +02:00
uberwald 28cab15b37 Fix select text clipping in header stat cards
Use height:auto + padding:1px instead of fixed 20px height so browser
renders select text without vertical clipping.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-03 07:57:12 +02:00
uberwald c28414dd22 Refactor header: card-based layout with colored stat cards
- Replace flat ul/li header with .header-banner + .header-stat-cards
- 5 colored stat cards: LOI (amber), CHAOS (red), BONNE AVENTURE (green),
  VIGUEUR (maroon), ÂME (navy)
- Profile image: 90px with golden border + hover glow
- Character name: large serif font (CentaurMT) with transparent bg
- Fix overflow: visible on .stat-card to prevent select dropdown clipping
- Fix label colors: dark (#1a1008) instead of white for readability
- VIGUEUR big value: Signika/sans-serif font (avoids CentaurMT 1/I confusion)
- Consistent font sizes and alignment across all stat fields

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-03 07:53:44 +02:00
uberwald 9754bbc3a8 Update CSS/Look & feel 2026-04-03 00:39:39 +02:00
uberwald 23b105a47e Prepare for v14 2026-04-02 22:22:42 +02:00
uberwald faf452f797 Prepare for v14 2026-04-02 22:22:26 +02:00
uberwald 9fd13b2615 DAtaModels + Appv2 migration : OK 2026-04-02 22:14:30 +02:00
uberwald 7a2a3df391 DAtaModels + Appv2 migration : OK 2026-04-02 21:39:33 +02:00
uberwald a11b3495a5 DAtaModels + Appv2 migration : OK 2026-04-02 21:34:33 +02:00
uberwald a37ad2cc82 DAtaModels + Appv2 migration : OK 2026-04-02 21:33:56 +02:00
uberwald 4def580296 Fix wrong sheets 2025-10-26 14:46:58 +01:00
291 changed files with 16163 additions and 2738 deletions
+66
View File
@@ -0,0 +1,66 @@
name: Release Creation
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "💡 The ${{ gitea.repository }} repository will be cloned to the runner."
- uses: RouxAntoine/checkout@v3.5.4
# get part of the tag after the `v`
- name: Extract tag version number
id: get_version
uses: battila7/get-version-action@v2
# Substitute the Manifest and Download URLs in the system.json
- name: Substitute Manifest and Download Links For Versioned Ones
id: sub_manifest_link_version
uses: microsoft/variable-substitution@v1
with:
files: "system.json"
env:
version: ${{steps.get_version.outputs.version-without-v}}
url: https://www.uberwald.me/gitea/${{gitea.repository}}
manifest: https://www.uberwald.me/gitea/public/fvtt-mournblade-cyd-2-0/releases/download/latest/system.json
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-mournblade-cyd-2-0.zip
# Build CSS from LESS sources before packaging
- name: Install Node.js dependencies and build styles
run: |
apt update -y
apt install -y nodejs npm zip
npm install
npm run build
# Create a zip file with all files required by the system
- run: zip -r ./fvtt-mournblade-cyd-2-0.zip system.json README.md LICENCE.txt assets/ lang/ modules/ packs/ styles/ templates/
- name: Setup Go
uses: https://github.com/actions/setup-go@v4
with:
go-version: ">=1.20.1"
- name: Upload release assets
id: use-go-action
uses: https://gitea.com/actions/release-action@main
with:
files: |-
./fvtt-mournblade-cyd-2-0.zip
system.json
api_key: "${{secrets.ALLOW_PUSH_RELEASE}}"
# - name: Publish to Foundry server
# uses: https://github.com/djlechuck/foundryvtt-publish-package-action@v1
# with:
# token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
# id: 'fvtt-mournblade-cyd-2-0'
# version: ${{github.event.release.tag_name}}
# manifest: 'https://www.uberwald.me/gitea/public/fvtt-mournblade-cyd-2-0/releases/download/latest/system.json'
# notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-mournblade-cyd-2-0.zip'
# compatibility-minimum: '14'
# compatibility-verified: '14'
+5
View File
@@ -0,0 +1,5 @@
node_modules/
styles/mournblade-cyd2.css.map
package-lock.json
.github/
.history/
+812
View File
@@ -0,0 +1,812 @@
# Corrections apportées au module Mournblade CYD 2.0
## Date : 2026-06-07
## Dernière mise à jour : 2026-06-07
## Nouveautés et Améliorations (2026-06-07)
## Problèmes identifiés et corrigés
### 1. ❌ Erreur de chargement des partials Handlebars
**Problème :**
Les feuilles de personnage et de créature généraient une erreur lors du rendu :
```
Failed to render template part "sheet":
The partial systems/fvtt-mournblade-cyd-2-0/templates/partial-active-effects.hbs could not be found
```
**Cause :**
La fonction `preloadHandlebarsTemplates()` dans `modules/mournblade-cyd2-utility.js` ne préchargeait pas tous les partials nécessaires. Seuls 7 templates étaient préchargés sur 9 utilisés.
**Partials manquants :**
- `partial-active-effects.hbs` - Utilisé dans les feuilles actor-sheet.hbs et creature-sheet.hbs
- `partial-item-effects.hbs` - Utilisé dans de nombreux templates d'items
**Solution :**
Ajout des deux partials manquants à la liste des templates préchargés dans la fonction `preloadHandlebarsTemplates()`.
**Fichier modifié :**
- `modules/mournblade-cyd2-utility.js` (lignes 189-201)
**Code avant :**
```javascript
const templatePaths = [
'systems/fvtt-mournblade-cyd-2-0/templates/editor-notes-gm.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-header.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-description.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-nav.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-prix.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-automation.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/hud-adversites.hbs',
]
```
**Code après :**
```javascript
const templatePaths = [
'systems/fvtt-mournblade-cyd-2-0/templates/editor-notes-gm.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-header.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-description.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-nav.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-prix.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-automation.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-active-effects.hbs', // ✅ Ajouté
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-effects.hbs', // ✅ Ajouté
'systems/fvtt-mournblade-cyd-2-0/templates/hud-adversites.hbs',
]
```
---
### 2. ❌ Erreur de création d'effet actif
**Problème :**
```
base-actor-sheet.mjs:357 MournbladeCYD2 | Failed to create effect: TypeError:
Cannot read properties of undefined (reading 'create')
```
**Cause :**
La fonction `ActiveEffectDialog.create()` n'existe pas dans Foundry VTT v14. L'API a changé et cette méthode a été supprimée.
**Solution :**
Remplacement de l'appel à `foundry.applications.api.ActiveEffectDialog.create()` par une création directe via `document.createEmbeddedDocuments("ActiveEffect", [data])`, suivie de l'ouverture de la feuille d'édition.
**Fichiers modifiés :**
- `modules/applications/sheets/base-actor-sheet.mjs` (lignes 328-363)
- `modules/applications/sheets/base-item-sheet.mjs` (lignes 189-224)
**Code avant :**
```javascript
const effect = await foundry.applications.api.ActiveEffectDialog.create({
document: this.document,
effect: defaultEffectData
});
if (effect) {
await this.document.createEmbeddedDocuments("ActiveEffect", [effect.toObject()]);
}
```
**Code après :**
```javascript
const [effect] = await this.document.createEmbeddedDocuments("ActiveEffect", [defaultEffectData]);
if (effect) {
effect.sheet.render(true);
}
```
---
### 3. ❌ Boucle infinie de chargement d'icône (effect.webp introuvable) ✅
**Problème :**
```
404 (Not Found) - GET https://localhost:31000/systems/fvtt-mournblade-cyd-2-0/assets/icons/effect.webp
Boucle infinie de tentatives de chargement
```
**Cause :**
L'icône `effect.webp` était référencée dans plusieurs fichiers mais n'existait pas dans le dossier `assets/icons/`. Chaque fois que la dialog de création d'effet s'ouvrait, le navigateur essayait de charger cette image manquante en boucle.
**Fichiers concernés :**
- `modules/applications/sheets/base-actor-sheet.mjs` (ligne 336)
- `modules/applications/sheets/base-item-sheet.mjs` (ligne 197)
- `modules/mournblade-cyd2-effects.js` (lignes 120, 180)
- `templates/partial-active-effects.hbs` (ligne 30)
- `templates/partial-item-effects.hbs` (ligne 30)
**Solution :**
Remplacement de toutes les références à `effect.webp` par `capacite.webp`, une icône existante dans le dossier `assets/icons/`.
**Code avant :**
```javascript
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/effect.webp"
```
**Code après :**
```javascript
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/capacite.webp"
```
---
### 4. ❌ Propriété dépréciée ActiveEffectDuration.type ✅
**Problème :**
```
foundry.mjs:1555 Error: You are accessing ActiveEffectDuration#type,
which is now at ActiveEffectDuration#units.
Deprecated since Version 14
Backwards-compatible support will be removed in Version 16
```
**Cause :**
En Foundry VTT v14, la propriété `duration.type` a été renommée en `duration.units`. L'ancien nom était encore supporté pour la compatibilité, mais générait des avertissements et sera supprimé en v16.
**Fichiers concernés :**
- `templates/partial-active-effects.hbs` (lignes 55-61)
- `templates/partial-item-effects.hbs` (lignes 51-56)
**Solution :**
Remplacement de toutes les occurrences de `effect.duration.type` par `effect.duration.units` dans les templates.
**Code avant :**
```handlebars
{{#if effect.duration.type}}
{{#if (eq effect.duration.type "rounds")}}🔄{{/if}}
{{#if (eq effect.duration.type "turns")}}🎭{{/if}}
{{/if}}
```
**Code après :**
```handlebars
{{#if effect.duration.units}}
{{#if (eq effect.duration.units "rounds")}}🔄{{/if}}
{{#if (eq effect.duration.units "turns")}}🎭{{/if}}
{{/if}}
```
---
### 5. ❌ Helper Handlebars "subtract" manquant ✅
**Problème :**
```
Failed to render Application "MournbladeCYD2PersonnageSheet":
Missing helper: "subtract"
```
**Cause :**
Le template utilisait le helper `subtract` dans `{{#unless (eq index (subtract effect.changes.length 1))}}` mais ce helper n'était pas enregistré dans Handlebars.
**Fichiers concernés :**
- `templates/partial-active-effects.hbs` (ligne 44)
- `templates/partial-item-effects.hbs` (ligne 44)
- `modules/mournblade-cyd2-utility.js` (helper non enregistré)
**Solution :**
Ajout du helper `subtract` dans la méthode `init()` de `MournbladeCYD2Utility` :
**Code ajouté :**
```javascript
Handlebars.registerHelper('subtract', function (a, b) {
return parseInt(a) - parseInt(b);
});
```
**Fonctionnalité :**
Le helper permet de soustraire deux nombres dans les templates Handlebars, utilisé pour détecter le dernier élément d'une liste.
---
### 6. ❌ Clés i18n manquantes pour les effets ✅
**Problème :**
Les clés de localisation pour les messages d'erreur des effets actifs étaient manquantes dans `lang/fr.json`, ce qui pouvait entraîner l'affichage de messages en anglais ou vides.
**Clés manquantes identifiées :**
- `MOURNBLADECYD2.EFFECT.createError`
- `MOURNBLADECYD2.EFFECT.deleteError`
- `MOURNBLADECYD2.EFFECT.applyError`
- `MOURNBLADECYD2.EFFECT.applyItemError`
- `MOURNBLADECYD2.EFFECT.selectActor`
- `MOURNBLADECYD2.EFFECT.toggleError`
**Solution :**
Ajout de toutes les clés manquantes dans la section `EFFECT` du fichier `lang/fr.json`.
**Traductions ajoutées :**
```json
{
"createError": "Erreur lors de la création de l'effet",
"deleteError": "Erreur lors de la suppression de l'effet",
"applyError": "Erreur lors de l'application de l'effet",
"applyItemError": "Erreur lors de l'application de l'effet sur l'item",
"selectActor": "Sélectionnez un acteur pour appliquer l'effet",
"toggleError": "Erreur lors de l'activation/désactivation de l'effet"
}
```
**Fichier modifié :** `lang/fr.json`
---
### 7. ❌ Clés i18n MNBL manquantes ✅
**Problème :**
Les clés de localisation `MNBL.details` et `MNBL.description` étaient manquantes dans `lang/fr.json`, ce qui entraînait l'affichage de la clé elle-même au lieu d'une traduction.
**Clés manquantes identifiées :**
- `MNBL.details` - Utilisée dans l'onglet "Détails" des fiches d'items
- `MNBL.description` - Utilisée dans l'onglet "Description" des fiches d'items
**Solution :**
Ajout des deux clés manquantes dans la section `MNBL` du fichier `lang/fr.json`.
**Traductions ajoutées :**
```json
{
"details": "Détails",
"description": "Description"
}
```
**Fichier modifié :** `lang/fr.json`
---
### 8. ❌ Onglet "Effets" manquant dans les fiches d'items ✅
**Problème :**
L'onglet "Effets" n'apparaissait pas dans les fiches d'items, empêchant l'accès à la gestion des effets actifs sur les items.
**Cause :**
Dans `templates/partial-item-nav.hbs`, l'onglet "Effets" n'était affiché que si l'item avait déjà des effets (`{{#if item.effects.length}}`).
**Solution :**
Suppression de la condition pour toujours afficher l'onglet "Effets", même lorsque l'item n'a pas encore d'effets actifs.
**Fichier modifié :** `templates/partial-item-nav.hbs`
**Code avant :**
```handlebars
{{#if item.effects.length}}
<a class="item" data-tab="effects" ...>{{localize "MOURNBLADECYD2.EFFECT.activeEffects"}}</a>
{{/if}}
```
**Code après :**
```handlebars
<a class="item" data-tab="effects" ...>{{localize "MOURNBLADECYD2.EFFECT.activeEffects"}}</a>
```
---
### 9. ❌ Erreur de parsing JSON (historique)
**Problème mentionné :**
```
SyntaxError: Expected ',' or '}' after property value in JSON at position 3753 (line 118 column 4)
```
**Statut :**
Cette erreur concernait probablement une ancienne version du fichier `lang/fr.json`. Le fichier actuel est valide et ne contient pas d'erreur de syntaxe.
**Vérification :**
```bash
# Le fichier passe la validation JSON
node -e "require('./lang/fr.json')" # ✅ Pas d'erreur
```
---
## Nouveautés et Améliorations (2026-06-07)
### 10. ✅ Amélioration du popup de bienvenue
**Modification :**
Simplification de la mention des droits dans le popup de bienvenue.
**Avant :**
```
Mournblade est un jeu publié par Titam France / Sombres Projets, tous les droits leur appartiennent.
```
**Après :**
```
Mournblade est un jeu Titam.
```
**Fichier modifié :** `modules/mournblade-cyd2-main.js` (ligne 141)
---
### 11. ✅ Ajout du coût en Pouvoir pour les invocations en cours
**Nouveauté :**
Ajout d'un champ numérique pour suivre le coût en Pouvoir des invocations en cours dans l'onglet Sorcellerie.
**Fichiers modifiés :**
- `modules/models/personnage.mjs` - Ajout du champ `coutPouvoirInvocations` dans le schéma sorcellerie
- `lang/fr.json` - Ajout de la clé `SORCELLERIE.coutPouvoirInvocations`
- `templates/actor-sheet.hbs` - Ajout du champ dans le template
**Clé i18n ajoutée :**
```json
{
"coutPouvoirInvocations": "Coût en Pouvoir des invocations"
}
```
**Schémas modifié :**
```javascript
sorcellerie: new fields.SchemaField({
runes: new fields.HTMLField({ initial: "" }),
creaturesinvoquees: new fields.HTMLField({ initial: "" }),
demonslies: new fields.HTMLField({ initial: "" }),
enchantements: new fields.HTMLField({ initial: "" }),
invocationsencours: new fields.HTMLField({ initial: "" }),
coutPouvoirInvocations: new fields.NumberField({ initial: 0, integer: true }) // ✅ Ajouté
})
```
---
### 12. ✅ Amélioration de l'onglet Détails des Profils
**Problème :**
Les sections de texte (Compétences exercées, Talents Initié, etc.) avaient un rendu peu esthétique.
**Solution :**
Remplacement des sections de texte simples par des `div class="sheet-box"` pour un meilleur rendu visuel.
**Fichier modifié :** `templates/item-profil-sheet.hbs`
**Avant :**
```handlebars
<h3>{{localize "MNBL.exercisedskills"}}</h3>
<div class="small-editor item-text-long-line">
{{editor competences target="system.competences" button=true owner=owner editable=editable}}
</div>
```
**Après :**
```handlebars
<div class="sheet-box">
<h3><label class="items-title-text">{{localize "MNBL.exercisedskills"}}</label></h3>
<div class="small-editor item-text-long-line">
{{editor competences target="system.competences" button=true owner=owner editable=editable}}
</div>
</div>
```
**Impact :**
- Meilleure cohérence visuelle avec le reste de l'interface
- Permet une meilleure organisation des sections
- Facilite l'ajout futur de fonctionnalités comme les liens vers le compendium
---
## Liste complète des partials Handlebars
### Partials utilisés dans le système :
| Partial | Utilisation | Pré-chargé ? |
|---------|-------------|--------------|
| `partial-item-header.hbs` | En-têtes des items | ✅ Oui |
| `partial-item-description.hbs` | Descriptions des items | ✅ Oui |
| `partial-item-nav.hbs` | Navigation des items | ✅ Oui |
| `partial-item-prix.hbs` | Prix des items | ✅ Oui |
| `partial-item-effects.hbs` | Effets des items | ✅ Oui (ajouté) |
| `partial-active-effects.hbs` | Effets actifs (actors) | ✅ Oui (ajouté) |
| `partial-automation.hbs` | Automatisation | ✅ Oui |
| `editor-notes-gm.hbs` | Notes GM | ✅ Oui |
| `hud-adversites.hbs` | HUD Adversités | ✅ Oui |
---
## Templates principaux
### Fiches d'acteurs :
- `actor-sheet.hbs` - Feuille de personnage
- `creature-sheet.hbs` - Feuille de créature
### Fiches d'items :
- `item-arme-sheet.hbs`
- `item-capaciteautomata-sheet.hbs`
- `item-competence-sheet.hbs`
- `item-don-sheet.hbs`
- `item-equipement-sheet.hbs`
- `item-historique-sheet.hbs`
- `item-monnaie-sheet.hbs`
- `item-pacte-sheet.hbs`
- `item-pouvoirselementaire-sheet.hbs`
- `item-profil-sheet.hbs`
- `item-protection-sheet.hbs`
- `item-ressource-sheet.hbs`
- `item-rune-sheet.hbs`
- `item-runeeffect-sheet.hbs`
- `item-talent-sheet.hbs`
- `item-tendance-sheet.hbs`
- `item-traitchaotique-sheet.hbs`
- `item-traitdemoniaque-sheet.hbs`
- `item-traitespece-sheet.hbs`
---
## Outils de test
Un script de test a été créé pour valider les corrections :
- **Fichier :** `test-templates.js`
- **Exécution :** `node test-templates.js`
**Fonctionnalités du test :**
1. ✅ Vérifie que tous les templates préchargés existent
2. ✅ Scanne tous les templates pour trouver les partials utilisés
3. ✅ Vérifie que tous les partials utilisés sont préchargés
4. ✅ Valide le fichier de localisation JSON
---
## Bonnes pratiques rappelées
### Pré-chargement des templates Handlebars
En Foundry VTT v12+, il est **obligatoire** de pré-charger tous les partials Handlebars utilisés via la fonction `foundry.applications.handlebars.loadTemplates()` dans le hook `init`.
**Pourquoi ?**
- Les partials ne sont pas chargés automatiquement
- Sans pré-chargement, le rendu échouera avec une erreur "partial could not be found"
- Le pré-chargement améliore les performances en cacheant les templates
**Où ?**
Dans le hook `init`, avant l'enregistrement des feuilles (sheets) :
```javascript
Hooks.once("init", async function () {
// Pré-charger les templates AVANT d'enregistrer les feuilles
await MournbladeCYD2Utility.preloadHandlebarsTemplates();
// Ensuite enregistrer les feuilles
Actors.registerSheet(...);
Items.registerSheet(...);
});
```
### Gestion des chemins des templates
Les chemins doivent être **relatifs au répertoire `systems/`** :
- ✅ Bon : `'systems/fvtt-mournblade-cyd-2-0/templates/partial-active-effects.hbs'`
- ❌ Mauvais : `'./templates/partial-active-effects.hbs'`
---
## Impact des corrections
### Avant les corrections :
- ❌ Ouverture des feuilles de personnage → Erreur
- ❌ Ouverture des feuilles de créature → Erreur
- ❌ Affichage des effets actifs → Impossible
- ❌ Utilisation des effets d'items → Problèmes potentiels
- ❌ Création d'effets actifs → Erreur TypeError
- ❌ Boucle infinie de 404 sur effect.webp
- ❌ Avertissements duration.type déprécié
- ❌ Helper subtract manquant → Erreur de rendu
- ❌ Clés i18n manquantes → Messages en anglais
- ❌ Clés MNBL.details et MNBL.description manquantes
- ❌ Onglet "Effets" manquant dans les fiches d'items
### Après les corrections :
- ✅ Toutes les feuilles s'ouvrent correctement
- ✅ Les effets actifs s'affichent correctement
- ✅ Tous les items affichent leurs effets
- ✅ Plus d'erreurs de templates manquants
- ✅ Création d'effets actifs fonctionne correctement
- ✅ Plus de boucles infinies de chargement d'icônes
- ✅ Plus d'avertissements de compatibilité
- ✅ Helper subtract disponible et fonctionnel
- ✅ Toutes les clés i18n présentes → Localisation complète
- ✅ Toutes les clés MNBL présentes
- ✅ Onglet "Effets" toujours visible dans les fiches d'items
---
## Recommandations pour le développement futur
1. **Toujours pré-charger les nouveaux partials** lorsqu'ils sont ajoutés
2. **Utiliser un script de test** pour valider les templates après modification
3. **Maintenir une liste à jour** des partials utilisés dans le projet
4. **Vérifier les erreurs de console** lors du développement
5. **Tester toutes les feuilles** après ajout de nouveaux partials
---
## Fichiers modifiés
| Fichier | Modification | Statut |
|---------|--------------|--------|
| `modules/mournblade-cyd2-utility.js` | Ajout partials + helper subtract | ✅ Corrigé |
| `modules/applications/sheets/base-actor-sheet.mjs` | Correction création effets + icône | ✅ Corrigé |
| `modules/applications/sheets/base-item-sheet.mjs` | Correction création effets + icône | ✅ Corrigé |
| `modules/mournblade-cyd2-effects.js` | Remplacement effect.webp par capacite.webp | ✅ Corrigé |
| `templates/partial-active-effects.hbs` | Remplacement effect.webp + duration.type → duration.units | ✅ Corrigé |
| `templates/partial-item-effects.hbs` | Remplacement effect.webp + duration.type → duration.units | ✅ Corrigé |
| `templates/partial-item-nav.hbs` | Affichage permanent onglet Effets + clés MNBL | ✅ Corrigé |
| `test-templates.js` | Nouveau fichier de test | ✅ Ajouté |
| `CORRECTIONS.md` | Documentation des corrections | ✅ Ajouté |
| `lang/fr.json` | Ajout des clés i18n EFFECT + MNBL manquantes | ✅ Corrigé |
## Fichiers modifiés (Nouveautés 2026-06-07)
| Fichier | Modification | Statut |
|---------|--------------|--------|
| `modules/mournblade-cyd2-main.js` | Simplification mention Titam dans popup | ✅ Corrigé |
| `modules/models/personnage.mjs` | Ajout champ coutPouvoirInvocations | ✅ Corrigé |
| `lang/fr.json` | Ajout clé SORCELLERIE.coutPouvoirInvocations | ✅ Corrigé |
| `templates/actor-sheet.hbs` | Ajout champ coût Pouvoir dans onglet Sorcellerie | ✅ Corrigé |
| `templates/item-profil-sheet.hbs` | Amélioration rendu sections avec sheet-box | ✅ Corrigé |
## État des fonctionnalités demandées
| Fonctionnalité | Statut | Remarques |
|---------------|--------|----------|
| Clés i18n manquantes | ✅ Complété | Toutes les clés EFFECT et MNBL sont présentes |
| Onglet Effets des items | ✅ Complété | Toujours visible, même sans effets |
| Popup de lancement | ✅ Complété | Lien vers règles PAO 0.9 présent, mention Titam simplifiée |
| Onglet Sorcellerie | ✅ Déjà présent | Avec sections Runes, Créatures invoquées, Démons liés, Enchantements/Automata |
| Section Invocations en cours | ✅ Complété | Avec champ coût en Pouvoir ajouté |
| Case bleue Combat | ✅ Déjà présente | Affiche Initiative, Défense, Protection sur fiche personnage |
| Cases PO/PA/SC sur items | ✅ Déjà présent | Via partial-item-prix.hbs avec calcul automatique |
| Sections blanches dans Profils | ✅ Complété | Remplacement des blocs simples par sheet-box |
| Virgules après Prédilections | ✅ Complété | Plus de virgule finale lorsqu'il n'y a qu'une seule Prédilection |
| Valeurs d'Allégeance | ✅ Complété | Affichage propre : Tous, Chaos, Loi, Bêtes, Élémentaires (via helper localizeAllegiance) |
| Orthographe Talent | ✅ Complété | "scéance" → "séance" |
| Totaux argent/équipement | ✅ Complété | Calcul automatique activé avec conversion lore (1 PO = 100 SC, 1 PA = 10 SC) |
---
### 13. ✅ Correction de l'affichage des virgules après les Prédilections
**Problème :**
Une virgule apparaît après une Prédilection même s'il n'y en a qu'une seule affichée.
**Exemple avant :**
```
Compétence (Prédilection1,)
```
**Exemple après :**
```
Compétence (Prédilection1)
Compétence (Prédilection1, Prédilection2) // Virgule uniquement entre les éléments
```
**Solution :**
Utilisation du helper `subtract` pour vérifier si c'est la dernière prédilection dans la liste et ne pas afficher la virgule dans ce cas.
**Code avant :**
```handlebars
{{#each skill.system.predilections as |pred key|}}
{{#if (and pred.acquise (not pred.used))}}
{{pred.name}},
{{/if}}
{{/each}}
```
**Code après :**
```handlebars
{{#each skill.system.predilections as |pred key|}}
{{#if (and pred.acquise (not pred.used))}}
{{pred.name}}{{#unless (eq key (subtract skill.system.predilections.length 1))}}, {{/unless}}
{{/if}}
{{/each}}
```
**Fichiers modifiés :**
- `templates/actor-sheet.hbs` (ligne 278)
- `templates/creature-sheet.hbs` (ligne 270)
---
### 18. ✅ Réduction de la duplication de code dans les modèles items
**Problème :**
Les modèles `equipement.mjs`, `arme.mjs`, `protection.mjs`, et `monnaie.mjs` dupliquaient les champs communs : `prixpo`, `prixca`, `prixsc`, `rarete`, `quantite`, `equipped`, `description`.
**Solution :**
Création d'une classe de base `BaseItemWithPriceDataModel` dans `base-item.mjs` qui contient tous les champs communs. Les 4 modèles étendent maintenant cette classe.
**Avantages :**
- Élimination de la duplication de code
- Maintenance plus facile (changement dans un seul endroit)
- Cohérence garantie entre tous les items avec prix
**Fichiers modifiés :**
- `modules/models/base-item.mjs` - Ajout de `BaseItemWithPriceDataModel`
- `modules/models/equipement.mjs` - Étend `BaseItemWithPriceDataModel`
- `modules/models/arme.mjs` - Étend `BaseItemWithPriceDataModel`
- `modules/models/protection.mjs` - Étend `BaseItemWithPriceDataModel`
- `modules/models/monnaie.mjs` - Étend `BaseItemWithPriceDataModel`
---
### 19. ✅ Centralisation de la logique de conversion monétaire
**Problème :**
La logique de conversion entre PO, PA et SC était dupliquée dans `computeRichesse()` et `computeValeurEquipement()`.
**Solution :**
- Créé `MournbladeCYD2Utility.calculateItemValueSC(prixpo, prixca, prixsc)` - méthode statique pour calculer la valeur SC
- Créé `MournbladeCYD2Utility.getItemValueSC(item)` - méthode qui calcule la valeur totale d'un item (prix × quantité)
- Refactorisé les deux méthodes de l'Actor pour utiliser ces helpers
- Le helper Handlebars `calculateItemValueSC` utilise maintenant la méthode statique
**Avantages :**
- Une seule source de vérité pour les conversions monétaires
- Maintenance plus facile
- Réutilisable dans tout le codebase
- Cohérent avec le lore Mournblade (1 PO = 100 SC, 1 PA = 10 SC)
**Fichiers modifiés :**
- `modules/mournblade-cyd2-utility.js` - Ajout des méthodes statiques
- `modules/mournblade-cyd2-actor.js` - Refactorisation pour utiliser les helpers
---
### 20. ✅ Amélioration des helpers Handlebars
**Nouveaux helpers ajoutés :**
1. **`localizeAllegiance(value)`** - Localise les valeurs d'allégeance
- Mappe : tous→MNBL.all, chaos→MNBL.chaos, loi→MNBL.law, betes→MNBL.betes, elementaires→MNBL.elementaires, balance→MNBL.balance
- Utilise `game.i18n.localize()` pour la traduction
2. **`joinPredilections(predilections)`** - Joint les prédilections avec des virgules
- Filtre les prédilections acquises et non utilisées
- Retourne une chaîne vide si aucune prédilection applicable
- Évite la virgule finale superflue
**Fichiers modifiés :**
- `modules/mournblade-cyd2-utility.js` - Ajout des helpers
- `templates/actor-sheet.hbs` - Utilisation de `joinPredilections` et `localizeAllegiance`
- `templates/creature-sheet.hbs` - Utilisation de `joinPredilections`
---
## Auteurs
Corrections réalisées par : Mistral Vibe (via Vibe CLI)
Date : 2026-06-07
---
### 14. ✅ Correction de l'affichage des valeurs d'Allégeance
**Problème :**
Les valeurs d'allégeance étaient affichées avec des noms trop longs ou non capitalisés.
**Solution :**
Ajout de nouvelles clés de localisation et mise à jour de la configuration des options d'allégeance.
**Clés i18n ajoutées dans `lang/fr.json` :**
```json
{
"betes": "Bêtes",
"elementaires": "Élémentaires"
}
```
**Configuration mise à jour dans `mournblade-cyd2-config.js` :**
```javascript
allegeanceOptions: {
tous: localizeOrFallback("MNBL.all", "Tous"),
chaos: localizeOrFallback("MNBL.chaos", "Chaos"),
loi: localizeOrFallback("MNBL.law", "Loi"),
balance: localizeOrFallback("MNBL.balance", "Balance"),
betes: localizeOrFallback("MNBL.betes", "Bêtes"),
elementaires: localizeOrFallback("MNBL.elementaires", "Élémentaires")
}
```
**Résultat :**
Les allégeances sont maintenant affichées ainsi :
- "Tous", "Chaos", "Loi", "Balance", "Bêtes", "Élémentaires"
**Fichiers modifiés :**
- `lang/fr.json` - Ajout des clés MNBL.betes et MNBL.elementaires
- `modules/mournblade-cyd2-config.js` - Mise à jour des allegeanceOptions
---
### 15. ✅ Correction orthographique "scéance" → "séance"
**Problème :**
Faute d'orthographe dans l'option d'utilisation des Talents : "Une fois par scéance" au lieu de "Une fois par séance".
**Solution :**
Correction directe dans la configuration des options.
**Modification dans `modules/mournblade-cyd2-config.js` :**
```javascript
// Avant
{ key: "sceance", label: "Une fois par scéance" },
// Après
{ key: "sceance", label: "Une fois par séance" },
```
**Fichier modifié :**
- `modules/mournblade-cyd2-config.js` (ligne 214)
---
### 16. ✅ Activation du calcul automatique des totaux d'argent et d'équipement
**Problème :**
Dans l'onglet Équipement des fiches de personnage, les lignes "Argent Total" et "Valeur Total Équipement" affichaient des valeurs vides ou non mises à jour. Ajouter un équipement avec une valeur en pièces ne changeait rien nulle part.
**Cause :**
Les méthodes `computeRichesse()` et `computeValeurEquipement()` existaient déjà dans `mournblade-cyd2-actor.js`, mais elles n'étaient pas appelées dans le contexte de la feuille.
**Solution :**
Ajout des appels à ces méthodes dans `_prepareContext()` de la feuille personnage.
**Fonctionnement :**
- **Argent Total** : Somme de toutes les monnaies (type "monnaie") converties en SC
- **Valeur Total Équipement** : Somme de tous les équipements, armes et protections converties en SC
- Les deux utilisent `computeMonnaieDetails()` pour convertir une valeur SC en {po, pa, sc, valueSC}
- **Conversions :** 1 PO = 100 SC, 1 PA = 10 SC (selon le lore : 1 PO = 10 SA, 1 SA = 10 PB)
**Code ajouté dans `mournblade-cyd2-personnage-sheet.mjs` :**
```javascript
context.richesse = actor.computeRichesse?.() ?? { po: 0, pa: 0, sc: 0, valueSC: 0 };
context.valeurEquipement = actor.computeValeurEquipement?.() ?? { po: 0, pa: 0, sc: 0, valueSC: 0 };
```
**Fichier modifié :**
- `modules/applications/sheets/mournblade-cyd2-personnage-sheet.mjs`
**Réponse à la question :**
- Les totaux sont des **additions** (pas des soustractions)
- Ils apparaissent dans l'onglet Équipement et sont maintenant mis à jour automatiquement
---
### 17. ✅ Correction des taux de conversion monétaire selon le lore
**Problème :**
Les taux de conversion monétaire dans le système ne correspondaient pas au lore des Jeunes Royaumes.
**Ancienne conversion (incorrecte) :**
- 1 PO = 400 SC
- 1 PA = 20 SC
- Donc : 1 PO = 20 PA
**Nouvelle conversion (selon le lore) :**
- 1 SA (Sou d'Argent) = 10 PB (Pièces de Bronze)
- 1 PO (Pièce d'Or) = 10 SA = 100 PB
- Donc : 1 PA/CA = 10 SC, 1 PO = 100 SC
**Correspondance code ↔ lore :**
- SC (Sous de Cuivre dans le code) = PB (Pièces de Bronze dans le lore)
- PA/CA (Pièces d'Argent dans le code) = SA (Sous d'Argent dans le lore)
- PO (Pièces d'Or) = PO (Pièces d'Or)
**Fichiers modifiés :**
- `modules/mournblade-cyd2-utility.js` : Helper `calculateItemValueSC` et méthode `computeMonnaieDetails`
- `modules/mournblade-cyd2-actor.js` : Méthode `computeValeurEquipement`
**Source :**
```
LA MONNAIE DANS LES JEUNES ROYAUMES
Le sou d'argent (SA), est la monnaie la plus commune...
Le bronze est une piécette de très petite valeur.
On échange 10 bronzes contre un sou d'argent, et 10 sous d'argent pour un or.
```
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

+30
View File
@@ -0,0 +1,30 @@
const gulp = require('gulp');
const less = require('gulp-less');
const sourcemaps = require('gulp-sourcemaps');
const paths = {
styles: {
src: 'less/**/*.less',
dest: 'styles/'
}
};
function styles() {
return gulp.src('less/mournblade-cyd2.less')
.pipe(sourcemaps.init())
.pipe(less())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(paths.styles.dest));
}
function watchFiles() {
gulp.watch(paths.styles.src, styles);
}
const build = gulp.series(styles);
const watch = gulp.series(build, watchFiles);
exports.styles = styles;
exports.build = build;
exports.watch = watch;
exports.default = build;
+108 -1
View File
@@ -20,7 +20,26 @@
"runeeffect": "Effet de Rune", "runeeffect": "Effet de Rune",
"tendance": "Tendance", "tendance": "Tendance",
"traitchaotique": "Trait Chaotique", "traitchaotique": "Trait Chaotique",
"traitespece": "Trait d'Espèce" "traitespece": "Trait d'Espèce",
"traitdemoniaque": "Trait Démoniaque",
"pouvoirselementaire": "Pouvoir Élémentaire",
"capaciteautomata": "Capacité d'Automata"
}
},
"SORCELLERIE": {
"tab": "Sorcellerie",
"runes": "Runes",
"creaturesinvoquees": "Créatures invoquées",
"demonslies": "Démons liés",
"enchantements": "Enchantements / Automata",
"invocationsencours": "Invocations en cours",
"coutPouvoirInvocations": "Coût en Pouvoir des invocations"
},
"SHEETS": {
"Item": {
"traitdemoniaque": "Trait Démoniaque",
"pouvoirselementaire": "Pouvoir Élémentaire",
"capaciteautomata": "Capacité d'Automata"
} }
}, },
"MOURNBLADE": { "MOURNBLADE": {
@@ -28,5 +47,93 @@
"editTrait": "Modifier le trait", "editTrait": "Modifier le trait",
"deleteTrait": "Supprimer le trait" "deleteTrait": "Supprimer le trait"
} }
},
"MNBL": {
"all": "Tous",
"allegiance": "Allégeance",
"balance": "Balance",
"beastslords": "Seigneurs des Bêtes",
"betes": "Bêtes",
"chaos": "Chaos",
"difficulty": "Difficulté",
"duration": "Durée",
"details": "Détails",
"description": "Description",
"demon": "Démon",
"automata": "Automata",
"elementaires": "Élémentaires",
"elementslords": "Seigneurs des Éléments",
"equipment": "Equipement",
"examples": "Exemples",
"exercisedskills": "Compétences exercées",
"highlanguage": "Haut-Parler",
"initiateTalents": "Talents Initié",
"law": "Loi",
"mode": "Mode",
"mainattribute": "Attribut principal",
"none": "Aucun",
"aguerriTalents": "Talents Aguerri",
"prerequisitesAguerri": "Prérequis Aguerri",
"prerequisitesMaitre": "Prérequis Maître",
"prerequisites": "Prérequis",
"maitreTalents": "Talents Maître",
"pronounced": "Prononcée",
"pronouncedrune": "Rune prononcée",
"pronouncerune": "Prononcer",
"rune": "Rune",
"soulcost": "Coût en points de pouvoir",
"soulpoints": "Points de Pouvoir",
"traced": "Tracée",
"tracedrune": "Rune tracée",
"tracerune": "Tracer",
"initiative": "Initiative",
"initShort": "Init.",
"defense": "Défense",
"defShort": "Déf.",
"protection": "Protection",
"protShort": "Prot."
},
"EFFECT": {
"new": "Nouvel Effet",
"edit": "Éditer l'effet",
"delete": "Supprimer l'effet",
"deleteConfirm": "Supprimer l'effet",
"deleteConfirmText": "Êtes-vous sûr de vouloir supprimer cet effet ?",
"deleteError": "Erreur lors de la suppression de l'effet",
"create": "Créer un effet",
"createError": "Erreur lors de la création de l'effet",
"applyError": "Erreur lors de l'application de l'effet",
"applyItemError": "Erreur lors de l'application de l'effet sur l'item",
"selectActor": "Sélectionnez un acteur pour appliquer l'effet",
"toggleError": "Erreur lors de l'activation/désactivation de l'effet",
"name": "Nom de l'effet",
"icon": "Icône",
"description": "Description",
"changes": "Modifications",
"addChange": "Ajouter une modification",
"duration": "Durée",
"durationType": "Type de durée",
"durationValue": "Valeur",
"disabled": "Désactivé",
"transfer": "Transférer au token",
"noDuration": "Aucune (permanent)",
"rounds": "Rounds",
"turns": "Tours",
"seconds": "Secondes",
"combat": "Jusqu'à la fin du combat",
"scene": "Jusqu'à la fin de la scène",
"attribute": "Attribut",
"value": "Valeur",
"mode": "Mode",
"modeAdd": "Ajouter",
"modeMultiply": "Multiplier",
"modeOverride": "Remplacer",
"modeUpgrade": "Améliorer",
"modeDowngrade": "Dégrader",
"activeEffects": "Effets Actifs",
"noActiveEffects": "Aucun effet actif",
"noItemEffects": "Aucun effet sur cet item",
"effectSummary": "Résumé des modifications",
"toggleEffect": "Activer/Désactiver"
} }
} }
File diff suppressed because it is too large Load Diff
+315
View File
@@ -0,0 +1,315 @@
/* ==================== Item Sheet Styles ==================== */
/* Item header with image and name */
.fvtt-mournblade-cyd-2-0.item {
/* Background pour toute la fiche d'item */
background: url("../assets/ui/pc_sheet_bg.webp") repeat;
display: flex;
flex-direction: column;
height: 100%;
padding: 0;
margin: 0;
/* AppV2 - Remove window content padding */
.window-content {
padding: 0;
margin: 0;
}
/* AppV2 - Main section structure */
section {
background: url("../assets/ui/pc_sheet_bg.webp") repeat-y;
color: black;
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
padding: 0;
margin: 0;
}
/* AppV2 Item Sheets - Disabled inputs readability */
input:disabled,
select:disabled {
color: #000000;
opacity: 0.8;
background-color: rgba(255, 255, 255, 0.5);
}
/* Inputs and selects styling */
input[type="text"],
input[type="number"],
select {
color: #000000;
background-color: rgba(255, 255, 255, 0.7);
border: 1px solid #999999;
margin: 0;
padding: 2px 4px;
font-family: "CentaurMT", serif;
font-size: 0.85rem;
}
textarea {
margin: 0;
padding: 2px 4px;
}
input[type="checkbox"] {
width: auto;
height: auto;
margin: 0 4px;
align-self: center;
}
.header {
flex: 0 0 auto;
border-bottom: 1px solid #999;
margin: 0;
}
.sheet-header {
display: flex;
flex-direction: row;
align-items: center;
gap: 1rem;
padding: 0.5rem;
background: url("../assets/ui/pc_sheet_bg.webp") repeat;
flex: 0 0 auto;
}
.item-sheet-img {
flex: 0 0 64px;
width: 64px;
height: 64px;
object-fit: cover;
border: 1px solid #999;
border-radius: 4px;
cursor: pointer;
&:hover {
box-shadow: 0 0 8px rgba(255, 102, 0, 0.8);
}
}
.header-fields {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.header-actions {
flex: 0 0 auto;
display: flex;
align-items: center;
gap: 0.5rem;
button {
background: rgba(0, 0, 0, 0.1);
border: 1px solid #999;
padding: 0.25rem 0.5rem;
cursor: pointer;
font-family: "CentaurMT", serif;
&:hover {
background: rgba(255, 102, 0, 0.2);
box-shadow: 0 0 5px rgba(255, 102, 0, 0.5);
}
}
.chat-card-button {
background: linear-gradient(to bottom, #21374afc 5%, #152833ab 100%);
border: 2px ridge #846109;
color: #d4b5a8;
padding: 0.3rem 0.5rem;
transition: all 0.2s ease;
i {
font-size: 0.9rem;
}
&:hover {
background: linear-gradient(to bottom, #800000 5%, #3e0101 100%);
color: #ffffff;
box-shadow: 0 0 8px rgba(128, 0, 0, 0.6);
}
&:active {
position: relative;
top: 1px;
}
}
}
.sheet-header h1.charname {
height: 50px;
padding: 0;
margin: 5px 0;
border-bottom: 0;
font-weight: bold;
font-size: 2rem;
font-family: "CentaurMT";
}
.sheet-header h1.charname input {
width: 100%;
height: 100%;
margin: 0;
font-weight: bold;
font-family: "CentaurMT";
font-size: 2rem;
text-align: left;
border: 0 none;
&:focus {
outline: 1px solid #ff6600;
}
}
/* Tabs - Modern style matching actor sheets */
nav.tabs {
display: flex;
border-bottom: 1px solid #7a7971;
margin: 0;
padding: 4px;
background-color: #1c1c1c;
flex: 0 0 auto;
}
nav.tabs a.item {
padding: 6px 12px;
color: #d4af37;
text-decoration: none;
border: 1px solid transparent;
border-radius: 4px 4px 0 0;
margin-right: 4px;
transition: all 0.2s;
i {
display: none; // Hide icons to match actor sheets
}
&:hover {
background-color: rgba(212, 175, 55, 0.1);
}
&.active {
background-color: #2a2a2a;
border-bottom-color: #d4af37;
color: #f5f5f5;
}
}
/* Tab content */
.tab {
display: none;
padding: 4px 8px;
overflow-y: auto;
flex: 1 1 auto;
&.active {
display: block;
}
}
/* Item list in details tab */
.item-list {
list-style: none;
margin: 0;
padding: 0;
li.item {
display: flex;
align-items: center;
margin-bottom: 2px;
padding: 2px 4px;
min-height: 24px;
border-bottom: none;
&.flexrow {
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
}
}
}
/* Labels */
.generic-label {
display: inline-block;
white-space: nowrap;
font-weight: 700;
color: #2a2515;
font-size: 0.9rem;
font-family: "CentaurMT", serif;
margin: 0;
padding: 0 4px 0 0;
}
/* Field labels */
.item-field-label-short {
flex: 0 0 60px;
max-width: 60px;
}
.item-field-label-medium {
flex: 0 0 100px;
max-width: 100px;
}
.item-field-label-long {
flex: 1;
min-width: 150px;
}
.item-field-label-long1 {
flex: 1;
min-width: 200px;
}
.item-field-label-long2 {
flex: 1;
min-width: 250px;
max-width: 250px;
}
.item-field-label-long3 {
flex: 1;
min-width: 350px;
max-width: 350px;
}
.numeric-input {
text-align: center;
width: 60px;
}
/* Editor fields */
.editor {
height: 300px;
border: 1px solid #999;
background: rgba(255, 255, 255, 0.9);
.editor-content {
height: 100%;
padding: 0.5rem;
color: #000;
}
}
/* Section headings in item sheet tabs */
.tab .item-list h3 {
font-family: "CentaurMT", "Palatino Linotype", serif;
font-size: 0.85rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
color: #f0dfc0 !important;
background: rgba(20, 10, 0, 0.65);
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
margin: 0.25rem 0;
padding: 0.2rem 0.4rem;
border-radius: 3px;
flex: 1;
}
}
+6
View File
@@ -0,0 +1,6 @@
// Main LESS file for Mournblade CYD 2.0 system
// Importing base styles and component-specific styles
@import "simple-converted";
@import "item-styles";
@import "actor-styles";
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,109 @@
import { MournbladeCYD2Utility } from "../mournblade-cyd2-utility.js";
/**
* Dialogue de jet de dé pour MournbladeCYD2 - Version DialogV2
*/
export class MournbladeCYD2RollDialog {
/**
* Create and display the roll dialog
* @param {MournbladeCYD2Actor} actor
* @param {Object} rollData
*/
static async create(actor, rollData) {
const context = {
...rollData,
difficulte: String(rollData.difficulte || 0),
img: actor.img,
name: actor.name,
config: game.system.mournbladecyd2.config,
attributs: game.system.mournbladecyd2.config.attributs,
};
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-mournblade-cyd-2-0/templates/roll-dialog-generic.hbs",
context
);
return foundry.applications.api.DialogV2.wait({
window: { title: "Test de Capacité", icon: "fa-solid fa-dice-d20" },
classes: ["mournblade-cyd2-roll-dialog"],
position: { width: 470 },
modal: false,
content,
buttons: [
{
action: "rolld10",
label: "Lancer 1d10",
icon: "fa-solid fa-dice-d10",
default: true,
callback: (event, button, dialog) => {
MournbladeCYD2RollDialog._updateRollDataFromForm(rollData, button.form, actor);
rollData.mainDice = "d10";
MournbladeCYD2Utility.rollMournbladeCYD2(rollData);
}
},
{
action: "rolld20",
label: "Lancer 1d20",
icon: "fa-solid fa-dice-d20",
callback: (event, button, dialog) => {
MournbladeCYD2RollDialog._updateRollDataFromForm(rollData, button.form, actor);
rollData.mainDice = "d20";
MournbladeCYD2Utility.rollMournbladeCYD2(rollData);
}
},
{
action: "cancel",
label: "Annuler",
icon: "fa-solid fa-times"
}
]
});
}
/* -------------------------------------------- */
static _updateRollDataFromForm(rollData, form, actor) {
const el = form.elements;
const getVal = (name) => el[name]?.value;
const getChecked = (name) => {
const e = form.querySelector(`#${name}`);
return e ? e.checked : false;
};
if (el.modificateur) rollData.modificateur = Number(getVal("modificateur"));
if (el["bonus-malus-context"]) rollData.bonusMalusContext = Number(getVal("bonus-malus-context"));
if (el.difficulte) rollData.difficulte = Number(getVal("difficulte"));
if (el.attrKey) rollData.attrKey = String(getVal("attrKey"));
if (el.attrKey2) rollData.attrKey2 = String(getVal("attrKey2"));
if (el["select-maitrise"]) rollData.maitriseId = String(getVal("select-maitrise"));
if (el["competence-talents"]) {
const sel = el["competence-talents"];
rollData.selectedTalents = Array.from(sel.selectedOptions).map(o => o.value);
}
if (el["taille-cible"]) rollData.tailleCible = String(getVal("taille-cible"));
if (el["tireur-deplacement"]) rollData.tireurDeplacement = String(getVal("tireur-deplacement"));
if (el["cible-couvert"]) rollData.cibleCouvert = String(getVal("cible-couvert"));
if (el["distance-tir"]) rollData.distanceTir = String(getVal("distance-tir"));
if (el["soutiens"]) rollData.soutiens = Number(getVal("soutiens"));
if (el["runemode"]) rollData.runemode = String(getVal("runemode"));
if (el["runeame"]) rollData.runeame = Number(getVal("runeame"));
rollData.defenseurAuSol = getChecked("defenseur-au-sol");
rollData.defenseurAveugle = getChecked("defenseur-aveugle");
rollData.defenseurDeDos = getChecked("defenseur-de-dos");
rollData.defenseurRestreint = getChecked("defenseur-restreint");
rollData.defenseurImmobilise = getChecked("defenseur-immobilise");
rollData.attaquantsMultiples = getChecked("attaquants-multiple");
rollData.ambidextre1 = getChecked("ambidextre-1");
rollData.ambidextre2 = getChecked("ambidextre-2");
rollData.feinte = getChecked("feinte");
rollData.attaqueCharge = getChecked("attaque-charge");
rollData.chargeCavalerie = getChecked("charge-cavalerie");
rollData.contenir = getChecked("contenir");
rollData.attaqueDesarme = getChecked("attaque-desarme");
rollData.cibleDeplace = getChecked("tireur-cible-deplace");
rollData.cibleCaC = getChecked("cible-cac");
rollData.cibleconsciente = getChecked("cibleconsciente");
}
}
+28
View File
@@ -0,0 +1,28 @@
/**
* Index des applications AppV2 pour Mournblade CYD 2.0
*/
// Fiches d'acteurs
export { default as MournbladeCYD2PersonnageSheet } from './mournblade-cyd2-personnage-sheet.mjs';
export { default as MournbladeCYD2CreatureSheet } from './mournblade-cyd2-creature-sheet.mjs';
// Fiches d'items
export { default as MournbladeCYD2ArmeSheet } from './mournblade-cyd2-arme-sheet.mjs';
export { default as MournbladeCYD2CompetenceSheet } from './mournblade-cyd2-competence-sheet.mjs';
export { default as MournbladeCYD2DonSheet } from './mournblade-cyd2-don-sheet.mjs';
export { default as MournbladeCYD2EquipementSheet } from './mournblade-cyd2-equipement-sheet.mjs';
export { default as MournbladeCYD2HistoriqueSheet } from './mournblade-cyd2-historique-sheet.mjs';
export { default as MournbladeCYD2MonnaieSheet } from './mournblade-cyd2-monnaie-sheet.mjs';
export { default as MournbladeCYD2PacteSheet } from './mournblade-cyd2-pacte-sheet.mjs';
export { default as MournbladeCYD2ProfilSheet } from './mournblade-cyd2-profil-sheet.mjs';
export { default as MournbladeCYD2ProtectionSheet } from './mournblade-cyd2-protection-sheet.mjs';
export { default as MournbladeCYD2RessourceSheet } from './mournblade-cyd2-ressource-sheet.mjs';
export { default as MournbladeCYD2RuneSheet } from './mournblade-cyd2-rune-sheet.mjs';
export { default as MournbladeCYD2RuneEffectSheet } from './mournblade-cyd2-runeeffect-sheet.mjs';
export { default as MournbladeCYD2TalentSheet } from './mournblade-cyd2-talent-sheet.mjs';
export { default as MournbladeCYD2TendanceSheet } from './mournblade-cyd2-tendance-sheet.mjs';
export { default as MournbladeCYD2TraitChaotiqueSheet } from './mournblade-cyd2-traitchaotique-sheet.mjs';
export { default as MournbladeCYD2TraitEspeceSheet } from './mournblade-cyd2-traitespece-sheet.mjs';
export { default as MournbladeCYD2TraitDemoniaqueSheet } from './mournblade-cyd2-traitdemoniaque-sheet.mjs';
export { default as MournbladeCYD2PouvoirElementaireSheet } from './mournblade-cyd2-pouvoirselementaire-sheet.mjs';
export { default as MournbladeCYD2CapaciteAutomataSheet } from './mournblade-cyd2-capaciteautomata-sheet.mjs';
@@ -0,0 +1,505 @@
const { HandlebarsApplicationMixin } = foundry.applications.api;
import { MournbladeCYD2Utility } from "../../mournblade-cyd2-utility.js";
export default class MournbladeCYD2ActorSheetV2 extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
static SHEET_MODES = { EDIT: 0, PLAY: 1 };
constructor(options = {}) {
super(options);
this.#dragDrop = this.#createDragDropHandlers();
this._sheetMode = this.constructor.SHEET_MODES.PLAY;
}
#dragDrop;
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-mournblade-cyd-2-0", "sheet", "actor"],
position: {
width: 750,
height: 820,
},
form: {
submitOnChange: true,
closeOnSubmit: false,
},
window: {
resizable: true,
},
tabs: [
{
navSelector: 'nav[data-group="primary"]',
contentSelector: "section.sheet-body",
initial: "stats",
},
],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
actions: {
editImage: MournbladeCYD2ActorSheetV2.#onEditImage,
toggleSheet: MournbladeCYD2ActorSheetV2.#onToggleSheet,
editItem: MournbladeCYD2ActorSheetV2.#onEditItem,
deleteItem: MournbladeCYD2ActorSheetV2.#onDeleteItem,
createItem: MournbladeCYD2ActorSheetV2.#onCreateItem,
equipItem: MournbladeCYD2ActorSheetV2.#onEquipItem,
modifyQuantity: MournbladeCYD2ActorSheetV2.#onModifyQuantity,
modifyAdversite: MournbladeCYD2ActorSheetV2.#onModifyAdversite,
modifySante: MournbladeCYD2ActorSheetV2.#onModifySante,
modifyAme: MournbladeCYD2ActorSheetV2.#onModifyAme,
rollAttribut: MournbladeCYD2ActorSheetV2.#onRollAttribut,
rollCompetence: MournbladeCYD2ActorSheetV2.#onRollCompetence,
rollRune: MournbladeCYD2ActorSheetV2.#onRollRune,
rollArmeOffensif: MournbladeCYD2ActorSheetV2.#onRollArmeOffensif,
rollArmeSpecial: MournbladeCYD2ActorSheetV2.#onRollArmeSpecial,
rollArmeDegats: MournbladeCYD2ActorSheetV2.#onRollArmeDegats,
rollAssommer: MournbladeCYD2ActorSheetV2.#onRollAssommer,
rollCoupBas: MournbladeCYD2ActorSheetV2.#onRollCoupBas,
rollImmobiliser: MournbladeCYD2ActorSheetV2.#onRollImmobiliser,
rollRepousser: MournbladeCYD2ActorSheetV2.#onRollRepousser,
rollDesengager: MournbladeCYD2ActorSheetV2.#onRollDesengager,
rollInitiative: MournbladeCYD2ActorSheetV2.#onRollInitiative,
rollFuir: MournbladeCYD2ActorSheetV2.#onRollFuir,
// Actions pour les ActiveEffects
createEffect: MournbladeCYD2ActorSheetV2.#onCreateEffect,
editEffect: MournbladeCYD2ActorSheetV2.#onEditEffect,
deleteEffect: MournbladeCYD2ActorSheetV2.#onDeleteEffect,
toggleEffect: MournbladeCYD2ActorSheetV2.#onToggleEffect,
applyEffect: MournbladeCYD2ActorSheetV2.#onApplyEffect,
},
};
get isPlayMode() {
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY;
return this._sheetMode === this.constructor.SHEET_MODES.PLAY;
}
get isEditMode() {
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY;
return this._sheetMode === this.constructor.SHEET_MODES.EDIT;
}
tabGroups = { primary: "stats" };
/**
* Intercept form data before ActorSheetV2 validates it, to sanitize
* empty numeric fields that would otherwise fail DataModel validation.
* Mirrors the same logic in MournbladeCYD2Actor._preUpdate.
* @override
*/
_prepareSubmitData(event, form, formData) {
const fd = formData ?? new foundry.applications.ux.FormDataExtended(form);
const data = {};
for (const [k, v] of fd) {
foundry.utils.setProperty(data, k, v);
}
this._sanitizeNumericData(data);
this.document.validate(data, { partial: true });
return data;
}
/**
* Walk submitted data and convert empty/invalid values in NumberField
* paths to 0 so schema validation passes.
* @param {object} data
*/
_sanitizeNumericData(data) {
if (!data?.system || typeof data.system !== "object") return;
const fields = this.document.system.schema.fields;
for (const [schemaPath, schemaField] of Object.entries(fields)) {
if (!(schemaField instanceof foundry.data.fields.SchemaField)) continue;
const branch = data.system[schemaPath];
if (!branch || typeof branch !== "object") continue;
for (const [key, value] of Object.entries(branch)) {
if (schemaField.fields[key] instanceof foundry.data.fields.NumberField) {
if (value === "" || value === null || value === undefined || isNaN(value)) {
data.system[schemaPath][key] = 0;
} else {
data.system[schemaPath][key] = Number(value);
}
}
}
}
}
/** @override */
async _prepareContext() {
const actor = this.document;
return {
actor,
system: actor.system,
source: actor.toObject(),
fields: actor.schema.fields,
systemFields: actor.system.schema.fields,
isEditable: this.isEditable,
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isGM: game.user.isGM,
config: game.system.mournbladecyd2.config,
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(
actor.system.biodata?.description || "", { async: true }
),
enrichedHabitat: await foundry.applications.ux.TextEditor.implementation.enrichHTML(
actor.system.biodata?.habitat || "", { async: true }
),
};
}
/** @override */
_onRender(context, options) {
super._onRender(context, options);
this.#dragDrop.forEach((d) => d.bind(this.element));
this.element.querySelectorAll('.edit-item-data').forEach(element => {
element.addEventListener('change', async (event) => {
const target = event.currentTarget;
const itemElement = target.closest('[data-item-id]');
if (!itemElement) return;
const itemId = itemElement.dataset.itemId;
const itemField = target.dataset.itemField;
const dataType = target.dataset.dtype;
const value = dataType === "Number" ? Number(target.value) : target.value;
const item = this.document.items.get(itemId);
if (item) await item.update({ [`system.${itemField}`]: value });
});
});
// Tab navigation
const nav = this.element.querySelector('nav.tabs[data-group]');
if (nav) {
const group = nav.dataset.group;
const activeTab = this.tabGroups[group] || "stats";
nav.querySelectorAll('[data-tab]').forEach(link => {
const tab = link.dataset.tab;
link.classList.toggle('active', tab === activeTab);
link.addEventListener('click', (event) => {
event.preventDefault();
this.tabGroups[group] = tab;
this.render();
});
});
this.element.querySelectorAll(`[data-group="${group}"][data-tab]`).forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab);
});
}
}
#createDragDropHandlers() {
return this.options.dragDrop.map(d => {
d.permissions = {
dragstart: this._canDragStart.bind(this),
drop: this._canDragDrop.bind(this)
};
d.callbacks = {
dragstart: this._onDragStart.bind(this),
dragover: this._onDragOver.bind(this),
drop: this._onDrop.bind(this)
};
return new foundry.applications.ux.DragDrop.implementation(d);
});
}
_canDragStart(selector) { return this.isEditable; }
_canDragDrop(selector) { return this.isEditable; }
_onDragStart(event) {
const li = event.currentTarget.closest("[data-item-id]");
if (!li) return;
const item = this.document.items.get(li.dataset.itemId);
if (!item) return;
event.dataTransfer.setData("text/plain", JSON.stringify(item.toDragData()));
}
_onDragOver(event) { event.preventDefault(); }
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
if (data?.type === "Item") return this._onDropItem(event, data);
if (data?.type === "Actor") return this._onDropActor(event, data);
}
async _onDropItem(event, data) {
if (!this.document.isOwner) return;
const item = await Item.fromDropData(data);
if (!item) return;
if (this.document.uuid === item.parent?.uuid) return;
await this.document.createEmbeddedDocuments("Item", [item.toObject()]);
this.render();
}
async _onDropActor(event, data) {}
// #region Actions
static async #onEditImage(event) {
const fp = new FilePicker({
type: "image",
current: this.document.img,
callback: (path) => this.document.update({ img: path })
});
fp.browse();
}
static #onToggleSheet(event) {
this._sheetMode = this.isEditMode
? this.constructor.SHEET_MODES.PLAY
: this.constructor.SHEET_MODES.EDIT;
this.render();
}
static async #onEditItem(event, target) {
const li = target.closest(".item");
const item = this.document.items.get(li?.dataset.itemId);
item?.sheet.render(true);
}
static async #onDeleteItem(event, target) {
const li = target.closest(".item");
await MournbladeCYD2Utility.confirmDelete(this, li);
}
static async #onCreateItem(event, target) {
const itemType = target.dataset.type;
await this.document.createEmbeddedDocuments("Item", [{ name: `Nouveau ${itemType}`, type: itemType }], { renderSheet: true });
}
static async #onEquipItem(event, target) {
const li = target.closest(".item");
const item = this.document.items.get(li?.dataset.itemId);
if (item) await item.update({ "system.equipped": !item.system.equipped });
}
static async #onModifyQuantity(event, target) {
const li = target.closest('[data-item-id]');
const item = this.document.items.get(li?.dataset.itemId);
const value = Number.parseInt(target.dataset.quantiteValue);
if (item) {
const newQty = Math.max(0, (item.system.quantite || 0) + value);
await item.update({ "system.quantite": newQty });
}
}
static async #onModifyAdversite(event, target) {
const li = target.closest('[data-adversite]');
const adversiteKey = li?.dataset.adversite;
if (!adversiteKey) return;
const value = Number.parseInt(target.dataset.adversiteValue);
const current = this.document.system.adversite[adversiteKey] || 0;
await this.document.update({ [`system.adversite.${adversiteKey}`]: Math.max(0, current + value) });
}
static async #onModifySante(event, target) {
const type = target.dataset.type;
const value = Number.parseInt(target.dataset.value);
const current = this.document.system.sante[type] || 0;
await this.document.update({ [`system.sante.${type}`]: Math.max(0, current + value) });
}
static async #onModifyAme(event, target) {
const value = Number.parseInt(target.dataset.value);
const current = this.document.system.ame.nbame || 0;
await this.document.update({ "system.ame.nbame": Math.max(0, current + value) });
}
static async #onRollAttribut(event, target) {
await this.document.rollAttribut(target.dataset.attrKey);
}
static async #onRollCompetence(event, target) {
const li = target.closest('[data-item-id]');
await this.document.rollCompetence(target.dataset.attrKey, li?.dataset.itemId);
}
static async #onRollRune(event, target) {
const li = target.closest('[data-item-id]');
await this.document.rollRune(li?.dataset.itemId);
}
static async #onRollArmeOffensif(event, target) {
const li = target.closest('[data-item-id]');
await this.document.rollArmeOffensif(target.dataset.armeId ?? li?.dataset.itemId);
}
static async #onRollArmeSpecial(event, target) {
const li = target.closest('[data-item-id]');
await this.document.rollArmeSpecial(target.dataset.armeId ?? li?.dataset.itemId);
}
static async #onRollArmeDegats(event, target) {
const li = target.closest('[data-item-id]');
await this.document.rollArmeDegats(target.dataset.armeId ?? li?.dataset.itemId);
}
static async #onRollAssommer(event, target) {
await this.document.rollAssommer();
}
static async #onRollCoupBas(event, target) {
await this.document.rollCoupBas();
}
static async #onRollImmobiliser(event, target) {
await this.document.rollImmobiliser();
}
static async #onRollRepousser(event, target) {
await this.document.rollRepousser();
}
static async #onRollDesengager(event, target) {
await this.document.rollDesengager();
}
static async #onRollInitiative(event, target) {
await this.document.rollAttribut("adr", true);
}
static async #onRollFuir(event, target) {
await this.document.rollFuir();
}
// #region ActiveEffects Management
/**
* Crée un nouvel effet actif
* @param {Event} event - Événement
* @param {HTMLElement} target - Éléments cible
* @private
*/
static async #onCreateEffect(event, target) {
event.preventDefault();
if (!this.isEditable || !this.document) return;
try {
// Créer les données par défaut pour un nouvel effet
const defaultEffectData = {
name: game.i18n.localize("EFFECT.new") || "Nouvel Effet",
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/capacite.webp",
description: "",
changes: [],
disabled: false,
duration: {},
origin: this.document.uuid,
tint: "",
transfer: true,
flags: {}
};
// Créer directement l'effet actif sur l'acteur
const [effect] = await this.document.createEmbeddedDocuments("ActiveEffect", [defaultEffectData]);
if (effect) {
// Ouvrir la feuille d'édition de l'effet
effect.sheet.render(true);
}
} catch (error) {
console.error("MournbladeCYD2 | Failed to create effect:", error);
ui.notifications.error(
game.i18n.localize("EFFECT.createError") ||
"Erreur lors de la création de l'effet"
);
}
}
/**
* Édite un effet actif existant
* @param {Event} event - Événement
* @param {HTMLElement} target - Éléments cible
* @private
*/
static async #onEditEffect(event, target) {
event.preventDefault();
if (!this.isEditable || !this.document) return;
const effectId = target?.dataset?.effectId;
if (!effectId) return;
const effect = this.document.effects.get(effectId);
if (effect) {
// Ouvrir la sheet de l'effet pour édition
effect.sheet.render(true);
}
}
/**
* Supprime un effet actif
* @param {Event} event - Événement
* @param {HTMLElement} target - Éléments cible
* @private
*/
static async #onDeleteEffect(event, target) {
event.preventDefault();
if (!this.isEditable || !this.document) return;
const effectId = target?.dataset?.effectId;
if (!effectId) return;
const effect = this.document.effects.get(effectId);
if (effect) {
const effectName = effect.name;
const confirmed = await foundry.applications.api.DialogV2.confirm({
title: game.i18n.localize("EFFECT.deleteConfirm") || "Supprimer l'effet",
content: game.i18n.localize("EFFECT.deleteConfirmText", {name: effectName}) ||
`Êtes-vous sûr de vouloir supprimer l'effet "${effectName}" ?`
});
if (confirmed) {
try {
await this.document.deleteEmbeddedDocuments("ActiveEffect", [effectId]);
} catch (error) {
console.error("MournbladeCYD2 | Failed to delete effect:", error);
ui.notifications.error(
game.i18n.localize("EFFECT.deleteError") ||
"Erreur lors de la suppression de l'effet"
);
}
}
}
}
/**
* Toggle l'état actif/désactivé d'un effet
* @param {Event} event - Événement
* @param {HTMLElement} target - Éléments cible
* @private
*/
static async #onToggleEffect(event, target) {
event.preventDefault();
if (!this.isEditable || !this.document) return;
const effectId = target?.dataset?.effectId;
if (!effectId) return;
const effect = this.document.effects.get(effectId);
if (effect) {
try {
await effect.update({ disabled: !effect.disabled });
} catch (error) {
console.error("MournbladeCYD2 | Failed to toggle effect:", error);
ui.notifications.error(
game.i18n.localize("EFFECT.toggleError") ||
"Erreur lors du basculement de l'effet"
);
}
}
}
/**
* Applique un effet à partir d'un item
* @param {Event} event - Événement
* @param {HTMLElement} target - Éléments cible
* @private
*/
static async #onApplyEffect(event, target) {
event.preventDefault();
const effectId = target?.dataset?.effectId;
if (!effectId) return;
const effect = this.document.effects.get(effectId);
if (effect) {
await effect.apply();
}
}
// #endregion
}
@@ -0,0 +1,335 @@
const { HandlebarsApplicationMixin } = foundry.applications.api;
import { MournbladeCYD2Utility } from "../../mournblade-cyd2-utility.js";
export default class MournbladeCYD2ItemSheetV2 extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options);
this.#dragDrop = this.#createDragDropHandlers();
}
#dragDrop;
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-mournblade-cyd-2-0", "item"],
position: {
width: 620,
height: 600,
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
tabs: [
{
navSelector: 'nav[data-group="primary"]',
contentSelector: "section.sheet-body",
initial: "description",
},
],
dragDrop: [{ dragSelector: "[data-drag]", dropSelector: null }],
actions: {
editImage: MournbladeCYD2ItemSheetV2.#onEditImage,
postItem: MournbladeCYD2ItemSheetV2.#onPostItem,
addPredilection: MournbladeCYD2ItemSheetV2.#onAddPredilection,
deletePredilection: MournbladeCYD2ItemSheetV2.#onDeletePredilection,
addAutomation: MournbladeCYD2ItemSheetV2.#onAddAutomation,
deleteAutomation: MournbladeCYD2ItemSheetV2.#onDeleteAutomation,
// Actions pour les ActiveEffects
createEffect: MournbladeCYD2ItemSheetV2.#onCreateEffect,
editEffect: MournbladeCYD2ItemSheetV2.#onEditEffect,
deleteEffect: MournbladeCYD2ItemSheetV2.#onDeleteEffect,
toggleEffect: MournbladeCYD2ItemSheetV2.#onToggleEffect,
applyEffect: MournbladeCYD2ItemSheetV2.#onApplyEffect,
},
};
tabGroups = { primary: "description" };
/** @override */
async _prepareContext() {
return {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
source: this.document.toObject(),
config: game.system.mournbladecyd2.config,
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(
this.document.system.description || "", { async: true }
),
isEditMode: this.isEditMode,
isEditable: this.isEditable,
isGM: game.user.isGM,
};
}
/** @override */
_onRender(context, options) {
super._onRender(context, options);
this.#dragDrop.forEach((d) => d.bind(this.element));
// Tab navigation
const nav = this.element.querySelector('nav.tabs[data-group]');
if (nav) {
const group = nav.dataset.group;
const activeTab = this.tabGroups[group] || "description";
nav.querySelectorAll('[data-tab]').forEach(link => {
const tab = link.dataset.tab;
link.classList.toggle('active', tab === activeTab);
link.addEventListener('click', (event) => {
event.preventDefault();
this.tabGroups[group] = tab;
this.render();
});
});
this.element.querySelectorAll(`[data-group="${group}"][data-tab]`).forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab);
});
}
}
#createDragDropHandlers() {
return this.options.dragDrop.map(d => {
d.permissions = { dragstart: () => this.isEditable };
d.callbacks = { dragstart: this._onDragStart.bind(this) };
return new foundry.applications.ux.DragDrop.implementation(d);
});
}
_onDragStart(event) {}
// #region Actions
static async #onEditImage(event) {
const fp = new FilePicker({
type: "image",
current: this.document.img,
callback: (path) => this.document.update({ img: path })
});
fp.browse();
}
static async #onPostItem(event) {
event.preventDefault();
const item = this.document;
const chatData = foundry.utils.duplicate(item);
if (item.actor) {
chatData.actor = { id: item.actor.id };
}
if (chatData.img?.includes("/blank.png")) {
chatData.img = null;
}
chatData.jsondata = JSON.stringify({
compendium: "postedItem",
payload: chatData,
});
const html = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-mournblade-cyd-2-0/templates/post-item.hbs",
chatData
);
ChatMessage.create({ user: game.user.id, content: html });
}
static async #onAddPredilection(event) {
const preds = foundry.utils.duplicate(this.document.system.predilections || []);
preds.push({ id: foundry.utils.randomID(), name: "Nouvelle prédilection", description: "", acquise: false, maitrise: false, used: false });
await this.document.update({ "system.predilections": preds });
}
static async #onDeletePredilection(event, target) {
const idx = Number(target.dataset.predilectionIndex);
const preds = foundry.utils.duplicate(this.document.system.predilections || []);
preds.splice(idx, 1);
await this.document.update({ "system.predilections": preds });
}
/* -------------------------------------------- */
static async #onAddAutomation(event) {
const automations = foundry.utils.duplicate(this.document.system.automations || []);
automations.push({
id: foundry.utils.randomID(),
eventtype: "on-drop",
name: "",
bonusname: "vigueur",
bonus: 0,
competence: "",
minLevel: 0,
baCost: 0
});
await this.document.update({
"system.automations": automations,
"system.isautomated": true
});
}
/* -------------------------------------------- */
static async #onDeleteAutomation(event, target) {
const idx = Number(target.dataset.automationIndex);
const automations = foundry.utils.duplicate(this.document.system.automations || []);
automations.splice(idx, 1);
await this.document.update({ "system.automations": automations });
if (automations.length === 0) {
await this.document.update({ "system.isautomated": false });
}
}
// #region ActiveEffects Management
/**
* Crée un nouvel effet actif sur l'item
* @param {Event} event - Événement
* @param {HTMLElement} target - Éléments cible
* @private
*/
static async #onCreateEffect(event, target) {
event.preventDefault();
if (!this.isEditable || !this.document) return;
try {
// Créer les données par défaut pour un nouvel effet
const defaultEffectData = {
name: game.i18n.localize("EFFECT.new") || "Nouvel Effet",
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/capacite.webp",
description: "",
changes: [],
disabled: false,
duration: {},
origin: this.document.uuid,
tint: "",
transfer: false,
flags: {}
};
// Créer directement l'effet actif sur l'item
const [effect] = await this.document.createEmbeddedDocuments("ActiveEffect", [defaultEffectData]);
if (effect) {
// Ouvrir la feuille d'édition de l'effet
effect.sheet.render(true);
}
} catch (error) {
console.error("MournbladeCYD2 | Failed to create effect:", error);
ui.notifications.error(
game.i18n.localize("EFFECT.createError") ||
"Erreur lors de la création de l'effet"
);
}
}
/**
* Édite un effet actif existant sur l'item
* @param {Event} event - Événement
* @param {HTMLElement} target - Éléments cible
* @private
*/
static async #onEditEffect(event, target) {
event.preventDefault();
if (!this.isEditable || !this.document) return;
const effectId = target?.dataset?.effectId;
if (!effectId) return;
const effect = this.document.effects.get(effectId);
if (effect) {
// Ouvrir la sheet de l'effet pour édition
effect.sheet.render(true);
}
}
/**
* Supprime un effet actif de l'item
* @param {Event} event - Événement
* @param {HTMLElement} target - Éléments cible
* @private
*/
static async #onDeleteEffect(event, target) {
event.preventDefault();
if (!this.isEditable || !this.document) return;
const effectId = target?.dataset?.effectId;
if (!effectId) return;
const effect = this.document.effects.get(effectId);
if (effect) {
const effectName = effect.name;
const confirmed = await foundry.applications.api.DialogV2.confirm({
title: game.i18n.localize("EFFECT.deleteConfirm") || "Supprimer l'effet",
content: game.i18n.localize("EFFECT.deleteConfirmText", {name: effectName}) ||
`Êtes-vous sûr de vouloir supprimer l'effet "${effectName}" ?`
});
if (confirmed) {
try {
await this.document.deleteEmbeddedDocuments("ActiveEffect", [effectId]);
} catch (error) {
console.error("MournbladeCYD2 | Failed to delete effect:", error);
ui.notifications.error(
game.i18n.localize("EFFECT.deleteError") ||
"Erreur lors de la suppression de l'effet"
);
}
}
}
}
/**
* Toggle l'état actif/désactivé d'un effet sur l'item
* @param {Event} event - Événement
* @param {HTMLElement} target - Éléments cible
* @private
*/
static async #onToggleEffect(event, target) {
event.preventDefault();
if (!this.isEditable || !this.document) return;
const effectId = target?.dataset?.effectId;
if (!effectId) return;
const effect = this.document.effects.get(effectId);
if (effect) {
try {
await effect.update({ disabled: !effect.disabled });
} catch (error) {
console.error("MournbladeCYD2 | Failed to toggle effect:", error);
ui.notifications.error(
game.i18n.localize("EFFECT.toggleError") ||
"Erreur lors du basculement de l'effet"
);
}
}
}
/**
* Applique un effet à partir de l'item à un acteur sélectionné
* Cette méthode n'est pas directement utilisable sans acteur cible
* Elle est gardée pour compatibilité mais devrait être appelée avec un acteur
* @param {Event} event - Événement
* @param {HTMLElement} target - Éléments cible
* @private
*/
static async #onApplyEffect(event, target) {
event.preventDefault();
if (!this.isEditable) return;
const effectId = target?.dataset?.effectId;
if (!effectId || !this.document) return;
const effect = this.document.effects.get(effectId);
if (!effect) return;
// Pour appliquer un effet d'item, il faut sélectionner un acteur
// Cette méthode est placeholders - l'application devrait être gérée par drag-drop
// ou par une action spécifique qui demande à l'utilisateur de sélectionner un acteur
ui.notifications.warn(
game.i18n.localize("EFFECT.selectActor") ||
"Veuillez d'abord sélectionner un acteur pour appliquer cet effet."
);
}
// #endregion
}
@@ -0,0 +1,39 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2ArmeSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "arme"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.arme",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-arme-sheet.hbs",
},
};
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" }
};
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id;
v.cssClass = v.active ? "active" : "";
}
return tabs;
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
context.tabs = this.#getTabs();
return context;
}
}
@@ -0,0 +1,27 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2CapaciteAutomataSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "capaciteautomata"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.capaciteautomata",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-capaciteautomata-sheet.hbs",
},
};
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
return context;
}
}
@@ -0,0 +1,39 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2CompetenceSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "competence"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.competence",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-competence-sheet.hbs",
},
};
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" }
};
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id;
v.cssClass = v.active ? "active" : "";
}
return tabs;
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
context.tabs = this.#getTabs();
return context;
}
}
@@ -0,0 +1,54 @@
import MournbladeCYD2ActorSheetV2 from "./base-actor-sheet.mjs";
import { MournbladeCYD2Utility } from "../../mournblade-cyd2-utility.js";
export default class MournbladeCYD2CreatureSheet extends MournbladeCYD2ActorSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "creature"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Actor.creature",
},
};
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/creature-sheet.hbs",
},
};
/** @override */
tabGroups = { primary: "principal" };
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
const actor = this.document;
context.skills = actor.getSkills?.() ?? [];
context.combativiteList = MournbladeCYD2Utility.getCombativiteList(actor.system.sante?.nbcombativite || 0);
context.ameList = MournbladeCYD2Utility.getAmeList(actor.system.ame.nbame, actor.getAmeMax?.() ?? 0);
context.ameMaxList = MournbladeCYD2Utility.getAmeMaxList(actor.system.ame.nbame);
context.armes = foundry.utils.duplicate(actor.getWeapons?.() ?? []);
context.protections = foundry.utils.duplicate(actor.getArmors?.() ?? []);
context.runes = foundry.utils.duplicate(actor.getRunes?.() ?? []);
context.combat = actor.getCombatValues?.() ?? {};
context.equipements = foundry.utils.duplicate(actor.getEquipments?.() ?? []);
context.monnaies = foundry.utils.duplicate(actor.getMonnaies?.() ?? []);
context.talents = foundry.utils.duplicate(actor.getTalents?.() ?? []);
context.traitsChaotiques = foundry.utils.duplicate(actor.getTraitsChaotiques?.() ?? []);
context.traitsEspeces = foundry.utils.duplicate(actor.getTraitsEspeces?.() ?? []);
context.protectionTotal = actor.getProtectionTotal?.() ?? 0;
context.adversiteTotal = (actor.system.adversite?.bleue || 0) + (actor.system.adversite?.rouge || 0) + (actor.system.adversite?.noire || 0);
// Utiliser les valeurs manuelles si elles existent, sinon les valeurs calculées
context.initiative = actor.system.combat?.inittotal !== undefined ? actor.system.combat.inittotal : (context.combat?.initTotal ?? 0);
context.combat.defenseTotal = actor.system.combat?.defensetotal !== undefined ? actor.system.combat.defensetotal : context.combat.defenseTotal;
context.protectionTotal = actor.system.combat?.protectiontotal !== undefined ? actor.system.combat.protectiontotal : context.protectionTotal;
return context;
}
}
@@ -0,0 +1,29 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2DonSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "don"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.don",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-don-sheet.hbs",
},
};
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
context.owner = this.document.isOwner;
context.editable = this.isEditable;
return context;
}
}
@@ -0,0 +1,27 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2EquipementSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "equipement"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.equipement",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-equipement-sheet.hbs",
},
};
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
return context;
}
}
@@ -0,0 +1,27 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2HistoriqueSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "historique"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.historique",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-historique-sheet.hbs",
},
};
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
return context;
}
}
@@ -0,0 +1,27 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2MonnaieSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "monnaie"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.monnaie",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-monnaie-sheet.hbs",
},
};
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
return context;
}
}
@@ -0,0 +1,27 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2PacteSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "pacte"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.pacte",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-pacte-sheet.hbs",
},
};
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
return context;
}
}
@@ -0,0 +1,187 @@
import MournbladeCYD2ActorSheetV2 from "./base-actor-sheet.mjs";
import { MournbladeCYD2Utility } from "../../mournblade-cyd2-utility.js";
export default class MournbladeCYD2PersonnageSheet extends MournbladeCYD2ActorSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Actor.personnage",
},
actions: {
...super.DEFAULT_OPTIONS.actions,
removeLinkedActor: MournbladeCYD2PersonnageSheet.#onRemoveLinkedActor,
},
};
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/actor-sheet.hbs",
},
};
/** @override */
tabGroups = { primary: "principal" };
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
const actor = this.document;
context.combativiteList = MournbladeCYD2Utility.getCombativiteList(actor.system.sante.nbcombativite);
context.ameList = MournbladeCYD2Utility.getAmeList(actor.system.ame.nbame, actor.getAmeMax?.() ?? 0);
context.ameMaxList = MournbladeCYD2Utility.getAmeMaxList(actor.system.ame.nbame);
context.skills = actor.getSkills?.() ?? [];
context.armes = foundry.utils.duplicate(actor.getWeapons?.() ?? []);
context.protections = foundry.utils.duplicate(actor.getArmors?.() ?? []);
context.dons = foundry.utils.duplicate(actor.getDons?.() ?? []);
context.pactes = foundry.utils.duplicate(actor.getPactes?.() ?? []);
context.tendances = foundry.utils.duplicate(actor.getTendances?.() ?? []);
context.runes = foundry.utils.duplicate(actor.getRunes?.() ?? []);
context.traitsChaotiques = foundry.utils.duplicate(actor.getTraitsChaotiques?.() ?? []);
context.traitsEspeces = foundry.utils.duplicate(actor.getTraitsEspeces?.() ?? []);
context.profil = actor.getProfil?.() ?? null;
context.historiques = foundry.utils.duplicate(actor.getHistoriques?.() ?? []);
context.combat = actor.getCombatValues?.() ?? {};
context.equipements = foundry.utils.duplicate(actor.getEquipments?.() ?? []);
context.monnaies = foundry.utils.duplicate(actor.getMonnaies?.() ?? []);
context.runeEffects = foundry.utils.duplicate(actor.getRuneEffects?.() ?? []);
context.ressources = foundry.utils.duplicate(actor.getRessources?.() ?? []);
context.talents = foundry.utils.duplicate(actor.getTalents?.() ?? []);
context.protectionTotal = actor.getProtectionTotal?.() ?? 0;
context.adversiteTotal = (actor.system.adversite?.bleue || 0) + (actor.system.adversite?.rouge || 0) + (actor.system.adversite?.noire || 0);
context.richesse = actor.computeRichesse?.() ?? { po: 0, pa: 0, sc: 0, valueSC: 0 };
context.valeurEquipement = actor.computeValeurEquipement?.() ?? { po: 0, pa: 0, sc: 0, valueSC: 0 };
// Prepare sorcellerie linked actors for display
context.creaturesInvoqueesActors = await this._getLinkedActors(actor.system.sorcellerie.creaturesinvoquees);
context.demonsLiesActors = await this._getLinkedActors(actor.system.sorcellerie.demonslies);
context.enchantementsActors = await this._getLinkedActors(actor.system.sorcellerie.enchantements);
// Prepare enriched HTML for sorcellerie fields
context.enrichedInvocationsEnCours = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
actor.system.sorcellerie?.invocationsencours || "", { async: true }
);
return context;
}
/**
* Get actor objects from UUID references
* @param {string[]} uuids - Array of actor UUIDs
* @returns {Promise<Actor[]>} Array of actor documents
*/
async _getLinkedActors(uuids) {
if (!uuids || !Array.isArray(uuids) || uuids.length === 0) return [];
const actors = [];
for (const uuid of uuids) {
const actor = await fromUuid(uuid);
if (actor) {
actors.push(actor);
}
}
return actors;
}
/**
* Handle actor drop on the sheet
* @override
*/
async _onDropActor(event, data) {
if (!this.document.isOwner) return;
const droppedActor = await Actor.fromDropData(data);
if (!droppedActor) return;
// Only allow dropping Creature type actors
if (droppedActor.type !== 'creature') return;
// Determine which list to add to based on creature subtype
let fieldPath = '';
const subType = droppedActor.system.soustype || 'creature';
switch (subType) {
case 'demon':
fieldPath = 'system.sorcellerie.demonslies';
break;
case 'automata':
fieldPath = 'system.sorcellerie.enchantements';
break;
case 'creature':
default:
fieldPath = 'system.sorcellerie.creaturesinvoquees';
break;
}
// Add the actor UUID to the appropriate array
const currentValue = foundry.utils.getProperty(this.document.system, fieldPath) || [];
// Avoid duplicates
if (!currentValue.includes(droppedActor.uuid)) {
currentValue.push(droppedActor.uuid);
await this.document.update({ [fieldPath]: currentValue });
}
this.render();
}
/**
* Handle removal of a linked actor from sorcellerie sections
* @param {Event} event - The click event
* @param {HTMLElement} target - The clicked element
*/
static async #onRemoveLinkedActor(event, target) {
const li = target.closest(".item");
if (!li) return;
const field = target.dataset.field;
const uuid = target.dataset.uuid;
if (!field || !uuid) return;
const fieldPath = `system.sorcellerie.${field}`;
const currentValue = foundry.utils.getProperty(this.document.system, fieldPath) || [];
// Remove the UUID from the array
const newValue = currentValue.filter(u => u !== uuid);
await this.document.update({ [fieldPath]: newValue });
this.render();
}
/**
* Handle click on linked actor name to open its sheet
* @param {Event} event - The click event
*/
static async #onOpenLinkedActor(event) {
const target = event.target.closest(".linked-actor-name");
if (!target) return;
const uuid = target.dataset.actorUuid;
if (!uuid) return;
const actor = await fromUuid(uuid);
if (actor) {
actor.sheet.render(true);
}
}
/** @override */
_onRender(context, options) {
super._onRender(context, options);
// Add click handler for linked actor names
this.element.querySelectorAll('.linked-actor-name').forEach(el => {
el.addEventListener('click', (event) => {
event.preventDefault();
this.constructor.#onOpenLinkedActor(event);
});
});
}
}
@@ -0,0 +1,27 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2PouvoirElementaireSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "pouvoirselementaire"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.pouvoirselementaire",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-pouvoirselementaire-sheet.hbs",
},
};
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
return context;
}
}
@@ -0,0 +1,27 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2ProfilSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "profil"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.profil",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-profil-sheet.hbs",
},
};
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
return context;
}
}
@@ -0,0 +1,27 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2ProtectionSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "protection"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.protection",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-protection-sheet.hbs",
},
};
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
return context;
}
}
@@ -0,0 +1,27 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2RessourceSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "ressource"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.ressource",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-ressource-sheet.hbs",
},
};
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
return context;
}
}
@@ -0,0 +1,39 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2RuneSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "rune"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.rune",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-rune-sheet.hbs",
},
};
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" }
};
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id;
v.cssClass = v.active ? "active" : "";
}
return tabs;
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
context.tabs = this.#getTabs();
return context;
}
}
@@ -0,0 +1,27 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2RuneEffectSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "runeeffect"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.runeeffect",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-runeeffect-sheet.hbs",
},
};
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
return context;
}
}
@@ -0,0 +1,39 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2TalentSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "talent"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.talent",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-talent-sheet.hbs",
},
};
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" }
};
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id;
v.cssClass = v.active ? "active" : "";
}
return tabs;
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
context.tabs = this.#getTabs();
return context;
}
}
@@ -0,0 +1,27 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2TendanceSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "tendance"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.tendance",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-tendance-sheet.hbs",
},
};
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
return context;
}
}
@@ -0,0 +1,27 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2TraitChaotiqueSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "traitchaotique"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.traitchaotique",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-traitchaotique-sheet.hbs",
},
};
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
return context;
}
}
@@ -0,0 +1,27 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2TraitDemoniaqueSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "traitdemoniaque"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.traitdemoniaque",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-traitdemoniaque-sheet.hbs",
},
};
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
return context;
}
}
@@ -0,0 +1,27 @@
import MournbladeCYD2ItemSheetV2 from "./base-item-sheet.mjs";
export default class MournbladeCYD2TraitEspeceSheet extends MournbladeCYD2ItemSheetV2 {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "traitespece"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Item.traitespece",
},
};
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade-cyd-2-0/templates/item-traitespece-sheet.hbs",
},
};
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
return context;
}
}
+5 -5
View File
@@ -1,6 +1,6 @@
/** /**
* Macro pour remplacer les chemins d'images dans les compendiums * Macro pour remplacer les chemins d'images dans les compendiums
* Remplace "fvtt-hawkmoon-cyd" par "fvtt-mournblade-cyd2" dans tous les champs 'img' * Remplace "fvtt-hawkmoon-cyd" par "fvtt-mournblade-cyd-2-0" dans tous les champs 'img'
*/ */
(async () => { (async () => {
@@ -10,7 +10,7 @@
content: `<p>Cette macro va :</p> content: `<p>Cette macro va :</p>
<ul> <ul>
<li>Déverrouiller tous les compendiums</li> <li>Déverrouiller tous les compendiums</li>
<li>Remplacer "fvtt-hawkmoon-cyd" par "fvtt-mournblade-cyd2" dans tous les champs 'img'</li> <li>Remplacer "fvtt-hawkmoon-cyd" par "fvtt-mournblade-cyd-2-0" dans tous les champs 'img'</li>
<li>Reverrouiller les compendiums</li> <li>Reverrouiller les compendiums</li>
</ul> </ul>
<p><strong>Voulez-vous continuer ?</strong></p>`, <p><strong>Voulez-vous continuer ?</strong></p>`,
@@ -49,14 +49,14 @@
// Vérifier le champ img principal // Vérifier le champ img principal
if (doc.img && doc.img.includes("fvtt-hawkmoon-cyd")) { if (doc.img && doc.img.includes("fvtt-hawkmoon-cyd")) {
updateData.img = doc.img.replace(/fvtt-hawkmoon-cyd/g, "fvtt-mournblade-cyd2"); updateData.img = doc.img.replace(/fvtt-hawkmoon-cyd/g, "fvtt-mournblade-cyd-2-0");
needsUpdate = true; needsUpdate = true;
} }
// Pour les acteurs, vérifier aussi prototypeToken.texture.src // Pour les acteurs, vérifier aussi prototypeToken.texture.src
if (doc.documentName === "Actor" && doc.prototypeToken?.texture?.src) { if (doc.documentName === "Actor" && doc.prototypeToken?.texture?.src) {
if (doc.prototypeToken.texture.src.includes("fvtt-hawkmoon-cyd")) { if (doc.prototypeToken.texture.src.includes("fvtt-hawkmoon-cyd")) {
updateData["prototypeToken.texture.src"] = doc.prototypeToken.texture.src.replace(/fvtt-hawkmoon-cyd/g, "fvtt-mournblade-cyd2"); updateData["prototypeToken.texture.src"] = doc.prototypeToken.texture.src.replace(/fvtt-hawkmoon-cyd/g, "fvtt-mournblade-cyd-2-0");
needsUpdate = true; needsUpdate = true;
} }
} }
@@ -74,7 +74,7 @@
// Pour les scènes, vérifier background.src et les tokens // Pour les scènes, vérifier background.src et les tokens
if (doc.documentName === "Scene") { if (doc.documentName === "Scene") {
if (doc.background?.src && doc.background.src.includes("fvtt-hawkmoon-cyd")) { if (doc.background?.src && doc.background.src.includes("fvtt-hawkmoon-cyd")) {
updateData["background.src"] = doc.background.src.replace(/fvtt-hawkmoon-cyd/g, "fvtt-mournblade-cyd2"); updateData["background.src"] = doc.background.src.replace(/fvtt-hawkmoon-cyd/g, "fvtt-mournblade-cyd-2-0");
needsUpdate = true; needsUpdate = true;
} }
} }
+27
View File
@@ -0,0 +1,27 @@
/**
* Data model pour les armes MournbladeCYD2
*/
import { BaseItemWithPriceDataModel } from "./base-item.mjs";
export default class ArmeDataModel extends BaseItemWithPriceDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
...super.defineSchema(),
typearme: new fields.StringField({ initial: "" }),
armenaturelle: new fields.BooleanField({ initial: false }),
armefortune: new fields.BooleanField({ initial: false }),
bonusmaniementoff: new fields.NumberField({ initial: 0, integer: true }),
seuildefense: new fields.NumberField({ initial: 0, integer: true }),
onlevelonly: new fields.BooleanField({ initial: false }),
degats: new fields.StringField({ initial: "" }),
deuxmains: new fields.BooleanField({ initial: false }),
percearmure: new fields.BooleanField({ initial: false }),
percearmurevalue: new fields.NumberField({ initial: 0, integer: true }),
courte: new fields.NumberField({ initial: 0, integer: true }),
moyenne: new fields.NumberField({ initial: 0, integer: true }),
longue: new fields.NumberField({ initial: 0, integer: true }),
tr: new fields.NumberField({ initial: 0, integer: true })
};
}
}
+29
View File
@@ -0,0 +1,29 @@
/**
* Data model de base pour les items MournbladeCYD2
*/
export default class BaseItemDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}
/**
* Data model de base pour les items avec prix MournbladeCYD2
*/
export class BaseItemWithPriceDataModel extends BaseItemDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
...super.defineSchema(),
prixpo: new fields.NumberField({ initial: 0, integer: true }),
prixca: new fields.NumberField({ initial: 0, integer: true }),
prixsc: new fields.NumberField({ initial: 0, integer: true }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
quantite: new fields.NumberField({ initial: 1, integer: true }),
equipped: new fields.BooleanField({ initial: false })
};
}
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Data model pour les capacités d'Automata MournbladeCYD2
*/
export default class CapaciteAutomataDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
bonusmalus: new fields.StringField({ initial: "" })
};
}
}
+42
View File
@@ -0,0 +1,42 @@
/**
* Data model pour les compétences MournbladeCYD2
* Prédilections enrichies (schema Hawkmoon CYD2)
*/
export default class CompetenceDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
attribut1: new fields.StringField({ initial: "" }),
attribut2: new fields.StringField({ initial: "" }),
attribut3: new fields.StringField({ initial: "" }),
doublebonus: new fields.BooleanField({ initial: false }),
predilections: new fields.ArrayField(
new fields.SchemaField({
id: new fields.StringField({ initial: "" }),
name: new fields.StringField({ initial: "" }),
description: new fields.StringField({ initial: "" }),
acquise: new fields.BooleanField({ initial: false }),
maitrise: new fields.BooleanField({ initial: false }),
used: new fields.BooleanField({ initial: false })
}),
{ initial: [] }
)
};
}
static migrateData(source) {
if (Array.isArray(source.predilections)) {
source.predilections = source.predilections.map(pred => {
if (typeof pred === "string") return { id: "", name: pred, description: "", acquise: false, maitrise: false, used: false };
if (!("acquise" in pred)) pred.acquise = false;
if (!("maitrise" in pred)) pred.maitrise = false;
if (!("id" in pred)) pred.id = "";
if (!("description" in pred)) pred.description = "";
return pred;
});
}
return super.migrateData(source);
}
}
+116
View File
@@ -0,0 +1,116 @@
/**
* Data model pour les créatures MournbladeCYD2
*/
export default class CreatureDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
// Template biodata
biodata: new fields.SchemaField({
name: new fields.StringField({ initial: "" }),
age: new fields.NumberField({ initial: 0, integer: true }),
poids: new fields.StringField({ initial: "" }),
taille: new fields.StringField({ initial: "" }),
cheveux: new fields.StringField({ initial: "" }),
sexe: new fields.StringField({ initial: "" }),
yeux: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
habitat: new fields.HTMLField({ initial: "" }),
notes: new fields.HTMLField({ initial: "" }),
statut: new fields.StringField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" }),
statutresistant: new fields.StringField({ initial: "commun" })
}),
// Template core
subactors: new fields.ArrayField(new fields.StringField(), { initial: [] }),
attributs: new fields.SchemaField({
adr: new fields.SchemaField({
label: new fields.StringField({ initial: "Adresse" }),
labelnorm: new fields.StringField({ initial: "adresse" }),
abbrev: new fields.StringField({ initial: "adr" }),
value: new fields.NumberField({ initial: 1, integer: true })
}),
pui: new fields.SchemaField({
label: new fields.StringField({ initial: "Puissance" }),
labelnorm: new fields.StringField({ initial: "puissance" }),
abbrev: new fields.StringField({ initial: "pui" }),
value: new fields.NumberField({ initial: 1, integer: true })
}),
cla: new fields.SchemaField({
label: new fields.StringField({ initial: "Clairvoyance" }),
labelnorm: new fields.StringField({ initial: "clairvoyance" }),
abbrev: new fields.StringField({ initial: "cla" }),
value: new fields.NumberField({ initial: 1, integer: true })
}),
pre: new fields.SchemaField({
label: new fields.StringField({ initial: "Présence" }),
labelnorm: new fields.StringField({ initial: "presence" }),
abbrev: new fields.StringField({ initial: "pre" }),
value: new fields.NumberField({ initial: 1, integer: true })
}),
tre: new fields.SchemaField({
label: new fields.StringField({ initial: "Trempe" }),
labelnorm: new fields.StringField({ initial: "trempe" }),
abbrev: new fields.StringField({ initial: "tre" }),
value: new fields.NumberField({ initial: 1, integer: true })
})
}),
bonneaventure: new fields.SchemaField({
base: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
actuelle: new fields.NumberField({ initial: 0, integer: true, nullable: true })
}),
experience: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true, nullable: true })
}),
eclat: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true, nullable: true })
}),
sante: new fields.SchemaField({
vigueur: new fields.NumberField({ initial: 0, integer: true, min: 0, nullable: true }),
etat: new fields.NumberField({ initial: 0, integer: true, min: 0, nullable: true }),
vigueurmodifier: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
nbcombativite: new fields.NumberField({ initial: 5, integer: true, min: 0, nullable: true })
}),
ame: new fields.SchemaField({
seuilpouvoir: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
etat: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
seuilpouvoirmodifier: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
nbame: new fields.NumberField({ initial: 7, integer: true, nullable: true }),
max: new fields.NumberField({ initial: 0, integer: true, nullable: true })
}),
adversite: new fields.SchemaField({
bleue: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
rouge: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
noire: new fields.NumberField({ initial: 0, integer: true, nullable: true })
}),
vitesse: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true, nullable: true })
}),
combat: new fields.SchemaField({
initbonus: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
inittotal: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
vitessebonus: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
bonusdegats: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
attaquebonus: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
defensebonus: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
defensetotal: new fields.NumberField({ initial: 0, integer: true, nullable: true }),
defensetotale: new fields.BooleanField({ initial: false }),
monte: new fields.BooleanField({ initial: false }),
protectiontotal: new fields.NumberField({ initial: 0, integer: true, nullable: true })
}),
balance: new fields.SchemaField({
loi: new fields.NumberField({ initial: 0, integer: true }),
chaos: new fields.NumberField({ initial: 0, integer: true }),
aspect: new fields.NumberField({ initial: 0, integer: true }),
marge: new fields.NumberField({ initial: 0, integer: true }),
pointschaos: new fields.NumberField({ initial: 0, integer: true }),
pointsloi: new fields.NumberField({ initial: 0, integer: true })
}),
// Spécifique aux créatures
ressources: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true, nullable: true })
}),
soustype: new fields.StringField({ initial: "creature" })
};
}
}
+14
View File
@@ -0,0 +1,14 @@
/**
* Data model pour les dons MournbladeCYD2
*/
export default class DonDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
allegeance: new fields.StringField({ initial: "" }),
prerequis: new fields.StringField({ initial: "" }),
sacrifice: new fields.StringField({ initial: "" })
};
}
}
+13
View File
@@ -0,0 +1,13 @@
/**
* Data model pour les équipements MournbladeCYD2
*/
import { BaseItemWithPriceDataModel } from "./base-item.mjs";
export default class EquipementDataModel extends BaseItemWithPriceDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
...super.defineSchema()
};
}
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Data model pour les historiques MournbladeCYD2
*/
export default class HistoriqueDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
bonusmalus: new fields.StringField({ initial: "" })
};
}
}
+29
View File
@@ -0,0 +1,29 @@
/**
* Index des DataModels pour MournbladeCYD2
* Ce fichier centralise tous les exports des modèles de données
*/
// Modèles d'items
export { default as TalentDataModel } from './talent.mjs';
export { default as HistoriqueDataModel } from './historique.mjs';
export { default as ProfilDataModel } from './profil.mjs';
export { default as CompetenceDataModel } from './competence.mjs';
export { default as ArmeDataModel } from './arme.mjs';
export { default as ProtectionDataModel } from './protection.mjs';
export { default as MonnaieDataModel } from './monnaie.mjs';
export { default as EquipementDataModel } from './equipement.mjs';
export { default as RessourceDataModel } from './ressource.mjs';
export { default as DonDataModel } from './don.mjs';
export { default as PacteDataModel } from './pacte.mjs';
export { default as RuneDataModel } from './rune.mjs';
export { default as RuneEffectDataModel } from './runeeffect.mjs';
export { default as TendanceDataModel } from './tendance.mjs';
export { default as TraitChaotiqueDataModel } from './traitchaotique.mjs';
export { default as TraitEspeceDataModel } from './traitespece.mjs';
export { default as TraitDemoniaqueDataModel } from './traitdemoniaque.mjs';
export { default as PouvoirElementaireDataModel } from './pouvoirselementaire.mjs';
export { default as CapaciteAutomataDataModel } from './capaciteautomata.mjs';
// Modèles d'acteurs
export { default as PersonnageDataModel } from './personnage.mjs';
export { default as CreatureDataModel } from './creature.mjs';
+13
View File
@@ -0,0 +1,13 @@
/**
* Data model pour les monnaies MournbladeCYD2
*/
import { BaseItemWithPriceDataModel } from "./base-item.mjs";
export default class MonnaieDataModel extends BaseItemWithPriceDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
...super.defineSchema()
};
}
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Data model pour les pactes MournbladeCYD2
*/
export default class PacteDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
allegeance: new fields.StringField({ initial: "" })
};
}
}
+117
View File
@@ -0,0 +1,117 @@
/**
* Data model pour les personnages MournbladeCYD2
*/
export default class PersonnageDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
// Template biodata
biodata: new fields.SchemaField({
name: new fields.StringField({ initial: "" }),
age: new fields.NumberField({ initial: 20, integer: true }),
poids: new fields.StringField({ initial: "" }),
taille: new fields.StringField({ initial: "" }),
cheveux: new fields.StringField({ initial: "" }),
sexe: new fields.StringField({ initial: "" }),
yeux: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
habitat: new fields.HTMLField({ initial: "" }),
notes: new fields.HTMLField({ initial: "" }),
statut: new fields.StringField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" }),
statutresistant: new fields.StringField({ initial: "commun" })
}),
// Template core
subactors: new fields.ArrayField(new fields.StringField(), { initial: [] }),
attributs: new fields.SchemaField({
adr: new fields.SchemaField({
label: new fields.StringField({ initial: "Adresse" }),
labelnorm: new fields.StringField({ initial: "adresse" }),
abbrev: new fields.StringField({ initial: "adr" }),
value: new fields.NumberField({ initial: 1, integer: true })
}),
pui: new fields.SchemaField({
label: new fields.StringField({ initial: "Puissance" }),
labelnorm: new fields.StringField({ initial: "puissance" }),
abbrev: new fields.StringField({ initial: "pui" }),
value: new fields.NumberField({ initial: 1, integer: true })
}),
cla: new fields.SchemaField({
label: new fields.StringField({ initial: "Clairvoyance" }),
labelnorm: new fields.StringField({ initial: "clairvoyance" }),
abbrev: new fields.StringField({ initial: "cla" }),
value: new fields.NumberField({ initial: 1, integer: true })
}),
pre: new fields.SchemaField({
label: new fields.StringField({ initial: "Présence" }),
labelnorm: new fields.StringField({ initial: "presence" }),
abbrev: new fields.StringField({ initial: "pre" }),
value: new fields.NumberField({ initial: 1, integer: true })
}),
tre: new fields.SchemaField({
label: new fields.StringField({ initial: "Trempe" }),
labelnorm: new fields.StringField({ initial: "trempe" }),
abbrev: new fields.StringField({ initial: "tre" }),
value: new fields.NumberField({ initial: 1, integer: true })
})
}),
bonneaventure: new fields.SchemaField({
base: new fields.NumberField({ initial: 0, integer: true }),
actuelle: new fields.NumberField({ initial: 0, integer: true })
}),
experience: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
eclat: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
sante: new fields.SchemaField({
vigueur: new fields.NumberField({ initial: 0, integer: true }),
etat: new fields.NumberField({ initial: 0, integer: true }),
vigueurmodifier: new fields.NumberField({ initial: 0, integer: true }),
nbcombativite: new fields.NumberField({ initial: 5, integer: true })
}),
ame: new fields.SchemaField({
seuilpouvoir: new fields.NumberField({ initial: 0, integer: true }),
etat: new fields.NumberField({ initial: 0, integer: true }),
seuilpouvoirmodifier: new fields.NumberField({ initial: 0, integer: true }),
nbame: new fields.NumberField({ initial: 7, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
adversite: new fields.SchemaField({
bleue: new fields.NumberField({ initial: 0, integer: true }),
rouge: new fields.NumberField({ initial: 0, integer: true }),
noire: new fields.NumberField({ initial: 0, integer: true })
}),
vitesse: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
combat: new fields.SchemaField({
initbonus: new fields.NumberField({ initial: 0, integer: true }),
vitessebonus: new fields.NumberField({ initial: 0, integer: true }),
bonusdegats: new fields.NumberField({ initial: 0, integer: true }),
attaquebonus: new fields.NumberField({ initial: 0, integer: true }),
defensebonus: new fields.NumberField({ initial: 0, integer: true }),
defensetotale: new fields.BooleanField({ initial: false }),
monte: new fields.BooleanField({ initial: false })
}),
balance: new fields.SchemaField({
loi: new fields.NumberField({ initial: 0, integer: true }),
chaos: new fields.NumberField({ initial: 0, integer: true }),
aspect: new fields.NumberField({ initial: 0, integer: true }),
marge: new fields.NumberField({ initial: 0, integer: true }),
pointschaos: new fields.NumberField({ initial: 0, integer: true }),
pointsloi: new fields.NumberField({ initial: 0, integer: true })
}),
// Sorcellerie
sorcellerie: new fields.SchemaField({
runes: new fields.HTMLField({ initial: "" }),
creaturesinvoquees: new fields.ArrayField(new fields.StringField(), { initial: [] }),
demonslies: new fields.ArrayField(new fields.StringField(), { initial: [] }),
enchantements: new fields.ArrayField(new fields.StringField(), { initial: [] }),
invocationsencours: new fields.HTMLField({ initial: "" }),
coutPouvoirInvocations: new fields.NumberField({ initial: 0, integer: true })
})
};
}
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Data model pour les pouvoirs élémentaires MournbladeCYD2
*/
export default class PouvoirElementaireDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
bonusmalus: new fields.StringField({ initial: "" })
};
}
}
+22
View File
@@ -0,0 +1,22 @@
/**
* Data model pour les profils MournbladeCYD2
*/
export default class ProfilDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
exemples: new fields.StringField({ initial: "" }),
attribut1: new fields.StringField({ initial: "" }),
attribut2: new fields.StringField({ initial: "" }),
attribut3: new fields.StringField({ initial: "" }),
competences: new fields.HTMLField({ initial: "" }),
talentsinitie: new fields.HTMLField({ initial: "" }),
prerequisaguerri: new fields.HTMLField({ initial: "" }),
talentsaguerri: new fields.HTMLField({ initial: "" }),
prerequismaitre: new fields.HTMLField({ initial: "" }),
talentsmaitre: new fields.HTMLField({ initial: "" }),
equipement: new fields.StringField({ initial: "" })
};
}
}
+15
View File
@@ -0,0 +1,15 @@
/**
* Data model pour les protections MournbladeCYD2
*/
import { BaseItemWithPriceDataModel } from "./base-item.mjs";
export default class ProtectionDataModel extends BaseItemWithPriceDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
...super.defineSchema(),
protection: new fields.NumberField({ initial: 0, integer: true }),
adversitepoids: new fields.NumberField({ initial: 0, integer: true })
};
}
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Data model pour les ressources MournbladeCYD2
*/
export default class RessourceDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
pointdev: new fields.NumberField({ initial: 0, integer: true })
};
}
}
+16
View File
@@ -0,0 +1,16 @@
/**
* Data model pour les runes MournbladeCYD2
*/
export default class RuneDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
formule: new fields.StringField({ initial: "" }),
seuil: new fields.NumberField({ initial: 0, integer: true }),
prononcee: new fields.StringField({ initial: "" }),
tracee: new fields.StringField({ initial: "" }),
coutAme: new fields.StringField({ initial: "" })
};
}
}
+15
View File
@@ -0,0 +1,15 @@
/**
* Data model pour les effets de rune MournbladeCYD2
*/
export default class RuneEffectDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
rune: new fields.StringField({ initial: "" }),
mode: new fields.StringField({ initial: "" }),
duree: new fields.StringField({ initial: "" }),
pointame: new fields.NumberField({ initial: 0, integer: true })
};
}
}
+30
View File
@@ -0,0 +1,30 @@
/**
* Data model pour les talents MournbladeCYD2
*/
export default class TalentDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
isautomated: new fields.BooleanField({ initial: false }),
automations: new fields.ArrayField(
new fields.SchemaField({
id: new fields.StringField({ initial: "" }),
eventtype: new fields.StringField({ initial: "on-drop" }),
name: new fields.StringField({ initial: "" }),
bonusname: new fields.StringField({ initial: "vigueur" }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
competence: new fields.StringField({ initial: "" }),
minLevel: new fields.NumberField({ initial: 0, integer: true }),
baCost: new fields.NumberField({ initial: 0, integer: true })
}),
{ initial: [] }
),
talenttype: new fields.StringField({ initial: "" }),
utilisation: new fields.StringField({ initial: "" }),
prerequis: new fields.StringField({ initial: "" }),
resumebonus: new fields.StringField({ initial: "" }),
used: new fields.BooleanField({ initial: false })
};
}
}
+13
View File
@@ -0,0 +1,13 @@
/**
* Data model pour les tendances MournbladeCYD2
*/
export default class TendanceDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
allegeance: new fields.StringField({ initial: "" }),
donlie: new fields.StringField({ initial: "" })
};
}
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Data model pour les traits chaotiques MournbladeCYD2
*/
export default class TraitChaotiqueDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
bonusmalus: new fields.StringField({ initial: "" })
};
}
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Data model pour les traits démoniaques MournbladeCYD2
*/
export default class TraitDemoniaqueDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
bonusmalus: new fields.StringField({ initial: "" })
};
}
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Data model pour les traits d'espèce MournbladeCYD2
*/
export default class TraitEspeceDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
bonusmalus: new fields.StringField({ initial: "" })
};
}
}
-211
View File
@@ -1,211 +0,0 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
import { MournbladeCYD2Automation } from "./mournblade-cyd2-automation.js";
/* -------------------------------------------- */
export class MournbladeCYD2ActorSheet extends foundry.appv1.sheets.ActorSheet {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["fvtt-mournblade-cyd2", "sheet", "actor"],
template: "systems/fvtt-mournblade-cyd2/templates/actor-sheet.html",
width: 640,
height: 720,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
editScore: false
})
}
/* -------------------------------------------- */
async getData() {
const objectData = foundry.utils.duplicate(this.object)
let formData = {
title: this.title,
id: objectData.id,
type: objectData.type,
img: objectData.img,
name: objectData.name,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
system: objectData.system,
effects: this.object.effects.map(e => foundry.utils.deepClone(e.data)),
limited: this.object.limited,
skills: this.actor.getSkills(),
armes: foundry.utils.duplicate(this.actor.getWeapons()),
monnaies: foundry.utils.duplicate(this.actor.getMonnaies()),
protections: foundry.utils.duplicate(this.actor.getArmors()),
historiques: foundry.utils.duplicate(this.actor.getHistoriques() || []),
talents: foundry.utils.duplicate(this.actor.getTalents() || []),
dons: foundry.utils.duplicate(this.actor.getDons() || []),
pactes: foundry.utils.duplicate(this.actor.getPactes() || []),
tendances: foundry.utils.duplicate(this.actor.getTendances() || []),
runes: foundry.utils.duplicate(this.actor.getRunes() || []),
traitsChaotiques: foundry.utils.duplicate(this.actor.getTraitsChaotiques() || []),
traitsEspeces: foundry.utils.duplicate(this.actor.getTraitsEspeces() || []),
aspect: this.actor.getAspect(),
marge: this.actor.getMarge(),
profils: foundry.utils.duplicate(this.actor.getProfils() || []),
combat: this.actor.getCombatValues(),
equipements: foundry.utils.duplicate(this.actor.getEquipments()),
richesse: this.actor.computeRichesse(),
coupDevastateur: this.actor.items.find(it => it.type == "talent" && it.name.toLowerCase() == "coup devastateur" && !it.system.used),
valeurEquipement: this.actor.computeValeurEquipement(),
nbCombativite: this.actor.system.sante.nbcombativite,
combativiteList: MournbladeCYD2Utility.getCombativiteList(this.actor.system.sante.nbcombativite),
nbAme: this.actor.system.ame.nbame,
ameMax: this.actor.getAmeMax(),
ameList: MournbladeCYD2Utility.getAmeList(this.actor.system.ame.nbame, this.actor.getAmeMax()),
ameMaxList: MournbladeCYD2Utility.getAmeMaxList(this.actor.system.ame.nbame),
initiative: this.actor.getFlag("world", "last-initiative") || -1,
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.biodata.description, { async: true }),
habitat: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.biodata.habitat, { async: true }),
options: this.options,
owner: this.document.isOwner,
editScore: this.options.editScore,
isGM: game.user.isGM,
config: game.system.mournbladecyd2.config
}
this.formData = formData;
console.log("PC : ", formData, this.object);
return formData;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item")
let itemId = li.data("item-id")
const item = this.actor.items.get(itemId)
item.sheet.render(true)
})
// Delete Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
MournbladeCYD2Utility.confirmDelete(this, li);
})
html.find('.edit-item-data').change(ev => {
const li = $(ev.currentTarget).parents(".item")
let itemId = li.data("item-id")
let itemType = li.data("item-type")
let itemField = $(ev.currentTarget).data("item-field")
let dataType = $(ev.currentTarget).data("dtype")
let value = ev.currentTarget.value
this.actor.editItemField(itemId, itemType, itemField, dataType, value)
})
html.find('.adversite-modify').click(event => {
const li = $(event.currentTarget).parents(".item")
let adv = li.data("adversite")
let value = Number($(event.currentTarget).data("adversite-value"))
this.actor.incDecAdversite(adv, value)
})
html.find('.quantity-modify').click(event => {
const li = $(event.currentTarget).parents(".item")
const value = Number($(event.currentTarget).data("quantite-value"))
this.actor.incDecQuantity(li.data("item-id"), value);
})
html.find('.roll-initiative').click((event) => {
this.actor.rollAttribut("adr", true)
})
html.find('.roll-attribut').click((event) => {
const li = $(event.currentTarget).parents(".item")
let attrKey = li.data("attr-key")
this.actor.rollAttribut(attrKey, false)
})
html.find('.roll-competence').click((event) => {
const li = $(event.currentTarget).parents(".item")
let attrKey = $(event.currentTarget).data("attr-key")
let compId = li.data("item-id")
this.actor.rollCompetence(attrKey, compId)
})
html.find('.roll-arme-offensif').click((event) => {
const li = $(event.currentTarget).parents(".item")
let armeId = li.data("item-id")
this.actor.rollArmeOffensif(armeId)
})
html.find('.roll-assommer').click((event) => {
this.actor.rollAssommer()
})
html.find('.roll-coup-bas').click((event) => {
this.actor.rollCoupBas()
})
html.find('.roll-immobiliser').click((event) => {
this.actor.rollImmobiliser()
})
html.find('.roll-repousser').click((event) => {
this.actor.rollRepousser()
})
html.find('.roll-desengager').click((event) => {
this.actor.rollDesengager()
})
html.find('.roll-arme-degats').click((event) => {
const li = $(event.currentTarget).parents(".item")
let armeId = li.data("item-id")
this.actor.rollArmeDegats(armeId)
})
html.find('.item-add').click((event) => {
const itemType = $(event.currentTarget).data("type")
this.actor.createEmbeddedDocuments('Item', [{ name: `Nouveau ${itemType}`, type: itemType }], { renderSheet: true })
})
html.find('.lock-unlock-sheet').click((event) => {
this.options.editScore = !this.options.editScore;
this.render(true);
});
html.find('.item-equip').click(ev => {
const li = $(ev.currentTarget).parents(".item");
this.actor.equipItem(li.data("item-id"));
this.render(true);
});
}
/* -------------------------------------------- */
/** @override */
setPosition(options = {}) {
const position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body");
const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
return position;
}
/* -------------------------------------------- */
async _onDropItem(event, dragData) {
let data = event.dataTransfer.getData('text/plain')
let dataItem = JSON.parse(data)
let item = fromUuidSync(dataItem.uuid)
if (item.pack) {
item = await MournbladeCYD2Utility.searchItem(item)
}
let autoresult = MournbladeCYD2Automation.processAutomations("on-drop", item, this.actor)
if (autoresult.isValid) {
super._onDropItem(event, dragData)
} else {
ui.notifications.warn(autoresult.warningMessage)
}
}
}
+139 -54
View File
@@ -1,6 +1,6 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js"; import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
import { MournbladeCYD2RollDialog } from "./mournblade-cyd2-roll-dialog.js"; import { MournbladeCYD2RollDialog } from "./applications/mournblade-cyd2-roll-dialog.mjs";
/* -------------------------------------------- */ /* -------------------------------------------- */
const __degatsBonus = [-2, -2, -1, -1, 0, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 8, 8, 9, 9, 10, 10] const __degatsBonus = [-2, -2, -1, -1, 0, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 8, 8, 9, 9, 10, 10]
@@ -39,14 +39,14 @@ export class MournbladeCYD2Actor extends Actor {
if (data.type == 'personnage') { if (data.type == 'personnage') {
console.log("Loading skills for personnage") console.log("Loading skills for personnage")
const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd2.skills") const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd-2-0.skills")
data.items = skills.map(i => i.toObject()) data.items = skills.map(i => i.toObject())
} }
if (data.type == 'creature') { if (data.type == 'creature') {
const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd2.skills-creatures") const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd-2-0.skills-creatures")
data.items = skills.map(i => i.toObject()) data.items = skills.map(i => i.toObject())
data.items.push({ name: "Arme naturelle 1", type: 'arme', img: "systems/fvtt-mournblade-cyd2/assets/icons/melee.webp", system: { typearme: "contact", bonusmaniementoff: 0, seuildefense: 0, degats: "0" } }) data.items.push({ name: "Arme naturelle 1", type: 'arme', img: "systems/fvtt-mournblade-cyd-2-0/assets/icons/melee.webp", system: { typearme: "contact", bonusmaniementoff: 0, seuildefense: 0, degats: "0" } })
data.items.push({ name: "Arme naturelle 2", type: 'arme', img: "systems/fvtt-mournblade-cyd2/assets/icons/melee.webp", system: { typearme: "contact", bonusmaniementoff: 0, seuildefense: 0, degats: "0" } }) data.items.push({ name: "Arme naturelle 2", type: 'arme', img: "systems/fvtt-mournblade-cyd-2-0/assets/icons/melee.webp", system: { typearme: "contact", bonusmaniementoff: 0, seuildefense: 0, degats: "0" } })
} }
return super.create(data, options); return super.create(data, options);
@@ -145,6 +145,12 @@ export class MournbladeCYD2Actor extends Actor {
getRunes() { getRunes() {
return this.getItemSorted(["rune"]) return this.getItemSorted(["rune"])
} }
getRuneEffects() {
return this.getItemSorted(["runeeffect"])
}
getProfil() {
return this.getProfils()[0] ?? null
}
getTraitsChaotiques() { getTraitsChaotiques() {
return this.getItemSorted(["traitchaotique"]) return this.getItemSorted(["traitchaotique"])
} }
@@ -220,6 +226,9 @@ export class MournbladeCYD2Actor extends Actor {
} }
return equipProtection // Uniquement la protection des armures + boucliers return equipProtection // Uniquement la protection des armures + boucliers
} }
getProtectionTotal() {
return this.getProtection()
}
/* -------------------------------------------- */ /* -------------------------------------------- */
getCombatValues() { getCombatValues() {
@@ -237,9 +246,6 @@ export class MournbladeCYD2Actor extends Actor {
return combat return combat
} }
/* -------------------------------------------- */
prepareBaseData() {
}
/* -------------------------------------------- */ /* -------------------------------------------- */
async prepareData() { async prepareData() {
@@ -267,12 +273,43 @@ export class MournbladeCYD2Actor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
_preUpdate(changed, options, user) { _preUpdate(changed, options, user) {
if (changed?.system?.sante?.etat && changed?.system?.sante?.etat != this.system.sante.etat) { // Clean up numeric fields in various schemas to ensure they are valid numbers
const numericCleanup = (schemaPath, dataPath) => {
if (changed?.system?.[dataPath]) {
const schema = foundry.utils.getProperty(this.system.schema.fields, schemaPath);
if (schema) {
for (const [key, value] of Object.entries(changed.system[dataPath])) {
if (schema.fields[key] instanceof foundry.data.fields.NumberField) {
if (value === "" || value === null || value === undefined || isNaN(value)) {
changed.system[dataPath][key] = 0;
} else {
changed.system[dataPath][key] = Number(value);
}
}
}
}
}
};
// Apply cleanup to schemas that may have numeric fields submitted from forms
numericCleanup("sante", "sante");
numericCleanup("ame", "ame");
numericCleanup("combat", "combat");
numericCleanup("balance", "balance");
numericCleanup("adversite", "adversite");
numericCleanup("vitesse", "vitesse");
numericCleanup("ressources", "ressources");
numericCleanup("eclat", "eclat");
numericCleanup("bonneaventure", "bonneaventure");
numericCleanup("experience", "experience");
if (changed?.system?.sante?.etat !== undefined && changed.system.sante.etat != this.system.sante.etat) {
const oldEtat = this.system.sante.etat
setTimeout(() => { setTimeout(() => {
this.processCombativite(changed.system.sante) this.processCombativite(changed.system.sante, oldEtat)
}, 800) }, 800)
} }
if (changed?.system?.ame?.etat && changed?.system?.ame?.etat != this.system.ame.etat) { if (changed?.system?.ame?.etat !== undefined && changed.system.ame.etat != this.system.ame.etat) {
// L'état d'Âme ne peut pas être inférieur au minimum (max dans le système) // L'état d'Âme ne peut pas être inférieur au minimum (max dans le système)
let minAme = this.system.ame.max !== undefined ? this.system.ame.max : 0 let minAme = this.system.ame.max !== undefined ? this.system.ame.max : 0
if (changed.system.ame.etat < minAme) { if (changed.system.ame.etat < minAme) {
@@ -282,8 +319,9 @@ export class MournbladeCYD2Actor extends Actor {
if (changed.system.ame.etat > this.system.ame.nbame) { if (changed.system.ame.etat > this.system.ame.nbame) {
changed.system.ame.etat = this.system.ame.nbame changed.system.ame.etat = this.system.ame.nbame
} }
const oldEtat = this.system.ame.etat
setTimeout(() => { setTimeout(() => {
this.processAme(changed.system.ame) this.processAme(changed.system.ame, oldEtat)
}, 800) }, 800)
} }
// Si le max d'Âme change, ajuster l'état actuel si nécessaire // Si le max d'Âme change, ajuster l'état actuel si nécessaire
@@ -318,7 +356,7 @@ export class MournbladeCYD2Actor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
editItemField(itemId, itemType, itemField, dataType, value) { async editItemField(itemId, itemType, itemField, dataType, value) {
let item = this.items.find(item => item.id == itemId) let item = this.items.find(item => item.id == itemId)
if (item) { if (item) {
console.log("Item ", item, itemField, dataType, value) console.log("Item ", item, itemField, dataType, value)
@@ -328,7 +366,7 @@ export class MournbladeCYD2Actor extends Actor {
value = String(value) value = String(value)
} }
let update = { _id: item.id, [`system.${itemField}`]: value }; let update = { _id: item.id, [`system.${itemField}`]: value };
this.updateEmbeddedDocuments("Item", [update]) await this.updateEmbeddedDocuments("Item", [update])
} }
} }
@@ -457,7 +495,15 @@ export class MournbladeCYD2Actor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
getAttribute(attrKey) { getAttribute(attrKey) {
return this.system.attributes[attrKey] return this.system.attributs[attrKey]
}
/* -------------------------------------------- */
hasTalent(nameOrPattern) {
if (nameOrPattern instanceof RegExp) {
return !!this.items.find(i => i.type === "talent" && nameOrPattern.test(i.name.toLowerCase()))
}
return !!this.items.find(i => i.type === "talent" && i.name.toLowerCase() === nameOrPattern.toLowerCase())
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -500,38 +546,57 @@ export class MournbladeCYD2Actor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
processCombativite(sante) { processCombativite(sante, oldEtat = undefined) {
sante = sante || foundry.utils.duplicate(this.system.sante) sante = sante || foundry.utils.duplicate(this.system.sante)
// Gestion des états affaibli et très affaibli const affaibli = this.system.sante.nbcombativite - 2
if (sante.etat == this.system.sante.nbcombativite - 2 || sante.etat == this.system.sante.nbcombativite - 1) { const tresAffaibli = this.system.sante.nbcombativite - 1
if (sante.etat == this.system.sante.nbcombativite - 2 && this.items.find(item => item.type == "talent" && item.name.toLowerCase() == "encaissement")) { // oldEtat permet de détecter les sauts qui franchissent Affaibli ou Très Affaibli
ChatMessage.create({ content: `<strong>${this.name} ne subit pas les 2 adversités rouge grâce à Encaissement. Pensez à les ajouter à la fin de la scène !</strong>` }) // sans y atterrir exactement (ex: 0 → 5 doit déclencher les deux seuils)
} else if (sante.etat == this.system.sante.nbcombativite - 1 && this.items.find(item => item.type == "talent" && item.name.toLowerCase().includes("vaillant"))) { const prev = oldEtat !== undefined ? oldEtat : sante.etat
ChatMessage.create({ content: `<strong>${this.name} ne subit pas les 2 adversités rouge grâce à Vaillant. Pensez à les ajouter à la fin de la scène !</strong>` }) const curr = sante.etat
const passedAffaibli = curr >= affaibli && prev < affaibli
const passedTresAffaibli = curr >= tresAffaibli && prev < tresAffaibli
if (passedAffaibli) {
if (this.hasTalent("encaissement")) {
ChatMessage.create({ content: `<strong>${this.name} ne subit pas les 2 adversités rouge (Affaibli) grâce à Encaissement. Pensez à les ajouter à la fin de la scène !</strong>` })
} else { } else {
ChatMessage.create({ content: `<strong>${this.name} subit 2 adversités rouge !</strong>` }) ChatMessage.create({ content: `<strong>${this.name} est Affaibli et subit 2 adversités rouge !</strong>` })
this.incDecAdversite("rouge", 2)
}
}
if (passedTresAffaibli) {
if (this.hasTalent(/vaillant/)) {
ChatMessage.create({ content: `<strong>${this.name} ne subit pas les 2 adversités rouge (Très Affaibli) grâce à Vaillant. Pensez à les ajouter à la fin de la scène !</strong>` })
} else {
ChatMessage.create({ content: `<strong>${this.name} est Très Affaibli et subit 2 adversités rouge supplémentaires !</strong>` })
this.incDecAdversite("rouge", 2) this.incDecAdversite("rouge", 2)
} }
} }
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
processAme(ame) { processAme(ame, oldEtat = undefined) {
ame = ame || foundry.utils.duplicate(this.system.ame) ame = ame || foundry.utils.duplicate(this.system.ame)
let traumatiseValue = this.system.ame.nbame - 2 const traumatiseValue = this.system.ame.nbame - 2
let tresTraumatiseValue = this.system.ame.nbame - 1 const tresTraumatiseValue = this.system.ame.nbame - 1
let briseValue = this.system.ame.nbame const briseValue = this.system.ame.nbame
const prev = oldEtat !== undefined ? oldEtat : ame.etat
const curr = ame.etat
// Gestion des états Traumatisé, Très Traumatisé et Brisé // Déclencher pour chaque seuil franchi ou atteint, même en cas de saut
if (ame.etat == traumatiseValue) { if (curr >= traumatiseValue && prev < traumatiseValue) {
ChatMessage.create({ content: `<strong>${this.name} est Traumatisé et subit 1 adversité bleue et 1 adversité noire !</strong>` }) ChatMessage.create({ content: `<strong>${this.name} est Traumatisé et subit 1 adversité bleue et 1 adversité noire !</strong>` })
this.incDecAdversite("bleue", 1) this.incDecAdversite("bleue", 1)
this.incDecAdversite("noire", 1) this.incDecAdversite("noire", 1)
} else if (ame.etat == tresTraumatiseValue) { }
if (curr >= tresTraumatiseValue && prev < tresTraumatiseValue) {
ChatMessage.create({ content: `<strong>${this.name} est Très Traumatisé et subit 1 adversité bleue et 1 adversité noire !</strong>` }) ChatMessage.create({ content: `<strong>${this.name} est Très Traumatisé et subit 1 adversité bleue et 1 adversité noire !</strong>` })
this.incDecAdversite("bleue", 1) this.incDecAdversite("bleue", 1)
this.incDecAdversite("noire", 1) this.incDecAdversite("noire", 1)
} else if (ame.etat >= briseValue) { }
if (curr >= briseValue && prev < briseValue) {
ChatMessage.create({ content: `<strong>${this.name} est Brisé et subit 1 adversité noire !</strong>` }) ChatMessage.create({ content: `<strong>${this.name} est Brisé et subit 1 adversité noire !</strong>` })
this.incDecAdversite("noire", 1) this.incDecAdversite("noire", 1)
} }
@@ -599,7 +664,7 @@ export class MournbladeCYD2Actor extends Actor {
let valueSC = 0 let valueSC = 0
for (let monnaie of this.items) { for (let monnaie of this.items) {
if (monnaie.type == "monnaie") { if (monnaie.type == "monnaie") {
valueSC += Number(monnaie.system.prixsc) * Number(monnaie.system.quantite) valueSC += MournbladeCYD2Utility.getItemValueSC(monnaie)
} }
} }
return MournbladeCYD2Utility.computeMonnaieDetails(valueSC) return MournbladeCYD2Utility.computeMonnaieDetails(valueSC)
@@ -610,9 +675,7 @@ export class MournbladeCYD2Actor extends Actor {
let valueSC = 0 let valueSC = 0
for (let equip of this.items) { for (let equip of this.items) {
if (equip.type == "equipement" || equip.type == "arme" || equip.type == "protection") { if (equip.type == "equipement" || equip.type == "arme" || equip.type == "protection") {
valueSC += Number(equip.system.prixsc) * Number(equip.system.quantite ?? 1) valueSC += MournbladeCYD2Utility.getItemValueSC(equip)
valueSC += (Number(equip.system.prixca) * Number(equip.system.quantite ?? 1)) * 20
valueSC += (Number(equip.system.prixpo) * Number(equip.system.quantite ?? 1)) * 400
} }
} }
return MournbladeCYD2Utility.computeMonnaieDetails(valueSC) return MournbladeCYD2Utility.computeMonnaieDetails(valueSC)
@@ -721,7 +784,7 @@ export class MournbladeCYD2Actor extends Actor {
if (attrKey) { if (attrKey) {
rollData.attrKey = attrKey rollData.attrKey = attrKey
if (attrKey != "tochoose") { if (attrKey != "tochoose") {
rollData.actionImg = "systems/fvtt-mournblade-cyd2/assets/icons/" + this.system.attributs[attrKey].labelnorm + ".webp" rollData.actionImg = "systems/fvtt-mournblade-cyd-2-0/assets/icons/" + this.system.attributs[attrKey].labelnorm + ".webp"
rollData.attr = foundry.utils.duplicate(this.system.attributs[attrKey]) rollData.attr = foundry.utils.duplicate(this.system.attributs[attrKey])
} }
} }
@@ -747,10 +810,9 @@ export class MournbladeCYD2Actor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
async rollAttribut(attrKey, isInit = false) { async rollAttribut(attrKey, isInit = false) {
let rollData = this.getCommonRollData(attrKey) let rollData = this.getCommonRollData(attrKey)
rollData.multiplier = (isInit) ? 1 : 2 rollData.multiplier = 1 // 1d10 + attr (optionally + attr2 if chosen in dialog)
rollData.isInit = isInit rollData.isInit = isInit
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData) await MournbladeCYD2RollDialog.create(this, rollData)
rollDialog.render(true)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -758,8 +820,7 @@ export class MournbladeCYD2Actor extends Actor {
let rollData = this.getCommonRollData(attrKey, compId) let rollData = this.getCommonRollData(attrKey, compId)
rollData.multiplier = 1 // Attr multiplier, always 1 in competence mode rollData.multiplier = 1 // Attr multiplier, always 1 in competence mode
console.log("RollDatra", rollData) console.log("RollDatra", rollData)
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData) await MournbladeCYD2RollDialog.create(this, rollData)
rollDialog.render(true)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -772,8 +833,7 @@ export class MournbladeCYD2Actor extends Actor {
rollData.arme = arme rollData.arme = arme
MournbladeCYD2Utility.updateWithTarget(rollData) MournbladeCYD2Utility.updateWithTarget(rollData)
console.log("ARME!", rollData) console.log("ARME!", rollData)
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData) await MournbladeCYD2RollDialog.create(this, rollData)
rollDialog.render(true)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async rollAssommer() { async rollAssommer() {
@@ -781,8 +841,7 @@ export class MournbladeCYD2Actor extends Actor {
rollData.assomer = true rollData.assomer = true
rollData.conditionsCommunes = true rollData.conditionsCommunes = true
MournbladeCYD2Utility.updateWithTarget(rollData) MournbladeCYD2Utility.updateWithTarget(rollData)
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData) await MournbladeCYD2RollDialog.create(this, rollData)
rollDialog.render(true)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async rollCoupBas() { async rollCoupBas() {
@@ -790,8 +849,7 @@ export class MournbladeCYD2Actor extends Actor {
rollData.coupBas = true rollData.coupBas = true
rollData.conditionsCommunes = true rollData.conditionsCommunes = true
MournbladeCYD2Utility.updateWithTarget(rollData) MournbladeCYD2Utility.updateWithTarget(rollData)
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData) await MournbladeCYD2RollDialog.create(this, rollData)
rollDialog.render(true)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async rollImmobiliser() { async rollImmobiliser() {
@@ -800,8 +858,7 @@ export class MournbladeCYD2Actor extends Actor {
rollData.conditionsCommunes = true rollData.conditionsCommunes = true
rollData.cibleconsciente = true rollData.cibleconsciente = true
MournbladeCYD2Utility.updateWithTarget(rollData) MournbladeCYD2Utility.updateWithTarget(rollData)
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData) await MournbladeCYD2RollDialog.create(this, rollData)
rollDialog.render(true)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async rollRepousser() { async rollRepousser() {
@@ -810,8 +867,7 @@ export class MournbladeCYD2Actor extends Actor {
rollData.conditionsCommunes = true rollData.conditionsCommunes = true
rollData.cibleconsciente = true rollData.cibleconsciente = true
MournbladeCYD2Utility.updateWithTarget(rollData) MournbladeCYD2Utility.updateWithTarget(rollData)
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData) await MournbladeCYD2RollDialog.create(this, rollData)
rollDialog.render(true)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async rollDesengager() { async rollDesengager() {
@@ -819,8 +875,34 @@ export class MournbladeCYD2Actor extends Actor {
rollData.desengager = true rollData.desengager = true
rollData.conditionsCommunes = true rollData.conditionsCommunes = true
MournbladeCYD2Utility.updateWithTarget(rollData) MournbladeCYD2Utility.updateWithTarget(rollData)
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData) await MournbladeCYD2RollDialog.create(this, rollData)
rollDialog.render(true) }
/* -------------------------------------------- */
subPointsAme(runeMode, value) {
let ame = foundry.utils.duplicate(this.system.ame)
if (runeMode == "prononcer") {
ame.etat += value
ame.etat = Math.min(ame.etat, ame.nbame)
} else {
ame.max = (ame.max || 0) + value
}
this.update({ 'system.ame': ame })
}
/* -------------------------------------------- */
async rollRune(runeId) {
let comp = this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "savoir : runes")
if (!comp) {
ui.notifications.warn("La compétence « Savoir : Runes » n'a pas été trouvée sur ce personnage.")
return
}
let rollData = this.getCommonRollData("cla", comp.id)
rollData.rune = foundry.utils.duplicate(this.items.get(runeId) || {})
rollData.difficulte = rollData.rune?.system?.seuil || 0
rollData.runemode = "prononcer"
rollData.runeame = 1
await MournbladeCYD2RollDialog.create(this, rollData)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -858,6 +940,8 @@ export class MournbladeCYD2Actor extends Actor {
roll = await new Roll("1d10+" + arme.system.totalDegats + "+" + bonus + "+" + bonus2).roll() roll = await new Roll("1d10+" + arme.system.totalDegats + "+" + bonus + "+" + bonus2).roll()
} }
await MournbladeCYD2Utility.showDiceSoNice(roll, game.settings.get("core", "rollMode")); await MournbladeCYD2Utility.showDiceSoNice(roll, game.settings.get("core", "rollMode"));
// CYD 2.0: états SUPPLÉMENTAIRES au-delà du -1 automatique à la réussite.
// Math.floor(total/SV) = 0 (<SV), 1 (≥SV), 2 (≥2×SV) → totaux finaux : 1, 2, 3
let nbEtatPerdus = 0 let nbEtatPerdus = 0
if (targetVigueur) { if (targetVigueur) {
nbEtatPerdus = Math.floor(roll.total / targetVigueur) nbEtatPerdus = Math.floor(roll.total / targetVigueur)
@@ -865,6 +949,7 @@ export class MournbladeCYD2Actor extends Actor {
//console.log(roll) //console.log(roll)
let rollData = { let rollData = {
arme: arme, arme: arme,
diceResult: roll.dice[0]?.total ?? roll.total,
finalResult: roll.total, finalResult: roll.total,
formula: roll.formula, formula: roll.formula,
alias: this.name, alias: this.name,
@@ -876,7 +961,7 @@ export class MournbladeCYD2Actor extends Actor {
nbEtatPerdus: nbEtatPerdus nbEtatPerdus: nbEtatPerdus
} }
MournbladeCYD2Utility.createChatWithRollMode(rollData.alias, { MournbladeCYD2Utility.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-mournblade-cyd2/templates/chat-degats-result.html`, rollData) content: await renderTemplate(`systems/fvtt-mournblade-cyd-2-0/templates/chat-degats-result.hbs`, rollData)
}) })
if (rollDataInput?.defenderTokenId && nbEtatPerdus) { if (rollDataInput?.defenderTokenId && nbEtatPerdus) {
+4 -5
View File
@@ -1,7 +1,7 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js"; import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
import { MournbladeCYD2RollDialog } from "./mournblade-cyd2-roll-dialog.js"; import { MournbladeCYD2RollDialog } from "./applications/mournblade-cyd2-roll-dialog.mjs";
/* -------------------------------------------- */ /* -------------------------------------------- */
export class MournbladeCYD2Commands { export class MournbladeCYD2Commands {
@@ -88,8 +88,8 @@ export class MournbladeCYD2Commands {
} }
if (command && command.func) { if (command && command.func) {
const result = command.func(content, msg, params); const result = command.func(content, msg, params);
if (result == false) { if (result === false) {
RdDCommands._chatAnswer(msg, command.descr); MournbladeCYD2Commands._chatAnswer(msg, command.descr);
} }
return true; return true;
} }
@@ -98,8 +98,7 @@ export class MournbladeCYD2Commands {
/* -------------------------------------------- */ /* -------------------------------------------- */
async createChar(msg) { async createChar(msg) {
game.system.MournbladeCYD2.creator = new MournbladeCYD2ActorCreate(); ui.notifications.warn("La création automatique de personnage n'est pas disponible dans cette version.")
game.system.MournbladeCYD2.creator.start();
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
+136 -13
View File
@@ -1,18 +1,28 @@
const localizeOrFallback = (key, fallback) => globalThis.game?.i18n?.localize?.(key) ?? fallback
export const MOURNBLADECYD2_CONFIG = { export const MOURNBLADECYD2_CONFIG = {
attributs: {
adr: "Adresse",
pui: "Puissance",
cla: "Clairvoyance",
pre: "Présence",
tre: "Trempe"
},
allegeanceOptions: { allegeanceOptions: {
tous: 'Tous', tous: localizeOrFallback("MNBL.all", "Tous"),
chaos: 'Chaos', chaos: localizeOrFallback("MNBL.chaos", "Chaos"),
loi: 'Loi', loi: localizeOrFallback("MNBL.law", "Loi"),
betes: 'Seigneurs des Bêtes', balance: localizeOrFallback("MNBL.balance", "Balance"),
elementaires: 'Seigneurs des Eléments' betes: localizeOrFallback("MNBL.betes", "Bêtes"),
elementaires: localizeOrFallback("MNBL.elementaires", "Élémentaires")
}, },
lancementRuneOptions: { lancementRuneOptions: {
prononcer: 'Prononcer', prononcer: localizeOrFallback("MNBL.pronouncerune", "Prononcer"),
inscrire: 'Tracer' inscrire: localizeOrFallback("MNBL.tracerune", "Tracer")
}, },
effetRuneOptions: { effetRuneOptions: {
prononcee: 'Prononcée', prononcee: localizeOrFallback("MNBL.pronounced", "Prononcée"),
inscrite: 'Inscrite' inscrite: localizeOrFallback("MNBL.traced", "Tracée")
}, },
optionsDifficulte: [ optionsDifficulte: [
@@ -45,9 +55,9 @@ export const MOURNBLADECYD2_CONFIG = {
{ key: "30", label: "Pure Folie (30)" } { key: "30", label: "Pure Folie (30)" }
], ],
optionsDistanceTir: [ optionsDistanceTir: [
{ key: "porteecourte", label: "Courte ({protectionDefenseur}+5)" }, { key: "porteecourte", label: "Courte (base +5)" },
{ key: "porteemoyenne", label: "Moyenne ({protectionDefenseur}+9)" }, { key: "porteemoyenne", label: "Moyenne (base +9)" },
{ key: "porteelongue", label: "Longue ({protectionDefenseur}+14)" } { key: "porteelongue", label: "Longue (base +14)" }
], ],
optionsBonusMalus: [ optionsBonusMalus: [
{ key: "-10", label: "-10" }, { key: "-10", label: "-10" },
@@ -109,9 +119,104 @@ export const MOURNBLADECYD2_CONFIG = {
{ key: "personnage", label: "Personnage" }, { key: "personnage", label: "Personnage" },
{ key: "traitespece", label: "Trait d'espèce" } { key: "traitespece", label: "Trait d'espèce" }
], ],
optionsSousTypeCreature: [
{ key: "creature", label: localizeOrFallback("MNBL.creature", "Créature") },
{ key: "demon", label: localizeOrFallback("MNBL.demon", "Démon") },
{ key: "automata", label: localizeOrFallback("MNBL.automata", "Automata") }
],
// Configuration des ActiveEffects
effectTypes: {
bonus: "Bonus",
malus: "Malus",
rune: "Effet de Rune",
don: "Effet de Don",
talent: "Effet de Talent",
trait: "Effet de Trait",
temporaire: "Effet Temporaire",
permanent: "Effet Permanent"
},
// Clés des attributs pour les modifications d'effets
effectAttributeKeys: {
// Attributs
adr: "system.attributs.adr.value",
pui: "system.attributs.pui.value",
cla: "system.attributs.cla.value",
pre: "system.attributs.pre.value",
tre: "system.attributs.tre.value",
// Santé
vigueur: "system.sante.vigueur",
etat: "system.sante.etat",
nbcombativite: "system.sante.nbcombativite",
// Âme
nbame: "system.ame.nbame",
seuilpouvoir: "system.ame.seuilpouvoir",
etatAme: "system.ame.etat",
// Bonne Aventure
bonneaventure: "system.bonneaventure.base",
bonneaventureActuelle: "system.bonneaventure.actuelle",
eclat: "system.eclat.value",
// Combat
initiative: "system.combat.inittotal",
defense: "system.combat.defensetotal",
protection: "system.combat.protectiontotal",
// Adversités
adversiteBleue: "system.adversite.bleue",
adversiteRouge: "system.adversite.rouge",
adversiteNoire: "system.adversite.noire",
// Balance
loi: "system.balance.loi",
chaos: "system.balance.chaos",
aspect: "system.balance.aspect",
// Ressources
ressources: "system.ressources.value",
// Vitesse
vitesse: "system.vitesse.value"
},
// Types de bonus/malus supportés (groupés par catégorie)
effectTypesConfig: {
attribut: {
label: "Attribut",
keys: ["adr", "pui", "cla", "pre", "tre"]
},
sante: {
label: "Santé",
keys: ["vigueur", "etat", "nbcombativite"]
},
ame: {
label: "Âme",
keys: ["nbame", "seuilpouvoir", "etatAme"]
},
combat: {
label: "Combat",
keys: ["initiative", "defense", "protection"]
},
bonneAventure: {
label: "Bonne Aventure",
keys: ["bonneaventure", "bonneaventureActuelle", "eclat"]
},
adversite: {
label: "Adversité",
keys: ["adversiteBleue", "adversiteRouge", "adversiteNoire"]
},
balance: {
label: "Balance",
keys: ["loi", "chaos", "aspect"]
}
},
optionsUseTalent: [ optionsUseTalent: [
{ key: "permanent", label: "Permanent" }, { key: "permanent", label: "Permanent" },
{ key: "sceance", label: "Une fois par scéance" }, { key: "sceance", label: "Une fois par séance" },
{ key: "scenario", label: "Une fois par scénario" }, { key: "scenario", label: "Une fois par scénario" },
{ key: "jour", label: "Une fois par jour" }, { key: "jour", label: "Une fois par jour" },
{ key: "unique", label: "Unique" } { key: "unique", label: "Unique" }
@@ -125,5 +230,23 @@ export const MOURNBLADECYD2_CONFIG = {
{ key: "vigueur", label: "Vigueur" }, { key: "vigueur", label: "Vigueur" },
{ key: "seuilpouvoir", label: "Seuil de Pouvoir" }, { key: "seuilpouvoir", label: "Seuil de Pouvoir" },
{ key: "bonus-defensif", label: "Bonus au Seuil de Défense" } { key: "bonus-defensif", label: "Bonus au Seuil de Défense" }
],
etatsCombativite: [
{ key: 0, label: "Combatif" },
{ key: 1, label: "Éprouvé 1" },
{ key: 2, label: "Éprouvé 2" },
{ key: 3, label: "Affaibli" },
{ key: 4, label: "Très Affaibli" },
{ key: 5, label: "Vaincu" }
],
etatsAme: [
{ key: 0, label: "Serein" },
{ key: 1, label: "Stressé 1" },
{ key: 2, label: "Stressé 2" },
{ key: 3, label: "Stressé 3" },
{ key: 4, label: "Stressé 4" },
{ key: 5, label: "Traumatisé" },
{ key: 6, label: "Très Traumatisé" },
{ key: 7, label: "Brisé" }
] ]
} }
-26
View File
@@ -1,26 +0,0 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
import { MournbladeCYD2ActorSheet } from "./mournblade-cyd2-actor-sheet.js";
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
import { MournbladeCYD2Automation } from "./mournblade-cyd2-automation.js";
/* -------------------------------------------- */
export class MournbladeCYD2CreatureSheet extends MournbladeCYD2ActorSheet {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["fvtt-mournblade-cyd2", "sheet", "actor"],
template: "systems/fvtt-mournblade-cyd2/templates/creature-sheet.html",
width: 640,
height: 720,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
editScore: false
})
}
}
+807
View File
@@ -0,0 +1,807 @@
/**
* Gestion des ActiveEffects pour Mournblade CYD 2.0
* Ce module fournit des utilitaires pour créer, appliquer et gérer les effets actifs
* sur les Acteurs et les Items.
*/
export class MournbladeCYD2Effects {
/**
* Initialise le système de gestion des effets
*/
static init() {
console.log("MournbladeCYD2 | Initializing ActiveEffects management");
// Hook pour appliquer les modifications des effets
Hooks.on("applyActiveEffect", (effect, change, current, delta, changes) => {
return this._onApplyActiveEffect(effect, change, current, delta, changes);
});
// Hook pour supprimer les modifications des effets
Hooks.on("removeActiveEffect", (effect, change, current, delta, changes) => {
return this._onRemoveActiveEffect(effect, change, current, delta, changes);
});
}
/**
* Parse une valeur d'effet en nombre
* Gère les strings comme "+2", "-3", "5"
* @param {string|number} value - Valeur à parser
* @returns {number} - Valeur numérique
* @private
*/
static _parseEffectValue(value) {
if (typeof value === 'number') return value;
if (typeof value !== 'string') return 0;
const trimmed = value.trim();
if (!trimmed) return 0;
if (trimmed.startsWith('+')) {
return parseFloat(trimmed.substring(1)) || 0;
} else if (trimmed.startsWith('-')) {
return -(parseFloat(trimmed.substring(1)) || 0);
}
return parseFloat(trimmed) || 0;
}
/**
* Hook appelé lorsqu'un effet est appliqué
* Permet de personnaliser le calcul des modifications
* @private
*/
static _onApplyActiveEffect(effect, change, current, delta, changes) {
// Pour Mournblade, nous voulons gérer les valeurs string (ex: "+1", "-2")
// Convertir delta en nombre si nécessaire
const numericDelta = this._parseEffectValue(delta);
const numericCurrent = current != null ? Number(current) : 0;
if (!isNaN(numericDelta) && !isNaN(numericCurrent)) {
return numericCurrent + numericDelta;
}
// Si on ne peut pas calculer, retourner delta tel quel
return delta;
}
/**
* Hook appelé lorsqu'un effet est supprimé
* @private
*/
static _onRemoveActiveEffect(effect, change, current, delta, changes) {
// Logique inverse de l'application
// Foundry gère déjà la suppression, ce hook est pour des calculs personnalisés
const numericDelta = this._parseEffectValue(delta);
const numericCurrent = current != null ? Number(current) : 0;
if (!isNaN(numericDelta) && !isNaN(numericCurrent)) {
return numericCurrent - numericDelta;
}
return current;
}
/* -------------------------------------------- */
/* Méthodes de création d'effets */
/* -------------------------------------------- */
/**
* Crée un effet simple de bonus/malus à un attribut
* @param {string} name - Nom de l'effet
* @param {string} attribute - Attribut cible (adr, pui, cla, pre, tre, vigueur, etc.)
* @param {number|string} value - Valeur du bonus/malus
* @param {object} options - Options supplémentaires
* @returns {Object|null} - Données de l'effet ou null
*/
static createSimpleEffect(name, attribute, value, options = {}) {
// Validation des paramètres
if (!name || typeof name !== "string") {
console.warn("MournbladeCYD2 | Effect name must be a non-empty string");
return null;
}
if (value == null) {
console.warn("MournbladeCYD2 | Effect value cannot be null or undefined");
return null;
}
const attributeKey = this.getAttributeKey(attribute);
if (!attributeKey) {
console.warn(`MournbladeCYD2 | Unknown attribute: ${attribute}`);
return null;
}
// Normaliser la valeur en string
const valueString = String(value).trim();
return {
name: name.trim(),
icon: options.icon || "systems/fvtt-mournblade-cyd-2-0/assets/icons/capacite.webp",
description: (options.description || "").trim(),
changes: [
{
key: attributeKey,
mode: CONST.ActiveEffect.MODES.ADD,
value: valueString,
priority: options.priority ?? 0
}
],
disabled: Boolean(options.disabled),
duration: options.duration || {},
origin: options.origin || null,
tint: options.tint || "",
transfer: options.transfer !== false,
statuses: options.statuses || [],
flags: options.flags || {}
};
}
/**
* Crée un effet de bonus permanent
* @param {string} name - Nom de l'effet
* @param {string} attribute - Attribut cible
* @param {number|string} value - Valeur du bonus
* @returns {Object} - Données de l'effet
*/
static createPermanentEffect(name, attribute, value) {
return this.createSimpleEffect(name, attribute, value, {
duration: {},
type: "base"
});
}
/**
* Crée un effet temporaire (rounds, turns, etc.)
* @param {string} name - Nom de l'effet
* @param {string} attribute - Attribut cible
* @param {number|string} value - Valeur du bonus/malus
* @param {string} durationType - Type de durée (rounds, turns, seconds, combat)
* @param {number} durationValue - Valeur de la durée
* @returns {Object} - Données de l'effet
*/
static createTemporaryEffect(name, attribute, value, durationType, durationValue) {
return this.createSimpleEffect(name, attribute, value, {
duration: { type: durationType, value: durationValue },
type: "temp"
});
}
/**
* Crée un effet avec plusieurs modifications
* @param {string} name - Nom de l'effet
* @param {Array} changes - Array de modifications {key, mode, value}
* @param {object} options - Options supplémentaires
* @returns {Object} - Données de l'effet
*/
static createMultiEffect(name, changes, options = {}) {
return {
name: name,
icon: options.icon || "systems/fvtt-mournblade-cyd-2-0/assets/icons/capacite.webp",
description: options.description || "",
changes: changes.map(c => ({
key: c.key,
mode: c.mode || CONST.ActiveEffect.MODES.ADD,
value: c.value.toString(),
priority: c.priority || 0
})),
disabled: options.disabled || false,
duration: options.duration || {},
origin: options.origin || null,
tint: options.tint || "",
transfer: options.transfer !== false
};
}
/* -------------------------------------------- */
/* Méthodes d'application d'effets */
/* -------------------------------------------- */
/**
* Applique un effet à un acteur
* @param {Actor} actor - L'acteur cible
* @param {Object|ActiveEffect} effectData - Données de l'effet ou instance ActiveEffect
* @returns {Promise<ActiveEffect|null>} - L'effet créé ou null
*/
static async applyEffectToActor(actor, effectData) {
if (!actor || !actor.canUserModify(game.user, "update")) return null;
let effect;
if (effectData instanceof foundry.documents.ActiveEffect) {
effect = effectData;
} else if (effectData?.toObject) {
effect = effectData;
} else {
effect = new CONFIG.ActiveEffect.documentClass(effectData);
}
try {
const createdEffects = await actor.createEmbeddedDocuments("ActiveEffect", [effect.toObject()]);
return createdEffects[0];
} catch (error) {
console.error("MournbladeCYD2 | Failed to apply effect:", error);
ui.notifications?.error(
game.i18n?.localize("EFFECT.applyError") ||
`Erreur: Impossible d'appliquer l'effet (${error.message})`
);
return null;
}
}
/**
* Applique les effets d'un item à un acteur
* @param {Item} item - L'item source
* @param {Actor} actor - L'acteur cible
* @returns {Promise<Array<ActiveEffect>>} - Liste des effets appliqués
*/
static async applyItemEffectsToActor(item, actor) {
if (!item?.effects?.length || !actor) return [];
if (!actor.canUserModify(game.user, "update")) return [];
const effectsToApply = [];
for (const effectData of item.effects) {
// Par défaut, appliquer automatiquement SAUF si explicitement désactivé
const autoApply = effectData.getFlag("mournblade-cyd2", "autoApply");
if (autoApply !== false) {
effectsToApply.push({
...effectData.toObject(),
origin: item.uuid,
name: `${item.name}: ${effectData.name}`
});
}
}
if (effectsToApply.length === 0) return [];
try {
const createdEffects = await actor.createEmbeddedDocuments("ActiveEffect", effectsToApply);
return createdEffects;
} catch (error) {
console.error("MournbladeCYD2 | Failed to apply item effects:", error);
ui.notifications?.error(
game.i18n?.localize("EFFECT.applyItemError") ||
`Erreur: Impossible d'appliquer les effets de l'item`
);
return [];
}
}
/* -------------------------------------------- */
/* Méthodes de gestion d'effets */
/* -------------------------------------------- */
/**
* Désactive un effet
* @param {Actor|Item} owner - Le propriétaire de l'effet
* @param {string} effectId - ID de l'effet
* @returns {Promise<ActiveEffect|null>} - L'effet désactivé
*/
static async disableEffect(owner, effectId) {
if (!owner?.canUserModify(game.user, "update")) return null;
const effect = owner.effects.get(effectId);
if (!effect) return null;
await effect.update({ disabled: true });
return effect;
}
/**
* Active un effet
* @param {Actor|Item} owner - Le propriétaire de l'effet
* @param {string} effectId - ID de l'effet
* @returns {Promise<ActiveEffect|null>} - L'effet activé
*/
static async enableEffect(owner, effectId) {
if (!owner?.canUserModify(game.user, "update")) return null;
const effect = owner.effects.get(effectId);
if (!effect) return null;
await effect.update({ disabled: false });
return effect;
}
/**
* Toggle l'état d'un effet (actif/désactivé)
* @param {Actor|Item} owner - Le propriétaire de l'effet
* @param {string} effectId - ID de l'effet
* @returns {Promise<ActiveEffect|null>} - L'effet togglé
*/
static async toggleEffect(owner, effectId) {
if (!owner?.canUserModify(game.user, "update")) return null;
const effect = owner.effects.get(effectId);
if (!effect) return null;
await effect.update({ disabled: !effect.disabled });
return effect;
}
/**
* Supprime un effet
* @param {Actor|Item} owner - Le propriétaire de l'effet
* @param {string} effectId - ID de l'effet
* @returns {Promise<ActiveEffect|null>} - L'effet supprimé
*/
static async deleteEffect(owner, effectId) {
if (!owner?.canUserModify(game.user, "delete")) return null;
const effect = owner.effects.get(effectId);
if (!effect) return null;
await owner.deleteEmbeddedDocuments("ActiveEffect", [effectId]);
return effect;
}
/* -------------------------------------------- */
/* Méthodes utilitaires */
/* -------------------------------------------- */
/**
* Obtient la clé complète pour un attribut
* @param {string} attribute - Attribut court (adr, pui, cla, pre, tre, vigueur, etc.)
* @returns {string|null} - Clé complète ou null
*/
static getAttributeKey(attribute) {
if (!attribute) return null;
const config = game.system.mournbladecyd2?.config;
if (!config?.effectAttributeKeys) return null;
// Normaliser en minuscules pour correspondre à la config
const normalizedAttribute = attribute.toLowerCase().trim();
return config.effectAttributeKeys[normalizedAttribute] || null;
}
/**
* Obtient tous les attributs modifiables
* @returns {Object} - Map des attributs courts vers les clés complètes
*/
static getAllAttributeKeys() {
const config = game.system.mournbladecyd2?.config;
return config?.effectAttributeKeys || {};
}
/**
* Obtient les effets actifs d'un acteur
* @param {Actor} actor - L'acteur
* @returns {Array<ActiveEffect>} - Liste des effets actifs (non désactivés)
*/
static getActiveEffects(actor) {
if (!actor?.effects) return [];
return actor.effects.filter(e => !e.disabled);
}
/**
* Obtient les effets désactivés d'un acteur
* @param {Actor} actor - L'acteur
* @returns {Array<ActiveEffect>} - Liste des effets désactivés
*/
static getDisabledEffects(actor) {
if (!actor?.effects) return [];
return actor.effects.filter(e => e.disabled);
}
/**
* Obtient les effets par origine
* @param {Actor} actor - L'acteur
* @param {string} originUuid - UUID de l'origine
* @returns {Array<ActiveEffect>} - Liste des effets de cette origine
*/
static getEffectsByOrigin(actor, originUuid) {
if (!actor?.effects) return [];
return actor.effects.filter(e => e.origin === originUuid);
}
/**
* Obtient les effets temporaires en cours
* @param {Actor} actor - L'acteur
* @returns {Array<ActiveEffect>} - Liste des effets temporaires actifs
*/
static getActiveTemporaryEffects(actor) {
if (!actor?.effects) return [];
return actor.effects.filter(e => !e.disabled && e.duration?.type);
}
/**
* Calcule la valeur totale des modifications pour une clé donnée
* @param {Actor} actor - L'acteur
* @param {string} key - Clé à vérifier
* @returns {number} - Somme des modifications
*/
static getTotalModificationForKey(actor, key) {
if (!actor?.effects) return 0;
let total = 0;
for (const effect of actor.effects) {
if (effect.disabled) continue;
for (const change of effect.changes || []) {
if (change.key === key && change.mode === CONST.ActiveEffect.MODES.ADD) {
total += Number(change.value) || 0;
}
}
}
return total;
}
/**
* Obtient toutes les modifications actives groupées par clé
* @param {Actor} actor - L'acteur
* @returns {Object} - Objet avec les clés et les valeurs totales
*/
static getAllActiveModifications(actor) {
if (!actor?.effects) return {};
const modifications = {};
for (const effect of actor.effects) {
if (effect.disabled) continue;
for (const change of effect.changes || []) {
if (!modifications[change.key]) {
modifications[change.key] = {
value: 0,
effects: []
};
}
// Appliquer selon le mode
const numericValue = Number(change.value) || 0;
switch (change.mode) {
case CONST.ActiveEffect.MODES.ADD:
modifications[change.key].value += numericValue;
break;
case CONST.ActiveEffect.MODES.OVERRIDE:
modifications[change.key].value = numericValue;
modifications[change.key].overridden = true;
break;
case CONST.ActiveEffect.MODES.MULTIPLY:
// Ne peut pas être additionné, stocké séparément
if (!modifications[change.key].multipliers) {
modifications[change.key].multipliers = [];
}
modifications[change.key].multipliers.push(numericValue);
break;
}
modifications[change.key].effects.push(effect.name);
}
}
return modifications;
}
/* -------------------------------------------- */
/* Méthodes de création d'effets prédéfinis */
/* -------------------------------------------- */
/**
* Crée un effet de bonus d'attribut
* @param {string} attribute - Attribut (adr, pui, cla, pre, tre)
* @param {number} value - Valeur du bonus
* @param {string} source - Source de l'effet
* @returns {Object} - Données de l'effet
*/
static createAttributeBonusEffect(attribute, value, source = "Effet") {
const attrNames = {
adr: "Adresse",
pui: "Puissance",
cla: "Clairvoyance",
pre: "Présence",
tre: "Trempe"
};
return this.createSimpleEffect(
`${source}: Bonus de ${attrNames[attribute] || attribute}`,
attribute,
`+${value}`,
{
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/attributs.webp",
type: "base"
}
);
}
/**
* Crée un effet de malus d'attribut
* @param {string} attribute - Attribut
* @param {number} value - Valeur du malus (positif)
* @param {string} source - Source de l'effet
* @returns {Object} - Données de l'effet
*/
static createAttributeMalusEffect(attribute, value, source = "Effet") {
const attrNames = {
adr: "Adresse",
pui: "Puissance",
cla: "Clairvoyance",
pre: "Présence",
tre: "Trempe"
};
return this.createSimpleEffect(
`${source}: Malus de ${attrNames[attribute] || attribute}`,
attribute,
`-${value}`,
{
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/malus.webp",
type: "base"
}
);
}
/**
* Crée un effet de bonus à la Vigueur
* @param {number} value - Valeur du bonus
* @param {string} source - Source de l'effet
* @returns {Object} - Données de l'effet
*/
static createVigueurBonusEffect(value, source = "Effet") {
return this.createSimpleEffect(
`${source}: Bonus de Vigueur`,
"vigueur",
`+${value}`,
{
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/vigueur.webp",
type: "base"
}
);
}
/**
* Crée un effet de bonus au Seuil de Pouvoir
* @param {number} value - Valeur du bonus
* @param {string} source - Source de l'effet
* @returns {Object} - Données de l'effet
*/
static createSeuilPouvoirBonusEffect(value, source = "Effet") {
return this.createSimpleEffect(
`${source}: Bonus au Seuil de Pouvoir`,
"seuilPouvoir",
`+${value}`,
{
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/ame.webp",
type: "base"
}
);
}
/**
* Crée un effet de bonus à la Bonne Aventure
* @param {number} value - Valeur du bonus
* @param {string} source - Source de l'effet
* @returns {Object} - Données de l'effet
*/
static createBonneAventureBonusEffect(value, source = "Effet") {
return this.createSimpleEffect(
`${source}: Bonus de Bonne Aventure`,
"bonneAventure",
`+${value}`,
{
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/bonneaventure.webp",
type: "base"
}
);
}
/**
* Crée un effet de bonus à l'Initiative
* @param {number} value - Valeur du bonus
* @param {string} source - Source de l'effet
* @returns {Object} - Données de l'effet
*/
static createInitiativeBonusEffect(value, source = "Effet") {
return this.createSimpleEffect(
`${source}: Bonus d'Initiative`,
"initiative",
`+${value}`,
{
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/initiative.webp",
type: "temp",
duration: { type: "rounds", value: 1 }
}
);
}
/**
* Crée un effet de bonus à la Défense
* @param {number} value - Valeur du bonus
* @param {string} source - Source de l'effet
* @returns {Object} - Données de l'effet
*/
static createDefenseBonusEffect(value, source = "Effet") {
return this.createSimpleEffect(
`${source}: Bonus de Défense`,
"defense",
`+${value}`,
{
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/defense.webp",
type: "temp",
duration: { type: "rounds", value: 1 }
}
);
}
/**
* Crée un effet de bonus à la Protection
* @param {number} value - Valeur du bonus
* @param {string} source - Source de l'effet
* @returns {Object} - Données de l'effet
*/
static createProtectionBonusEffect(value, source = "Effet") {
return this.createSimpleEffect(
`${source}: Bonus de Protection`,
"protection",
`+${value}`,
{
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/protection.webp",
type: "base"
}
);
}
/* -------------------------------------------- */
/* Méthodes de gestion des statuts */
/* -------------------------------------------- */
/**
* Crée un effet qui applique un statut
* @param {string} status - Nom du statut
* @param {string} source - Source de l'effet
* @returns {Object} - Données de l'effet
*/
static createStatusEffect(status, source = "Effet") {
return {
name: `${source}: ${status}`,
icon: `systems/fvtt-mournblade-cyd-2-0/assets/icons/status_${status.toLowerCase()}.webp`,
description: `Applique le statut ${status}`,
changes: [],
statuses: [status],
disabled: false,
duration: {},
origin: null,
tint: "",
transfer: true
};
}
/**
* Crée un effet d'adversité bleue
* @param {number} value - Nombre d'adversités
* @returns {Object|null} - Données de l'effet ou null
*/
static createAdversiteBleueEffect(value) {
if (value == null) return null;
return this.createSimpleEffect(
`Adversité Bleue: +${value}`,
"adversite.bleue",
value,
{
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/gemme_bleue.webp",
duration: { type: "rounds", value: 1 },
statuses: ["adversite-bleue"]
}
);
}
/**
* Crée un effet d'adversité rouge
* @param {number} value - Nombre d'adversités
* @returns {Object|null} - Données de l'effet ou null
*/
static createAdversiteRougeEffect(value) {
if (value == null) return null;
return this.createSimpleEffect(
`Adversité Rouge: +${value}`,
"adversite.rouge",
value,
{
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/gemme_rouge.webp",
duration: { type: "rounds", value: 1 },
statuses: ["adversite-rouge"]
}
);
}
/**
* Crée un effet d'adversité noire
* @param {number} value - Nombre d'adversités
* @returns {Object|null} - Données de l'effet ou null
*/
static createAdversiteNoireEffect(value) {
if (value == null) return null;
return this.createSimpleEffect(
`Adversité Noire: +${value}`,
"adversite.noire",
value,
{
icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/gemme_noire.webp",
duration: { type: "rounds", value: 1 },
statuses: ["adversite-noire"]
}
);
}
/* -------------------------------------------- */
/* Méthodes pour les Runes */
/* -------------------------------------------- */
/**
* Crée un effet de Rune prononcée
* @param {Object} rune - Données de la rune
* @param {number} pointsAme - Points de pouvoir dépensés
* @returns {Object|null} - Données de l'effet ou null
*/
static createRunePrononceeEffect(rune, pointsAme) {
if (!rune || !rune.name || pointsAme == null) return null;
// Utiliser une icône par défaut si l'image de la rune est l'image par défaut
const icon = rune.img?.includes('/blank.png') || !rune.img
? "systems/fvtt-mournblade-cyd-2-0/assets/icons/rune.webp"
: rune.img;
return {
name: `Rune: ${rune.name} (Prononcée)`,
icon: icon,
description: rune.system?.description || "",
changes: [], // Les modifications spécifiques peuvent être ajoutées par les appels
disabled: false,
duration: { type: "rounds", value: Math.ceil(pointsAme / 3) },
origin: rune.uuid || null,
tint: "#00ff00",
transfer: true,
flags: {
"mournblade-cyd2": {
runeId: rune._id,
runeType: "prononcee",
pointsAme: pointsAme
}
}
};
}
/**
* Crée un effet de Rune tracée
* @param {Object} rune - Données de la rune
* @param {number} pointsAme - Points de pouvoir dépensés
* @returns {Object|null} - Données de l'effet ou null
*/
static createRuneTraceeEffect(rune, pointsAme) {
if (!rune || !rune.name || pointsAme == null) return null;
// Utiliser une icône par défaut si l'image de la rune est l'image par défaut
const icon = rune.img?.includes('/blank.png') || !rune.img
? "systems/fvtt-mournblade-cyd-2-0/assets/icons/rune.webp"
: rune.img;
return {
name: `Rune: ${rune.name} (Tracée)`,
icon: icon,
description: rune.system?.description || "",
changes: [], // Les modifications spécifiques peuvent être ajoutées par les appels
disabled: false,
duration: { type: "rounds", value: Math.ceil(pointsAme / 3) * 2 },
origin: rune.uuid || null,
tint: "#0000ff",
transfer: true,
flags: {
"mournblade-cyd2": {
runeId: rune._id,
runeType: "tracee",
pointsAme: pointsAme
}
}
};
}
}
// Initialisation automatique
Hooks.once("init", () => {
MournbladeCYD2Effects.init();
});
+5 -1
View File
@@ -17,6 +17,10 @@ export class MournbladeCYD2TokenHud {
static async addExtensionHud(app, html, tokenId) { static async addExtensionHud(app, html, tokenId) {
let token = canvas.tokens.get(tokenId) let token = canvas.tokens.get(tokenId)
if (!token) {
console.warn("MournbladeCYD2TokenHud.addExtensionHud : token introuvable", tokenId)
return
}
let actor = token.actor let actor = token.actor
app.hasExtension = true app.hasExtension = true
@@ -24,7 +28,7 @@ export class MournbladeCYD2TokenHud {
const controlIconActions = $(html).find('.control-icon[data-action=combat]'); const controlIconActions = $(html).find('.control-icon[data-action=combat]');
// initiative // initiative
await MournbladeCYD2TokenHud._configureSubMenu(controlIconActions, 'systems/fvtt-mournblade-cyd2/templates/hud-adversites.html', hudData, await MournbladeCYD2TokenHud._configureSubMenu(controlIconActions, 'systems/fvtt-mournblade-cyd-2-0/templates/hud-adversites.hbs', hudData,
(event) => { (event) => {
let adversite = event.currentTarget.attributes['data-action-index'].value let adversite = event.currentTarget.attributes['data-action-index'].value
let value = Number(event.currentTarget.attributes['data-action-value'].value) let value = Number(event.currentTarget.attributes['data-action-value'].value)
-228
View File
@@ -1,228 +0,0 @@
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
*/
export class MournbladeCYD2ItemSheet extends foundry.appv1.sheets.ItemSheet {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["fvtt-mournblade-cyd2", "sheet", "item"],
template: "systems/fvtt-mournblade-cyd2/templates/item-sheet.html",
dragDrop: [{ dragSelector: null, dropSelector: null }],
width: 620,
height: 550,
tabs: [{navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description"}]
});
}
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
// Add "Post to chat" button
// We previously restricted this to GM and editable items only. If you ever find this comment because it broke something: eh, sorry!
buttons.unshift(
{
class: "post",
icon: "fas fa-comment",
onclick: ev => { }
})
return buttons
}
/* -------------------------------------------- */
/** @override */
setPosition(options = {}) {
const position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body");
const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
if (this.item.type.includes('weapon')) {
position.width = 640;
}
return position;
}
/* -------------------------------------------- */
async getData() {
const objectData = foundry.utils.duplicate(this.object)
let formData = {
title: this.title,
id: this.id,
type: objectData.type,
img: objectData.img,
name: objectData.name,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
attributs: MournbladeCYD2Utility.getAttributs(),
system: objectData.system,
limited: this.object.limited,
options: this.options,
owner: this.document.isOwner,
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.description, {async: true}),
mr: (this.object.type == 'specialisation'),
isGM: game.user.isGM,
config: game.system.mournbladecyd2.config
}
if ( objectData.type == "don") {
formData.sacrifice = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.sacrifice, {async: true})
}
//this.options.editable = !(this.object.origin == "embeddedItem");
console.log("ITEM DATA", formData, this);
return formData;
}
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
buttons.unshift({
class: "post",
icon: "fas fa-comment",
onclick: ev => this.postItem()
});
return buttons
}
/* -------------------------------------------- */
postItem() {
let chatData = foundry.utils.duplicate(MournbladeCYD2Utility.data(this.item));
if (this.actor) {
chatData.actor = { id: this.actor.id };
}
// Don't post any image for the item (which would leave a large gap) if the default image is used
if (chatData.img.includes("/blank.png")) {
chatData.img = null;
}
// JSON object for easy creation
chatData.jsondata = JSON.stringify(
{
compendium: "postedItem",
payload: chatData,
});
renderTemplate('systems/fvtt-mournblade-cyd2/templates/post-item.html', chatData).then(html => {
let chatOptions = MournbladeCYD2Utility.chatDataSetup(html);
ChatMessage.create(chatOptions)
});
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item")
const item = this.object.options.actor.getOwnedItem(li.data("item-id"))
item.sheet.render(true);
});
html.find('.delete-subitem').click(ev => {
this.deleteSubitem(ev);
})
html.find('.edit-predilection').change(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred[index].name = ev.currentTarget.value
pred[index].id = pred[index].id || randomID(16)
this.object.update( { 'system.predilections': pred })
})
html.find('.edit-predilection-description').change(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred[index].description = ev.currentTarget.value
pred[index].id = pred[index].id || randomID(16)
this.object.update( { 'system.predilections': pred })
})
html.find('.predilection-acquise').change(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred[index].acquise = ev.currentTarget.checked
pred[index].id = pred[index].id || randomID(16)
this.object.update( { 'system.predilections': pred })
})
html.find('.predilection-maitrise').change(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred[index].maitrise = ev.currentTarget.checked
pred[index].id = pred[index].id || randomID(16)
this.object.update( { 'system.predilections': pred })
})
html.find('.predilection-used').change(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred[index].used = ev.currentTarget.checked
pred[index].id = pred[index].id || randomID(16)
this.object.update( { 'system.predilections': pred })
})
html.find('#add-predilection').click(ev => {
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred.push( { name: "Nouvelle prédilection", id: randomID(16), used: false })
this.object.update( { 'system.predilections': pred })
})
html.find('.delete-prediction').click(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred.splice(index,1)
this.object.update( { 'system.predilections': pred })
})
html.find('#add-automation').click(ev => {
let autom = foundry.utils.duplicate(this.object.system.automations)
autom.push( { eventtype: "on-drop", name: "Automatisation 1", bonusname: "vigueur", bonus: 0, competence: "", minLevel: 0, baCost: 0, id: randomID(16) })
this.object.update( { 'system.automations': autom })
})
html.find('.delete-automation').click(ev => {
const li = $(ev.currentTarget).parents(".automation-item")
let index = li.data("automation-index")
let autom = foundry.utils.duplicate(this.object.system.automations)
autom.splice(index,1)
this.object.update( { 'system.automations': autom })
})
html.find('.automation-edit-field').change(ev => {
let index = $(ev.currentTarget).data("automation-index")
let field = $(ev.currentTarget).data("automation-field")
let auto = foundry.utils.duplicate(this.object.system.automations)
auto[index][field] = ev.currentTarget.value
auto[index].id = auto[index].id || randomID(16)
this.object.update( { 'system.automations': auto })
})
// Update Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
let itemId = li.data("item-id");
let itemType = li.data("item-type");
});
}
/* -------------------------------------------- */
get template() {
let type = this.item.type;
return `systems/fvtt-mournblade-cyd2/templates/item-${type}-sheet.html`;
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
return this.object.update(formData);
}
}
+16 -16
View File
@@ -1,22 +1,22 @@
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js"; import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
export const defaultItemImg = { export const defaultItemImg = {
competence: "systems/fvtt-mournblade-cyd2/assets/icons/competence.webp", competence: "systems/fvtt-mournblade-cyd-2-0/assets/icons/competence.webp",
arme: "systems/fvtt-mournblade-cyd2/assets/icons/melee.webp", arme: "systems/fvtt-mournblade-cyd-2-0/assets/icons/melee.webp",
equipement: "systems/fvtt-mournblade-cyd2/assets/icons/equipement.webp", equipement: "systems/fvtt-mournblade-cyd-2-0/assets/icons/equipement.webp",
monnaie: "systems/fvtt-mournblade-cyd2/assets/icons/monnaie.webp", monnaie: "systems/fvtt-mournblade-cyd-2-0/assets/icons/monnaie.webp",
predilection: "systems/fvtt-mournblade-cyd2/assets/icons/predilection.webp", predilection: "systems/fvtt-mournblade-cyd-2-0/assets/icons/competence.webp",
protection: "systems/fvtt-mournblade-cyd2/assets/icons/protection.webp", protection: "systems/fvtt-mournblade-cyd-2-0/assets/icons/protection.webp",
talent: "systems/fvtt-mournblade-cyd2/assets/icons/talent.webp", talent: "systems/fvtt-mournblade-cyd-2-0/assets/icons/talent.webp",
historique: "systems/fvtt-mournblade-cyd2/assets/icons/historique.webp", historique: "systems/fvtt-mournblade-cyd-2-0/assets/icons/historique.webp",
profil: "systems/fvtt-mournblade-cyd2/assets/icons/profil.webp", profil: "systems/fvtt-mournblade-cyd-2-0/assets/icons/profil.webp",
ressource: "systems/fvtt-mournblade-cyd2/assets/icons/ressources.webp", ressource: "systems/fvtt-mournblade-cyd-2-0/assets/icons/ressources.webp",
traitchaotique: "systems/fvtt-mournblade-cyd2/assets/icons/traitchaotique.webp", traitchaotique: "systems/fvtt-mournblade-cyd-2-0/assets/icons/traitchaotique.webp",
traitespece: "systems/fvtt-mournblade-cyd2/assets/icons/traitespece.webp", traitespece: "systems/fvtt-mournblade-cyd-2-0/assets/icons/traitespece.webp",
don: "systems/fvtt-mournblade-cyd2/assets/icons/don.webp", don: "systems/fvtt-mournblade-cyd-2-0/assets/icons/don.webp",
pacte: "systems/fvtt-mournblade-cyd2/assets/icons/pacte.webp", pacte: "systems/fvtt-mournblade-cyd-2-0/assets/icons/pacte.webp",
rune: "systems/fvtt-mournblade-cyd2/assets/icons/rune.webp", rune: "systems/fvtt-mournblade-cyd-2-0/assets/icons/rune.webp",
tendance: "systems/fvtt-mournblade-cyd2/assets/icons/tendance.webp", tendance: "systems/fvtt-mournblade-cyd-2-0/assets/icons/tendance.webp",
} }
/** /**
+95 -18
View File
@@ -9,15 +9,17 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
// Import Modules // Import Modules
import { MournbladeCYD2Actor } from "./mournblade-cyd2-actor.js"; import { MournbladeCYD2Actor } from "./mournblade-cyd2-actor.js";
import { MournbladeCYD2ItemSheet } from "./mournblade-cyd2-item-sheet.js"; import * as sheets from "./applications/sheets/_module.mjs";
import { MournbladeCYD2ActorSheet } from "./mournblade-cyd2-actor-sheet.js";
import { MournbladeCYD2CreatureSheet } from "./mournblade-cyd2-creature-sheet.js";
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js"; import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
import { MournbladeCYD2Combat } from "./mournblade-cyd2-combat.js"; import { MournbladeCYD2Combat } from "./mournblade-cyd2-combat.js";
import { MournbladeCYD2Item } from "./mournblade-cyd2-item.js"; import { MournbladeCYD2Item } from "./mournblade-cyd2-item.js";
import { MournbladeCYD2Automation } from "./mournblade-cyd2-automation.js"; import { MournbladeCYD2Automation } from "./mournblade-cyd2-automation.js";
import { MournbladeCYD2TokenHud } from "./mournblade-cyd2-hud.js"; import { MournbladeCYD2TokenHud } from "./mournblade-cyd2-hud.js";
import { MOURNBLADECYD2_CONFIG } from "./mournblade-cyd2-config.js"; import { MOURNBLADECYD2_CONFIG } from "./mournblade-cyd2-config.js";
import { MournbladeCYD2Effects } from "./mournblade-cyd2-effects.js";
// Import DataModels
import * as models from "./models/index.mjs";
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Foundry VTT Initialization */ /* Foundry VTT Initialization */
@@ -39,29 +41,72 @@ Hooks.once("init", async function () {
}; };
/* -------------------------------------------- */ /* -------------------------------------------- */
game.socket.on("system.fvtt-mournblade-cyd2", data => { game.socket.on("system.fvtt-mournblade-cyd-2-0", data => {
MournbladeCYD2Utility.onSocketMesssage(data) MournbladeCYD2Utility.onSocketMessage(data)
}); });
/* -------------------------------------------- */ /* -------------------------------------------- */
// Define custom Entity classes // Define custom Entity classes
CONFIG.Combat.documentClass = MournbladeCYD2Combat CONFIG.Combat.documentClass = MournbladeCYD2Combat
CONFIG.Actor.documentClass = MournbladeCYD2Actor CONFIG.Actor.documentClass = MournbladeCYD2Actor
CONFIG.Actor.dataModels = {
personnage: models.PersonnageDataModel,
creature: models.CreatureDataModel
}
CONFIG.Item.documentClass = MournbladeCYD2Item CONFIG.Item.documentClass = MournbladeCYD2Item
CONFIG.Item.dataModels = {
talent: models.TalentDataModel,
historique: models.HistoriqueDataModel,
profil: models.ProfilDataModel,
competence: models.CompetenceDataModel,
arme: models.ArmeDataModel,
protection: models.ProtectionDataModel,
monnaie: models.MonnaieDataModel,
equipement: models.EquipementDataModel,
ressource: models.RessourceDataModel,
don: models.DonDataModel,
pacte: models.PacteDataModel,
rune: models.RuneDataModel,
runeeffect: models.RuneEffectDataModel,
tendance: models.TendanceDataModel,
traitchaotique: models.TraitChaotiqueDataModel,
traitespece: models.TraitEspeceDataModel,
traitdemoniaque: models.TraitDemoniaqueDataModel,
pouvoirselementaire: models.PouvoirElementaireDataModel,
capaciteautomata: models.CapaciteAutomataDataModel
}
game.system.mournbladecyd2 = { game.system.mournbladecyd2 = {
MournbladeCYD2Utility, MournbladeCYD2Utility,
MournbladeCYD2Automation, MournbladeCYD2Automation,
MournbladeCYD2Effects,
config: MOURNBLADECYD2_CONFIG config: MOURNBLADECYD2_CONFIG
} }
/* -------------------------------------------- */ // Register sheet application classes (AppV2)
// Regster sheet application classes
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet); foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
foundry.documents.collections.Actors.registerSheet("fvtt-mournblade-cyd2", MournbladeCYD2ActorSheet, { types: ["personnage"], makeDefault: true }) foundry.documents.collections.Actors.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2PersonnageSheet, { types: ["personnage"], makeDefault: true });
foundry.documents.collections.Actors.registerSheet("fvtt-mournblade-cyd2", MournbladeCYD2CreatureSheet, { types: ["creature"], makeDefault: true }) foundry.documents.collections.Actors.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2CreatureSheet, { types: ["creature"], makeDefault: true });
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet); foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd2", MournbladeCYD2ItemSheet, { makeDefault: true }) foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2CompetenceSheet, { types: ["competence"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2TalentSheet, { types: ["talent"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2HistoriqueSheet, { types: ["historique"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2ProfilSheet, { types: ["profil"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2ArmeSheet, { types: ["arme"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2ProtectionSheet, { types: ["protection"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2MonnaieSheet, { types: ["monnaie"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2EquipementSheet, { types: ["equipement"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2RessourceSheet, { types: ["ressource"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2DonSheet, { types: ["don"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2PacteSheet, { types: ["pacte"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2RuneSheet, { types: ["rune"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2RuneEffectSheet, { types: ["runeeffect"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2TendanceSheet, { types: ["tendance"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2TraitChaotiqueSheet, { types: ["traitchaotique"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2TraitEspeceSheet, { types: ["traitespece"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2TraitDemoniaqueSheet, { types: ["traitdemoniaque"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2PouvoirElementaireSheet, { types: ["pouvoirselementaire"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd-2-0", sheets.MournbladeCYD2CapaciteAutomataSheet, { types: ["capaciteautomata"], makeDefault: true });
MournbladeCYD2Utility.init() MournbladeCYD2Utility.init()
MournbladeCYD2Automation.init() MournbladeCYD2Automation.init()
@@ -74,19 +119,51 @@ function welcomeMessage() {
ChatMessage.create({ ChatMessage.create({
user: game.user.id, user: game.user.id,
whisper: [game.user.id], whisper: [game.user.id],
content: `<div id="welcome-message-MournbladeCYD2"><span class="rdd-roll-part"> content: `<div class="mournblade-welcome-message">
<strong>Bienvenue dans Mournblade CYD 2.0 !</strong> <div class="welcome-header">
<p>Les livres de Mournblade sont nécessaires pour jouer : https://www.titam-france.fr ainsi que le supplément de conversion de règle pour le CYD 2.0</p> <div class="welcome-icon"><i class="fas fa-sword"></i></div>
<p>Mournblade est un jeu de rôle publié par Titam France/Sombres projets, tous les droits leur appartiennent.</p> <h2 class="welcome-title">Mournblade CYD 2.0</h2>
<p>Système développé par LeRatierBretonnien, support sur le <a href="https://discord.gg/pPSDNJk">Discord FR de Foundry</a>.</p> <div class="welcome-subtitle">Système FoundryVTT — Bienvenue !</div>
` }); </div>
<div class="welcome-content">
<div class="welcome-section">
<div class="section-icon"><i class="fas fa-book-open"></i></div>
<div class="section-text">
<strong>Livres nécessaires</strong>
<p>Les livres de Mournblade sont nécessaires pour jouer. Supplément de conversion CYD 2.0 requis.</p>
<a class="welcome-link" href="https://www.lahiette.com/leratierbretonnien/mournblade-et-le-cyd-2-0/" target="_blank"><i class="fas fa-external-link-alt"></i>Règles PAO 0.9</a>
</div>
</div>
<div class="welcome-section">
<div class="section-icon"><i class="fas fa-copyright"></i></div>
<div class="section-text">
<strong>Droits</strong>
<p>Mournblade est un jeu Titam.</p>
</div>
</div>
<div class="welcome-section">
<div class="section-icon"><i class="fab fa-discord"></i></div>
<div class="section-text">
<strong>Support</strong>
<p>Système développé par LeRatierBretonnien.</p>
<a class="welcome-link" href="https://discord.gg/pPSDNJk" target="_blank"><i class="fab fa-discord"></i>Discord FR Foundry</a>
<a class="welcome-link" href="https://www.uberwald.me" target="_blank"><i class="fas fa-external-link-alt"></i>Uberwald</a>
</div>
</div>
</div>
<div class="welcome-footer">
<i class="fas fa-dice-d20"></i>
<span>Bonne partie !</span>
</div>
</div>`
});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async function importDefaultScene() { async function importDefaultScene() {
let exists = game.scenes.find(j => j.name == "Accueil"); let exists = game.scenes.find(j => j.name == "Accueil");
if (!exists) { if (!exists) {
const scenes = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd2.scenes") const scenes = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd-2-0.scenes")
let newDocuments = scenes.filter(i => i.name == "Accueil"); let newDocuments = scenes.filter(i => i.name == "Accueil");
if (newDocuments) { if (newDocuments) {
await game.scenes.documentClass.create(newDocuments); await game.scenes.documentClass.create(newDocuments);
@@ -130,7 +207,7 @@ Hooks.on("chatMessage", (html, content, msg) => {
if (content[0] == '/') { if (content[0] == '/') {
let regExp = /(\S+)/g; let regExp = /(\S+)/g;
let commands = content.match(regExp); let commands = content.match(regExp);
if (game.system.mournblade.commands.processChatCommand(commands, content, msg)) { if (game.system.mournbladecyd2.commands?.processChatCommand(commands, content, msg)) {
return false; return false;
} }
} }
-142
View File
@@ -1,142 +0,0 @@
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
export class MournbladeCYD2RollDialog extends Dialog {
/* -------------------------------------------- */
static async create(actor, rollData ) {
let options = { classes: ["MournbladeCYD2Dialog"], width: 320, height: 'fit-content', 'z-index': 99999 };
let html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-mournblade-cyd2/templates/roll-dialog-generic.html', rollData);
return new MournbladeCYD2RollDialog(actor, rollData, html, options );
}
/* -------------------------------------------- */
constructor(actor, rollData, html, options, close = undefined) {
let conf = {
title: "Test de Capacité",
content: html,
buttons: {
rolld10: {
icon: '<i class="fas fa-check"></i>',
label: "Lancer 1d10",
callback: () => { this.roll("d10") }
},
rolld20: {
icon: '<i class="fas fa-check"></i>',
label: "Lancer 1d20",
callback: () => { this.roll("d20") }
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Annuler",
callback: () => { this.close() }
} },
close: close
}
super(conf, options);
this.actor = actor
this.rollData = rollData
}
/* -------------------------------------------- */
roll ( dice) {
this.rollData.mainDice = dice
MournbladeCYD2Utility.rollMournbladeCYD2( this.rollData )
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
var dialog = this;
function onLoad() {
}
$(function () { onLoad(); });
html.find('#modificateur').change(async (event) => {
this.rollData.modificateur = Number(event.currentTarget.value)
})
html.find('#difficulte').change( (event) => {
console.log("Difficulte: " + event.currentTarget.value)
this.rollData.difficulte = Number(event.currentTarget.value)
})
html.find('#attrKey').change(async (event) => {
this.rollData.attrKey = String(event.currentTarget.value)
})
html.find('#attrKey2').change(async (event) => {
this.rollData.attrKey2 = String(event.currentTarget.value)
})
html.find('#select-maitrise').change(async (event) => {
this.rollData.maitriseId = String(event.currentTarget.value)
})
html.find('#competence-talents').change((event) => {
this.rollData.selectedTalents = $('#competence-talents').val()
})
html.find('#taille-cible').change((event) => {
this.rollData.tailleCible = String(event.currentTarget.value)
})
html.find('#tireur-deplacement').change((event) => {
this.rollData.tireurDeplacement = String(event.currentTarget.value)
})
html.find('#cible-couvert').change((event) => {
this.rollData.cibleCouvert = String(event.currentTarget.value)
})
html.find('#distance-tir').change((event) => {
this.rollData.distanceTir = String(event.currentTarget.value)
})
html.find('#bonus-malus-context').change((event) => {
this.rollData.bonusMalusContext = Number(event.currentTarget.value)
})
html.find('#defenseur-au-sol').change((event) => {
this.rollData.defenseurAuSol = event.currentTarget.checked
})
html.find('#ambidextre-1').change((event) => {
this.rollData.ambidextre1 = event.currentTarget.checked
})
html.find('#ambidextre-2').change((event) => {
this.rollData.ambidextre2 = event.currentTarget.checked
})
html.find('#attaque-monte').change((event) => {
this.rollData.attqueMonte = event.currentTarget.checked
})
html.find('#defenseur-aveugle').change((event) => {
this.rollData.defenseurAveugle = event.currentTarget.checked
})
html.find('#defenseur-de-dos').change((event) => {
this.rollData.defenseurDeDos = event.currentTarget.checked
})
html.find('#defenseur-restreint').change((event) => {
this.rollData.defenseurRestreint = event.currentTarget.checked
})
html.find('#defenseur-immobilise').change((event) => {
this.rollData.defenseurImmobilise = event.currentTarget.checked
})
html.find('#attaque-charge').change((event) => {
this.rollData.attaqueCharge = event.currentTarget.checked
})
html.find('#charge-cavalerie').change((event) => {
this.rollData.chargeCavalerie = event.currentTarget.checked
})
html.find('#attaquants-multiple').change((event) => {
this.rollData.attaquantsMultiples = event.currentTarget.checked
})
html.find('#soutiens').change((event) => {
this.rollData.soutiens = Number(event.currentTarget.value)
})
html.find('#feinte').change((event) => {
this.rollData.feinte = event.currentTarget.checked
})
html.find('#contenir').change((event) => {
this.rollData.contenir = event.currentTarget.checked
})
html.find('#attaque-desarme').change((event) => {
this.rollData.attaqueDesarme = event.currentTarget.checked
})
}
}
+197 -77
View File
@@ -12,6 +12,26 @@ const __tailleCible = { normal: 0, main: 10, enfant: 3, maison: -10 }
export class MournbladeCYD2Utility { export class MournbladeCYD2Utility {
/* -------------------------------------------- */
// Helper pour calculer la valeur totale d'un item en SC (Sous de Cuivre / Pièces de Bronze)
// Conversion selon le lore Mournblade :
// 1 SA (Sou d'Argent / Pièce d'Argent) = 10 PB (Pièces de Bronze / Sous de Cuivre)
// 1 PO (Pièce d'Or) = 10 SA = 100 PB
// Donc : 1 PA (Pièce d'Argent) = 10 SC, 1 PO (Pièce d'Or) = 100 SC
static calculateItemValueSC(prixpo, prixca, prixsc) {
const po = parseInt(prixpo) || 0;
const ca = parseInt(prixca) || 0;
const sc = parseInt(prixsc) || 0;
return po * 100 + ca * 10 + sc;
}
// Helper pour calculer la valeur SC d'un item avec quantité
static getItemValueSC(item) {
const value = this.calculateItemValueSC(item.system?.prixpo, item.system?.prixca, item.system?.prixsc);
const quantity = item.system?.quantite || 1;
return value * quantity;
}
/* -------------------------------------------- */ /* -------------------------------------------- */
static async init() { static async init() {
Hooks.on('renderChatLog', (log, html, data) => MournbladeCYD2Utility.chatListeners(html)) Hooks.on('renderChatLog', (log, html, data) => MournbladeCYD2Utility.chatListeners(html))
@@ -34,11 +54,11 @@ export class MournbladeCYD2Utility {
return text.toUpperCase(); return text.toUpperCase();
}) })
Handlebars.registerHelper('lower', function (text) { Handlebars.registerHelper('lower', function (text) {
return text.toLowerCase() return text.toLowerCase();
}) })
Handlebars.registerHelper('upperFirst', function (text) { Handlebars.registerHelper('upperFirst', function (text) {
if (typeof text !== 'string') return text if (typeof text !== 'string') return text;
return text.charAt(0).toUpperCase() + text.slice(1) return text.charAt(0).toUpperCase() + text.slice(1);
}) })
Handlebars.registerHelper('notEmpty', function (list) { Handlebars.registerHelper('notEmpty', function (list) {
return list.length > 0; return list.length > 0;
@@ -46,8 +66,46 @@ export class MournbladeCYD2Utility {
Handlebars.registerHelper('mul', function (a, b) { Handlebars.registerHelper('mul', function (a, b) {
return parseInt(a) * parseInt(b); return parseInt(a) * parseInt(b);
}) })
Handlebars.registerHelper('subtract', function (a, b) {
return parseInt(a) - parseInt(b);
})
game.settings.register("fvtt-mournblade-cyd2", "mournblade-cyd2-pause-logo", { // Helper Handlebars pour les templates
Handlebars.registerHelper('calculateItemValueSC', function (prixpo, prixca, prixsc) {
return MournbladeCYD2Utility.calculateItemValueSC(prixpo, prixca, prixsc);
});
// Helper pour localiser les valeurs d'allégeance
Handlebars.registerHelper('localizeAllegiance', function (value) {
if (!value) return value;
const allegianceMap = {
'tous': 'MNBL.all',
'chaos': 'MNBL.chaos',
'loi': 'MNBL.law',
'betes': 'MNBL.betes',
'elementaires': 'MNBL.elementaires',
'balance': 'MNBL.balance'
};
const key = allegianceMap[value?.toLowerCase?.()] || value;
return game.i18n?.localize?.(key) ?? value;
});
// Helper pour joindre les prédilections sans virgule superflue
Handlebars.registerHelper('joinPredilections', function (predilections) {
if (!predilections || !Array.isArray(predilections)) return '';
return predilections
.filter(pred => pred && pred.acquise && !pred.used)
.map(pred => pred.name)
.join(', ');
});
Handlebars.registerHelper('select', function(value, opts) {
const html = opts.fn(this);
const escaped = String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return html.replace(new RegExp(`value="${escaped}"`, 'g'), `value="${value}" selected="selected"`);
});
game.settings.register("fvtt-mournblade-cyd-2-0", "mournblade-cyd2-pause-logo", {
name: "Logo de pause", name: "Logo de pause",
scope: "world", scope: "world",
config: true, config: true,
@@ -60,6 +118,11 @@ export class MournbladeCYD2Utility {
}, },
}) })
// Initialise les listes de sélection dès le hook init (avant le rendu des fiches)
game.system.mournbladecyd2.config.listeNiveauSkill = this.createDirectOptionList(0, 10)
game.system.mournbladecyd2.config.listeNiveauCreature = this.createDirectOptionList(0, 35)
game.system.mournbladecyd2.config.pointsAmeOptions = this.createDirectOptionList(1, 20)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -102,16 +165,13 @@ export class MournbladeCYD2Utility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static async ready() { static async ready() {
const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd2.skills") const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd-2-0.skills")
this.skills = skills.map(i => i.toObject()) this.skills = skills.map(i => i.toObject())
// Setup pause logo // Setup pause logo
let logoPause = "systems/fvtt-mournblade-cyd2/assets/logos/" + game.settings.get("fvtt-mournblade-cyd2", "mournblade-cyd2-pause-logo") + ".webp" let logoPause = "systems/fvtt-mournblade-cyd-2-0/assets/logos/" + game.settings.get("fvtt-mournblade-cyd-2-0", "mournblade-cyd2-pause-logo") + ".webp"
let logoImg = document.querySelector('#pause').children[0] let logoImg = document.querySelector('#pause').children[0]
logoImg.setAttribute('style', `content: url(${logoPause})`) logoImg.setAttribute('style', `content: url(${logoPause})`)
game.system.mournbladecyd2.config.listeNiveauSkill = this.createDirectOptionList(0, 10)
game.system.mournbladecyd2.config.listeNiveauCreature = this.createDirectOptionList(0, 35)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -150,7 +210,7 @@ export class MournbladeCYD2Utility {
static async chatListeners(html) { static async chatListeners(html) {
$(html).on("click", '.predilection-reroll', async event => { $(html).on("click", '.predilection-reroll', async event => {
let predIdx = $(event.currentTarget).data("predilection-index") let predIdx = event.currentTarget.dataset.predilectionIndex
let messageId = MournbladeCYD2Utility.findChatMessageId(event.currentTarget) let messageId = MournbladeCYD2Utility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId) let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "mournblade-cyd2-roll") let rollData = message.getFlag("world", "mournblade-cyd2-roll")
@@ -182,13 +242,15 @@ export class MournbladeCYD2Utility {
static async preloadHandlebarsTemplates() { static async preloadHandlebarsTemplates() {
const templatePaths = [ const templatePaths = [
'systems/fvtt-mournblade-cyd2/templates/editor-notes-gm.html', 'systems/fvtt-mournblade-cyd-2-0/templates/editor-notes-gm.hbs',
'systems/fvtt-mournblade-cyd2/templates/partial-item-header.html', 'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-header.hbs',
'systems/fvtt-mournblade-cyd2/templates/partial-item-description.html', 'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-description.hbs',
'systems/fvtt-mournblade-cyd2/templates/partial-item-nav.html', 'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-nav.hbs',
'systems/fvtt-mournblade-cyd2/templates/partial-item-prix.html', 'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-prix.hbs',
'systems/fvtt-mournblade-cyd2/templates/partial-automation.html', 'systems/fvtt-mournblade-cyd-2-0/templates/partial-automation.hbs',
'systems/fvtt-mournblade-cyd2/templates/hud-adversites.html', 'systems/fvtt-mournblade-cyd-2-0/templates/partial-active-effects.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-effects.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/hud-adversites.hbs',
] ]
return foundry.applications.handlebars.loadTemplates(templatePaths); return foundry.applications.handlebars.loadTemplates(templatePaths);
} }
@@ -222,15 +284,6 @@ export class MournbladeCYD2Utility {
return undefined; return undefined;
} }
/* -------------------------------------------- */
static createDirectOptionList(min, max) {
let options = {};
for (let i = min; i <= max; i++) {
options[`${i}`] = `${i}`;
}
return options;
}
/* -------------------------------------------- */ /* -------------------------------------------- */
static buildListOptions(min, max) { static buildListOptions(min, max) {
let options = "" let options = ""
@@ -272,13 +325,13 @@ export class MournbladeCYD2Utility {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static onSocketMesssage(msg) { static onSocketMessage(msg) {
if (msg.msg == "msg_apply_combativite") { if (msg.msg == "msg_apply_combativite") {
let defender = game.canvas.tokens.get(msg.data.defenderTokenId)?.actor let defender = game.canvas.tokens.get(msg.data.defenderTokenId)?.actor
if (defender) { if (defender) {
defender.changeEtatCombativite(msg.data.value) defender.changeEtatCombativite(msg.data.value)
} else { } else {
console.warn("MournbladeCYD2Utility.onSocketMesssage : Impossible de trouver le token pour appliquer la combativité", msg.defenderTokenId) console.warn("MournbladeCYD2Utility.onSocketMessage : Impossible de trouver le token pour appliquer la combativité", msg.defenderTokenId)
} }
} }
} }
@@ -330,9 +383,11 @@ export class MournbladeCYD2Utility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static computeMonnaieDetails(valueSC) { static computeMonnaieDetails(valueSC) {
let po = Math.floor(valueSC / 400) // Conversion selon le lore Mournblade :
let pa = Math.floor((valueSC - (po * 400)) / 20) // 1 PO = 100 SC, 1 CA (PA) = 10 SC
let sc = valueSC - (po * 400) - (pa * 20) let po = Math.floor(valueSC / 100)
let pa = Math.floor((valueSC - (po * 100)) / 10)
let sc = valueSC - (po * 100) - (pa * 10)
return { return {
po, pa, sc, valueSC po, pa, sc, valueSC
} }
@@ -340,11 +395,11 @@ export class MournbladeCYD2Utility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static computeResult(rollData) { static computeResult(rollData) {
rollData.diceResult = rollData.roll.terms[0].results[0].result rollData.diceResult = rollData.roll?.terms?.[0]?.results?.[0]?.result ?? 0
if (rollData.mainDice.includes("d20")) { if (rollData.mainDice.includes("d20")) {
let diceValue = rollData.roll.terms[0].results[0].result let diceValue = rollData.roll?.terms?.[0]?.results?.[0]?.result ?? 0
if (diceValue % 2 == 1) { if (diceValue % 2 == 1) {
//console.log("PAIR/IMP2", diceValue) rollData.isD20Impair = true
rollData.finalResult -= rollData.roll.terms[0].results[0].result // Substract value rollData.finalResult -= rollData.roll.terms[0].results[0].result // Substract value
if (diceValue == 1 || diceValue == 11) { if (diceValue == 1 || diceValue == 11) {
rollData.isDramatique = true rollData.isDramatique = true
@@ -369,10 +424,15 @@ export class MournbladeCYD2Utility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static applyCombativite(rollData, value) { static applyCombativite(rollData, value) {
if (game.user.isGM) { if (game.user.isGM) {
let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor const token = game.canvas.tokens.get(rollData.defenderTokenId)
const defender = token?.actor
if (!defender) {
console.warn("MournbladeCYD2Utility.applyCombativite : token défenseur introuvable", rollData.defenderTokenId)
return
}
defender.changeEtatCombativite(value) defender.changeEtatCombativite(value)
} else { } else {
game.socket.emit("system.fvtt-mournblade-cyd2", { msg: "msg_apply_combativite", data: { defenderTokenId: rollData.defenderTokenId, value } }); game.socket.emit("system.fvtt-mournblade-cyd-2-0", { msg: "msg_apply_combativite", data: { defenderTokenId: rollData.defenderTokenId, value } });
} }
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -383,7 +443,7 @@ export class MournbladeCYD2Utility {
rollData.attrKey = "adr" rollData.attrKey = "adr"
} }
if (!rollData.attr) { if (!rollData.attr) {
rollData.actionImg = "systems/fvtt-mournblade-cyd2/assets/icons/" + actor.system.attributs[rollData.attrKey].labelnorm + ".webp" rollData.actionImg = "systems/fvtt-mournblade-cyd-2-0/assets/icons/" + actor.system.attributs[rollData.attrKey].labelnorm + ".webp"
rollData.attr = foundry.utils.duplicate(actor.system.attributs[rollData.attrKey]) rollData.attr = foundry.utils.duplicate(actor.system.attributs[rollData.attrKey])
} }
if (rollData.attrKey2 != "none") { if (rollData.attrKey2 != "none") {
@@ -424,7 +484,8 @@ export class MournbladeCYD2Utility {
} else if (rollData.attr2) { } else if (rollData.attr2) {
rollData.diceFormula += `+${rollData.attr.value}+${rollData.attr2.value}+${rollData.modificateur}+${rollData.bonusMalusContext}` rollData.diceFormula += `+${rollData.attr.value}+${rollData.attr2.value}+${rollData.modificateur}+${rollData.bonusMalusContext}`
} else { } else {
rollData.diceFormula += `+${rollData.attr.value}*${rollData.multiplier}+${rollData.modificateur}+${rollData.bonusMalusContext}` const attrPart = rollData.multiplier > 1 ? `${rollData.attr.value}*${rollData.multiplier}` : `${rollData.attr.value}`
rollData.diceFormula += `+${attrPart}+${rollData.modificateur}+${rollData.bonusMalusContext}`
} }
// Bonus arme naturelle en défense // Bonus arme naturelle en défense
@@ -490,6 +551,21 @@ export class MournbladeCYD2Utility {
rollData.finalResult = myRoll.total rollData.finalResult = myRoll.total
this.computeResult(rollData) this.computeResult(rollData)
// Rune post-roll: calculate duration and apply soul cost
if (rollData.rune) {
rollData.runeduree = Math.ceil(rollData.runeame / 3)
if (rollData.runemode == "inscrire") {
rollData.runeduree *= 2
}
let subAme = rollData.runeame
if (!rollData.isSuccess && !rollData.isDramatique) {
subAme = Math.ceil(rollData.runeame / 2)
}
rollData.runeAmeCout = subAme
actor.subPointsAme(rollData.runemode, subAme)
}
if (rollData.isInit) { if (rollData.isInit) {
actor.setFlag("world", "last-initiative", rollData.finalResult) actor.setFlag("world", "last-initiative", rollData.finalResult)
} }
@@ -503,15 +579,18 @@ export class MournbladeCYD2Utility {
} }
this.createChatWithRollMode(rollData.alias, { this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-mournblade-cyd2/templates/chat-generic-result.html`, rollData) content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-mournblade-cyd-2-0/templates/chat-generic-result.hbs`, rollData)
}, rollData) }, rollData)
if ((rollData.coupBas || rollData.arme) && rollData.isSuccess && rollData.defenderTokenId) { if ((rollData.coupBas || rollData.arme) && rollData.isSuccess && rollData.defenderTokenId) {
this.applyCombativite(rollData, rollData.nbCombativitePerdu) this.applyCombativite(rollData, rollData.nbCombativitePerdu)
} }
if (rollData.coupBas && rollData.isSuccess && rollData.defenderTokenId) { if (rollData.coupBas && rollData.isSuccess && rollData.defenderTokenId) {
let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor const token = game.canvas.tokens.get(rollData.defenderTokenId)
defender.incDecAdversite("bleue", -2) const defender = token?.actor
if (defender) {
defender.incDecAdversite("bleue", -2)
}
} }
} }
@@ -597,7 +676,7 @@ export class MournbladeCYD2Utility {
rollData.bonusFormula = rollData.addedBonus rollData.bonusFormula = rollData.addedBonus
console.log("Bonus Roll MournbladeCYD2", rollData.bonusFormula) console.log("Bonus Roll MournbladeCYD2", rollData.bonusFormula)
if (!Number(rollData.bonusFormula)) { if (isNaN(Number(rollData.bonusFormula))) {
let bonusRoll = await new Roll(rollData.bonusFormula).roll() let bonusRoll = await new Roll(rollData.bonusFormula).roll()
await this.showDiceSoNice(bonusRoll, game.settings.get("core", "rollMode")); await this.showDiceSoNice(bonusRoll, game.settings.get("core", "rollMode"));
rollData.bonusRoll = foundry.utils.duplicate(bonusRoll) rollData.bonusRoll = foundry.utils.duplicate(bonusRoll)
@@ -610,7 +689,7 @@ export class MournbladeCYD2Utility {
this.computeResultQuality(rollData) this.computeResultQuality(rollData)
this.createChatWithRollMode(rollData.alias, { this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-mournblade-cyd2/templates/chat-generic-result.html`, rollData) content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-mournblade-cyd-2-0/templates/chat-generic-result.hbs`, rollData)
}, rollData) }, rollData)
} }
@@ -641,7 +720,7 @@ export class MournbladeCYD2Utility {
chatGM.whisper = this.getUsers(user => user.isGM); chatGM.whisper = this.getUsers(user => user.isGM);
chatGM.content = "Blinde message of " + game.user.name + "<br>" + chatOptions.content; chatGM.content = "Blinde message of " + game.user.name + "<br>" + chatOptions.content;
console.log("blindMessageToGM", chatGM); console.log("blindMessageToGM", chatGM);
game.socket.emit("system.fvtt-mournblade-cyd2", { msg: "msg_gm_chat_message", data: chatGM }); game.socket.emit("system.fvtt-mournblade-cyd-2-0", { msg: "msg_gm_chat_message", data: chatGM });
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -767,9 +846,19 @@ export class MournbladeCYD2Utility {
this.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions, rollData) this.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions, rollData)
} }
/* -------------------------------------------- */
static getActorAlignment(actor) {
const loi = actor.system?.balance?.loi ?? 0
const chaos = actor.system?.balance?.chaos ?? 0
if (loi > chaos) return 'loyal'
if (chaos > loi) return 'chaotique'
return 'neutre'
}
/* -------------------------------------------- */ /* -------------------------------------------- */
static applyBonneAventureRoll(li, changed, addedBonus) { static applyBonneAventureRoll(li, changed, addedBonus) {
let msgId = $(li).data("message-id") const el = li instanceof HTMLElement ? li : li[0];
let msgId = el.dataset.messageId ?? el.closest("[data-message-id]")?.dataset.messageId
let msg = game.messages.get(msgId) let msg = game.messages.get(msgId)
if (msg) { if (msg) {
let rollData = msg.getFlag("world", "mournblade-cyd2-roll") let rollData = msg.getFlag("world", "mournblade-cyd2-roll")
@@ -788,7 +877,8 @@ export class MournbladeCYD2Utility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static applyEclatRoll(li, changed, addedBonus) { static applyEclatRoll(li, changed, addedBonus) {
let msgId = $(li).data("message-id") const el = li instanceof HTMLElement ? li : li[0];
let msgId = el.dataset.messageId ?? el.closest("[data-message-id]")?.dataset.messageId
let msg = game.messages.get(msgId) let msg = game.messages.get(msgId)
if (msg) { if (msg) {
let rollData = msg.getFlag("world", "mournblade-cyd2-roll") let rollData = msg.getFlag("world", "mournblade-cyd2-roll")
@@ -807,49 +897,79 @@ export class MournbladeCYD2Utility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static chatRollMenu(html, options) { static chatRollMenu(html, options) {
let canApply = li => canvas.tokens.controlled.length && li.find(".mournblade-cyd2-roll").length let canApply = li => {
let canApplyBA = function (li) { const el = li instanceof HTMLElement ? li : li[0];
let message = game.messages.get($(li).attr("data-message-id")) return canvas.tokens.controlled.length && el.querySelector(".mournblade-cyd2-roll");
}
let getActor = function (li) {
const el = li instanceof HTMLElement ? li : li[0];
let message = game.messages.get(el.dataset.messageId)
let rollData = message.getFlag("world", "mournblade-cyd2-roll") let rollData = message.getFlag("world", "mournblade-cyd2-roll")
return MournbladeCYD2Utility.getActorFromRollData(rollData)
}
let getRollData = function (li) {
const el = li instanceof HTMLElement ? li : li[0];
let message = game.messages.get(el.dataset.messageId)
return message.getFlag("world", "mournblade-cyd2-roll")
}
let canApplyBA = function (li) {
let rollData = getRollData(li)
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData) let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
return (!rollData.isReroll && actor.getBonneAventure() > 0) return (!rollData.isReroll && actor.getBonneAventure() > 0)
} }
let canApplyPE = function (li) { let canApplyPE = function (li) {
let message = game.messages.get($(li).attr("data-message-id")) let rollData = getRollData(li)
let rollData = message.getFlag("world", "mournblade-cyd2-roll")
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData) let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
return (!rollData.isReroll && actor.getEclat() > 0) return (!rollData.isReroll && actor.getEclat() > 0)
} }
options.push( let isLoyal = function (li) {
{ return MournbladeCYD2Utility.getActorAlignment(getActor(li)) === 'loyal'
name: "Ajouer +3 (1 point de Bonne Aventure)", }
icon: "<i class='fas fa-user-plus'></i>", let isChaotique = function (li) {
condition: canApply && canApplyBA, return MournbladeCYD2Utility.getActorAlignment(getActor(li)) === 'chaotique'
callback: li => MournbladeCYD2Utility.applyBonneAventureRoll(li, -1, "+3") }
}
) // Bonne Aventure — loyal : +3 fixe
options.push( options.push({
{ name: "Ajouter +3 (1 point de Bonne Aventure — Loyal)",
name: "Ajouter +10 (1 Point d'Eclat)", icon: "<i class='fas fa-balance-scale'></i>",
icon: "<i class='fas fa-user-plus'></i>", condition: li => canApply(li) && canApplyBA(li) && !isChaotique(li),
condition: canApply && canApplyPE, callback: li => MournbladeCYD2Utility.applyBonneAventureRoll(li, -1, "+3")
callback: li => MournbladeCYD2Utility.applyEclatRoll(li, -1, "+10") })
} // Bonne Aventure — chaotique : +1d6
) options.push({
options.push( name: "Ajouter +1d6 (1 point de Bonne Aventure — Chaotique)",
{ icon: "<i class='fas fa-dice'></i>",
name: "Relancer le dé (1 point d'Eclat)", condition: li => canApply(li) && canApplyBA(li) && !isLoyal(li),
icon: "<i class='fas fa-user-plus'></i>", callback: li => MournbladeCYD2Utility.applyBonneAventureRoll(li, -1, "+1d6")
condition: canApply && canApplyPE, })
callback: li => MournbladeCYD2Utility.applyEclatRoll(li, -1, "reroll") // Éclat — loyal : +10
} options.push({
) name: "Ajouter +10 (1 point d'Éclat — Loyal)",
icon: "<i class='fas fa-star'></i>",
condition: li => canApply(li) && canApplyPE(li) && !isChaotique(li),
callback: li => MournbladeCYD2Utility.applyEclatRoll(li, -1, "+10")
})
// Éclat — chaotique : +1d20
options.push({
name: "Ajouter +1d20 (1 point d'Éclat — Chaotique)",
icon: "<i class='fas fa-dice-d20'></i>",
condition: li => canApply(li) && canApplyPE(li) && !isLoyal(li),
callback: li => MournbladeCYD2Utility.applyEclatRoll(li, -1, "+1d20")
})
// Éclat — relancer (tous)
options.push({
name: "Relancer le dé (1 point d'Éclat)",
icon: "<i class='fas fa-redo'></i>",
condition: li => canApply(li) && canApplyPE(li),
callback: li => MournbladeCYD2Utility.applyEclatRoll(li, -1, "reroll")
})
return options return options
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static async confirmDelete(actorSheet, li) { static async confirmDelete(actorSheet, li) {
let itemId = li.data("item-id"); let itemId = li.dataset?.itemId ?? li.dataset?.["item-id"];
let msgTxt = "<p>Etes vous certain de vouloir supprimer cet item ?"; let msgTxt = "<p>Etes vous certain de vouloir supprimer cet item ?";
let buttons = { let buttons = {
delete: { delete: {
@@ -857,7 +977,7 @@ export class MournbladeCYD2Utility {
label: "Oui !", label: "Oui !",
callback: () => { callback: () => {
actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]); actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]);
li.slideUp(200, () => actorSheet.render(false)); actorSheet.render(false);
} }
}, },
cancel: { cancel: {
+16
View File
@@ -0,0 +1,16 @@
{
"name": "fvtt-mournblade-cyd-2-0",
"version": "2.0.0",
"description": "Mournblade CYD 2.0 RPG for FoundryVTT - French",
"scripts": {
"build": "gulp build",
"watch": "gulp watch"
},
"author": "Uberwald/LeRatierBretonnien",
"license": "SEE LICENSE IN LICENCE.txt",
"devDependencies": {
"gulp": "^4.0.2",
"gulp-less": "^5.0.0",
"gulp-sourcemaps": "^3.0.0"
}
}
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000372 MANIFEST-000451
+7 -8
View File
@@ -1,8 +1,7 @@
2025/10/26-14:17:34.089807 7f058c9f96c0 Recovering log #370 2026/06/07-23:29:46.605565 7f15ce3fc6c0 Recovering log #449
2025/10/26-14:17:34.150991 7f058c9f96c0 Delete type=3 #368 2026/06/07-23:29:46.614959 7f15ce3fc6c0 Delete type=3 #447
2025/10/26-14:17:34.151050 7f058c9f96c0 Delete type=0 #370 2026/06/07-23:29:46.614979 7f15ce3fc6c0 Delete type=0 #449
2025/10/26-14:43:50.641754 7f0586bff6c0 Level-0 table #375: started 2026/06/07-23:51:38.199273 7f15cdbfb6c0 Level-0 table #454: started
2025/10/26-14:43:50.641778 7f0586bff6c0 Level-0 table #375: 0 bytes OK 2026/06/07-23:51:38.199286 7f15cdbfb6c0 Level-0 table #454: 0 bytes OK
2025/10/26-14:43:50.648553 7f0586bff6c0 Delete type=0 #373 2026/06/07-23:51:38.205066 7f15cdbfb6c0 Delete type=0 #452
2025/10/26-14:43:50.648712 7f0586bff6c0 Manual compaction at level-0 from '!journal!gVybbv17TFY8o3Y4' @ 72057594037927935 : 1 .. '!journal.pages!gVybbv17TFY8o3Y4.fQidyqfF1TbsZKHM' @ 0 : 0; will stop at (end) 2026/06/07-23:51:38.211362 7f15cdbfb6c0 Manual compaction at level-0 from '!journal!gVybbv17TFY8o3Y4' @ 72057594037927935 : 1 .. '!journal.pages!gVybbv17TFY8o3Y4.fQidyqfF1TbsZKHM' @ 0 : 0; will stop at (end)
2025/10/26-14:43:50.648735 7f0586bff6c0 Manual compaction at level-1 from '!journal!gVybbv17TFY8o3Y4' @ 72057594037927935 : 1 .. '!journal.pages!gVybbv17TFY8o3Y4.fQidyqfF1TbsZKHM' @ 0 : 0; will stop at (end)
+7 -8
View File
@@ -1,8 +1,7 @@
2025/10/26-14:10:00.107133 7f058d1fa6c0 Recovering log #367 2026/06/07-22:26:23.395630 7f15cfbff6c0 Recovering log #445
2025/10/26-14:10:00.170337 7f058d1fa6c0 Delete type=0 #367 2026/06/07-22:26:23.404603 7f15cfbff6c0 Delete type=3 #443
2025/10/26-14:10:00.170407 7f058d1fa6c0 Delete type=3 #366 2026/06/07-22:26:23.404618 7f15cfbff6c0 Delete type=0 #445
2025/10/26-14:11:11.035066 7f0586bff6c0 Level-0 table #371: started 2026/06/07-23:28:18.037162 7f15cdbfb6c0 Level-0 table #450: started
2025/10/26-14:11:11.035107 7f0586bff6c0 Level-0 table #371: 0 bytes OK 2026/06/07-23:28:18.037182 7f15cdbfb6c0 Level-0 table #450: 0 bytes OK
2025/10/26-14:11:11.041259 7f0586bff6c0 Delete type=0 #369 2026/06/07-23:28:18.043745 7f15cdbfb6c0 Delete type=0 #448
2025/10/26-14:11:11.052382 7f0586bff6c0 Manual compaction at level-0 from '!journal!gVybbv17TFY8o3Y4' @ 72057594037927935 : 1 .. '!journal.pages!gVybbv17TFY8o3Y4.fQidyqfF1TbsZKHM' @ 0 : 0; will stop at (end) 2026/06/07-23:28:18.050582 7f15cdbfb6c0 Manual compaction at level-0 from '!journal!gVybbv17TFY8o3Y4' @ 72057594037927935 : 1 .. '!journal.pages!gVybbv17TFY8o3Y4.fQidyqfF1TbsZKHM' @ 0 : 0; will stop at (end)
2025/10/26-14:11:11.058628 7f0586bff6c0 Manual compaction at level-1 from '!journal!gVybbv17TFY8o3Y4' @ 72057594037927935 : 1 .. '!journal.pages!gVybbv17TFY8o3Y4.fQidyqfF1TbsZKHM' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000252 MANIFEST-000446
+7 -11
View File
@@ -1,11 +1,7 @@
2025/10/26-14:17:33.329160 7f0587fff6c0 Recovering log #250 2026/06/07-23:29:46.486418 7f15ce3fc6c0 Recovering log #444
2025/10/26-14:17:33.329199 7f0587fff6c0 Recovering log #369 2026/06/07-23:29:46.496573 7f15ce3fc6c0 Delete type=3 #442
2025/10/26-14:17:33.500980 7f0587fff6c0 Delete type=2 #352 2026/06/07-23:29:46.496587 7f15ce3fc6c0 Delete type=0 #444
2025/10/26-14:17:33.501082 7f0587fff6c0 Delete type=0 #369 2026/06/07-23:51:38.132071 7f15cdbfb6c0 Level-0 table #449: started
2025/10/26-14:17:33.501136 7f0587fff6c0 Delete type=3 #248 2026/06/07-23:51:38.132089 7f15cdbfb6c0 Level-0 table #449: 0 bytes OK
2025/10/26-14:17:33.501178 7f0587fff6c0 Delete type=0 #250 2026/06/07-23:51:38.138027 7f15cdbfb6c0 Delete type=0 #447
2025/10/26-14:43:50.551774 7f0586bff6c0 Level-0 table #372: started 2026/06/07-23:51:38.149430 7f15cdbfb6c0 Manual compaction at level-0 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
2025/10/26-14:43:50.551804 7f0586bff6c0 Level-0 table #372: 0 bytes OK
2025/10/26-14:43:50.557801 7f0586bff6c0 Delete type=0 #370
2025/10/26-14:43:50.570810 7f0586bff6c0 Manual compaction at level-0 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
2025/10/26-14:43:50.570851 7f0586bff6c0 Manual compaction at level-1 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
+7 -8
View File
@@ -1,8 +1,7 @@
2025/10/26-11:41:45.111518 7fe806ffd6c0 Recovering log #246 2026/06/07-22:26:23.277424 7f15cf3fe6c0 Recovering log #440
2025/10/26-11:41:45.121406 7fe806ffd6c0 Delete type=3 #244 2026/06/07-22:26:23.287266 7f15cf3fe6c0 Delete type=3 #438
2025/10/26-11:41:45.121474 7fe806ffd6c0 Delete type=0 #246 2026/06/07-22:26:23.287281 7f15cf3fe6c0 Delete type=0 #440
2025/10/26-11:42:27.673791 7fe804ff96c0 Level-0 table #251: started 2026/06/07-23:28:17.956926 7f15cdbfb6c0 Level-0 table #445: started
2025/10/26-11:42:27.673818 7fe804ff96c0 Level-0 table #251: 0 bytes OK 2026/06/07-23:28:17.956939 7f15cdbfb6c0 Level-0 table #445: 0 bytes OK
2025/10/26-11:42:27.702748 7fe804ff96c0 Delete type=0 #249 2026/06/07-23:28:17.962818 7f15cdbfb6c0 Delete type=0 #443
2025/10/26-11:42:27.787796 7fe804ff96c0 Manual compaction at level-0 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end) 2026/06/07-23:28:17.968789 7f15cdbfb6c0 Manual compaction at level-0 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
2025/10/26-11:42:27.787822 7fe804ff96c0 Manual compaction at level-1 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000251 MANIFEST-000345
+7 -8
View File
@@ -1,8 +1,7 @@
2025/10/26-14:17:33.616386 7f05877fe6c0 Recovering log #249 2026/06/07-23:29:46.519301 7f15cebfd6c0 Recovering log #343
2025/10/26-14:17:33.676039 7f05877fe6c0 Delete type=3 #247 2026/06/07-23:29:46.528855 7f15cebfd6c0 Delete type=3 #341
2025/10/26-14:17:33.676103 7f05877fe6c0 Delete type=0 #249 2026/06/07-23:29:46.528876 7f15cebfd6c0 Delete type=0 #343
2025/10/26-14:43:50.589531 7f0586bff6c0 Level-0 table #254: started 2026/06/07-23:51:38.149435 7f15cdbfb6c0 Level-0 table #348: started
2025/10/26-14:43:50.589558 7f0586bff6c0 Level-0 table #254: 0 bytes OK 2026/06/07-23:51:38.149444 7f15cdbfb6c0 Level-0 table #348: 0 bytes OK
2025/10/26-14:43:50.595890 7f0586bff6c0 Delete type=0 #252 2026/06/07-23:51:38.155961 7f15cdbfb6c0 Delete type=0 #346
2025/10/26-14:43:50.596069 7f0586bff6c0 Manual compaction at level-0 from '!items!5dGXNiL3WN4cAk7X' @ 72057594037927935 : 1 .. '!items!zzz9JrtWjELdoAfK' @ 0 : 0; will stop at (end) 2026/06/07-23:51:38.162070 7f15cdbfb6c0 Manual compaction at level-0 from '!items!5dGXNiL3WN4cAk7X' @ 72057594037927935 : 1 .. '!items!zzz9JrtWjELdoAfK' @ 0 : 0; will stop at (end)
2025/10/26-14:43:50.596098 7f0586bff6c0 Manual compaction at level-1 from '!items!5dGXNiL3WN4cAk7X' @ 72057594037927935 : 1 .. '!items!zzz9JrtWjELdoAfK' @ 0 : 0; will stop at (end)
+7 -8
View File
@@ -1,8 +1,7 @@
2025/10/26-11:41:45.148682 7fe8057fa6c0 Recovering log #245 2026/06/07-22:26:23.309282 7f15cfbff6c0 Recovering log #339
2025/10/26-11:41:45.159005 7fe8057fa6c0 Delete type=3 #243 2026/06/07-22:26:23.319052 7f15cfbff6c0 Delete type=3 #337
2025/10/26-11:41:45.159071 7fe8057fa6c0 Delete type=0 #245 2026/06/07-22:26:23.319068 7f15cfbff6c0 Delete type=0 #339
2025/10/26-11:42:27.892214 7fe804ff96c0 Level-0 table #250: started 2026/06/07-23:28:17.984230 7f15cdbfb6c0 Level-0 table #344: started
2025/10/26-11:42:27.892241 7fe804ff96c0 Level-0 table #250: 0 bytes OK 2026/06/07-23:28:17.984244 7f15cdbfb6c0 Level-0 table #344: 0 bytes OK
2025/10/26-11:42:27.929964 7fe804ff96c0 Delete type=0 #248 2026/06/07-23:28:17.993689 7f15cdbfb6c0 Delete type=0 #342
2025/10/26-11:42:27.930136 7fe804ff96c0 Manual compaction at level-0 from '!items!5dGXNiL3WN4cAk7X' @ 72057594037927935 : 1 .. '!items!zzz9JrtWjELdoAfK' @ 0 : 0; will stop at (end) 2026/06/07-23:28:17.999974 7f15cdbfb6c0 Manual compaction at level-0 from '!items!5dGXNiL3WN4cAk7X' @ 72057594037927935 : 1 .. '!items!zzz9JrtWjELdoAfK' @ 0 : 0; will stop at (end)
2025/10/26-11:42:27.930151 7fe804ff96c0 Manual compaction at level-1 from '!items!5dGXNiL3WN4cAk7X' @ 72057594037927935 : 1 .. '!items!zzz9JrtWjELdoAfK' @ 0 : 0; will stop at (end)
Binary file not shown.

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