8 Commits
Author SHA1 Message Date
uberwald 18180a8445 fix: apprentissage de Groupe - dés dépensés en cas d'échec et gardes
Release Creation / build (release) Failing after 1m40s
- Les Dés d'Expérience du Groupe sont désormais dépensés que le jet
  d'apprentissage soit réussi ou raté (cohérent avec l'apprentissage
  de personnage et les règles ; le décompte est sorti de #applySuccess).
- Garde sur le type Capacité : si aucune Capacité de Groupe n'est
  sélectionnée, le jet est bloqué avec un avertissement (avant, il
  marquait learned.ability et dépensait les dés sans rien apprendre).
- Limites de Capacités de Totem (personnage) : seules les Capacités du
  Totem courant du personnage comptent dans les compteurs
  3×N1 / 1×N2-N3 / max 5.
2026-08-05 14:15:11 +02:00
uberwald 3235017a4c fix: fiche Communauté - doublons de champs (Origine, Effectif)
- Origine était rendue à la fois dans l'en-tête (main) et l'onglet
  Identité : deux inputs du même nom, soumis en tableau puis concaténés
  avec une virgule à chaque sortie de champ.
- Effectif (population) était aussi dupliqué entre l'en-tête et
  l'onglet Domaines.
- Garde l'Origine dans l'en-tête, l'Effectif dans l'onglet Domaines
  (regroupé avec la Taille) ; plus aucun champ texte/nombre dupliqué.
2026-08-05 13:58:37 +02:00
uberwald b2a044640c feat: Instincts et Interdits du Totem sur la fiche de Groupe
- Onglet Info du Groupe : affichage des Instincts/Interdits de base du
  Totem (TOTEMS.*.instincts / bans), comme sur la fiche de personnage.
2026-08-05 13:54:00 +02:00
uberwald a13b0fc2d6 fix: affichage des Instincts et Interdits du Totem sur la fiche PJ
- Helper smarttl : honore la clé passée en 3e argument (défaut "name") ;
  il renvoyait systématiquement le nom du Totem, donc Instincts et
  Interdits n'apparaissaient pas (description et autres clés aussi).
- Onglet Totem (AppV2 et legacy) : libellés localisés
  IDENTITY.instincts / IDENTITY.prohibits au lieu de textes codés en dur.
2026-08-05 13:06:57 +02:00
uberwald 0ad240bd03 feat: section Rites chamaniques sur la fiche de personnage
- Nouveau booléen system.identity.chaman (statut de Chaman).
- Onglet Totem : case à cocher « Chaman » toujours visible ; quand
  elle est cochée, la section « Rites chamaniques » apparaît avec la
  liste des items de type rite maîtrisés (création, édition,
  suppression en mode édition).
- i18n FR/EN (VERMINE.chaman).
2026-08-05 12:05:31 +02:00
uberwald 9d36b92fa9 feat: nouveaux apprentissages (Capacités de Totem, Historique, Réputation) et expérience de Groupe
- Dialogue d'apprentissage personnage : 4 nouveaux types d'achat.
  - Capacité de Totem : options filtrées (mode de jeu, niveau de
    groupe, totem, non-apprises), difficulté = valeur d'Apprentissage,
    limites 3×N1 / 1×N2-N3 / max 5, marque system.learned de l'item
    (convention type character) et learned.ability du personnage.
  - Élément d'Historique : difficulté 9, handicap 1 si astérisque
    (background.asterisk), marque learned.background + item.
  - Test de Réputation (réduire/augmenter) : réduire = difficulté
    10 − score visé (−1 pt), augmenter = difficulté = réputation
    actuelle (+1 par succès), bornes 0-10, marque learned.reputation.
  - Évolution : option Dé de Totem Adapté ×2 quand Adapté > Humain
    (1D remplacé par un dé adapté compté ×2 via VermineUtils.roll).
- Correction règles : les Dés d'Expérience sont dépensés même en cas
  d'échec du jet d'apprentissage (décompte hors #applySuccess).
- Expérience de Groupe : nouveau dialogue d'apprentissage de Groupe
  (niveau, réserve, capacité de totem), réserve d'expérience et phase
  dans system.experience, boutons sur la fiche Groupe, reset des
  limites par phase (level/reserve/ability).
- Phase d'expérience : filtre par groupe sélectionné, associe chaque
  personnage à son premier groupe, reset des limites de tous les
  nouveaux types d'apprentissage.
- Corrections : l'attaque de créature n'ajoute plus la Réaction du Rôle
  (regles_creatures.txt), suppression de la case XP des en-têtes PJ
  (legacy et AppV2).
- Divers : .eslintrc.js (règles inexistantes/obsolètes retirées),
  libellé jaune de dialogue en couleur chitine, i18n FR/EN, chips
  « appris » sur l'onglet expérience du personnage.
2026-08-05 11:56:59 +02:00
uberwald d0373fab5a feat: capacités de créature, blessures séquentielles et refonte fiche Groupe
- Nouvel item Capacité de créature (creaturecapacity) : DataModel, fiche
  AppV2, chat card, types Base/Survie/Cauchemar/Apocalypse, liste sur
  l'onglet Informations de la créature avec drag&drop et post au chat.
- Blessures : coches séquentielles respectant la gravité (onClickWound),
  malus de blessure -1D/-2D/-3D appliqué aux jets (Règles p.42),
  montée en gravité automatique si les cercles sont pleins.
- Fiche Groupe : objectifs majeurs/mineurs éditables, liste de membres
  simplifiée, notes de voyage (roadNotes), totem qui remplit
  instinct/interdits, refonte visuelle des onglets.
- Créature : Réaction lançable, seuils/cercles de blessures corrigés
  (Taille cumulée, Groupe sans seuil), libellés Taille/Meute.
- Corrections : icône de carte de chat plafonnée à 64px, slash orphelin
  d'entrave à 0, notes de matériel du Groupe éditable (formInput),
  bonus de Réaction non double-compté.
- Divers : suppression des captures de débogage, licence README.
2026-08-05 09:38:36 +02:00
uberwald cf905cdcfb feat: fiche PNJ (profil + niveaux) et passage du véhicule en acteur
Release Creation / build (release) Failing after 1m17s
2026-08-04 20:34:20 +02:00
94 changed files with 3130 additions and 1949 deletions
+3 -7
View File
@@ -86,7 +86,6 @@ module.exports = {
'default-case': 'warn', 'default-case': 'warn',
'default-case-last': 'warn', 'default-case-last': 'warn',
'default-param-last': 'warn', 'default-param-last': 'warn',
'dot-locale-compare': 'warn',
'dot-notation': ['warn', { allowKeywords: true }], 'dot-notation': ['warn', { allowKeywords: true }],
'eqeqeq': ['error', 'always', { null: 'ignore' }], 'eqeqeq': ['error', 'always', { null: 'ignore' }],
'func-name-matching': 'warn', 'func-name-matching': 'warn',
@@ -123,7 +122,6 @@ module.exports = {
'no-delete-var': 'error', 'no-delete-var': 'error',
'no-div-regex': 'warn', 'no-div-regex': 'warn',
'no-else-return': ['warn', { allowElseIf: true }], 'no-else-return': ['warn', { allowElseIf: true }],
'no-empty-destructuring': 'warn',
'no-empty-function': ['warn', { allow: ['constructors'] }], 'no-empty-function': ['warn', { allow: ['constructors'] }],
'no-empty-pattern': 'warn', 'no-empty-pattern': 'warn',
'no-eq-null': 'off', 'no-eq-null': 'off',
@@ -153,7 +151,6 @@ module.exports = {
'no-new-func': 'warn', 'no-new-func': 'warn',
'no-new-wrappers': 'error', 'no-new-wrappers': 'error',
'no-nonoctal-decimal-escape': 'error', 'no-nonoctal-decimal-escape': 'error',
'no-object-multi-space': 'warn',
'no-octal-escape': 'error', 'no-octal-escape': 'error',
'no-param-reassign': ['warn', { props: false }], 'no-param-reassign': ['warn', { props: false }],
'no-plusplus': 'off', 'no-plusplus': 'off',
@@ -267,10 +264,9 @@ module.exports = {
VariableDeclarator: { var: 2, let: 2, const: 3 }, VariableDeclarator: { var: 2, let: 2, const: 3 },
outerIIFEBody: 1, outerIIFEBody: 1,
MemberExpression: 'off', MemberExpression: 'off',
FunctionDeclaration: { body: 1, parameters: 1, parameters: { var: 2, let: 2, const: 3 } }, FunctionDeclaration: { body: 1, parameters: 1 },
FunctionExpression: { body: 1, parameters: 1, parameters: { var: 2, let: 2, const: 3 } }, FunctionExpression: { body: 1, parameters: 1 },
StaticBlock: { body: 1 }, StaticBlock: { body: 1 }
ClassBody: 1
}], }],
'jsx-quotes': 'off', 'jsx-quotes': 'off',
'key-spacing': ['warn', { beforeColon: false, afterColon: true, mode: 'strict' }], 'key-spacing': ['warn', { beforeColon: false, afterColon: true, mode: 'strict' }],
+7 -1
View File
@@ -133,4 +133,10 @@ make build|watch|lint # Raccourcis Makefile
## Licence ## Licence
Projet fan non-officiel. L'univers et les marques Vermine 2047 appartiennent aux Éditions John Doe. VERMINE 2047, Un jeu de Julien Blondel
© 2023 Studio Agate
TOTEM System
© 2023 Julien Blondel
Tous droits réservés
Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 855 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 776 KiB

-680
View File
@@ -1,680 +0,0 @@
uid=78_0 RootWebArea "Foundry Virtual Tabletop" url="https://localhost:31000/game"
uid=78_1 ignored
uid=78_2 generic
uid=78_3 list
uid=78_4 generic
uid=78_5 generic
uid=78_6 generic
uid=78_7 generic
uid=78_8 generic
uid=78_9 generic
uid=78_10 list
uid=78_11 listitem level="1"
uid=78_12 tab "Outils de token" selectable
uid=78_13 listitem level="1"
uid=78_14 tab "Outils de tuile" selectable
uid=78_15 listitem level="1"
uid=78_16 tab "Outils de dessin" selectable
uid=78_17 listitem level="1"
uid=78_18 tab "Outils de mur" selectable
uid=78_19 listitem level="1"
uid=78_20 tab "Outils de lumière" selectable
uid=78_21 listitem level="1"
uid=78_22 tab "Outils de son dambiance" selectable
uid=78_23 listitem level="1"
uid=78_24 tab "Outils de région" selectable
uid=78_25 listitem level="1"
uid=78_26 tab "Notes" selectable
uid=78_27 list
uid=78_28 listitem level="1"
uid=78_29 button "Sélection de tokens" pressed
uid=78_30 listitem level="1"
uid=78_31 button "Sélection de cibles"
uid=78_32 listitem level="1"
uid=78_33 button "Règle"
uid=78_34 listitem level="1"
uid=78_35 button "Déplacement sans contrainte"
uid=78_36 generic
uid=78_37 generic
uid=78_38 list
uid=78_39 listitem level="1"
uid=78_40 ignored
uid=78_41 StaticText "Gamemaster [MJ]"
uid=78_42 InlineTextBox "Gamemaster [MJ]"
uid=78_43 generic
uid=78_44 generic
uid=78_45 LabelText
uid=78_46 StaticText "Latence"
uid=78_42 InlineTextBox "Latence"
uid=78_47 StaticText " "
uid=78_42 InlineTextBox " "
uid=78_48 StaticText "1ms"
uid=78_42 InlineTextBox "1ms"
uid=78_49 generic
uid=78_50 LabelText
uid=78_51 StaticText "IPS"
uid=78_42 InlineTextBox "IPS"
uid=78_52 StaticText " "
uid=78_42 InlineTextBox " "
uid=78_53 StaticText "--"
uid=78_42 InlineTextBox "--"
uid=78_54 button
uid=78_55 generic
uid=78_56 navigation
uid=78_57 list
uid=78_58 generic
uid=78_59 sectionheader
uid=78_60 generic
uid=78_61 generic
uid=78_62 LabelText
uid=78_63 LabelText
uid=78_64 sectionfooter
uid=78_65 generic roledescription="Barre de raccourcis"
uid=78_66 generic
uid=78_67 button "Couper le son"
uid=78_68 button "Menu principal"
uid=78_69 list
uid=78_70 button "Emplacement vide"
uid=78_71 ignored
uid=78_72 StaticText "1"
uid=78_42 InlineTextBox "1"
uid=78_73 button "Emplacement vide"
uid=78_74 ignored
uid=78_75 StaticText "2"
uid=78_42 InlineTextBox "2"
uid=78_76 button "Emplacement vide"
uid=78_77 ignored
uid=78_78 StaticText "3"
uid=78_42 InlineTextBox "3"
uid=78_79 button "Emplacement vide"
uid=78_80 ignored
uid=78_81 StaticText "4"
uid=78_42 InlineTextBox "4"
uid=78_82 button "Emplacement vide"
uid=78_83 ignored
uid=78_84 StaticText "5"
uid=78_42 InlineTextBox "5"
uid=78_85 button "Emplacement vide"
uid=78_86 ignored
uid=78_87 StaticText "6"
uid=78_42 InlineTextBox "6"
uid=78_88 button "Emplacement vide"
uid=78_89 ignored
uid=78_90 StaticText "7"
uid=78_42 InlineTextBox "7"
uid=78_91 button "Emplacement vide"
uid=78_92 ignored
uid=78_93 StaticText "8"
uid=78_42 InlineTextBox "8"
uid=78_94 button "Emplacement vide"
uid=78_95 ignored
uid=78_96 StaticText "9"
uid=78_42 InlineTextBox "9"
uid=78_97 button "Emplacement vide"
uid=78_98 ignored
uid=78_99 StaticText "0"
uid=78_42 InlineTextBox "0"
uid=78_100 generic
uid=78_101 navigation
uid=78_102 button "Page suivante"
uid=78_103 ignored
uid=78_104 StaticText "1"
uid=78_42 InlineTextBox "1"
uid=78_105 button "Page précédente"
uid=78_106 ignored
uid=78_107 button "Verrouiller la barre de raccourcis"
uid=78_108 button "Effacer la barre de raccourcis"
uid=78_109 generic
uid=78_110 generic
uid=78_111 generic
uid=78_112 ignored
uid=78_113 list
uid=78_114 generic "Tchat"
uid=78_115 generic
uid=78_116 generic value="
"
uid=78_117 paragraph
uid=78_118 LineBreak "
"
uid=78_42 InlineTextBox "
"
uid=78_119 ignored
uid=78_42 generic
uid=78_42 StaticText "Entrer un message"
uid=78_42 InlineTextBox "Entrer un message"
uid=78_120 generic
uid=78_121 generic
uid=78_122 button "Public en tant qu'utilisateur" pressed
uid=78_123 button "Privé pour les maîtres de jeu"
uid=78_124 button "Aveugle pour les maîtres de jeu"
uid=78_125 button "Seulement pour soi-même"
uid=78_126 button "Public en tant que personnage"
uid=78_127 generic
uid=78_128 tablist orientation="horizontal"
uid=78_129 list
uid=78_130 listitem level="1"
uid=78_131 tab "Messages du tchat" selectable
uid=78_132 listitem level="1"
uid=78_133 tab "Rencontres de combat" selectable
uid=78_134 listitem level="1"
uid=78_135 tab "Scènes" selectable
uid=78_136 listitem level="1"
uid=78_137 tab "Objets plaçables" selectable
uid=78_138 listitem level="1"
uid=78_139 tab "Acteurs" selectable
uid=78_140 listitem level="1"
uid=78_141 tab "Objets" selectable
uid=78_142 listitem level="1"
uid=78_143 tab "Journaux" selectable
uid=78_144 listitem level="1"
uid=78_145 tab "Tables aléatoires" selectable
uid=78_146 listitem level="1"
uid=78_147 tab "Jeux de cartes" selectable
uid=78_148 listitem level="1"
uid=78_149 tab "Macros" selectable
uid=78_150 listitem level="1"
uid=78_151 tab "Playlists" selectable
uid=78_152 listitem level="1"
uid=78_153 tab "Compendiums" selectable
uid=78_154 listitem level="1"
uid=78_155 tab "Paramètres" selectable
uid=78_156 listitem level="1"
uid=78_157 button "Réduire"
uid=78_158 generic
uid=78_159 ignored
uid=78_160 ignored
uid=78_161 ignored
uid=78_162 ignored
uid=78_163 generic
uid=78_164 sectionheader
uid=78_165 ignored
uid=78_166 button "Créer un acteur"
uid=78_167 ignored
uid=78_168 ignored
uid=78_169 StaticText "Créer un acteur"
uid=78_42 InlineTextBox "Créer un acteur"
uid=78_170 button "Créer un dossier"
uid=78_171 ignored
uid=78_172 ignored
uid=78_173 StaticText "Créer un dossier"
uid=78_42 InlineTextBox "Créer un dossier"
uid=78_174 search
uid=78_175 button "Recherche par nom uniquement"
uid=78_176 searchbox "Chercher dans les Acteurs"
uid=78_177 ignored
uid=78_178 ignored
uid=78_179 generic
uid=78_180 button "Trier par ordre alphabétique"
uid=78_181 button "Réduire tous les dossiers"
uid=78_182 list
uid=78_183 listitem level="1"
uid=78_184 image "Acteur" url="https://localhost:31000/systems/vermine2047/assets/icons/actors/creature.webp"
uid=78_185 generic
uid=78_186 StaticText "Acteur"
uid=78_42 InlineTextBox "Acteur"
uid=78_187 listitem level="1"
uid=78_188 image "Acteur (2)" url="https://localhost:31000/systems/vermine2047/assets/icons/actors/group.webp"
uid=78_189 generic
uid=78_190 StaticText "Acteur (2)"
uid=78_42 InlineTextBox "Acteur (2)"
uid=78_191 listitem level="1"
uid=78_192 image "Acteur (3)" url="https://localhost:31000/systems/vermine2047/assets/icons/actors/character.webp"
uid=78_193 generic
uid=78_194 StaticText "Acteur (3)"
uid=78_42 InlineTextBox "Acteur (3)"
uid=78_195 sectionfooter
uid=78_196 ignored
uid=78_197 ignored
uid=78_198 ignored
uid=78_199 ignored
uid=78_200 ignored
uid=78_201 ignored
uid=78_202 ignored
uid=78_203 ignored
uid=78_204 ignored
uid=78_205 ignored
uid=78_206 ignored
uid=78_207 ignored
uid=78_208 ignored
uid=78_209 ignored
uid=78_210 ignored
uid=78_211 ignored
uid=78_212 ignored
uid=78_213 ignored
uid=78_214 ignored
uid=78_215 ignored
uid=78_216 ignored
uid=78_217 ignored
uid=78_218 ignored
uid=78_219 figure
uid=78_220 image url="https://localhost:31000/ui/pause.svg"
uid=78_221 Figcaption
uid=78_222 StaticText "JEU EN PAUSE"
uid=78_42 InlineTextBox "JEU EN PAUSE"
uid=78_223 form
uid=78_224 banner
uid=78_225 heading "Personnage: Acteur (3)" level="1"
uid=78_226 StaticText "Personnage: Acteur (3)"
uid=78_42 InlineTextBox "Personnage: Acteur (3)"
uid=78_227 button "Basculer les contrôles"
uid=78_228 button "Copier l'UUID du document"
uid=78_229 button "Fermer la fenêtre"
uid=78_230 generic
uid=78_231 ignored
uid=78_232 ignored
uid=78_233 button "MODE ÉDITION"
uid=78_234 StaticText "MODE ÉDITION"
uid=78_42 InlineTextBox "MODE ÉDITION"
uid=78_235 image "logo Vermine" url="https://localhost:31000/systems/vermine2047/assets/images/ui/logo.webp"
uid=78_236 ignored
uid=78_237 image "Acteur (3)" url="https://localhost:31000/systems/vermine2047/assets/icons/actors/character.webp"
uid=78_238 generic
uid=78_239 heading "ADAPTATION" level="3"
uid=78_240 StaticText "ADAPTATION"
uid=78_42 InlineTextBox "ADAPTATION"
uid=78_241 heading "L'Humain" level="5"
uid=78_242 StaticText "L'Humain"
uid=78_42 InlineTextBox "L'Humain"
uid=78_243 image url="https://localhost:31000/systems/vermine2047/assets/images/ui/totems/human.webp"
uid=78_244 heading "L'adapté" level="5"
uid=78_245 StaticText "L'adapté"
uid=78_42 InlineTextBox "L'adapté"
uid=78_246 image url="https://localhost:31000/systems/vermine2047/assets/images/ui/totems/adapted.webp"
uid=78_247 ignored
uid=78_248 ignored
uid=78_249 ignored
uid=78_250 generic
uid=78_251 generic
uid=78_252 ignored
uid=78_253 ignored
uid=78_254 ignored
uid=78_255 ignored
uid=78_256 generic
uid=78_257 generic
uid=78_258 ignored
uid=78_259 ignored
uid=78_260 list
uid=78_261 listitem level="1"
uid=78_262 ignored
uid=78_263 heading "LA HORDE" level="4"
uid=78_264 StaticText "LA HORDE"
uid=78_42 InlineTextBox "LA HORDE"
uid=78_265 image "La Horde" url="https://localhost:31000/systems/vermine2047/assets/images/ui/totems/horde.webp"
uid=78_266 navigation roledescription="Navigation dans l’onglet des feuilles de personnages"
uid=78_267 generic
uid=78_268 ignored
uid=78_269 StaticText "Caractéristiques et compétences"
uid=78_42 InlineTextBox "Caractéristiques "
uid=78_42 InlineTextBox "et compétences"
uid=78_270 generic
uid=78_271 ignored
uid=78_272 StaticText "Totem et ajustements"
uid=78_42 InlineTextBox "Totem et "
uid=78_42 InlineTextBox "ajustements"
uid=78_273 generic
uid=78_274 ignored
uid=78_275 StaticText "Matériel"
uid=78_42 InlineTextBox "Matériel"
uid=78_276 generic
uid=78_277 ignored
uid=78_278 StaticText "Histoire"
uid=78_42 InlineTextBox "Histoire"
uid=78_279 generic
uid=78_280 ignored
uid=78_281 StaticText "Combat et reserves"
uid=78_42 InlineTextBox "Combat "
uid=78_42 InlineTextBox "et "
uid=78_42 InlineTextBox "reserves"
uid=78_282 generic
uid=78_283 sectionheader
uid=78_284 generic
uid=78_285 heading "Nom Acteur (3)" level="1"
uid=78_286 LabelText
uid=78_287 StaticText "Nom"
uid=78_42 InlineTextBox "Nom"
uid=78_288 ignored
uid=78_289 StaticText "Acteur (3)"
uid=78_42 InlineTextBox "Acteur (3)"
uid=78_290 ignored
uid=78_291 LabelText
uid=78_292 StaticText "Profil"
uid=78_42 InlineTextBox "Profil"
uid=78_293 ignored
uid=78_294 ignored
uid=78_295 LabelText
uid=78_296 StaticText "Age"
uid=78_42 InlineTextBox "Age"
uid=78_297 ignored
uid=78_298 ignored
uid=78_299 StaticText "15"
uid=78_42 InlineTextBox "15"
uid=78_300 generic
uid=78_301 StaticText "(Jeune)"
uid=78_42 InlineTextBox "(Jeune)"
uid=78_302 generic
uid=78_303 heading "Totem La Horde" level="1"
uid=78_304 LabelText
uid=78_305 StaticText "Totem"
uid=78_42 InlineTextBox "Totem"
uid=78_306 generic
uid=78_307 StaticText "La Horde"
uid=78_42 InlineTextBox "La Horde"
uid=78_308 ignored
uid=78_309 LabelText
uid=78_310 StaticText "Réputation"
uid=78_42 InlineTextBox "Réputation"
uid=78_311 ignored
uid=78_312 StaticText "0"
uid=78_42 InlineTextBox "0"
uid=78_313 LabelText
uid=78_314 StaticText "Expérience"
uid=78_42 InlineTextBox "Expérience"
uid=78_315 ignored
uid=78_316 StaticText "0"
uid=78_42 InlineTextBox "0"
uid=78_317 heading "CARACTÉRISTIQUES" level="3"
uid=78_318 StaticText "CARACTÉRISTIQUES"
uid=78_42 InlineTextBox "CARACTÉRISTIQUES"
uid=78_319 ignored
uid=78_320 ignored
uid=78_321 heading "PHYSIQUE" level="4"
uid=78_322 StaticText "PHYSIQUE"
uid=78_42 InlineTextBox "PHYSIQUE"
uid=78_323 generic
uid=78_324 LabelText
uid=78_325 StaticText "Vigueur"
uid=78_42 InlineTextBox "Vigueur"
uid=78_326 generic
uid=78_327 LabelText
uid=78_328 StaticText "Santé"
uid=78_42 InlineTextBox "Santé"
uid=78_329 ignored
uid=78_330 heading "MANUEL" level="4"
uid=78_331 StaticText "MANUEL"
uid=78_42 InlineTextBox "MANUEL"
uid=78_332 generic
uid=78_333 LabelText
uid=78_334 StaticText "Précision"
uid=78_42 InlineTextBox "Précision"
uid=78_335 generic
uid=78_336 LabelText
uid=78_337 StaticText "Réflexes"
uid=78_42 InlineTextBox "Réflexes"
uid=78_338 ignored
uid=78_339 heading "MENTAL" level="4"
uid=78_340 StaticText "MENTAL"
uid=78_42 InlineTextBox "MENTAL"
uid=78_341 generic
uid=78_342 LabelText
uid=78_343 StaticText "Savoir"
uid=78_42 InlineTextBox "Savoir"
uid=78_344 generic
uid=78_345 LabelText
uid=78_346 StaticText "Perception"
uid=78_42 InlineTextBox "Perception"
uid=78_347 ignored
uid=78_348 heading "SOCIAL" level="4"
uid=78_349 StaticText "SOCIAL"
uid=78_42 InlineTextBox "SOCIAL"
uid=78_350 generic
uid=78_351 LabelText
uid=78_352 StaticText "Volonté"
uid=78_42 InlineTextBox "Volonté"
uid=78_353 generic
uid=78_354 LabelText
uid=78_355 StaticText "Empathie"
uid=78_42 InlineTextBox "Empathie"
uid=78_356 heading "COMPÉTENCES" level="3"
uid=78_357 StaticText "COMPÉTENCES"
uid=78_42 InlineTextBox "COMPÉTENCES"
uid=78_358 ignored
uid=78_359 ignored
uid=78_360 heading "L'HOMME" level="4"
uid=78_361 StaticText "L'HOMME"
uid=78_42 InlineTextBox "L'HOMME"
uid=78_362 generic
uid=78_363 LabelText
uid=78_364 StaticText "Arts "
uid=78_42 InlineTextBox "Arts "
uid=78_365 superscript
uid=78_366 StaticText "(I)"
uid=78_42 InlineTextBox "(I)"
uid=78_367 generic
uid=78_368 LabelText
uid=78_369 StaticText "Civilisation "
uid=78_42 InlineTextBox "Civilisation "
uid=78_370 superscript
uid=78_371 StaticText "(II)"
uid=78_42 InlineTextBox "(II)"
uid=78_372 generic
uid=78_373 LabelText
uid=78_374 StaticText "Psychologie "
uid=78_42 InlineTextBox "Psychologie "
uid=78_375 superscript
uid=78_376 StaticText "(I)"
uid=78_42 InlineTextBox "(I)"
uid=78_377 generic
uid=78_378 LabelText
uid=78_379 StaticText "Rumeurs"
uid=78_42 InlineTextBox "Rumeurs"
uid=78_380 generic
uid=78_381 LabelText
uid=78_382 StaticText "Soins "
uid=78_42 InlineTextBox "Soins "
uid=78_383 superscript
uid=78_384 StaticText "(I)"
uid=78_42 InlineTextBox "(I)"
uid=78_385 ignored
uid=78_386 heading "L'ANIMAL" level="4"
uid=78_387 StaticText "L'ANIMAL"
uid=78_42 InlineTextBox "L'ANIMAL"
uid=78_388 generic
uid=78_389 LabelText
uid=78_390 StaticText "Animalisme "
uid=78_42 InlineTextBox "Animalisme "
uid=78_391 superscript
uid=78_392 StaticText "(I)"
uid=78_42 InlineTextBox "(I)"
uid=78_393 generic
uid=78_394 LabelText
uid=78_395 StaticText "Dissection "
uid=78_42 InlineTextBox "Dissection "
uid=78_396 superscript
uid=78_397 StaticText "(II)"
uid=78_42 InlineTextBox "(II)"
uid=78_398 generic
uid=78_399 LabelText
uid=78_400 StaticText "Faune "
uid=78_42 InlineTextBox "Faune "
uid=78_401 superscript
uid=78_402 StaticText "(I)"
uid=78_42 InlineTextBox "(I)"
uid=78_403 generic
uid=78_404 LabelText
uid=78_405 StaticText "Répulsion"
uid=78_42 InlineTextBox "Répulsion"
uid=78_406 generic
uid=78_407 LabelText
uid=78_408 StaticText "Pistage"
uid=78_42 InlineTextBox "Pistage"
uid=78_409 ignored
uid=78_410 heading "LA MACHINE" level="4"
uid=78_411 StaticText "LA MACHINE"
uid=78_42 InlineTextBox "LA MACHINE"
uid=78_412 generic
uid=78_413 LabelText
uid=78_414 StaticText "Artisanat "
uid=78_42 InlineTextBox "Artisanat "
uid=78_415 superscript
uid=78_416 StaticText "(II)"
uid=78_42 InlineTextBox "(II)"
uid=78_417 generic
uid=78_418 LabelText
uid=78_419 StaticText "Bricolage"
uid=78_42 InlineTextBox "Bricolage"
uid=78_420 generic
uid=78_421 LabelText
uid=78_422 StaticText "Mécanique "
uid=78_42 InlineTextBox "Mécanique "
uid=78_423 superscript
uid=78_424 StaticText "(II)"
uid=78_42 InlineTextBox "(II)"
uid=78_425 generic
uid=78_426 LabelText
uid=78_427 StaticText "Pilotage "
uid=78_42 InlineTextBox "Pilotage "
uid=78_428 superscript
uid=78_429 StaticText "(I)"
uid=78_42 InlineTextBox "(I)"
uid=78_430 generic
uid=78_431 LabelText
uid=78_432 StaticText "Technologie "
uid=78_42 InlineTextBox "Technologie "
uid=78_433 superscript
uid=78_434 StaticText "(II)"
uid=78_42 InlineTextBox "(II)"
uid=78_435 ignored
uid=78_436 heading "L'ARME" level="4"
uid=78_437 StaticText "L'ARME"
uid=78_42 InlineTextBox "L'ARME"
uid=78_438 generic
uid=78_439 LabelText
uid=78_440 StaticText "Armes à feu "
uid=78_42 InlineTextBox "Armes à feu "
uid=78_441 superscript
uid=78_442 StaticText "(II)"
uid=78_42 InlineTextBox "(II)"
uid=78_443 generic
uid=78_444 LabelText
uid=78_445 StaticText "Tir à l'arc"
uid=78_42 InlineTextBox "Tir à l'arc"
uid=78_446 generic
uid=78_447 LabelText
uid=78_448 StaticText "Armurerie "
uid=78_42 InlineTextBox "Armurerie "
uid=78_449 superscript
uid=78_450 StaticText "(II)"
uid=78_42 InlineTextBox "(II)"
uid=78_451 generic
uid=78_452 LabelText
uid=78_453 StaticText "Lancer"
uid=78_42 InlineTextBox "Lancer"
uid=78_454 generic
uid=78_455 LabelText
uid=78_456 StaticText "Mêlée"
uid=78_42 InlineTextBox "Mêlée"
uid=78_457 ignored
uid=78_458 heading "LA SURVIE" level="4"
uid=78_459 StaticText "LA SURVIE"
uid=78_42 InlineTextBox "LA SURVIE"
uid=78_460 generic
uid=78_461 LabelText
uid=78_462 StaticText "Vigilance"
uid=78_42 InlineTextBox "Vigilance"
uid=78_463 generic
uid=78_464 LabelText
uid=78_465 StaticText "Athlétisme"
uid=78_42 InlineTextBox "Athlétisme"
uid=78_466 generic
uid=78_467 LabelText
uid=78_468 StaticText "Alimentation"
uid=78_42 InlineTextBox "Alimentation"
uid=78_469 generic
uid=78_470 LabelText
uid=78_471 StaticText "Discrétion"
uid=78_42 InlineTextBox "Discrétion"
uid=78_472 generic
uid=78_473 LabelText
uid=78_474 StaticText "Corps-à-corps"
uid=78_42 InlineTextBox "Corps-à-corps"
uid=78_475 ignored
uid=78_476 heading "LA TERRE" level="4"
uid=78_477 StaticText "LA TERRE"
uid=78_42 InlineTextBox "LA TERRE"
uid=78_478 generic
uid=78_479 LabelText
uid=78_480 StaticText "Environnement "
uid=78_42 InlineTextBox "Environnement "
uid=78_481 superscript
uid=78_482 StaticText "(I)"
uid=78_42 InlineTextBox "(I)"
uid=78_483 generic
uid=78_484 LabelText
uid=78_485 StaticText "Flore "
uid=78_42 InlineTextBox "Flore "
uid=78_486 superscript
uid=78_487 StaticText "(I)"
uid=78_42 InlineTextBox "(I)"
uid=78_488 generic
uid=78_489 LabelText
uid=78_490 StaticText "Route"
uid=78_42 InlineTextBox "Route"
uid=78_491 generic
uid=78_492 LabelText
uid=78_493 StaticText "Toxiques "
uid=78_42 InlineTextBox "Toxiques "
uid=78_494 superscript
uid=78_495 StaticText "(II)"
uid=78_42 InlineTextBox "(II)"
uid=78_496 generic
uid=78_497 LabelText
uid=78_498 StaticText "Vestiges "
uid=78_42 InlineTextBox "Vestiges "
uid=78_499 superscript
uid=78_500 StaticText "(I)"
uid=78_42 InlineTextBox "(I)"
uid=78_501 ignored
uid=78_502 ignored
uid=78_503 ignored
uid=78_504 ignored
uid=78_505 ignored
uid=78_506 ignored
uid=78_507 ignored
uid=78_508 ignored
uid=78_509 ignored
uid=78_510 ignored
uid=78_511 ignored
uid=78_512 ignored
uid=78_513 ignored
uid=78_514 ignored
uid=78_515 ignored
uid=78_516 ignored
uid=78_517 ignored
uid=78_518 ignored
uid=78_519 ignored
uid=78_520 ignored
uid=78_521 ignored
uid=78_522 ignored
uid=78_523 ignored
uid=78_524 ignored
uid=78_525 ignored
uid=78_526 ignored
uid=78_527 ignored
uid=78_528 ignored
uid=78_529 ignored
uid=78_530 ignored
uid=78_531 ignored
uid=78_532 ignored
uid=78_533 ignored
uid=78_534 ignored
uid=78_535 ignored
uid=78_536 ignored
uid=78_537 ignored
uid=78_538 ignored
uid=78_539 ignored
uid=78_540 ignored
uid=78_541 ignored
uid=78_542 ignored
uid=78_543 ignored
uid=78_544 ignored
uid=78_545 ignored
uid=78_546 ignored
uid=78_547 ignored
uid=78_548 ignored
uid=78_549 ignored
uid=78_550 ignored
uid=78_551 ignored
uid=78_552 ignored
uid=78_553 ignored
uid=78_554 ignored
uid=78_555 generic
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 834 KiB

+73 -2
View File
@@ -16,8 +16,9 @@
"TYPES.Item.item": "Item", "TYPES.Item.item": "Item",
"TYPES.Item.weapon": "Weapon", "TYPES.Item.weapon": "Weapon",
"TYPES.Item.defense": "Defense", "TYPES.Item.defense": "Defense",
"TYPES.Item.vehicle": "Vehicle", "TYPES.Actor.vehicle": "Vehicle",
"TYPES.Item.ability": "Ability", "TYPES.Item.ability": "Ability",
"TYPES.Item.creaturecapacity": "Creature capacity",
"TYPES.Item.specialty": "Specialty", "TYPES.Item.specialty": "Specialty",
"TYPES.Item.background": "Background", "TYPES.Item.background": "Background",
"TYPES.Item.trauma": "Trauma", "TYPES.Item.trauma": "Trauma",
@@ -43,6 +44,20 @@
"nightmare": "Nightmare", "nightmare": "Nightmare",
"apocalypse": "Apocalypse" "apocalypse": "Apocalypse"
}, },
"CREATURE_CAPACITY_TYPES": {
"base": {
"name": "Base"
},
"survival": {
"name": "Survival"
},
"nightmare": {
"name": "Nightmare"
},
"apocalypse": {
"name": "Apocalypse"
}
},
"ROLLS": { "ROLLS": {
"tool": "Dice Roller", "tool": "Dice Roller",
"roll_dice": "Roll the dice" "roll_dice": "Roll the dice"
@@ -140,11 +155,15 @@
"level": "Level", "level": "Level",
"experience": "Experience", "experience": "Experience",
"reputation": "Reputation", "reputation": "Reputation",
"chaman": "Shaman",
"sheet": { "sheet": {
"description": "Description" "description": "Description"
}, },
"pool": "Reserve", "pool": "Reserve",
"pools": "Reserves", "pools": "Reserves",
"dice_pool": "Dice pool",
"pool_malus": "Wound penalty",
"auto_successes": "automatic successes",
"self_control": "Self-control", "self_control": "Self-control",
"effort": "Effort", "effort": "Effort",
"equip": "Equip", "equip": "Equip",
@@ -174,6 +193,7 @@
"range": "Range", "range": "Range",
"ranges": "Ranges", "ranges": "Ranges",
"mobility": "Mobility", "mobility": "Mobility",
"vehicle": "Vehicle",
"vehicle_specs": "Vehicle specifications", "vehicle_specs": "Vehicle specifications",
"kind": "Kind", "kind": "Kind",
"kind_vehicle": "Vehicle", "kind_vehicle": "Vehicle",
@@ -183,6 +203,10 @@
"speed_duration_ph": "top speed duration (e.g. 10 min)", "speed_duration_ph": "top speed duration (e.g. 10 min)",
"effort_reserve": "Effort reserve", "effort_reserve": "Effort reserve",
"maintenance": "Maintenance", "maintenance": "Maintenance",
"owner": "Owner",
"no_vehicles": "No vehicles",
"no_encounters": "No encounters",
"travel_notes": "Travel notes",
"rarity": "Rarity", "rarity": "Rarity",
"rarity_sm": "Rar.", "rarity_sm": "Rar.",
"reliability": "Reliability", "reliability": "Reliability",
@@ -209,11 +233,13 @@
"boost": "Boost", "boost": "Boost",
"group_abilities": "Group abilities", "group_abilities": "Group abilities",
"totem_abilities": "Totem abilities", "totem_abilities": "Totem abilities",
"creature_capacities": "Creature capacities",
"type_group": "Group", "type_group": "Group",
"type_character": "Character", "type_character": "Character",
"type_npc": "NPC", "type_npc": "NPC",
"type_creature": "Creature", "type_creature": "Creature",
"type_community": "Community", "type_community": "Community",
"type_vehicle": "Vehicle",
"type": "Type", "type": "Type",
"group_members": "Members", "group_members": "Members",
"encounters": "Encounters", "encounters": "Encounters",
@@ -321,6 +347,8 @@
"no_wound": "No wound", "no_wound": "No wound",
"apply_wound": "Apply wound", "apply_wound": "Apply wound",
"wound_applied": "Wound applied", "wound_applied": "Wound applied",
"wound_escalated": "{to} wound (escalated from {from})",
"wound_death": "All wounds checked — death",
"error_creature_no_defense": "Creatures cannot defend (passive defense)", "error_creature_no_defense": "Creatures cannot defend (passive defense)",
"error_no_permission": "Insufficient permissions", "error_no_permission": "Insufficient permissions",
"error_no_target": "No target selected for this exchange", "error_no_target": "No target selected for this exchange",
@@ -354,6 +382,9 @@
"learned_reserve": "Reserve", "learned_reserve": "Reserve",
"learned_characteristic": "Characteristic", "learned_characteristic": "Characteristic",
"learned_evolution": "Evolution", "learned_evolution": "Evolution",
"learned_ability": "Totem capacity",
"learned_background": "Background",
"learned_reputation": "Reputation",
"no_character": "No character in this world.", "no_character": "No character in this world.",
"apply": "Apply", "apply": "Apply",
"characteristic": "Characteristic", "characteristic": "Characteristic",
@@ -362,11 +393,22 @@
"learn_characteristic": "Characteristic", "learn_characteristic": "Characteristic",
"learn_reserve": "Reserve", "learn_reserve": "Reserve",
"learn_evolution": "Evolution die", "learn_evolution": "Evolution die",
"learn_totem_ability": "Totem capacity",
"learn_background": "Background element",
"learn_reputation": "Reputation",
"learn_adapted_die": "Replace 1 Experience die with an Adapted totem die (×2)",
"adapted_die_x2": "1 Adapted totem die ×2",
"learn_specialty_name": "Specialty name", "learn_specialty_name": "Specialty name",
"specialty_hint": "Prerequisite: skill at least Proficient (≥2).", "specialty_hint": "Prerequisite: skill at least Proficient (≥2).",
"characteristic_hint": "Difficulty 9 + Handicap = current value. Max 3 (except Superhuman).", "characteristic_hint": "Difficulty 9 + Handicap = current value. Max 3 (except Superhuman).",
"reserve_hint": "Difficulty = current value + age Handicap. Max 10.", "reserve_hint": "Difficulty = current value + age Handicap. Max 10.",
"evolution_hint": "Difficulty by age + Handicap = accumulated Evolution dice. Adapted totem +1D, Human -1D.", "evolution_hint": "Difficulty by age + Handicap = accumulated Evolution dice. Adapted totem +1D, Human -1D.",
"totem_ability_hint": "Prerequisites: same Totem as the character, Group level ≥ Capacity level, game mode compatible (Survival N1, Nightmare N2, Apocalypse N3).",
"background_hint": "Difficulty 9. Handicap (I) if the Background is marked with an asterisk (*). GM authorization required.",
"reputation_hint": "Reduce = more famous (Difficulty 10 target score, 1 pt max per phase). Increase = fade into obscurity (requires a major Objective, Difficulty = current Reputation, +1 per Success).",
"reputation_reduce": "Become more famous (reduce)",
"reputation_increase": "Fade into obscurity (increase)",
"reputation_current": "Current Reputation",
"dice_spent": "Dice spent", "dice_spent": "Dice spent",
"learning_success": "Learning successful!", "learning_success": "Learning successful!",
"error_no_experience_dice": "No Experience dice available.", "error_no_experience_dice": "No Experience dice available.",
@@ -375,6 +417,25 @@
"error_characteristic_max": "Characteristic already at maximum (3).", "error_characteristic_max": "Characteristic already at maximum (3).",
"error_specialty_requirement": "Specialty requires: skill at least Proficient (≥2).", "error_specialty_requirement": "Specialty requires: skill at least Proficient (≥2).",
"error_reserve_max": "Reserve already at maximum (10).", "error_reserve_max": "Reserve already at maximum (10).",
"no_totem_ability": "No Totem capacity available to learn.",
"no_background": "No background element to acquire.",
"error_totem_mode": "Capacity level incompatible with the game mode.",
"error_totem_totem": "The character must share the Totem of the Capacity.",
"error_totem_group_level": "Group level insufficient for this Capacity.",
"error_totem_limit": "Totem capacity limit reached (3× Level 1, 1× Levels 2 and 3, max 5).",
"error_reputation_min": "Reputation already at minimum (0).",
"error_reputation_max": "Reputation already at maximum (10).",
"error_evolution_no_adapted": "The character must have strictly more Adapted than Human totem dice.",
"learn_level": "Group level",
"learn_group_reserve": "Group reserve",
"learn_ability": "Group capacity",
"group_level_hint": "Difficulty = number of members. Handicap = current Group level. Max 10.",
"group_reserve_hint": "Difficulty = current Reserve value. Handicap II. Max 10.",
"group_ability_hint": "Difficulty = Learning. Handicap = Capacity level. Requires Group level ≥ Capacity level.",
"no_group_ability": "No Group capacity available to develop.",
"error_group_level_max": "Group level already at maximum (10).",
"error_group_reserve_max": "Group reserve already at maximum.",
"error_group_level_requirement": "Group level insufficient for this Capacity.",
"experience_transfer": "Experience dice", "experience_transfer": "Experience dice",
"experience_pull": "Pull from group", "experience_pull": "Pull from group",
"experience_give": "Give 1D to group", "experience_give": "Give 1D to group",
@@ -394,7 +455,8 @@
"npc": "NPC", "npc": "NPC",
"group": "Group", "group": "Group",
"creature": "Creature", "creature": "Creature",
"community": "Community" "community": "Community",
"vehicle": "Vehicle"
}, },
"base_values": "Base Values", "base_values": "Base Values",
"computed_values": "Computed Values", "computed_values": "Computed Values",
@@ -411,6 +473,8 @@
"morale": "Morale", "morale": "Morale",
"needed": "Needed", "needed": "Needed",
"objective_placeholder": "eg: Survive, Explore", "objective_placeholder": "eg: Survive, Explore",
"major_objective_placeholder": "eg: Reach a distant goal",
"minor_objective_placeholder": "eg: Find a medic, scavenge",
"objectives": "Objectives", "objectives": "Objectives",
"pack": "Pack", "pack": "Pack",
"pattern": "Pattern", "pattern": "Pattern",
@@ -563,6 +627,8 @@
"ability": "Ability", "ability": "Ability",
"new_ability": "New ability", "new_ability": "New ability",
"abilities": "Abilities", "abilities": "Abilities",
"creaturecapacity": "Creature capacity",
"new_creaturecapacity": "New creature capacity",
"specialties": "Specialties", "specialties": "Specialties",
"new_specialty": "New specialty", "new_specialty": "New specialty",
"target": "Target", "target": "Target",
@@ -637,6 +703,11 @@
"dog": "Dog", "dog": "Dog",
"bear": "Bear" "bear": "Bear"
}, },
"PACK_LEVELS": {
"small": "Small",
"large": "Large",
"giant": "Giant"
},
"TOTEMS": { "TOTEMS": {
"human": { "human": {
"name": "Human", "name": "Human",
+73 -2
View File
@@ -8,8 +8,9 @@
"TYPES.Item.item": "Objet", "TYPES.Item.item": "Objet",
"TYPES.Item.weapon": "Arme", "TYPES.Item.weapon": "Arme",
"TYPES.Item.defense": "Protection", "TYPES.Item.defense": "Protection",
"TYPES.Item.vehicle": "Véhicule", "TYPES.Actor.vehicle": "Véhicule",
"TYPES.Item.ability": "Capacité", "TYPES.Item.ability": "Capacité",
"TYPES.Item.creaturecapacity": "Capacité de créature",
"TYPES.Item.specialty": "Spécialité", "TYPES.Item.specialty": "Spécialité",
"TYPES.Item.background": "Historique", "TYPES.Item.background": "Historique",
"TYPES.Item.trauma": "Traumatisme", "TYPES.Item.trauma": "Traumatisme",
@@ -49,6 +50,20 @@
"nightmare": "Cauchemar", "nightmare": "Cauchemar",
"apocalypse": "Apocalypse" "apocalypse": "Apocalypse"
}, },
"CREATURE_CAPACITY_TYPES": {
"base": {
"name": "Base"
},
"survival": {
"name": "Survie"
},
"nightmare": {
"name": "Cauchemar"
},
"apocalypse": {
"name": "Apocalypse"
}
},
"ROLLS": { "ROLLS": {
"tool": "Lanceur de dés", "tool": "Lanceur de dés",
"roll_dice": "Jeter les dés" "roll_dice": "Jeter les dés"
@@ -146,12 +161,16 @@
"level": "Niveau", "level": "Niveau",
"experience": "Expérience", "experience": "Expérience",
"reputation": "Réputation", "reputation": "Réputation",
"chaman": "Chaman",
"sheet": { "sheet": {
"description": "Description" "description": "Description"
}, },
"pool": "Réserve", "pool": "Réserve",
"pools": "Réserves", "pools": "Réserves",
"reserves": "Réserves", "reserves": "Réserves",
"dice_pool": "Pool de dés",
"pool_malus": "Malus Blessure",
"auto_successes": "réussites automatiques",
"self_control": "Sang-Froid", "self_control": "Sang-Froid",
"effort": "Effort", "effort": "Effort",
"equip": "Équiper", "equip": "Équiper",
@@ -255,6 +274,7 @@
"range": "Portée", "range": "Portée",
"ranges": "Portées", "ranges": "Portées",
"mobility": "Mobilité", "mobility": "Mobilité",
"vehicle": "Véhicule",
"vehicle_specs": "Caractéristiques du véhicule", "vehicle_specs": "Caractéristiques du véhicule",
"kind": "Type", "kind": "Type",
"kind_vehicle": "Véhicule", "kind_vehicle": "Véhicule",
@@ -264,6 +284,10 @@
"speed_duration_ph": "durée de pointe (ex: 10 min)", "speed_duration_ph": "durée de pointe (ex: 10 min)",
"effort_reserve": "Réserve d'Effort", "effort_reserve": "Réserve d'Effort",
"maintenance": "Entretien", "maintenance": "Entretien",
"owner": "Propriétaire",
"no_vehicles": "Aucun véhicule",
"no_encounters": "Aucune rencontre",
"travel_notes": "Notes de voyage",
"rarity": "Rareté", "rarity": "Rareté",
"rarity_sm": "Rar.", "rarity_sm": "Rar.",
"reliability": "Fiabilité", "reliability": "Fiabilité",
@@ -306,6 +330,8 @@
"no_wound": "Aucune blessure", "no_wound": "Aucune blessure",
"apply_wound": "Appliquer la blessure", "apply_wound": "Appliquer la blessure",
"wound_applied": "Blessure appliquée", "wound_applied": "Blessure appliquée",
"wound_escalated": "Blessure {to} (montée depuis {from})",
"wound_death": "Toutes les blessures sont cochées — mort",
"error_creature_no_defense": "Les créatures ne peuvent pas se défendre (défense passive)", "error_creature_no_defense": "Les créatures ne peuvent pas se défendre (défense passive)",
"error_no_permission": "Permissions insuffisantes", "error_no_permission": "Permissions insuffisantes",
"error_no_target": "Aucune cible sélectionnée pour cet échange", "error_no_target": "Aucune cible sélectionnée pour cet échange",
@@ -339,6 +365,9 @@
"learned_reserve": "Réserve", "learned_reserve": "Réserve",
"learned_characteristic": "Caractéristique", "learned_characteristic": "Caractéristique",
"learned_evolution": "Évolution", "learned_evolution": "Évolution",
"learned_ability": "Capacité de Totem",
"learned_background": "Historique",
"learned_reputation": "Réputation",
"no_character": "Aucun personnage dans ce monde.", "no_character": "Aucun personnage dans ce monde.",
"apply": "Appliquer", "apply": "Appliquer",
"characteristic": "Caractéristique", "characteristic": "Caractéristique",
@@ -347,11 +376,22 @@
"learn_characteristic": "Caractéristique", "learn_characteristic": "Caractéristique",
"learn_reserve": "Réserve", "learn_reserve": "Réserve",
"learn_evolution": "Dé d'Évolution", "learn_evolution": "Dé d'Évolution",
"learn_totem_ability": "Capacité de Totem",
"learn_background": "Élément d'Historique",
"learn_reputation": "Réputation",
"learn_adapted_die": "Remplacer 1D d'Expérience par un Dé de Totem Adapté (×2)",
"adapted_die_x2": "1 Dé de Totem Adapté ×2",
"learn_specialty_name": "Nom de la spécialité", "learn_specialty_name": "Nom de la spécialité",
"specialty_hint": "Prérequis : compétence au moins Confirmée (≥2).", "specialty_hint": "Prérequis : compétence au moins Confirmée (≥2).",
"characteristic_hint": "Difficulté 9 + Handicap = valeur actuelle. Max 3 (Surhumain excepté).", "characteristic_hint": "Difficulté 9 + Handicap = valeur actuelle. Max 3 (Surhumain excepté).",
"reserve_hint": "Difficulté = valeur actuelle + Handicap d'âge. Max 10.", "reserve_hint": "Difficulté = valeur actuelle + Handicap d'âge. Max 10.",
"evolution_hint": "Difficulté selon l'âge + Handicap = Dés d'Évolution accumulés. Totem Adapté +1D, Humain -1D.", "evolution_hint": "Difficulté selon l'âge + Handicap = Dés d'Évolution accumulés. Totem Adapté +1D, Humain -1D.",
"totem_ability_hint": "Prérequis : même Totem que le personnage, Niveau de Groupe ≥ Niveau de la Capacité, Mode de jeu compatible (Survie N1, Cauchemar N2, Apocalypse N3).",
"background_hint": "Difficulté 9. Handicap (I) si l'Historique est marqué d'un astérisque (*). Autorisation du MJ requise.",
"reputation_hint": "Réduire = plus célèbre (Difficulté 10 score visé, 1 pt max/phase). Augmenter = se faire oublier (exige un Objectif majeur, Difficulté = Réputation actuelle, +1 par Réussite).",
"reputation_reduce": "Devenir plus célèbre (réduire)",
"reputation_increase": "Se faire oublier (augmenter)",
"reputation_current": "Réputation actuelle",
"dice_spent": "Dés dépensés", "dice_spent": "Dés dépensés",
"learning_success": "Apprentissage réussi !", "learning_success": "Apprentissage réussi !",
"error_no_experience_dice": "Pas de Dés d'Expérience disponibles.", "error_no_experience_dice": "Pas de Dés d'Expérience disponibles.",
@@ -360,6 +400,25 @@
"error_characteristic_max": "Caractéristique déjà au maximum (3).", "error_characteristic_max": "Caractéristique déjà au maximum (3).",
"error_specialty_requirement": "Spécialité requise : compétence au moins Confirmée (≥2).", "error_specialty_requirement": "Spécialité requise : compétence au moins Confirmée (≥2).",
"error_reserve_max": "Réserve déjà au maximum (10).", "error_reserve_max": "Réserve déjà au maximum (10).",
"no_totem_ability": "Aucune Capacité de Totem disponible à apprendre.",
"no_background": "Aucun élément d'Historique à acquérir.",
"error_totem_mode": "Niveau de Capacité incompatible avec le Mode de jeu.",
"error_totem_totem": "Le personnage doit être du même Totem que la Capacité.",
"error_totem_group_level": "Niveau de Groupe insuffisant pour cette Capacité.",
"error_totem_limit": "Limite de Capacités de Totem atteinte (3× Niveau 1, 1× Niveaux 2 et 3, max 5).",
"error_reputation_min": "Réputation déjà au minimum (0).",
"error_reputation_max": "Réputation déjà au maximum (10).",
"error_evolution_no_adapted": "Il faut strictement plus de Dés du Totem Adapté que Humains.",
"learn_level": "Niveau de Groupe",
"learn_group_reserve": "Réserve de Groupe",
"learn_ability": "Capacité de Groupe",
"group_level_hint": "Difficulté = nombre de membres. Handicap = Niveau actuel du Groupe. Max 10.",
"group_reserve_hint": "Difficulté = valeur actuelle de la Réserve. Handicap II. Max 10.",
"group_ability_hint": "Difficulté = Apprentissage. Handicap = Niveau de la Capacité. Nécessite un Niveau de Groupe ≥ Niveau de la Capacité.",
"no_group_ability": "Aucune Capacité de Groupe disponible à développer.",
"error_group_level_max": "Niveau de Groupe déjà au maximum (10).",
"error_group_reserve_max": "Réserve de Groupe déjà au maximum.",
"error_group_level_requirement": "Niveau de Groupe insuffisant pour cette Capacité.",
"experience_transfer": "Dés d'Expérience", "experience_transfer": "Dés d'Expérience",
"experience_pull": "Puiser du Groupe", "experience_pull": "Puiser du Groupe",
"experience_give": "Donner 1D au Groupe", "experience_give": "Donner 1D au Groupe",
@@ -400,11 +459,13 @@
"boost": "boost", "boost": "boost",
"group_abilities": "Capacités de groupe", "group_abilities": "Capacités de groupe",
"totem_abilities": "Capacités de totem", "totem_abilities": "Capacités de totem",
"creature_capacities": "Capacités de créature",
"type_group": "Groupe", "type_group": "Groupe",
"type_character": "Personnage", "type_character": "Personnage",
"type_npc": "PNJ", "type_npc": "PNJ",
"type_creature": "Créature", "type_creature": "Créature",
"type_community": "Communauté", "type_community": "Communauté",
"type_vehicle": "Véhicule",
"type": "Type", "type": "Type",
"group_members": "Membres", "group_members": "Membres",
"encounters": "Rencontres", "encounters": "Rencontres",
@@ -425,6 +486,8 @@
"morale_low": "Bas", "morale_low": "Bas",
"morale_crisis": "Crise", "morale_crisis": "Crise",
"objective_placeholder": "Objectif...", "objective_placeholder": "Objectif...",
"major_objective_placeholder": "Objectif majeur...",
"minor_objective_placeholder": "Objectif mineur...",
"instincts_placeholder": "Ex: Triompher, relever un défi...", "instincts_placeholder": "Ex: Triompher, relever un défi...",
"prohibits_placeholder": "Ex: Fuir, abandonner...", "prohibits_placeholder": "Ex: Fuir, abandonner...",
"error_unknown_actor": "Acteur inconnu", "error_unknown_actor": "Acteur inconnu",
@@ -451,7 +514,8 @@
"npc": "PNJ", "npc": "PNJ",
"group": "Groupe", "group": "Groupe",
"creature": "Créature", "creature": "Créature",
"community": "Communauté" "community": "Communauté",
"vehicle": "Véhicule"
} }
}, },
"UI": { "UI": {
@@ -574,6 +638,8 @@
"ability": "Capacité", "ability": "Capacité",
"new_ability": "Nouvelle capacité", "new_ability": "Nouvelle capacité",
"abilities": "Capacités", "abilities": "Capacités",
"creaturecapacity": "Capacité de créature",
"new_creaturecapacity": "Nouvelle capacité de créature",
"specialties": "Spécialités", "specialties": "Spécialités",
"new_specialty": "Nouvelle spécialité", "new_specialty": "Nouvelle spécialité",
"target": "Cible", "target": "Cible",
@@ -654,6 +720,11 @@
"dog": "Chien", "dog": "Chien",
"bear": "Ours" "bear": "Ours"
}, },
"PACK_LEVELS": {
"small": "Petit",
"large": "Grand",
"giant": "Géant"
},
"TOTEMS": { "TOTEMS": {
"human": { "human": {
"name": "L'Humain", "name": "L'Humain",
+64
View File
@@ -188,6 +188,42 @@
} }
} }
// ── Stacked layout for creature sheets (header + tabs + content) ───
&.creature .window-content {
display: grid;
grid-template-rows: auto auto 1fr;
grid-template-columns: 1fr;
overflow: hidden;
> .tab.main {
grid-column: 1;
grid-row: 1;
display: flex !important;
flex-direction: column;
overflow-y: auto;
padding: 8px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
> nav.tabs {
grid-column: 1;
grid-row: 2;
}
> .tab:not(.main) {
grid-column: 1;
grid-row: 3;
display: none;
overflow-y: auto;
padding: 8px;
&.active {
display: flex;
flex-direction: column;
}
}
}
form { form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -304,6 +340,34 @@
} }
} }
// Fiche personnage (ApplicationV2 : l'élément fiche EST le <form>, le
// sélecteur "form div.hexa" ci-dessus ne s'y applique donc pas).
&.character div.hexa {
.hexa-style();
.transition();
margin: 0.2rem;
position: relative;
&:hover {
.hexa-style(rgba(255, 255, 255, 0.425), rgba(0, 0, 0, 0.288));
}
input {
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
z-index: 1;
margin: 0;
cursor: pointer;
}
&.checked {
.hexa-style(rgb(0, 0, 0), rgba(0, 0, 0, 0.288));
&:hover { .hexa-style(rgb(43, 43, 43), rgba(0, 0, 0, 0.288)); }
}
}
.sheet-header-toggle { .sheet-header-toggle {
text-align: right; text-align: right;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
+94 -2
View File
@@ -5,7 +5,7 @@
.sheet-header { .sheet-header {
.background-image(url("@{ui-path}/barre_haut.webp"), no-repeat, 100% 100%, right top); .background-image(url("@{ui-path}/barre_haut.webp"), no-repeat, 100% 100%, right top);
padding: 10px; padding: 10px;
max-height: 110px; overflow: visible;
} }
.header-fields { flex: 1; } .header-fields { flex: 1; }
@@ -13,6 +13,10 @@
.resource { .resource {
.card-style(); .card-style();
// Le mixin card-style ajoute un margin-bottom (15px) : inutile dans le
// header, on le neutralise pour garder une hauteur compacte.
margin-bottom: 8px;
} }
.resource-label { .resource-label {
@@ -34,13 +38,20 @@
.charname { .charname {
margin: 0; margin: 0;
height: auto;
font-size: @font-size-18;
line-height: 1.2;
span,
input { input {
width: 100%;
font-size: @font-size-18; font-size: @font-size-18;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
} }
input {
width: 100%;
}
} }
.stats-grid { .stats-grid {
@@ -116,4 +127,85 @@
gap: 8px; gap: 8px;
} }
// ── Cases de blessures (hexagones) ──────────────────────────────────
// Le radio est masqué et recouvre l'hexagone : cliquer sur la case la coche.
div.hexa {
.hexa-style();
.transition();
margin: 0.2rem;
position: relative;
&:hover {
.hexa-style(rgba(255, 255, 255, 0.425), rgba(0, 0, 0, 0.288));
}
input {
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
z-index: 1;
margin: 0;
cursor: pointer;
}
&.checked {
.hexa-style(rgb(0, 0, 0), rgba(0, 0, 0, 0.288));
&:hover { .hexa-style(rgb(43, 43, 43), rgba(0, 0, 0, 0.288)); }
}
}
// ── Capacités de créature ───────────────────────────────────────────
.creature-list {
list-style: none;
margin: 0;
padding: 0;
> li {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 6px;
border-bottom: 1px solid @color-border-dark-3;
&:last-child { border-bottom: none; }
&:hover { box-shadow: 0 0 10px @theme-color-highlight inset; }
}
.item-name {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: flex;
align-items: center;
gap: 8px;
.item-roll { cursor: pointer; }
}
.capacity-type {
flex: 0 0 auto;
font-size: @font-size-12;
color: @color-amber;
text-transform: uppercase;
}
.item-controls {
flex: 0 0 auto;
display: flex;
gap: 6px;
}
}
h4 {
a.item-control {
color: inherit;
opacity: 0.85;
&:hover { opacity: 1; }
}
}
} }
+100 -46
View File
@@ -15,12 +15,111 @@
} }
} }
// ── Titres de section : uniforme (centré, scotch, majuscules) ───────
h4 { h4 {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-family: "DistressBlack", sans-serif; font-family: "DistressBlack", sans-serif;
text-transform: uppercase; text-transform: uppercase;
font-size: @font-size-14; font-size: @font-size-14;
text-align: center;
padding: 2px 8px;
margin: 0 0 8px;
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp"); background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
a {
color: inherit;
opacity: 0.85;
&:hover { opacity: 1; }
} }
}
// ── Sous-titres (ex. Objectifs majeurs / mineurs) ───────────────────
h5 {
font-family: "DistressBlack", sans-serif;
text-transform: uppercase;
font-size: @font-size-12;
color: @color-amber;
margin: 8px 0 4px;
text-align: left;
border-bottom: none;
}
// ── Liste uniforme (objectifs, capacités, membres, rencontres) ──────
.group-list {
list-style: none;
margin: 0;
padding: 0;
> li {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 6px;
border-bottom: 1px solid @color-border-dark-3;
&:last-child { border-bottom: none; }
&:hover { box-shadow: 0 0 10px @theme-color-highlight inset; }
}
> li.group-list-header {
font-weight: bold;
text-transform: uppercase;
font-size: @font-size-12;
border-bottom: 2px solid @color-border-dark-3;
box-shadow: none;
> div { text-align: center; }
}
.item-name, .actor-name {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item-controls {
flex: 0 0 auto;
display: flex;
gap: 6px;
}
input[type="text"] {
flex: 1;
min-width: 0;
}
}
// ── Champs (Origine, Concept, Instincts, Interdits…) ────────────────
.group-field {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
label {
flex: 0 0 auto;
font-weight: bold;
white-space: nowrap;
}
> :last-child {
flex: 1;
min-width: 0;
}
input[type="text"] { width: 100%; }
}
// ── Colonnes des tableaux (Rencontres) ──────────────────────────────
.gcol-name { flex: 2; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gcol-profile { flex: 3; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gcol-totem { flex: 2; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.reserve-control, .reserve-control,
.morale-control { .morale-control {
@@ -39,58 +138,13 @@
} }
} }
.actor-list {
list-style: none;
padding: 0;
margin: 0;
li.actor {
padding: 5px;
border-bottom: 1px solid @color-border-dark-3;
&:last-child { border-bottom: none; }
&:hover {
box-shadow: 0 0 10px @theme-color-highlight inset;
}
}
.actor-name, .item-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.objective-item {
padding: 5px;
border-bottom: 1px solid @color-border-dark-4;
input {
flex: 1;
width: 100%;
}
}
.totem-display { .totem-display {
.card-style(); .card-style();
flex: 1;
span { font-weight: bold; } span { font-weight: bold; }
} }
.identity-field {
margin-bottom: 8px;
label {
font-weight: bold;
display: block;
margin-bottom: 3px;
}
input { width: 100%; }
}
.level input, .level input,
.reputation input { .reputation input {
width: 60px; width: 60px;
+105 -89
View File
@@ -5,7 +5,25 @@
.sheet-header { .sheet-header {
.background-image(url("@{ui-path}/barre_haut.webp"), no-repeat, 100% 100%, right top); .background-image(url("@{ui-path}/barre_haut.webp"), no-repeat, 100% 100%, right top);
padding: 0.5rem; padding: 0.5rem;
max-height: 150px; overflow: visible;
}
.charname {
margin: 0;
height: auto;
font-size: @font-size-18;
line-height: 1.2;
span,
input {
font-size: @font-size-18;
font-weight: bold;
text-align: center;
}
input {
width: 100%;
}
} }
.ability-value, .ability-value,
@@ -30,77 +48,81 @@
} }
} }
.skills-container { .npc-trait-list {
display: flex; li {
flex-direction: column;
gap: 15px;
}
.skill-category {
.card-style();
padding: 10px;
h4 {
font-family: "DistressBlack", sans-serif;
font-size: @font-size-14;
text-transform: uppercase;
margin-top: 0;
margin-bottom: 8px;
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
}
}
.skill-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 6px;
padding: 5px; padding: 4px 0;
border-bottom: 1px solid @color-border-dark-3; border-bottom: 1px solid @color-border-dark-3;
font-size: @font-size-12;
&:last-child { border-bottom: none; } &:last-child { border-bottom: none; }
label { strong { flex: 1; }
flex: 1;
font-size: @font-size-12;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
&:hover { .trait-value {
box-shadow: 0 0 10px @theme-color-highlight inset; min-width: 30px;
}
}
.skill-control {
display: flex;
align-items: center;
gap: 5px;
}
.skill-value {
min-width: 25px;
text-align: center; text-align: center;
font-weight: bold; font-weight: bold;
white-space: nowrap;
}
} }
.rarity-badge { .npc-roll-btn {
display: inline-block; color: @color-text-light-primary;
padding: 1px 4px; opacity: 0.7;
border-radius: 3px; transition: opacity 0.15s ease;
font-size: 10px;
&:hover {
opacity: 1;
color: @color-text-light-1;
text-shadow: 0 0 6px @theme-color-highlight;
}
}
.npc-wounds {
flex-wrap: wrap;
gap: 8px 16px;
margin-bottom: 8px;
.wound-cat {
display: inline-flex;
align-items: center;
gap: 4px;
font-weight: bold; font-weight: bold;
margin-left: 5px;
&.rarity-0 { .rarity-badge(@rarity-0-bg, @rarity-0-text); } .wound-label {
&.rarity-1 { .rarity-badge(@rarity-1-bg, @rarity-1-text); } font-size: @font-size-12;
&.rarity-2 { .rarity-badge(@rarity-2-bg, @rarity-2-text); } white-space: nowrap;
&.rarity-3 { .rarity-badge(@rarity-3-bg, @rarity-3-text); } }
} }
.skill-category-selector { .wound-boxes {
margin-bottom: 15px; display: inline-flex;
.card-style(); gap: 2px;
i {
font-size: 8px;
color: @color-border-dark-2;
}
.hexa {
margin: 0;
}
}
}
.npc-free-skills {
margin-top: 15px;
.form-group-style();
label {
font-size: @font-size-12;
font-weight: bold;
white-space: nowrap;
}
} }
.wounds-card { .wounds-card {
@@ -133,39 +155,6 @@
} }
} }
.ability-section {
.card-style();
}
.ability-category-header {
font-family: "DistressBlack", sans-serif;
font-size: @font-size-14;
text-transform: uppercase;
margin-top: 0;
margin-bottom: 8px;
border-bottom: 1px solid @color-border-dark-3;
padding-bottom: 5px;
}
.ability-item {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 0;
label {
flex: 1;
font-size: @font-size-12;
white-space: nowrap;
}
}
.ability-control {
display: flex;
align-items: center;
gap: 8px;
}
.tab { .tab {
padding: 10px; padding: 10px;
overflow-y: auto; overflow-y: auto;
@@ -213,6 +202,17 @@
padding: 3px 5px; padding: 3px 5px;
min-width: 100px; min-width: 100px;
} }
input[type="text"] {
font-size: @font-size-12;
padding: 3px 5px;
min-width: 0;
width: 100%;
}
}
.npc-profile-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
} }
.editor { .editor {
@@ -228,9 +228,25 @@
.hexa { .hexa {
.hexa-style(); .hexa-style();
.transition(); .transition();
position: relative;
&:hover { &:hover {
.hexa-style(rgba(255, 255, 255, 0.425), rgba(0, 0, 0, 0.288)); .hexa-style(rgba(255, 255, 255, 0.425), rgba(0, 0, 0, 0.288));
} }
input {
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
z-index: 1;
margin: 0;
cursor: pointer;
}
&.checked {
.hexa-style(rgb(0, 0, 0), rgba(0, 0, 0, 0.288));
&:hover { .hexa-style(rgb(43, 43, 43), rgba(0, 0, 0, 0.288)); }
}
} }
} }
+183
View File
@@ -0,0 +1,183 @@
@import "../variables";
@import "../utilities";
.system-vermine2047 .vermine2047.actor.vehicle {
display: grid !important;
grid-template-columns: 1fr !important;
grid-template-rows: auto 1fr auto !important;
> header.window-header {
grid-column: 1;
grid-row: 1;
}
> section.window-content {
grid-column: 1;
grid-row: 2;
}
> div.window-resize-handle {
grid-column: 1;
grid-row: 3;
}
.window-content {
display: grid;
grid-template-rows: auto auto 1fr;
grid-template-columns: 1fr;
overflow: hidden;
> .tab.main {
grid-column: 1;
grid-row: 1;
display: flex !important;
flex-direction: column;
overflow-y: auto;
padding: 8px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
> nav.tabs {
grid-column: 1;
grid-row: 2;
}
> .tab:not(.main) {
grid-column: 1;
grid-row: 3;
display: none;
overflow-y: auto;
padding: 8px;
&.active {
display: flex;
flex-direction: column;
}
}
}
.sheet-header {
.background-image(url("@{ui-path}/barre_haut.webp"), no-repeat, 100% 100%, right top);
padding: 10px;
overflow: visible;
}
.header-fields { flex: 1; }
.resource {
.card-style();
// Le mixin card-style ajoute un margin-bottom (15px) : inutile dans le
// header, on le neutralise pour garder une hauteur compacte.
margin-bottom: 8px;
}
.resource-label {
font-weight: bold;
margin-right: 8px;
min-width: 60px;
font-size: @font-size-12;
}
.resource-content {
display: flex;
align-items: center;
select {
margin-right: 8px;
min-width: 100px;
}
input[type="number"] {
width: 60px;
text-align: center;
}
}
.charname {
margin: 0;
height: auto;
font-size: @font-size-18;
line-height: 1.2;
span,
input {
font-size: @font-size-18;
font-weight: bold;
text-align: center;
}
input {
width: 100%;
}
}
.vehicle-owner {
display: flex;
gap: 6px;
margin-bottom: 8px;
padding: 6px 8px;
border: 1px dashed @color-hemolymph;
background: fade(@color-hemolymph, 8%);
.label {
font-family: "DistressBlack", sans-serif;
text-transform: uppercase;
font-size: @font-size-11;
color: @color-amber;
}
}
.vehicle-section {
padding: 8px;
border: 1px solid @color-border-dark-4;
background: fade(@color-chitin-dark, 20%);
margin-bottom: 8px;
h3 {
font-family: "DistressBlack", sans-serif;
text-transform: uppercase;
font-size: @font-size-13;
color: @theme-color-dark;
margin: 0 0 8px;
text-align: left;
border-bottom: none;
}
.resource-label {
display: block;
margin: 0 0 4px;
min-width: 0;
}
.unit {
font-size: @font-size-11;
color: @color-text-light-0;
opacity: 0.8;
}
.maintenance {
margin-top: 8px;
.flexrow label {
gap: 4px;
margin-right: 12px;
font-size: @font-size-12;
}
}
.form-group {
label {
font-family: "DistressBlack", sans-serif;
text-transform: uppercase;
font-size: @font-size-11;
color: @color-amber;
}
}
}
input[type="text"] {
width: 100%;
padding: 5px;
}
}
+5 -1
View File
@@ -665,7 +665,11 @@ ol.chat-log {
cursor: pointer; cursor: pointer;
&:hover { transform: translateY(0.5rem); } &:hover { transform: translateY(0.5rem); }
} }
&.success { border-bottom-color: @color-acid-green; } &.success {
border-bottom-color: @color-acid-green;
border-bottom-width: 6px;
box-shadow: 0 0 8px fade(@color-acid-green, 55%);
}
&.adapted::after { content: "adapté"; } &.adapted::after { content: "adapté"; }
&.human::after { content: "humain"; } &.human::after { content: "humain"; }
&.rerolled { transform: translateY(0rem); } &.rerolled { transform: translateY(0rem); }
+1 -1
View File
@@ -273,7 +273,7 @@ iframe {
label { label {
font-size: @font-size-11; font-size: @font-size-11;
color: @color-amber; color: @color-chitin-dark;
} }
} }
} }
+11
View File
@@ -98,3 +98,14 @@ ol#chat-log div.item-card header img,
ol.chat-log div.item-card header img { ol.chat-log div.item-card header img {
max-width: 30%; max-width: 30%;
} }
// Icônes des cartes d'item envoyées au chat (ApplicationV2).
// Le template des cartes (templates/item/chatCards/parts/base.hbs) rend
// une <img> pleine taille : on la plafonne à 64×64.
ol#chat-log .chat-card-img,
ol.chat-log .chat-card-img {
max-width: 64px;
max-height: 64px;
object-fit: contain;
flex: 0 0 auto;
}
+1 -1
View File
@@ -138,7 +138,7 @@
@theme-color-highlight: #005a3c; @theme-color-highlight: #005a3c;
// Couleurs insecte / organique // Couleurs insecte / organique
@color-acid-green: #6ab04c; @color-acid-green: #1e7d32;
@color-amber: #e8b84b; @color-amber: #e8b84b;
@color-chitin-dark: #2a2520; @color-chitin-dark: #2a2520;
@color-hemolymph: #8b4513; @color-hemolymph: #8b4513;
+1
View File
@@ -23,6 +23,7 @@
@import "actor/group"; @import "actor/group";
@import "actor/creature"; @import "actor/creature";
@import "actor/community"; @import "actor/community";
@import "actor/vehicle";
// 6. Styles des items // 6. Styles des items
@import "items"; @import "items";
+2 -1
View File
@@ -5,12 +5,13 @@ export { default as VermineNpcSheetV2 } from "./npc-sheet.mjs"
export { default as VermineGroupSheetV2 } from "./group-sheet.mjs" export { default as VermineGroupSheetV2 } from "./group-sheet.mjs"
export { default as VermineCreatureSheetV2 } from "./creature-sheet.mjs" export { default as VermineCreatureSheetV2 } from "./creature-sheet.mjs"
export { default as VermineCommunitySheetV2 } from "./community-sheet.mjs" export { default as VermineCommunitySheetV2 } from "./community-sheet.mjs"
export { default as VermineVehicleSheetV2 } from "./vehicle-sheet.mjs"
export { export {
VermineItemSheetV2, VermineItemSheetV2,
VermineWeaponSheetV2, VermineWeaponSheetV2,
VermineDefenseSheetV2, VermineDefenseSheetV2,
VermineVehicleSheetV2,
VermineAbilitySheetV2, VermineAbilitySheetV2,
VermineCreatureCapacitySheetV2,
VermineSpecialtySheetV2, VermineSpecialtySheetV2,
VermineBackgroundSheetV2, VermineBackgroundSheetV2,
VermineTraumaSheetV2, VermineTraumaSheetV2,
@@ -32,9 +32,13 @@ export default class VermineBaseActorSheet extends HandlebarsApplicationMixin(fo
delete: VermineBaseActorSheet.#onItemDelete, delete: VermineBaseActorSheet.#onItemDelete,
toggleEquip: VermineBaseActorSheet.#onToggleEquip, toggleEquip: VermineBaseActorSheet.#onToggleEquip,
create: VermineBaseActorSheet.#onItemCreate, create: VermineBaseActorSheet.#onItemCreate,
createVehicle: VermineBaseActorSheet.#onCreateVehicle,
editVehicle: VermineBaseActorSheet.#onEditVehicle,
deleteVehicle: VermineBaseActorSheet.#onDeleteVehicle,
roll: VermineBaseActorSheet.#onRollItem, roll: VermineBaseActorSheet.#onRollItem,
attack: VermineBaseActorSheet.#onAttack, attack: VermineBaseActorSheet.#onAttack,
clickRadio: VermineBaseActorSheet.#onClickRadioHexa, clickRadio: VermineBaseActorSheet.#onClickRadioHexa,
clickWound: VermineBaseActorSheet.#onClickWound,
effectControl: VermineBaseActorSheet.#onEffectControl, effectControl: VermineBaseActorSheet.#onEffectControl,
chooseTotem: VermineBaseActorSheet.#onChooseTotem chooseTotem: VermineBaseActorSheet.#onChooseTotem
} }
@@ -229,11 +233,47 @@ export default class VermineBaseActorSheet extends HandlebarsApplicationMixin(fo
await this.document.createEmbeddedDocuments("Item", [{ name, type }]) await this.document.createEmbeddedDocuments("Item", [{ name, type }])
} }
// ── Véhicules (acteurs) ──────────────────────────────────────────────
/** Crée un acteur "vehicle" rattaché à cet acteur (groupe ou personnage). */
static async #onCreateVehicle(event, target) {
const name = game.i18n.localize("ITEMS.new_vehicle")
const vehicle = await Actor.create({
name,
type: "vehicle",
system: { ownerId: this.document.id },
// Le véhicule est un bien partagé : reprendre la propriété du propriétaire
ownership: foundry.utils.deepClone(this.document.ownership)
})
vehicle?.sheet.render(true)
}
static async #onEditVehicle(event, target) {
const id = target.closest("[data-vehicle-id]")?.dataset?.vehicleId
const vehicle = game.actors.get(id)
vehicle?.sheet.render(true)
}
static async #onDeleteVehicle(event, target) {
const id = target.closest("[data-vehicle-id]")?.dataset?.vehicleId
const vehicle = game.actors.get(id)
await vehicle?.deleteDialog()
}
static async #onRollItem(event, target) { static async #onRollItem(event, target) {
const id = target.closest("[data-item-id]")?.dataset?.itemId const id = target.closest("[data-item-id]")?.dataset?.itemId
if (!id) return if (!id) return
const item = this.document.items.get(id) const item = this.document.items.get(id)
if (!item) return if (!item) return
// Armes : PNJ et créatures → dialogue de combat (valeur d'Attaque) ;
// personnage → dialogue de compétence. PNJ/créatures n'ont ni
// caractéristiques ni compétences.
if (item.type === "weapon" && (this.document.type === 'npc' || this.document.type === 'creature')) {
const { default: CombatDialog } = await import("../../system/dialogs/combatDialog.mjs")
const dialog = await CombatDialog.create({ actorId: this.document.id, weapon: item })
if (dialog) dialog.render(true)
return
}
if (item.type === "weapon" && this.document.system.abilities && this.document.system.skills) { if (item.type === "weapon" && this.document.system.abilities && this.document.system.skills) {
const { default: RollDialog } = await import("../../system/dialogs/rollDialog.mjs") const { default: RollDialog } = await import("../../system/dialogs/rollDialog.mjs")
const dialog = await RollDialog.create({ const dialog = await RollDialog.create({
@@ -259,6 +299,8 @@ export default class VermineBaseActorSheet extends HandlebarsApplicationMixin(fo
} }
static async #onToggleEquip(event, target) { static async #onToggleEquip(event, target) {
event.preventDefault()
event.stopPropagation()
const id = target.closest("[data-item-id]")?.dataset?.itemId const id = target.closest("[data-item-id]")?.dataset?.itemId
if (!id) return if (!id) return
const item = this.document.items.get(id) const item = this.document.items.get(id)
@@ -284,6 +326,36 @@ export default class VermineBaseActorSheet extends HandlebarsApplicationMixin(fo
this.document.update(update) this.document.update(update)
} }
/**
* Coche/décoche les cases de Blessure en respectant l'ordre de gravité
* (règles p.39) : on ne peut cocher que la case suivante dans l'ordre
* (une blessure s'applique toujours au Seuil égal ou inférieur le plus
* élevé), et on ne peut pas en sauter. Décocher une case retire toutes
* celles situées après elle.
*/
static #onClickWound(event, target) {
event.preventDefault()
event.stopPropagation()
const input = target
const box = parseInt(input.value, 10) || 0 // numéro de case (1-based)
let current = this.document
const propTree = input.name.split(".")
for (const prop of propTree) {
current = current[prop]
}
const currentValue = parseInt(current, 10) || 0
let next = currentValue
if (box === currentValue + 1) {
// Cocher la case suivante dans l'ordre.
next = box
} else if (box <= currentValue) {
// Décocher : retire la case cliquée et toutes celles après.
next = box - 1
}
// Si box > currentValue + 1 : on ne permet pas de sauter des cases.
this.document.update({ [input.name]: next })
}
static #onEffectControl(event, target) { static #onEffectControl(event, target) {
onManageActiveEffect(event, this.document) onManageActiveEffect(event, this.document)
} }
@@ -4,7 +4,7 @@ export default class VermineCharacterSheetV2 extends VermineBaseActorSheet {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ["character"], classes: ["character"],
position: { width: 860, height: 720 }, position: { width: 920, height: 720 },
window: { contentClasses: ["character-content"] }, window: { contentClasses: ["character-content"] },
actions: { actions: {
addSpecialty: VermineCharacterSheetV2.#onAddSpecialty, addSpecialty: VermineCharacterSheetV2.#onAddSpecialty,
@@ -65,13 +65,14 @@ export default class VermineCharacterSheetV2 extends VermineBaseActorSheet {
context.backgrounds = doc.itemTypes.background context.backgrounds = doc.itemTypes.background
context.traumas = doc.itemTypes.trauma context.traumas = doc.itemTypes.trauma
context.evolutions = doc.itemTypes.evolution context.evolutions = doc.itemTypes.evolution
context.rites = doc.itemTypes.rite
break break
case "equipment": case "equipment":
context.tab = context.tabs.equipment context.tab = context.tabs.equipment
context.gear = doc.itemTypes.item context.gear = doc.itemTypes.item
context.weapons = doc.itemTypes.weapon context.weapons = doc.itemTypes.weapon
context.defenses = doc.itemTypes.defense context.defenses = doc.itemTypes.defense
context.vehicles = doc.itemTypes.vehicle context.vehicles = game.actors.filter(a => a.type === "vehicle" && a.system.ownerId === doc.id)
break break
case "stories": case "stories":
context.tab = context.tabs.stories context.tab = context.tabs.stories
+17 -2
View File
@@ -59,16 +59,25 @@ export default class VermineCreatureSheetV2 extends VermineBaseActorSheet {
context.roleOptions = CONFIG.VERMINE.creatureRoleLevels context.roleOptions = CONFIG.VERMINE.creatureRoleLevels
context.sizeOptions = CONFIG.VERMINE.creatureSizeLevels context.sizeOptions = CONFIG.VERMINE.creatureSizeLevels
context.packOptions = CONFIG.VERMINE.creaturePackLevels context.packOptions = CONFIG.VERMINE.creaturePackLevels
context.sizeLabel = doc.system.size?.value !== undefined
? game.i18n.localize(CONFIG.VERMINE.creatureSizeLevels[doc.system.size.value]?.label ?? "")
: ""
context.packLabel = doc.system.pack?.value
? game.i18n.localize(CONFIG.VERMINE.creaturePackLevels[doc.system.pack.value]?.label ?? "")
: ""
break break
case "info": case "info":
context.tab = context.tabs.info context.tab = context.tabs.info
context.capacities = doc.itemTypes.creaturecapacity
break break
case "stats": case "stats":
context.tab = context.tabs.stats context.tab = context.tabs.stats
context.patternLabel = doc.system.pattern?.value ? game.i18n.localize(CONFIG.VERMINE.creaturePatternLevels[doc.system.pattern.value]?.label ?? "") : "" context.patternLabel = doc.system.pattern?.value ? game.i18n.localize(CONFIG.VERMINE.creaturePatternLevels[doc.system.pattern.value]?.label ?? "") : ""
context.sizeLabel = doc.system.size?.value !== undefined ? game.i18n.localize(CONFIG.VERMINE.creatureSizeLevels[doc.system.size.value]?.label ?? "") : "" context.sizeLabel = doc.system.size?.value !== undefined ? game.i18n.localize(CONFIG.VERMINE.creatureSizeLevels[doc.system.size.value]?.label ?? "") : ""
context.roleLabel = doc.system.role?.value ? game.i18n.localize(CONFIG.VERMINE.creatureRoleLevels[doc.system.role.value]?.label ?? "") : "" context.roleLabel = doc.system.role?.value ? game.i18n.localize(CONFIG.VERMINE.creatureRoleLevels[doc.system.role.value]?.label ?? "") : ""
context.packLabel = doc.system.pack?.value || game.i18n.localize("VERMINE.none") context.packLabel = doc.system.pack?.value
? `${doc.system.pack.value} (${game.i18n.localize(CONFIG.VERMINE.creaturePackLevels[doc.system.pack.value]?.label ?? "")})`
: game.i18n.localize("VERMINE.none")
// Contribution cumulée de la Taille (les modificateurs se cumulent : Taille N = somme 1..N) // Contribution cumulée de la Taille (les modificateurs se cumulent : Taille N = somme 1..N)
const sizeLevels = CONFIG.VERMINE.creatureSizeLevels || {} const sizeLevels = CONFIG.VERMINE.creatureSizeLevels || {}
const sizeLevel = doc.system.size?.value ?? 0 const sizeLevel = doc.system.size?.value ?? 0
@@ -97,11 +106,17 @@ export default class VermineCreatureSheetV2 extends VermineBaseActorSheet {
return context return context
} }
/** @override - Les créatures n'ont pas de caractéristiques/compétences : on ouvre le dialogue d'attaque (pool calculé). */ /** @override - Les créatures n'ont pas de caractéristiques/compétences : on ouvre le dialogue d'attaque (pool calculé) ou de Réaction. */
async _onRoll(event) { async _onRoll(event) {
event.preventDefault() event.preventDefault()
const el = event.currentTarget const el = event.currentTarget
const type = el.dataset.type const type = el.dataset.type
if (type === "creature-reaction") {
const { default: NpcRollDialog } = await import("../../system/dialogs/npcRollDialog.mjs")
const dialog = await NpcRollDialog.create({ actorId: this.document.id, rolltype: "reaction" })
if (dialog) dialog.render(true)
return
}
if (type !== "creature-attack") return super._onRoll(event) if (type !== "creature-attack") return super._onRoll(event)
const { default: CombatDialog } = await import("../../system/dialogs/combatDialog.mjs") const { default: CombatDialog } = await import("../../system/dialogs/combatDialog.mjs")
const dialog = await CombatDialog.create({ actorId: this.document.id }) const dialog = await CombatDialog.create({ actorId: this.document.id })
+38 -8
View File
@@ -7,12 +7,15 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
position: { width: 700, height: 600 }, position: { width: 700, height: 600 },
window: { contentClasses: ["group-content"] }, window: { contentClasses: ["group-content"] },
actions: { actions: {
chooseTotem: VermineGroupSheetV2.#onChooseTotem,
chooseActor: VermineGroupSheetV2.#onChooseActor, chooseActor: VermineGroupSheetV2.#onChooseActor,
deleteMember: VermineGroupSheetV2.#onDeleteMember, deleteMember: VermineGroupSheetV2.#onDeleteMember,
deleteEncounter: VermineGroupSheetV2.#onDeleteEncounter, deleteEncounter: VermineGroupSheetV2.#onDeleteEncounter,
deleteObjective: VermineGroupSheetV2.#onDeleteObjective, deleteObjective: VermineGroupSheetV2.#onDeleteObjective,
addObjective: VermineGroupSheetV2.#onAddObjective, addObjective: VermineGroupSheetV2.#onAddObjective,
createTotemAbility: VermineGroupSheetV2.#onCreateTotemAbility createTotemAbility: VermineGroupSheetV2.#onCreateTotemAbility,
openLearning: VermineGroupSheetV2.#onOpenLearning,
openExperiencePhase: VermineGroupSheetV2.#onOpenExperiencePhase
} }
} }
@@ -56,9 +59,22 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
if (this.document.system.encounters?.length > 0) { if (this.document.system.encounters?.length > 0) {
for (const encId of this.document.system.encounters) { for (const encId of this.document.system.encounters) {
const a = game.actors.get(encId) const a = game.actors.get(encId)
if (a) context.resolvedEncounters[encId] = { name: a.name, id: a.id } if (a) {
context.resolvedEncounters[encId] = {
name: a.name,
id: a.id,
type: a.type,
profile: a.system?.identity?.profile || "",
totem: a.system?.identity?.totem || ""
} }
} }
}
}
// Notes de voyage (riche texte)
const roadNotes = this.document.system?.roadNotes
context.enrichedRoadNotes = roadNotes
? await foundry.applications.ux.TextEditor.implementation.enrichHTML(roadNotes, { async: true })
: ""
return context return context
} }
@@ -70,20 +86,16 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
context.tab = context.tabs.info context.tab = context.tabs.info
context.abilities = doc.itemTypes.ability.filter(i => i.system.type !== "totem") context.abilities = doc.itemTypes.ability.filter(i => i.system.type !== "totem")
context.totem_abilities = doc.itemTypes.ability.filter(i => i.system.type === "totem") context.totem_abilities = doc.itemTypes.ability.filter(i => i.system.type === "totem")
context.specialties = doc.itemTypes.specialty
context.backgrounds = doc.itemTypes.background
context.traumas = doc.itemTypes.trauma
context.evolutions = doc.itemTypes.evolution
break break
case "gear": case "gear":
context.tab = context.tabs.gear context.tab = context.tabs.gear
context.gear = doc.itemTypes.item context.gear = doc.itemTypes.item
context.weapons = doc.itemTypes.weapon context.weapons = doc.itemTypes.weapon
context.defenses = doc.itemTypes.defense context.defenses = doc.itemTypes.defense
context.vehicles = this.#getOwnedVehicles()
break break
case "road": case "road":
context.tab = context.tabs.road context.tab = context.tabs.road
context.vehicles = doc.itemTypes.vehicle
break break
case "reserve": case "reserve":
context.tab = context.tabs.reserve context.tab = context.tabs.reserve
@@ -92,10 +104,16 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
return context return context
} }
/** Véhicules (acteurs) appartenant à ce groupe via system.ownerId. */
#getOwnedVehicles() {
return game.actors.filter(a => a.type === "vehicle" && a.system.ownerId === this.document.id)
}
// Actions : délégation aux applications AppV1 existantes pour TotemPicker/ActorPicker // Actions : délégation aux applications AppV1 existantes pour TotemPicker/ActorPicker
static async #onChooseTotem(event, target) { static async #onChooseTotem(event, target) {
const { TotemPicker } = await import("../../system/applications.mjs") const { TotemPicker } = await import("../../system/applications.mjs")
new TotemPicker(target, this.document).render(true) // Le Totem du Groupe remplit automatiquement Instincts/Interdits.
new TotemPicker(target, this.document, { fillInstincts: true }).render(true)
} }
static async #onChooseActor(event, target) { static async #onChooseActor(event, target) {
const { ActorPicker } = await import("../../system/applications.mjs") const { ActorPicker } = await import("../../system/applications.mjs")
@@ -142,4 +160,16 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
const name = game.i18n.localize("ITEMS.new_ability") const name = game.i18n.localize("ITEMS.new_ability")
await this.document.createEmbeddedDocuments("Item", [{ name, type: "ability", system: { type: "totem" } }]) await this.document.createEmbeddedDocuments("Item", [{ name, type: "ability", system: { type: "totem" } }])
} }
static async #onOpenExperiencePhase(event, target) {
const { default: ExperiencePhaseDialog } = await import("../../system/dialogs/experiencePhaseDialog.mjs")
const dialog = await ExperiencePhaseDialog.create()
if (dialog) dialog.render(true)
}
static async #onOpenLearning(event, target) {
const { default: GroupLearningDialog } = await import("../../system/dialogs/groupLearningDialog.mjs")
const dialog = await GroupLearningDialog.create({ actorId: this.document.id })
if (dialog) dialog.render(true)
}
} }
+6 -28
View File
@@ -18,40 +18,18 @@ export class VermineDefenseSheetV2 extends VermineBaseItemSheet {
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-defense-sheet.hbs", scrollable: [""] } } static PARTS = { main: { template: "systems/vermine2047/templates/item/item-defense-sheet.hbs", scrollable: [""] } }
} }
// ── Véhicule ──────────────────────────────────────────────────────────
export class VermineVehicleSheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["vehicle"], position: { width: 520 } }
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-vehicle-sheet.hbs", scrollable: [""] } }
async _prepareContext() {
const context = await super._prepareContext()
// Compétences d'Entretien possibles (mécanique, technologie, artisanat, animalisme…)
const maintenanceKeys = ["mecanical", "technology", "crafting", "animalism", "environment", "wildlife", "flora", "repulsion", "toxics"]
const current = this.document.system.maintenance || []
context.maintenanceSkills = maintenanceKeys.map(key => ({
key,
label: game.i18n.localize("SKILLS." + key + ".name"),
checked: current.includes(key)
}))
return context
}
/** @override - nettoie le tableau d'Entretien (retire les cases non cochées). */
_prepareSubmitData(event, form, formData, updateData) {
const result = super._prepareSubmitData(event, form, formData, updateData)
if (Array.isArray(result?.system?.maintenance)) {
result.system.maintenance = result.system.maintenance.filter(v => typeof v === "string" && v.trim() !== "")
}
return result
}
}
// ── Capacité ────────────────────────────────────────────────────────── // ── Capacité ──────────────────────────────────────────────────────────
export class VermineAbilitySheetV2 extends VermineBaseItemSheet { export class VermineAbilitySheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["ability"], position: { width: 560 } } static DEFAULT_OPTIONS = { classes: ["ability"], position: { width: 560 } }
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-ability-sheet.hbs", scrollable: [""] } } static PARTS = { main: { template: "systems/vermine2047/templates/item/item-ability-sheet.hbs", scrollable: [""] } }
} }
// ── Capacité de créature ────────────────────────────────────────────────
export class VermineCreatureCapacitySheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["creaturecapacity"], position: { width: 560 } }
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-creaturecapacity-sheet.hbs", scrollable: [""] } }
}
// ── Spécialité ──────────────────────────────────────────────────────── // ── Spécialité ────────────────────────────────────────────────────────
export class VermineSpecialtySheetV2 extends VermineBaseItemSheet { export class VermineSpecialtySheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["specialty"], position: { width: 400 } } static DEFAULT_OPTIONS = { classes: ["specialty"], position: { width: 400 } }
+31 -17
View File
@@ -2,8 +2,9 @@ import VermineBaseActorSheet from "./base-actor-sheet.mjs"
export default class VermineNpcSheetV2 extends VermineBaseActorSheet { export default class VermineNpcSheetV2 extends VermineBaseActorSheet {
// La fiche PNJ s'ouvre en mode édition : caractéristiques, compétences et // La fiche PNJ s'ouvre en mode édition : profil et niveaux (Menace,
// niveaux se règlent dès l'ouverture (le mode jeu reste accessible via le toggle). // Expérience, Rôle) se règlent dès l'ouverture (le mode jeu reste accessible
// via le toggle).
_sheetMode = this.constructor.SHEET_MODES.EDIT _sheetMode = this.constructor.SHEET_MODES.EDIT
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
@@ -15,22 +16,18 @@ export default class VermineNpcSheetV2 extends VermineBaseActorSheet {
static PARTS = { static PARTS = {
main: { template: "systems/vermine2047/templates/actor/appv2/npc-main.hbs", scrollable: [""] }, main: { template: "systems/vermine2047/templates/actor/appv2/npc-main.hbs", scrollable: [""] },
tabs: { template: "templates/generic/tab-navigation.hbs" }, tabs: { template: "templates/generic/tab-navigation.hbs" },
characteristics: { template: "systems/vermine2047/templates/actor/appv2/npc-characteristics.hbs", scrollable: [""] }, traits: { template: "systems/vermine2047/templates/actor/appv2/npc-traits.hbs", scrollable: [""] },
skills: { template: "systems/vermine2047/templates/actor/appv2/npc-skills.hbs", scrollable: [""] },
equipment: { template: "systems/vermine2047/templates/actor/appv2/npc-equipment.hbs", scrollable: [""] }, equipment: { template: "systems/vermine2047/templates/actor/appv2/npc-equipment.hbs", scrollable: [""] },
threat: { template: "systems/vermine2047/templates/actor/appv2/npc-threat.hbs", scrollable: [""] },
combat: { template: "systems/vermine2047/templates/actor/appv2/npc-combat.hbs", scrollable: [""] }, combat: { template: "systems/vermine2047/templates/actor/appv2/npc-combat.hbs", scrollable: [""] },
notes: { template: "systems/vermine2047/templates/actor/appv2/npc-notes.hbs", scrollable: [""] } notes: { template: "systems/vermine2047/templates/actor/appv2/npc-notes.hbs", scrollable: [""] }
} }
tabGroups = { sheet: "characteristics" } tabGroups = { sheet: "traits" }
#getTabs() { #getTabs() {
const tabs = { const tabs = {
characteristics: { id: "characteristics", group: "sheet", icon: "fas fa-dice", label: "VERMINE.abilities" }, traits: { id: "traits", group: "sheet", icon: "fas fa-dice", label: "VERMINE.traits" },
skills: { id: "skills", group: "sheet", icon: "fas fa-brain", label: "VERMINE.skills" },
equipment: { id: "equipment", group: "sheet", icon: "fas fa-hammer", label: "VERMINE.tabs.equipment" }, equipment: { id: "equipment", group: "sheet", icon: "fas fa-hammer", label: "VERMINE.tabs.equipment" },
threat: { id: "threat", group: "sheet", icon: "fas fa-exclamation-triangle", label: "ADVERSITY.threat" },
combat: { id: "combat", group: "sheet", icon: "fas fa-sword", label: "VERMINE.combat" }, combat: { id: "combat", group: "sheet", icon: "fas fa-sword", label: "VERMINE.combat" },
notes: { id: "notes", group: "sheet", icon: "fas fa-sticky-note", label: "IDENTITY.notes" } notes: { id: "notes", group: "sheet", icon: "fas fa-sticky-note", label: "IDENTITY.notes" }
} }
@@ -57,20 +54,14 @@ export default class VermineNpcSheetV2 extends VermineBaseActorSheet {
context.totemOptions = CONFIG.VERMINE.totems context.totemOptions = CONFIG.VERMINE.totems
context.originOptions = CONFIG.VERMINE.origins context.originOptions = CONFIG.VERMINE.origins
break break
case "characteristics": case "traits":
context.tab = context.tabs.characteristics context.tab = context.tabs.traits
break
case "skills":
context.tab = context.tabs.skills
break break
case "equipment": case "equipment":
context.tab = context.tabs.equipment context.tab = context.tabs.equipment
context.weapons = doc.itemTypes.weapon context.weapons = doc.itemTypes.weapon
context.defenses = doc.itemTypes.defense context.defenses = doc.itemTypes.defense
break break
case "threat":
context.tab = context.tabs.threat
break
case "combat": case "combat":
context.tab = context.tabs.combat context.tab = context.tabs.combat
context.equippedWeapons = doc.itemTypes.weapon.filter(i => i.system.equipped) context.equippedWeapons = doc.itemTypes.weapon.filter(i => i.system.equipped)
@@ -84,4 +75,27 @@ export default class VermineNpcSheetV2 extends VermineBaseActorSheet {
} }
return context return context
} }
/** @override - Le PNJ n'a ni caractéristiques ni compétences : les jets
* rapides utilisent la valeur d'Attaque (dialogue de combat) ou les valeurs
* dérivées Action / Spécialité / Réaction (dialogue PNJ simplifié). */
async _onRoll(event) {
event.preventDefault()
const el = event.currentTarget
const type = el.dataset.type
if (!type) return
if (type === "attack") {
const { default: CombatDialog } = await import("../../system/dialogs/combatDialog.mjs")
const dialog = await CombatDialog.create({ actorId: this.document.id })
if (dialog) dialog.render(true)
return
}
if (["action", "specialty", "reaction"].includes(type)) {
const { default: NpcRollDialog } = await import("../../system/dialogs/npcRollDialog.mjs")
const dialog = await NpcRollDialog.create({ actorId: this.document.id, rolltype: type })
if (dialog) dialog.render(true)
return
}
return super._onRoll(event)
}
} }
@@ -0,0 +1,76 @@
import VermineBaseActorSheet from "./base-actor-sheet.mjs"
export default class VermineVehicleSheetV2 extends VermineBaseActorSheet {
static DEFAULT_OPTIONS = {
classes: ["vehicle"],
position: { width: 640, height: 580 },
window: { contentClasses: ["vehicle-content"] }
}
static PARTS = {
main: { template: "systems/vermine2047/templates/actor/appv2/vehicle-main.hbs", scrollable: [""] },
tabs: { template: "templates/generic/tab-navigation.hbs" },
specs: { template: "systems/vermine2047/templates/actor/appv2/vehicle-specs.hbs", scrollable: [""] }
}
tabGroups = { sheet: "specs" }
#getTabs() {
const tabs = {
specs: { id: "specs", group: "sheet", icon: "fas fa-car-side", label: "VERMINE.vehicle_specs" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
// Compétences d'Entretien possibles (mécanique, technologie, artisanat, animalisme…)
const maintenanceKeys = ["mecanical", "technology", "crafting", "animalism", "environment", "wildlife", "flora", "repulsion", "toxics"]
const current = this.document.system.maintenance || []
context.maintenanceSkills = maintenanceKeys.map(key => ({
key,
label: game.i18n.localize("SKILLS." + key + ".name"),
checked: current.includes(key)
}))
// Propriétaire (groupe ou personnage) résolu
context.resolvedOwner = null
if (this.document.system.ownerId) {
const owner = game.actors.get(this.document.system.ownerId)
if (owner) context.resolvedOwner = { id: owner.id, name: owner.name }
}
return context
}
async _preparePartContext(partId, context) {
switch (partId) {
case "main": break
case "specs":
context.tab = context.tabs.specs
break
}
return context
}
changeTab(tab, group, options = {}) {
super.changeTab(tab, group, options)
if (group === "sheet") {
const main = this.element?.querySelector('[data-group="sheet"][data-tab="main"]')
if (main) main.classList.add("active")
}
}
/** @override - nettoie le tableau d'Entretien (retire les cases non cochées). */
_prepareSubmitData(event, form, formData, updateData) {
const result = super._prepareSubmitData(event, form, formData, updateData)
if (Array.isArray(result?.system?.maintenance)) {
result.system.maintenance = result.system.maintenance.filter(v => typeof v === "string" && v.trim() !== "")
}
return result
}
}
+1
View File
@@ -8,6 +8,7 @@ export { default as VermineWeaponData } from "./weapon.mjs"
export { default as VermineDefenseData } from "./defense.mjs" export { default as VermineDefenseData } from "./defense.mjs"
export { default as VermineVehicleData } from "./vehicle.mjs" export { default as VermineVehicleData } from "./vehicle.mjs"
export { default as VermineAbilityData } from "./ability.mjs" export { default as VermineAbilityData } from "./ability.mjs"
export { default as VermineCreatureCapacityData } from "./creaturecapacity.mjs"
export { default as VermineSpecialtyData } from "./specialty.mjs" export { default as VermineSpecialtyData } from "./specialty.mjs"
export { default as VermineBackgroundData } from "./background.mjs" export { default as VermineBackgroundData } from "./background.mjs"
export { default as VermineTraumaData } from "./trauma.mjs" export { default as VermineTraumaData } from "./trauma.mjs"
+1
View File
@@ -17,6 +17,7 @@ export default class VermineAbilityData extends foundry.abstract.TypeDataModel {
totem: new fields.StringField({ required: true, initial: "" }), totem: new fields.StringField({ required: true, initial: "" }),
learn: learnSchema(), learn: learnSchema(),
level: levelSchema(1, 1, 3), level: levelSchema(1, 1, 3),
learned: new fields.BooleanField({ required: true, initial: false }),
effects: new fields.ArrayField(new fields.StringField({ required: true, initial: "" }), { effects: new fields.ArrayField(new fields.StringField({ required: true, initial: "" }), {
initial: [] initial: []
}) })
+3 -1
View File
@@ -14,7 +14,9 @@ export default class VermineBackgroundData extends foundry.abstract.TypeDataMode
const reqInt = { required: true, nullable: false, integer: true } const reqInt = { required: true, nullable: false, integer: true }
return { return {
...listItemSchema(), ...listItemSchema(),
cost: new fields.NumberField({ ...reqInt, initial: 1, min: 0 }) cost: new fields.NumberField({ ...reqInt, initial: 1, min: 0 }),
asterisk: new fields.BooleanField({ required: true, initial: false }),
learned: new fields.BooleanField({ required: true, initial: false })
} }
} }
} }
+6 -1
View File
@@ -54,6 +54,8 @@ export default class VermineCharacterData extends foundry.abstract.TypeDataModel
height: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }), height: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
weight: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }), weight: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
totem: new fields.StringField({ required: true, nullable: false, initial: "" }), totem: new fields.StringField({ required: true, nullable: false, initial: "" }),
// Statut de Chaman : débloque l'affichage des Rites chamaniques maîtrisés.
chaman: new fields.BooleanField({ required: true, initial: false }),
age: new fields.StringField({ required: true, nullable: false, initial: "15" }), age: new fields.StringField({ required: true, nullable: false, initial: "15" }),
ageType: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 2 }), ageType: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 2 }),
profile: new fields.StringField({ required: true, nullable: false, initial: "" }), profile: new fields.StringField({ required: true, nullable: false, initial: "" }),
@@ -104,7 +106,10 @@ export default class VermineCharacterData extends foundry.abstract.TypeDataModel
specialty: new fields.BooleanField({ required: true, initial: false }), specialty: new fields.BooleanField({ required: true, initial: false }),
reserve: new fields.BooleanField({ required: true, initial: false }), reserve: new fields.BooleanField({ required: true, initial: false }),
characteristic: new fields.BooleanField({ required: true, initial: false }), characteristic: new fields.BooleanField({ required: true, initial: false }),
evolution: new fields.BooleanField({ required: true, initial: false }) evolution: new fields.BooleanField({ required: true, initial: false }),
ability: new fields.BooleanField({ required: true, initial: false }),
background: new fields.BooleanField({ required: true, initial: false }),
reputation: new fields.BooleanField({ required: true, initial: false })
}) })
}), }),
+30 -26
View File
@@ -90,7 +90,7 @@ export default class VermineCreatureData extends foundry.abstract.TypeDataModel
* .creatureRoleLevels, .creaturePackLevels. * .creatureRoleLevels, .creaturePackLevels.
* *
* Règles : * Règles :
* - Attaque = pattern.attack + size.attack + pack.attack + role.reaction * - Attaque = pattern.attack + size.attack + pack.attack
* - Dégâts = pattern.damage + size.vigor + pack.damage * - Dégâts = pattern.damage + size.vigor + pack.damage
* - Vigueur = size.vigor + pack.damage * - Vigueur = size.vigor + pack.damage
* - Réaction = role.reaction + role.reaction_bonus * - Réaction = role.reaction + role.reaction_bonus
@@ -116,11 +116,12 @@ export default class VermineCreatureData extends foundry.abstract.TypeDataModel
sizeVigor += cfg.vigor || 0 sizeVigor += cfg.vigor || 0
} }
// Attaque : patron + taille + meute + réaction du rôle // Attaque : patron + taille + meute.
// NB : le Rôle ne donne que la Réaction (dés + réussites automatiques),
// il n'ajoute pas de dés d'attaque (regles_creatures.txt).
this.computed.attack = (patternConfig.attack || 0) this.computed.attack = (patternConfig.attack || 0)
+ sizeAttack + sizeAttack
+ (packConfig.attack || 0) + (packConfig.attack || 0)
+ (roleConfig.reaction || 0)
// Dégâts : patron + vigueur de taille + meute // Dégâts : patron + vigueur de taille + meute
this.computed.damage = (patternConfig.damage || 0) this.computed.damage = (patternConfig.damage || 0)
@@ -130,8 +131,9 @@ export default class VermineCreatureData extends foundry.abstract.TypeDataModel
// Vigueur : taille + meute // Vigueur : taille + meute
this.computed.vigor = sizeVigor + (packConfig.damage || 0) this.computed.vigor = sizeVigor + (packConfig.damage || 0)
// Réaction : rôle // Réaction : rôle (le bonus de rôle est compté séparément et ajouté
this.computed.reaction = (roleConfig.reaction || 0) + (roleConfig.reaction_bonus || 0) // comme réussites automatiques par le NpcRollDialog — cf. modèle PNJ).
this.computed.reaction = roleConfig.reaction || 0
this.computed.reactionBonus = roleConfig.reaction_bonus || 0 this.computed.reactionBonus = roleConfig.reaction_bonus || 0
// Réserves // Réserves
@@ -146,8 +148,13 @@ export default class VermineCreatureData extends foundry.abstract.TypeDataModel
} }
/** /**
* Calcule les seuils de blessures à partir du patron, de la taille et de la meute. * Calcule les seuils et les cercles de blessures à partir du patron, de la
* Les seuils sont la somme des valeurs correspondantes des trois sources. * taille et de la meute.
*
* Règles (regles_creatures.txt) :
* - Seuil = contribution du Gabarit + contributions de Taille (cumulées).
* Le Groupe n'ajoute PAS de seuil (tableau « Groupe » : cercles seuls).
* - Cercles = cercles du Gabarit + cercles de Taille (cumulés) + cercles du Groupe.
*/ */
_calculateCreatureWoundThresholds() { _calculateCreatureWoundThresholds() {
const patternLevel = this.pattern?.value || 1 const patternLevel = this.pattern?.value || 1
@@ -159,30 +166,27 @@ export default class VermineCreatureData extends foundry.abstract.TypeDataModel
// Les modificateurs de Taille se cumulent (Taille N = somme des tailles 1..N). // Les modificateurs de Taille se cumulent (Taille N = somme des tailles 1..N).
const sizeLevels = CONFIG.VERMINE.creatureSizeLevels || {} const sizeLevels = CONFIG.VERMINE.creatureSizeLevels || {}
let sizeMinor = 0 const sizeThresholds = { minor: 0, major: 0, deadly: 0 }
let sizeMajor = 0 const sizeWounds = { minor: 0, major: 0, deadly: 0 }
let sizeDeadly = 0
for (let i = 1; i <= sizeLevel; i++) { for (let i = 1; i <= sizeLevel; i++) {
const cfg = sizeLevels[i] || {} const cfg = sizeLevels[i] || {}
sizeMinor += cfg.minorWound || 0 sizeThresholds.minor += cfg.minorThreshold || 0
sizeMajor += cfg.majorWound || 0 sizeThresholds.major += cfg.majorThreshold || 0
sizeDeadly += cfg.deadlyWound || 0 sizeThresholds.deadly += cfg.deadlyThreshold || 0
sizeWounds.minor += cfg.minorWound || 0
sizeWounds.major += cfg.majorWound || 0
sizeWounds.deadly += cfg.deadlyWound || 0
} }
this.minorWound.threshold = (patternConfig.minorWound || 0) // Seuils : gabarit + taille cumulée (pas de contribution du groupe).
+ sizeMinor this.minorWound.threshold = (patternConfig.minorThreshold ?? 0) + sizeThresholds.minor
+ (packConfig.minorWound || 0) this.majorWound.threshold = (patternConfig.majorThreshold ?? 0) + sizeThresholds.major
this.majorWound.threshold = (patternConfig.majorWound || 0) this.deadlyWound.threshold = (patternConfig.deadlyThreshold ?? 0) + sizeThresholds.deadly
+ sizeMajor
+ (packConfig.majorWound || 0)
this.deadlyWound.threshold = (patternConfig.deadlyWound || 0)
+ sizeDeadly
+ (packConfig.deadlyWound || 0)
// Max de blessures // Cercles : gabarit + taille cumulée + groupe.
this.minorWound.max = Math.min(5, this.minorWound.threshold + 2) this.minorWound.max = (patternConfig.minorWound ?? 0) + sizeWounds.minor + (packConfig.minorWound ?? 0)
this.majorWound.max = Math.min(4, this.majorWound.threshold + 1) this.majorWound.max = (patternConfig.majorWound ?? 0) + sizeWounds.major + (packConfig.majorWound ?? 0)
this.deadlyWound.max = Math.min(2, this.deadlyWound.threshold) this.deadlyWound.max = (patternConfig.deadlyWound ?? 0) + sizeWounds.deadly + (packConfig.deadlyWound ?? 0)
} }
/** /**
+19
View File
@@ -0,0 +1,19 @@
import { listItemSchema } from "./_shared.mjs"
/**
* DataModel pour les items de type "creaturecapacity" (capacités de créature).
* @augments {foundry.abstract.TypeDataModel}
*/
export default class VermineCreatureCapacityData extends foundry.abstract.TypeDataModel {
/** @override */
static LOCALIZATION_PREFIXES = ["VERMINE.item.creaturecapacity"]
/** @override */
static defineSchema() {
const fields = foundry.data.fields
return {
...listItemSchema(),
type: new fields.StringField({ required: true, initial: "base" })
}
}
}
+12 -1
View File
@@ -42,6 +42,9 @@ export default class VermineGroupData extends foundry.abstract.TypeDataModel {
// Équipement // Équipement
equipment: equipmentSchema(), equipment: equipmentSchema(),
// Notes de voyage (lieux, dangers, routes…)
roadNotes: new fields.HTMLField({ required: true, initial: "" }),
// Niveau du groupe (1-10) // Niveau du groupe (1-10)
level: levelSchema(1, 1, 10), level: levelSchema(1, 1, 10),
@@ -77,7 +80,15 @@ export default class VermineGroupData extends foundry.abstract.TypeDataModel {
// Dés d'Expérience collectifs (réserve de Groupe) // Dés d'Expérience collectifs (réserve de Groupe)
experience: new fields.SchemaField({ experience: new fields.SchemaField({
dice: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }) dice: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
phase: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
// Limites d'apprentissage par phase
learned: new fields.SchemaField({
phase: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
level: new fields.BooleanField({ required: true, initial: false }),
reserve: new fields.BooleanField({ required: true, initial: false }),
ability: new fields.BooleanField({ required: true, initial: false })
})
}), }),
// Rencontres // Rencontres
+75 -62
View File
@@ -2,17 +2,22 @@
* DataModel pour les acteurs de type "npc" (PNJ). * DataModel pour les acteurs de type "npc" (PNJ).
* Étend foundry.abstract.TypeDataModel. * Étend foundry.abstract.TypeDataModel.
* *
* Note : le champ libre de compétences (texte descriptif) est nommé "freeSkills" * Conforme aux règles des PNJ (regles/regles_pnj.txt) : un PNJ n'a ni
* pour éviter le conflit avec le SchemaField "skills" qui contient les 30 compétences. * caractéristiques chiffrées, ni réserves détaillées, ni liste de compétences.
* Il est défini uniquement par son Profil (identité) et trois critères :
* Menace, Expérience et Rôle. Toutes les valeurs utiles en jeu sont dérivées
* des tableaux de référence (CONFIG.VERMINE.npc*Levels) et exposées dans
* "system.computed".
*
* Note : le champ libre de description des Spécialités est nommé "freeSkills"
* pour éviter le conflit avec le champ "skills" que le template.json historique
* définissait comme texte libre pour les PNJ.
*/ */
import { import {
woundSchema, woundSchema,
combatStatusSchema, combatStatusSchema,
equipmentSchema, equipmentSchema,
attributeSchema, attributeSchema
abilitiesSchema,
skillCategoriesSchema,
skillsSchema
} from "./_shared.mjs" } from "./_shared.mjs"
export default class VermineNpcData extends foundry.abstract.TypeDataModel { export default class VermineNpcData extends foundry.abstract.TypeDataModel {
@@ -23,8 +28,7 @@ export default class VermineNpcData extends foundry.abstract.TypeDataModel {
/** /**
* Migration des données avant traitement par le schéma. * Migration des données avant traitement par le schéma.
* Avant DataModel, template.json définissait "skills" comme un champ texte libre * Avant DataModel, template.json définissait "skills" comme un champ texte libre
* pour les PNJ. Le DataModel réserve "skills" pour les 30 compétences individuelles * pour les PNJ. Le DataModel utilise "freeSkills" pour cette description.
* (SchemaField) et utilise "freeSkills" pour le texte libre.
* @param {Object} source Données brutes avant validation du schéma * @param {Object} source Données brutes avant validation du schéma
* @returns {Object} Données migrées * @returns {Object} Données migrées
*/ */
@@ -48,9 +52,10 @@ export default class VermineNpcData extends foundry.abstract.TypeDataModel {
// Statut de combat (base, difficulté par défaut 9 pour PNJ) // Statut de combat (base, difficulté par défaut 9 pour PNJ)
combatStatus: combatStatusSchema("9"), combatStatus: combatStatusSchema("9"),
// Identité // Identité (Profil du PNJ)
identity: new fields.SchemaField({ identity: new fields.SchemaField({
name: new fields.StringField({ required: true, nullable: false, initial: "" }), name: new fields.StringField({ required: true, nullable: false, initial: "" }),
age: new fields.StringField({ required: true, nullable: false, initial: "" }),
profile: new fields.StringField({ required: true, nullable: false, initial: "" }), profile: new fields.StringField({ required: true, nullable: false, initial: "" }),
origin: new fields.StringField({ required: true, nullable: false, initial: "" }), origin: new fields.StringField({ required: true, nullable: false, initial: "" }),
totem: new fields.StringField({ required: true, nullable: false, initial: "" }), totem: new fields.StringField({ required: true, nullable: false, initial: "" }),
@@ -58,30 +63,29 @@ export default class VermineNpcData extends foundry.abstract.TypeDataModel {
notes: new fields.HTMLField({ required: true, initial: "" }) notes: new fields.HTMLField({ required: true, initial: "" })
}), }),
// Attributs (XP, réputation, sang-froid, effort) // Critères de définition du PNJ (Menace, Expérience, Rôle)
attributes: new fields.SchemaField({
xp: attributeSchema(0, 0, 10),
reputation: attributeSchema(0, 0, 10),
self_control: attributeSchema(0, 0, 5),
effort: attributeSchema(0, 0, 5)
}),
// Niveaux PNJ (menace, expérience, rôle)
threat: attributeSchema(1, 1, 4), threat: attributeSchema(1, 1, 4),
experience: attributeSchema(1, 1, 4), experience: attributeSchema(1, 1, 4),
role: attributeSchema(1, 1, 4), role: attributeSchema(1, 1, 4),
// Compétences (les 30 compétences individuelles) // Description libre des Spécialités (champ texte PNJ)
skills: skillsSchema(),
// Description libre des compétences (champ texte PNJ)
freeSkills: new fields.StringField({ required: true, nullable: false, initial: "" }), freeSkills: new fields.StringField({ required: true, nullable: false, initial: "" }),
// Catégories de compétences // Valeurs calculées (dérivées des niveaux de menace / expérience / rôle)
skill_categories: skillCategoriesSchema(), computed: new fields.SchemaField({
attack: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
// Caractéristiques (8) vigor: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
abilities: abilitiesSchema(), action: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 3 }),
specialties: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 4 }),
rerolls: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
contact: new fields.StringField({ required: true, nullable: false, initial: "7" }),
reaction: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 3 }),
reactionBonus: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
pools: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
gear: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 9 }),
gearHindrance: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
protection: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1 })
}),
// Équipement // Équipement
equipment: equipmentSchema() equipment: equipmentSchema()
@@ -92,58 +96,67 @@ export default class VermineNpcData extends foundry.abstract.TypeDataModel {
prepareDerivedData() { prepareDerivedData() {
super.prepareDerivedData() super.prepareDerivedData()
// 1. Calculer les seuils de blessures selon le niveau de menace // 1. Calculer les valeurs dérivées selon les niveaux de menace/expérience/rôle
this._setNpcComputedValues()
// 2. Calculer les seuils de blessures selon le niveau de menace
this._setNpcWoundThresholds() this._setNpcWoundThresholds()
// 2. Calculer les réserves selon le niveau de rôle // 3. Mettre à jour le statut de combat
this._setNpcAttributes()
// 3. Définir les libellés des caractéristiques
this._setAbilityLabels()
// 4. Mettre à jour le statut de combat
this._updateCombatStatus() this._updateCombatStatus()
} }
/** /**
* Calcule les seuils de blessures à partir du niveau de menace. * Calcule les valeurs dérivées à partir des niveaux de Menace, Expérience
* Utilise CONFIG.VERMINE.npcThreatLevels. * et Rôle. Utilise CONFIG.VERMINE.npcThreatLevels, .npcExperienceLevels
* et .npcRoleLevels.
*/
_setNpcComputedValues() {
const threatLevel = this.threat?.value || 1
const expLevel = this.experience?.value || 1
const roleLevel = this.role?.value || 1
const threatConfig = CONFIG.VERMINE.npcThreatLevels[threatLevel] || {}
const expConfig = CONFIG.VERMINE.npcExperienceLevels[expLevel] || {}
const roleConfig = CONFIG.VERMINE.npcRoleLevels[roleLevel] || {}
// Menace
this.computed.attack = threatConfig.attack || 0
this.computed.vigor = threatConfig.vigor || 0
// Expérience
this.computed.action = expConfig.action || 3
this.computed.specialties = expConfig.specialties || 4
this.computed.rerolls = expConfig.rerolls || 0
this.computed.contact = expConfig.contact || "7"
// Rôle
this.computed.reaction = roleConfig.reaction || 0
this.computed.reactionBonus = roleConfig.reaction_bonus || 0
this.computed.pools = roleConfig.pools || 0
this.computed.gear = roleConfig.gear || 9
this.computed.gearHindrance = roleConfig.gear_hindrance || 0
this.computed.protection = roleConfig.protection || 1
}
/**
* Calcule les seuils et le nombre de boîtes de blessures à partir du
* niveau de menace (tableau "Niveau de Menace"). Utilise
* CONFIG.VERMINE.npcThreatLevels.
*/ */
_setNpcWoundThresholds() { _setNpcWoundThresholds() {
const health = this.abilities?.health?.value || 1
const threatLevel = this.threat?.value || 1 const threatLevel = this.threat?.value || 1
const threatConfig = CONFIG.VERMINE.npcThreatLevels[threatLevel] || {} const threatConfig = CONFIG.VERMINE.npcThreatLevels[threatLevel] || {}
this.minorWound.threshold = health this.minorWound.threshold = threatConfig.minorThreshold ?? 1
this.majorWound.threshold = health + 3 this.majorWound.threshold = threatConfig.majorThreshold ?? 4
this.deadlyWound.threshold = Math.min(health + 7, 10) this.deadlyWound.threshold = threatConfig.deadlyThreshold ?? 6
this.minorWound.max = threatConfig.minorWound || 1 this.minorWound.max = threatConfig.minorWound || 1
this.majorWound.max = threatConfig.majorWound || 1 this.majorWound.max = threatConfig.majorWound || 1
this.deadlyWound.max = threatConfig.deadlyWound || 1 this.deadlyWound.max = threatConfig.deadlyWound || 1
} }
/**
* Définit les attributs dérivés (effort, sang-froid) selon le niveau de rôle.
* Utilise CONFIG.VERMINE.npcRoleLevels.
*/
_setNpcAttributes() {
const roleLevel = this.role?.value || 1
const roleConfig = CONFIG.VERMINE.npcRoleLevels[roleLevel] || {}
this.attributes.effort.max = roleConfig.pools || 0
this.attributes.self_control.max = roleConfig.reaction_bonus || 0
}
/**
* Définit les libellés localisés des caractéristiques.
*/
_setAbilityLabels() {
for (const [k, v] of Object.entries(this.abilities)) {
v.label = game.i18n.localize(CONFIG.VERMINE.abilities[k]) ?? k
}
}
/** /**
* Met à jour le label du statut de combat en fonction de la difficulté. * Met à jour le label du statut de combat en fonction de la difficulté.
*/ */
+34 -10
View File
@@ -1,43 +1,67 @@
import { baseItemSchema } from "./_shared.mjs"
/** /**
* DataModel pour les items de type "vehicle" (véhicules et montures). * DataModel pour les acteurs de type "vehicle" (véhicules et montures).
* Caractéristiques : Rareté, Fiabilité, Distance, Vitesse (croisière/pointe), * Un véhicule est désormais un acteur (et non un objet) : il possède sa propre
* Entretien (compétences) et Réserve d'Effort (montures). * fiche, peut être posé sur la scène comme un jeton et appartient à un groupe
* @augments {foundry.abstract.TypeDataModel} * ou un personnage via `ownerId`.
*
* Caractéristiques (regles_vehicules.txt) : Rareté, Fiabilité (véhicules),
* Réserve d'Effort (montures), Distance, Vitesse (croisière/pointe) et Entretien.
* Pas de statistiques de combat (les règles ne définissent pas de points de vie).
*/ */
import { raritySchema } from "./_shared.mjs"
export default class VermineVehicleData extends foundry.abstract.TypeDataModel { export default class VermineVehicleData extends foundry.abstract.TypeDataModel {
/** @override */ /** @override */
static LOCALIZATION_PREFIXES = ["VERMINE.item.vehicle"] static LOCALIZATION_PREFIXES = ["VERMINE.vehicle"]
/** @override */ /** @override */
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields const fields = foundry.data.fields
const reqInt = { required: true, nullable: false, integer: true } const reqInt = { required: true, nullable: false, integer: true }
return { return {
...baseItemSchema(), // Identité
identity: new fields.SchemaField({
profile: new fields.StringField({ required: true, nullable: false, initial: "" }),
origin: new fields.StringField({ required: true, nullable: false, initial: "" }),
theme: new fields.StringField({ required: true, nullable: false, initial: "" }),
notes: new fields.HTMLField({ required: true, initial: "", textSearch: true })
}),
// Rareté (valeur 1-10 + handicap)
rarity: raritySchema(),
// Fiabilité étendue (1-10) pour les véhicules // Fiabilité étendue (1-10) pour les véhicules
reliability: new fields.NumberField({ ...reqInt, initial: 5, min: 1, max: 10 }), reliability: new fields.NumberField({ ...reqInt, initial: 5, min: 1, max: 10 }),
// Type : "vehicle" (véhicule) ou "mount" (monture) // Type : "vehicle" (véhicule) ou "mount" (monture)
kind: new fields.StringField({ required: true, nullable: false, initial: "vehicle" }), kind: new fields.StringField({ required: true, nullable: false, initial: "vehicle" }),
// Distance : km parcourus avec un "plein" // Distance : km parcourus avec un "plein"
distance: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), distance: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
// Vitesse : croisière / pointe (durée de la pointe) // Vitesse : croisière / pointe (durée de la pointe)
speed: new fields.SchemaField({ speed: new fields.SchemaField({
cruise: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), cruise: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
top: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), top: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
topDuration: new fields.StringField({ required: true, nullable: false, initial: "10 min" }) topDuration: new fields.StringField({ required: true, nullable: false, initial: "10 min" })
}), }),
// Entretien : compétences nécessaires (clés de CONFIG.VERMINE.skills) // Entretien : compétences nécessaires (clés de CONFIG.VERMINE.skills)
maintenance: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })), maintenance: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })),
// Réserve d'Effort (montures) // Réserve d'Effort (montures)
effortReserve: new fields.SchemaField({ effortReserve: new fields.SchemaField({
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), value: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
max: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }) max: new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
}), }),
// Mobilité (héritée)
mobility: new fields.NumberField({ ...reqInt, initial: 3, min: 0 }) // Mobilité
mobility: new fields.NumberField({ ...reqInt, initial: 3, min: 0 }),
// Propriétaire (id d'acteur : groupe ou personnage)
ownerId: new fields.StringField({ required: true, nullable: false, initial: "" })
} }
} }
} }
+13 -2
View File
@@ -2,10 +2,11 @@ const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api
export class TotemPicker extends HandlebarsApplicationMixin(ApplicationV2) { export class TotemPicker extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(linkEl, actor) { constructor(linkEl, actor, options = {}) {
super(); super();
this.linkEl = linkEl; this.linkEl = linkEl;
this.actor = actor; this.actor = actor;
this.fillInstincts = options.fillInstincts === true;
} }
static get DEFAULT_OPTIONS() { static get DEFAULT_OPTIONS() {
@@ -32,7 +33,17 @@ export class TotemPicker extends HandlebarsApplicationMixin(ApplicationV2) {
el.addEventListener('click', event => { el.addEventListener('click', event => {
const link = event.target.closest('a'); const link = event.target.closest('a');
if (!link?.dataset.totem) return; if (!link?.dataset.totem) return;
this.actor.update({ 'system.identity.totem': link.dataset.totem }); const update = { 'system.identity.totem': link.dataset.totem };
// Remplit automatiquement Instincts/Interdits depuis la config du totem
// (utilisé pour le Totem du Groupe).
if (this.fillInstincts && this.actor.system?.identity) {
const totemKey = link.dataset.totem;
const instincts = game.i18n.localize(`TOTEMS.${totemKey}.instincts`);
const bans = game.i18n.localize(`TOTEMS.${totemKey}.bans`);
if (!instincts.startsWith("TOTEMS.")) update['system.identity.instincts'] = instincts;
if (!bans.startsWith("TOTEMS.")) update['system.identity.prohibits'] = bans;
}
this.actor.update(update);
this.close(); this.close();
}); });
}); });
+35 -16
View File
@@ -99,10 +99,10 @@ VERMINE.totemDomains = {
* NPC Threat Levels configuration * NPC Threat Levels configuration
*/ */
VERMINE.npcThreatLevels = { VERMINE.npcThreatLevels = {
1: { "label": "THREAT_LEVELS.minor", "attack": 3, "vigor": 1, "minorWound": 1, "majorWound": 1, "deadlyWound": 1 }, 1: { "label": "THREAT_LEVELS.minor", "attack": 3, "vigor": 1, "minorThreshold": 1, "majorThreshold": 4, "deadlyThreshold": 6, "minorWound": 1, "majorWound": 1, "deadlyWound": 1 },
2: { "label": "THREAT_LEVELS.serious", "attack": 4, "vigor": 2, "minorWound": 2, "majorWound": 1, "deadlyWound": 1 }, 2: { "label": "THREAT_LEVELS.serious", "attack": 4, "vigor": 2, "minorThreshold": 2, "majorThreshold": 5, "deadlyThreshold": 8, "minorWound": 2, "majorWound": 1, "deadlyWound": 1 },
3: { "label": "THREAT_LEVELS.major", "attack": 5, "vigor": 3, "minorWound": 2, "majorWound": 1, "deadlyWound": 1 }, 3: { "label": "THREAT_LEVELS.major", "attack": 5, "vigor": 3, "minorThreshold": 2, "majorThreshold": 5, "deadlyThreshold": 9, "minorWound": 2, "majorWound": 2, "deadlyWound": 1 },
4: { "label": "THREAT_LEVELS.deadly", "attack": 6, "vigor": 4, "minorWound": 2, "majorWound": 2, "deadlyWound": 2 } 4: { "label": "THREAT_LEVELS.deadly", "attack": 6, "vigor": 4, "minorThreshold": 3, "majorThreshold": 6, "deadlyThreshold": 9, "minorWound": 2, "majorWound": 2, "deadlyWound": 2 }
} }
/** /**
@@ -129,30 +129,30 @@ VERMINE.npcRoleLevels = {
* Creature Pattern Levels configuration * Creature Pattern Levels configuration
*/ */
VERMINE.creaturePatternLevels = { VERMINE.creaturePatternLevels = {
1: { "label": "PATTERN_LEVELS.insect", "attack": 2, "damage": 0, "minorWound": 0, "majorWound": 0, "deadlyWound": 1 }, 1: { "label": "PATTERN_LEVELS.insect", "attack": 2, "damage": 0, "minorThreshold": 0, "majorThreshold": 0, "deadlyThreshold": 1, "minorWound": 0, "majorWound": 0, "deadlyWound": 1 },
2: { "label": "PATTERN_LEVELS.rat", "attack": 3, "damage": 1, "minorWound": 0, "majorWound": 1, "deadlyWound": 1 }, 2: { "label": "PATTERN_LEVELS.rat", "attack": 3, "damage": 1, "minorThreshold": 0, "majorThreshold": 1, "deadlyThreshold": 3, "minorWound": 0, "majorWound": 1, "deadlyWound": 1 },
3: { "label": "PATTERN_LEVELS.dog", "attack": 4, "damage": 3, "minorWound": 1, "majorWound": 1, "deadlyWound": 1 }, 3: { "label": "PATTERN_LEVELS.dog", "attack": 4, "damage": 3, "minorThreshold": 1, "majorThreshold": 3, "deadlyThreshold": 5, "minorWound": 1, "majorWound": 1, "deadlyWound": 1 },
4: { "label": "PATTERN_LEVELS.bear", "attack": 6, "damage": 6, "minorWound": 2, "majorWound": 2, "deadlyWound": 2 } 4: { "label": "PATTERN_LEVELS.bear", "attack": 6, "damage": 6, "minorThreshold": 3, "majorThreshold": 5, "deadlyThreshold": 7, "minorWound": 2, "majorWound": 2, "deadlyWound": 2 }
} }
/** /**
* Creature Size Levels configuration * Creature Size Levels configuration
*/ */
VERMINE.creatureSizeLevels = { VERMINE.creatureSizeLevels = {
0: { "label": "SIZE_LEVELS.tiny", "attack": 0, "vigor": 0, "minorWound": 0, "majorWound": 0, "deadlyWound": 0 }, 0: { "label": "SIZE_LEVELS.tiny", "attack": 0, "vigor": 0, "minorThreshold": 0, "majorThreshold": 0, "deadlyThreshold": 0, "minorWound": 0, "majorWound": 0, "deadlyWound": 0 },
1: { "label": "SIZE_LEVELS.small", "attack": 1, "vigor": 1, "minorWound": 0, "majorWound": 1, "deadlyWound": 1 }, 1: { "label": "SIZE_LEVELS.small", "attack": 1, "vigor": 1, "minorThreshold": 0, "majorThreshold": 1, "deadlyThreshold": 1, "minorWound": 0, "majorWound": 1, "deadlyWound": 1 },
2: { "label": "SIZE_LEVELS.medium", "attack": 2, "vigor": 2, "minorWound": 2, "majorWound": 2, "deadlyWound": 2 }, 2: { "label": "SIZE_LEVELS.medium", "attack": 2, "vigor": 2, "minorThreshold": 2, "majorThreshold": 2, "deadlyThreshold": 2, "minorWound": 1, "majorWound": 1, "deadlyWound": 1 },
3: { "label": "SIZE_LEVELS.large", "attack": 3, "vigor": 3, "minorWound": 3, "majorWound": 3, "deadlyWound": 3 } 3: { "label": "SIZE_LEVELS.large", "attack": 3, "vigor": 3, "minorThreshold": 3, "majorThreshold": 3, "deadlyThreshold": 3, "minorWound": 1, "majorWound": 1, "deadlyWound": 1 }
} }
/** /**
* Creature Pack Levels configuration * Creature Pack Levels configuration
*/ */
VERMINE.creaturePackLevels = { VERMINE.creaturePackLevels = {
0: { "attack": 0, "damage": 0, "minorWound": 0, "majorWound": 0, "deadlyWound": 0 }, 0: { "label": "VERMINE.none", "attack": 0, "damage": 0, "minorWound": 0, "majorWound": 0, "deadlyWound": 0 },
1: { "attack": 1, "damage": 1, "minorWound": 0, "majorWound": 0, "deadlyWound": 1 }, 1: { "label": "PACK_LEVELS.small", "attack": 1, "damage": 1, "minorWound": 0, "majorWound": 0, "deadlyWound": 1 },
2: { "attack": 2, "damage": 2, "minorWound": 2, "majorWound": 2, "deadlyWound": 2 }, 2: { "label": "PACK_LEVELS.large", "attack": 2, "damage": 2, "minorWound": 2, "majorWound": 2, "deadlyWound": 2 },
3: { "attack": 5, "damage": 5, "minorWound": 3, "majorWound": 3, "deadlyWound": 3 } 3: { "label": "PACK_LEVELS.giant", "attack": 5, "damage": 5, "minorWound": 3, "majorWound": 3, "deadlyWound": 3 }
} }
/** /**
@@ -180,6 +180,25 @@ VERMINE.abilityCategories = {
} }
} }
/**
* Types de Capacités de créature (mode d'activation/disponibilité).
* Correspond aux Modes de jeu + une catégorie "base".
*/
VERMINE.creatureCapacityTypes = {
"base": {
"label": "CREATURE_CAPACITY_TYPES.base"
},
"survival": {
"label": "CREATURE_CAPACITY_TYPES.survival"
},
"nightmare": {
"label": "CREATURE_CAPACITY_TYPES.nightmare"
},
"apocalypse": {
"label": "CREATURE_CAPACITY_TYPES.apocalypse"
}
}
VERMINE.abilities = { VERMINE.abilities = {
"vigor": "ABILITIES.vigor.name", "vigor": "ABILITIES.vigor.name",
"health": "ABILITIES.health.name", "health": "ABILITIES.health.name",
+10 -5
View File
@@ -260,8 +260,9 @@ export default class CombatDialog extends HandlebarsApplicationMixin(foundry.app
const toolsChecked = this.#el.querySelector("input[name='usingTools']:checked"); const toolsChecked = this.#el.querySelector("input[name='usingTools']:checked");
const tooling = toolsChecked && toolsChecked.value !== "0" ? 1 : 0; const tooling = toolsChecked && toolsChecked.value !== "0" ? 1 : 0;
const group = parseInt(this.#el.querySelector("#group")?.value, 10) || 0; const group = parseInt(this.#el.querySelector("#group")?.value, 10) || 0;
const total = this.basePool + selfControl + pools + specialty + help + tooling + group; const malus = VermineExchange.woundMalus(this.actor);
return { ability, skill, selfControl, pools, specialty, help, tooling, group, total }; const total = Math.max(0, this.basePool + selfControl + pools + specialty + help + tooling + group - malus);
return { ability, skill, selfControl, pools, specialty, help, tooling, group, malus, total };
} }
getDicePool() { getDicePool() {
@@ -297,7 +298,7 @@ export default class CombatDialog extends HandlebarsApplicationMixin(foundry.app
const b = this.getPoolBreakdown(); const b = this.getPoolBreakdown();
const bonusEl = this.#el.querySelector("#total-bonus"); const bonusEl = this.#el.querySelector("#total-bonus");
if (bonusEl) bonusEl.textContent = b.selfControl + b.pools + b.specialty + b.help + b.tooling + b.group; if (bonusEl) bonusEl.textContent = b.selfControl + b.pools + b.specialty + b.help + b.tooling + b.group - b.malus;
const handSel = this.#el.querySelector("#handicap"); const handSel = this.#el.querySelector("#handicap");
const handEl = this.#el.querySelector("#current-handicap"); const handEl = this.#el.querySelector("#current-handicap");
@@ -367,7 +368,7 @@ export default class CombatDialog extends HandlebarsApplicationMixin(foundry.app
: null : null
}; };
await VermineUtils.roll({ const rollParams = {
actor: this.actor, actor: this.actor,
NoD: this.getDicePool(), NoD: this.getDicePool(),
Reroll: this.getReroll(), Reroll: this.getReroll(),
@@ -387,9 +388,13 @@ export default class CombatDialog extends HandlebarsApplicationMixin(foundry.app
targets: targets.map(t => t.name), targets: targets.map(t => t.name),
attack, attack,
messageFlags: { "vermine-exchange": attack } messageFlags: { "vermine-exchange": attack }
}); };
// Fermeture immédiate du dialogue : le jet (et l'animation 3D) continue
// en arrière-plan sans bloquer la fermeture.
this.close(); this.close();
await VermineUtils.roll(rollParams);
} }
#getRangeKey() { #getRangeKey() {
@@ -41,11 +41,19 @@ export default class ExperiencePhaseDialog extends HandlebarsApplicationMixin(fo
async _prepareContext() { async _prepareContext() {
const characters = game.actors.filter(a => a.type === "character"); const characters = game.actors.filter(a => a.type === "character");
const groups = game.actors.filter(a => a.type === "group"); const groups = game.actors.filter(a => a.type === "group");
// Associe chaque personnage à son premier groupe (même logique que RollDialog.#findGroup)
const memberOf = new Map();
for (const g of groups) {
for (const id of g.system?.members || []) {
if (!memberOf.has(id)) memberOf.set(id, g.id);
}
}
return { return {
characters: characters.map(a => ({ characters: characters.map(a => ({
id: a.id, id: a.id,
name: a.name, name: a.name,
dice: a.system?.experience?.dice || 0 dice: a.system?.experience?.dice || 0,
groupId: memberOf.get(a.id) || ""
})), })),
groups: groups.map(a => ({ groups: groups.map(a => ({
id: a.id, id: a.id,
@@ -63,7 +71,9 @@ export default class ExperiencePhaseDialog extends HandlebarsApplicationMixin(fo
async _onRender(context, options) { async _onRender(context, options) {
this.element.querySelectorAll("[data-roll]").forEach(inp => inp.addEventListener("change", () => this.#updateUI())); this.element.querySelectorAll("[data-roll]").forEach(inp => inp.addEventListener("change", () => this.#updateUI()));
this.element.querySelector("#group-select")?.addEventListener("change", () => this.#updateGroupFilter());
this.#updateUI(); this.#updateUI();
this.#updateGroupFilter();
} }
#updateUI() { #updateUI() {
@@ -74,6 +84,15 @@ export default class ExperiencePhaseDialog extends HandlebarsApplicationMixin(fo
if (el) el.textContent = `${danger + discoveries + duration}D`; if (el) el.textContent = `${danger + discoveries + duration}D`;
} }
/** Filtre les personnages selon le groupe sélectionné (vide = tous). */
#updateGroupFilter() {
const groupId = this.element.querySelector("#group-select")?.value || "";
for (const row of this.element.querySelectorAll("[data-character-id]")) {
const show = !groupId || row.dataset.groupId === groupId;
row.hidden = !show;
}
}
static async #onCancel(event, target) { static async #onCancel(event, target) {
this.close(); this.close();
} }
@@ -88,12 +107,22 @@ export default class ExperiencePhaseDialog extends HandlebarsApplicationMixin(fo
const group = game.actors.get(groupId); const group = game.actors.get(groupId);
if (group && collective > 0) { if (group && collective > 0) {
const current = group.system?.experience?.dice || 0; const current = group.system?.experience?.dice || 0;
await group.update({ "system.experience.dice": current + collective }); const phase = (group.system?.experience?.phase || 0) + 1;
await group.update({
"system.experience.dice": current + collective,
"system.experience.phase": phase,
"system.experience.learned.phase": phase,
"system.experience.learned.level": false,
"system.experience.learned.reserve": false,
"system.experience.learned.ability": false
});
} }
} }
const results = []; const results = [];
for (const row of this.element.querySelectorAll("[data-character-id]")) { for (const row of this.element.querySelectorAll("[data-character-id]")) {
// Quand un groupe précis est choisi, ne récompenser que ses membres.
if (groupId && row.dataset.groupId !== groupId) continue;
const id = row.dataset.characterId; const id = row.dataset.characterId;
const actor = game.actors.get(id); const actor = game.actors.get(id);
if (!actor) continue; if (!actor) continue;
@@ -125,7 +154,10 @@ export default class ExperiencePhaseDialog extends HandlebarsApplicationMixin(fo
"system.experience.learned.specialty": false, "system.experience.learned.specialty": false,
"system.experience.learned.reserve": false, "system.experience.learned.reserve": false,
"system.experience.learned.characteristic": false, "system.experience.learned.characteristic": false,
"system.experience.learned.evolution": false "system.experience.learned.evolution": false,
"system.experience.learned.ability": false,
"system.experience.learned.background": false,
"system.experience.learned.reputation": false
}; };
await actor.update(update); await actor.update(update);
results.push({ name: actor.name, gained }); results.push({ name: actor.name, gained });
@@ -0,0 +1,254 @@
import { VermineUtils } from '../roll.mjs';
const { HandlebarsApplicationMixin } = foundry.applications.api;
/**
* Dialogue d'apprentissage de Groupe (phase d'Expérience).
* Permet de dépenser des Dés d'Expérience du Groupe pour tenter d'améliorer
* le Niveau de Groupe, la Réserve de Groupe ou de développer une Capacité
* collective. La progression est appliquée automatiquement en cas de réussite.
*/
export default class GroupLearningDialog extends HandlebarsApplicationMixin(foundry.applications.api.ApplicationV2) {
#actor;
static DEFAULT_OPTIONS = {
classes: ['vermine-roll'],
tag: 'form',
window: {
icon: 'fas fa-graduation-cap',
resizable: false,
},
position: {
width: 560,
height: 560,
},
actions: {
roll: GroupLearningDialog.#onRoll,
cancel: GroupLearningDialog.#onCancel,
},
};
static PARTS = {
main: { template: 'systems/vermine2047/templates/dialogs/group-learning-dialog.hbs', scrollable: [''] },
};
static async create({ actorId }) {
const actor = await game.actors.get(actorId);
if (!actor) {
ui.notifications.warn(game.i18n.localize('VERMINE.error_no_actor_selected'));
return null;
}
return new GroupLearningDialog({ actor });
}
constructor(options = {}) {
super(options);
this.#actor = options.actor;
}
get title() {
return game.i18n.localize('VERMINE.open_learning');
}
_prepareContext() {
const actor = this.#actor;
return {
actor,
system: actor.system,
dice: actor.system?.experience?.dice || 0,
abilities: actor.itemTypes.ability
.filter((i) => i.system.type !== 'totem' && !i.system.learned)
.map((a) => ({
id: a.id,
name: a.name,
threshold: a.system?.learn?.threshold ?? 5,
level: a.system?.level?.value ?? 1,
})),
};
}
_onRender() {
this.element.dataset.actorId = this.#actor.id;
for (const inp of this.element.querySelectorAll('[data-roll]')) {
inp.addEventListener('change', () => this.#updateUI());
}
this.element.querySelector('#learning-dice')?.addEventListener('input', () => this.#updateUI());
this.#updateUI();
}
get #el() { return this.element; }
#getType() {
return this.#el.querySelector('input[name="learning-type"]:checked')?.value ?? 'level';
}
#getAbilityId() {
return this.#el.querySelector('#learning-ability')?.value ?? '';
}
#getDiceSpent() {
return Math.max(1, parseInt(this.#el.querySelector('#learning-dice')?.value, 10) || 1);
}
#compute() {
const actor = this.#actor;
const type = this.#getType();
const key = type === 'ability' ? this.#getAbilityId() : '';
let difficulty = 7;
let handicap = 0;
let targetLevel = null;
let targetLabel = '';
if (type === 'level') {
const current = actor.system?.level?.value || 1;
difficulty = actor.system?.members?.length || 0;
handicap = current;
targetLevel = current + 1;
} else if (type === 'reserve') {
const current = actor.system?.reserve?.value || 0;
difficulty = current;
handicap = 2;
targetLevel = current + 1;
} else {
const ability = key ? actor.items.get(key) : null;
if (ability) {
difficulty = ability.system?.learn?.threshold ?? 5;
handicap = ability.system?.level?.value ?? 1;
targetLabel = ability.name;
}
}
const labels = {
level: 'VERMINE.learn_level',
reserve: 'VERMINE.learn_group_reserve',
ability: 'VERMINE.learn_ability',
};
return {
type,
key,
targetLevel,
difficulty,
handicap,
poolModifier: 0,
label: game.i18n.localize(labels[type] || labels.level),
targetLabel,
};
}
#updateUI() {
const type = this.#getType();
const show = (id, visible) => {
const el = this.#el.querySelector(id);
if (el) el.style.display = visible ? '' : 'none';
};
show('#learning-level-section', type === 'level');
show('#learning-reserve-section', type === 'reserve');
show('#learning-ability-section', type === 'ability');
const c = this.#compute();
const set = (sel, txt) => {
const el = this.#el.querySelector(sel);
if (el) el.textContent = txt;
};
set('#diff-value', c.difficulty);
set('#handicap-value', c.handicap);
set('#required-value', 1 + c.handicap);
const pool = Math.max(0, this.#getDiceSpent() + c.poolModifier);
set('#pool-value', `${pool}D${c.poolModifier ? ` (${c.poolModifier >= 0 ? '+' : ''}${c.poolModifier}D)` : ''}`);
const poolInput = this.#el.querySelector('#learning-dice');
if (poolInput) poolInput.max = this.#actor.system?.experience?.dice || 0;
}
static #onCancel() {
this.close();
}
/** Renvoie la clé i18n d'erreur si le jet est impossible, sinon null. */
#getError(c) {
const actor = this.#actor;
const ex = actor.system?.experience;
// Limite : un seul Niveau, une seule Réserve et une seule Capacité par phase.
if (ex?.learned?.phase === ex?.phase && ex.learned[c.type]) {
return 'VERMINE.error_learning_limit';
}
if (c.type === 'level' && c.targetLevel !== null && (actor.system?.level?.value || 0) >= 10) {
return 'VERMINE.error_group_level_max';
}
if (c.type === 'reserve' && c.targetLevel !== null &&
(actor.system?.reserve?.value || 0) >= (actor.system?.reserve?.max || 10)) {
return 'VERMINE.error_group_reserve_max';
}
if (c.type === 'ability') {
if (!c.key) {
return 'VERMINE.no_group_ability';
}
const ability = c.key ? actor.items.get(c.key) : null;
const requiredLevel = ability?.system?.level?.value ?? 1;
if (ability && (actor.system?.level?.value || 1) < requiredLevel) {
return 'VERMINE.error_group_level_requirement';
}
}
return null;
}
static async #onRoll() {
const actor = this.#actor;
const ex = actor.system?.experience;
const spent = Math.min(this.#getDiceSpent(), ex?.dice || 0);
if (spent < 1) {
ui.notifications.warn(game.i18n.localize('VERMINE.error_no_experience_dice'));
return;
}
const c = this.#compute();
const errorKey = this.#getError(c);
if (errorKey) {
ui.notifications.warn(game.i18n.localize(errorKey));
return;
}
const pool = Math.max(0, spent + c.poolModifier);
const rollLabel = `${game.i18n.localize('VERMINE.open_learning')}${c.label}${c.targetLabel ? ` (${c.targetLabel})` : ''}`;
const roll = await VermineUtils.roll({
actor,
NoD: pool,
Reroll: 0,
difficulty: c.difficulty,
handicap: c.handicap,
rollLabel,
poolBreakdown: { ability: 0, skill: 0, selfControl: 0, specialty: 0, help: 0, tooling: 0, group: 0, total: pool },
messageFlags: { 'vermine-learning': { type: c.type, key: c.key, spent, success: false } },
});
const required = 1 + c.handicap;
if ((roll._total ?? 0) >= required) {
await this.#applySuccess(c);
}
// Les Dés d'Expérience sont perdus que le jet soit réussi ou raté.
await actor.update({ "system.experience.dice": Math.max(0, (actor.system.experience.dice || 0) - spent) });
this.close();
}
async #applySuccess(c) {
const actor = this.#actor;
const updates = {};
if (c.type === 'level') {
const current = actor.system.level.value || 1;
updates['system.level.value'] = Math.min(10, current + 1);
updates['system.experience.learned.level'] = true;
} else if (c.type === 'reserve') {
const current = actor.system.reserve.value || 0;
updates['system.reserve.value'] = Math.min(actor.system.reserve.max || 10, current + 1);
updates['system.experience.learned.reserve'] = true;
} else {
if (c.key) {
await actor.updateEmbeddedDocuments('Item', [{ _id: c.key, 'system.learned': true }]);
}
updates['system.experience.learned.ability'] = true;
}
updates['system.experience.learned.phase'] = actor.system.experience.phase;
await actor.update(updates);
ui.notifications.info(game.i18n.localize('VERMINE.learning_success'));
}
}
+230 -15
View File
@@ -64,6 +64,29 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
rarity: actor.system?.skills?.[key]?.rarity || 0, rarity: actor.system?.skills?.[key]?.rarity || 0,
isDomain: VermineExperience.isPreferredDomain(actor, key) isDomain: VermineExperience.isPreferredDomain(actor, key)
})); }));
const totem = actor.system?.identity?.totem || "";
const gameMode = this.#getGameMode();
const groupLevel = this.#getGroupLevel();
const totemAbilities = (actor.itemTypes?.ability || [])
.filter(i =>
i.system?.type === "character"
&& !i.system?.learned
&& i.system?.totem === totem
&& (i.system?.level?.value || 0) <= gameMode
&& groupLevel >= (i.system?.level?.value || 0)
)
.map(a => ({
id: a.id,
name: a.name,
level: a.system.level.value,
threshold: a.system.learn.threshold,
hindrance: a.system.learn.hindrance
}));
const backgrounds = (actor.itemTypes?.background || [])
.filter(i => !i.system?.learned)
.map(b => ({ id: b.id, name: b.name, asterisk: b.system?.asterisk || false }));
const human = actor.system?.adaptation?.totems?.human?.value || 0;
const adapted = actor.system?.adaptation?.totems?.adapted?.value || 0;
return { return {
actor, actor,
system: actor.system, system: actor.system,
@@ -81,11 +104,48 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
], ],
specialties: actor.itemTypes?.specialty || [], specialties: actor.itemTypes?.specialty || [],
skills, skills,
specialtyCount: (actor.itemTypes?.specialty || []).length specialtyCount: (actor.itemTypes?.specialty || []).length,
totem,
gameMode,
groupLevel,
totemAbilities,
backgrounds,
reputation: actor.system?.attributes?.reputation?.value ?? 0,
human,
adapted,
canUseAdaptedDie: VermineExperience.canUseAdaptedDie(human, adapted)
}; };
} }
async _onRender(context, options) { #getGameMode() {
return parseInt(game.settings.get("vermine2047", "game-mode") || "1", 10) || 1;
}
#getGroupLevel() {
const groups = game.actors.filter(a => a.type === "group" && (a.system?.members || []).includes(this.#actor.id));
return Math.max(0, ...groups.map(g => g.system?.level?.value || 0));
}
#canUseAdaptedDie() {
const a = this.#actor.system?.adaptation?.totems;
return VermineExperience.canUseAdaptedDie(a?.human?.value || 0, a?.adapted?.value || 0);
}
#totemCapacityCounts() {
// Ne comptabilise que les Capacités du Totem courant du personnage
// (les Capacités liées à un autre Totem ne bloquent pas les limites).
const totem = this.#actor.system?.identity?.totem;
const items = (this.#actor.itemTypes?.ability || []).filter(i =>
i.system?.type === "character" && i.system?.learned && i.system?.totem === totem);
const byLevel = { 1: 0, 2: 0, 3: 0 };
for (const i of items) {
const level = i.system?.level?.value || 0;
byLevel[level] = (byLevel[level] || 0) + 1;
}
return { total: items.length, byLevel };
}
async _onRender() {
this.element.dataset.actorId = this.#actor.id; this.element.dataset.actorId = this.#actor.id;
for (const inp of this.element.querySelectorAll("[data-roll]")) { for (const inp of this.element.querySelectorAll("[data-roll]")) {
inp.addEventListener("change", () => this.#updateUI()); inp.addEventListener("change", () => this.#updateUI());
@@ -104,9 +164,19 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
const type = this.#getType(); const type = this.#getType();
if (type === "characteristic") return this.#el.querySelector("#learning-ability")?.value ?? ""; if (type === "characteristic") return this.#el.querySelector("#learning-ability")?.value ?? "";
if (type === "reserve") return this.#el.querySelector("#learning-reserve")?.value ?? ""; if (type === "reserve") return this.#el.querySelector("#learning-reserve")?.value ?? "";
if (type === "totem") return this.#el.querySelector("#learning-totem")?.value ?? "";
if (type === "background") return this.#el.querySelector("#learning-background")?.value ?? "";
return this.#el.querySelector("#learning-target")?.value ?? ""; return this.#el.querySelector("#learning-target")?.value ?? "";
} }
#getReputationDirection() {
return this.#el.querySelector("#learning-reputation-direction")?.value ?? "reduce";
}
#isAdaptedDieChecked() {
return Boolean(this.#el.querySelector("#learning-adapted-die")?.checked);
}
#getDiceSpent() { #getDiceSpent() {
return Math.max(1, parseInt(this.#el.querySelector("#learning-dice")?.value, 10) || 1); return Math.max(1, parseInt(this.#el.querySelector("#learning-dice")?.value, 10) || 1);
} }
@@ -119,9 +189,6 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
const actor = this.#actor; const actor = this.#actor;
const type = this.#getType(); const type = this.#getType();
const key = this.#getTargetKey(); const key = this.#getTargetKey();
const difficulty = 7;
let handicap = 0;
let poolModifier = 0;
let label = ""; let label = "";
let targetLevel = null; let targetLevel = null;
@@ -182,10 +249,71 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
difficulty: VermineExperience.reserveLearningDifficulty(current), difficulty: VermineExperience.reserveLearningDifficulty(current),
handicap: VermineExperience.ageHandicap(actor.system?.identity?.ageType || 2), handicap: VermineExperience.ageHandicap(actor.system?.identity?.ageType || 2),
poolModifier: 0, poolModifier: 0,
adaptedDie: false,
label, label,
targetLabel: key ? game.i18n.localize(key === "self_control" ? "VERMINE.self_control" : "VERMINE.effort") : "" targetLabel: key ? game.i18n.localize(key === "self_control" ? "VERMINE.self_control" : "VERMINE.effort") : ""
}; };
} }
if (type === "totem") {
const item = actor.itemTypes?.ability?.find(i => i.id === key);
const data = item?.system;
label = game.i18n.localize("VERMINE.learn_totem_ability");
return {
type,
key,
targetLevel: data?.level?.value || 0,
difficulty: VermineExperience.totemAbilityLearningDifficulty(data),
handicap: VermineExperience.totemAbilityLearningHandicap(data),
poolModifier: 0,
adaptedDie: false,
label,
targetLabel: item?.name || ""
};
}
if (type === "background") {
const item = actor.itemTypes?.background?.find(i => i.id === key);
const data = item?.system;
label = game.i18n.localize("VERMINE.learn_background");
return {
type,
key,
targetLevel: 0,
difficulty: VermineExperience.backgroundLearningDifficulty(),
handicap: VermineExperience.backgroundLearningHandicap(data?.asterisk),
poolModifier: 0,
adaptedDie: false,
label,
targetLabel: item?.name || ""
};
}
if (type === "reputation") {
const current = actor.system?.attributes?.reputation?.value ?? 0;
const direction = this.#getReputationDirection();
if (direction === "increase") {
return {
type,
key: "increase",
targetLevel: current,
difficulty: VermineExperience.reputationIncreaseDifficulty(current),
handicap: 0,
poolModifier: 0,
adaptedDie: false,
label: game.i18n.localize("VERMINE.learn_reputation"),
targetLabel: game.i18n.localize("VERMINE.reputation_increase")
};
}
return {
type,
key: "reduce",
targetLevel: Math.max(0, current - 1),
difficulty: VermineExperience.reputationReduceDifficulty(current),
handicap: 0,
poolModifier: 0,
adaptedDie: false,
label: game.i18n.localize("VERMINE.learn_reputation"),
targetLabel: game.i18n.localize("VERMINE.reputation_reduce")
};
}
// evolution // evolution
const human = actor.system?.adaptation?.totems?.human?.value || 0; const human = actor.system?.adaptation?.totems?.human?.value || 0;
const adapted = actor.system?.adaptation?.totems?.adapted?.value || 0; const adapted = actor.system?.adaptation?.totems?.adapted?.value || 0;
@@ -197,6 +325,7 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
difficulty: VermineExperience.evolutionDifficulty(actor.system?.identity?.ageType || 2), difficulty: VermineExperience.evolutionDifficulty(actor.system?.identity?.ageType || 2),
handicap: actor.system?.experience?.evolution || 0, handicap: actor.system?.experience?.evolution || 0,
poolModifier: VermineExperience.evolutionTotemModifier(human, adapted), poolModifier: VermineExperience.evolutionTotemModifier(human, adapted),
adaptedDie: this.#canUseAdaptedDie() && this.#isAdaptedDieChecked(),
label, label,
targetLabel: "" targetLabel: ""
}; };
@@ -213,6 +342,10 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
show("#learning-ability-section", type === "characteristic"); show("#learning-ability-section", type === "characteristic");
show("#learning-reserve-section", type === "reserve"); show("#learning-reserve-section", type === "reserve");
show("#learning-evolution-section", type === "evolution"); show("#learning-evolution-section", type === "evolution");
show("#learning-totem-section", type === "totem");
show("#learning-background-section", type === "background");
show("#learning-reputation-section", type === "reputation");
show("#learning-adapted-die-row", type === "evolution" && this.#canUseAdaptedDie());
// Pour une Spécialité, seule une compétence au moins Confirmée (≥2) est valable. // Pour une Spécialité, seule une compétence au moins Confirmée (≥2) est valable.
const skillSelect = this.#el.querySelector("#learning-target"); const skillSelect = this.#el.querySelector("#learning-target");
@@ -229,16 +362,25 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
set("#handicap-value", c.handicap); set("#handicap-value", c.handicap);
set("#required-value", 1 + c.handicap); set("#required-value", 1 + c.handicap);
const pool = Math.max(0, this.#getDiceSpent() + c.poolModifier); const pool = Math.max(0, this.#getDiceSpent() + c.poolModifier);
set("#pool-value", `${pool}D${c.poolModifier ? ` (${c.poolModifier >= 0 ? "+" : ""}${c.poolModifier}D)` : ""}`); let poolText = `${pool}D${c.poolModifier ? ` (${c.poolModifier >= 0 ? "+" : ""}${c.poolModifier}D)` : ""}`;
if (c.adaptedDie) poolText += " — " + game.i18n.localize("VERMINE.adapted_die_x2");
set("#pool-value", poolText);
if (type === "reputation") {
const current = this.#actor.system?.attributes?.reputation?.value ?? 0;
const direction = this.#getReputationDirection();
set("#reputation-target", direction === "increase"
? `${current}${Math.min(10, current + 1)}+`
: `${current}${Math.max(0, current - 1)}`);
}
const poolInput = this.#el.querySelector("#learning-dice"); const poolInput = this.#el.querySelector("#learning-dice");
if (poolInput) poolInput.max = this.#actor.system?.experience?.dice || 0; if (poolInput) poolInput.max = this.#actor.system?.experience?.dice || 0;
} }
static async #onCancel(event, target) { static async #onCancel() {
this.close(); this.close();
} }
static async #onRoll(event, target) { static async #onRoll() {
const actor = this.#actor; const actor = this.#actor;
const ex = actor.system?.experience; const ex = actor.system?.experience;
const dice = ex?.dice || 0; const dice = ex?.dice || 0;
@@ -250,10 +392,11 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
const c = this.#compute(); const c = this.#compute();
// Limite : une seule compétence, spécialité, réserve, caractéristique et // Limite : une seule compétence, spécialité, réserve, caractéristique,
// un seul Dé d'Évolution par phase. // un seul Dé d'Évolution, Capacité de Totem, Historique ou jet de
// Réputation par phase.
if (ex?.learned?.phase === ex?.phase) { if (ex?.learned?.phase === ex?.phase) {
const flagMap = { skill: "skill", specialty: "specialty", reserve: "reserve", characteristic: "characteristic", evolution: "evolution" }; const flagMap = { skill: "skill", specialty: "specialty", reserve: "reserve", characteristic: "characteristic", evolution: "evolution", totem: "ability", background: "background", reputation: "reputation" };
if (ex.learned[flagMap[c.type]]) { if (ex.learned[flagMap[c.type]]) {
ui.notifications.warn(game.i18n.localize("VERMINE.error_learning_limit")); ui.notifications.warn(game.i18n.localize("VERMINE.error_learning_limit"));
return; return;
@@ -275,6 +418,61 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
ui.notifications.warn(game.i18n.localize("VERMINE.error_reserve_max")); ui.notifications.warn(game.i18n.localize("VERMINE.error_reserve_max"));
return; return;
} }
if (c.type === "totem") {
const item = actor.itemTypes?.ability?.find(i => i.id === c.key);
if (!item || item.system?.learned) {
ui.notifications.warn(game.i18n.localize("VERMINE.no_totem_ability"));
return;
}
const level = item.system?.level?.value || 0;
if (level > this.#getGameMode()) {
ui.notifications.warn(game.i18n.localize("VERMINE.error_totem_mode"));
return;
}
if (item.system?.totem !== actor.system?.identity?.totem) {
ui.notifications.warn(game.i18n.localize("VERMINE.error_totem_totem"));
return;
}
if (this.#getGroupLevel() < level) {
ui.notifications.warn(game.i18n.localize("VERMINE.error_totem_group_level"));
return;
}
const counts = this.#totemCapacityCounts();
if (level === 1 && (counts.byLevel[1] || 0) >= 3) {
ui.notifications.warn(game.i18n.localize("VERMINE.error_totem_limit"));
return;
}
if (level >= 2 && (counts.byLevel[level] || 0) >= 1) {
ui.notifications.warn(game.i18n.localize("VERMINE.error_totem_limit"));
return;
}
if (counts.total >= 5) {
ui.notifications.warn(game.i18n.localize("VERMINE.error_totem_limit"));
return;
}
}
if (c.type === "background") {
const item = actor.itemTypes?.background?.find(i => i.id === c.key);
if (!item || item.system?.learned) {
ui.notifications.warn(game.i18n.localize("VERMINE.no_background"));
return;
}
}
if (c.type === "reputation") {
const current = actor.system?.attributes?.reputation?.value ?? 0;
if (c.key === "reduce" && current <= 0) {
ui.notifications.warn(game.i18n.localize("VERMINE.error_reputation_min"));
return;
}
if (c.key === "increase" && current >= 10) {
ui.notifications.warn(game.i18n.localize("VERMINE.error_reputation_max"));
return;
}
}
if (c.type === "evolution" && c.adaptedDie && !this.#canUseAdaptedDie()) {
ui.notifications.warn(game.i18n.localize("VERMINE.error_evolution_no_adapted"));
return;
}
const pool = Math.max(0, spent + c.poolModifier); const pool = Math.max(0, spent + c.poolModifier);
const rollLabel = `${game.i18n.localize("VERMINE.open_learning")}${c.label}${c.targetLabel ? " (" + c.targetLabel + ")" : ""}`; const rollLabel = `${game.i18n.localize("VERMINE.open_learning")}${c.label}${c.targetLabel ? " (" + c.targetLabel + ")" : ""}`;
@@ -285,6 +483,7 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
Reroll: 0, Reroll: 0,
difficulty: c.difficulty, difficulty: c.difficulty,
handicap: c.handicap, handicap: c.handicap,
totems: c.adaptedDie ? { adapted: true } : { human: false, adapted: false },
rollLabel, rollLabel,
poolBreakdown: { ability: 0, skill: 0, selfControl: 0, specialty: 0, help: 0, tooling: 0, group: 0, total: pool }, poolBreakdown: { ability: 0, skill: 0, selfControl: 0, specialty: 0, help: 0, tooling: 0, group: 0, total: pool },
messageFlags: { "vermine-learning": { type: c.type, key: c.key, spent, success: false } } messageFlags: { "vermine-learning": { type: c.type, key: c.key, spent, success: false } }
@@ -293,14 +492,16 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
const required = 1 + c.handicap; const required = 1 + c.handicap;
const specialtyName = this.#getSpecialtyName(); const specialtyName = this.#getSpecialtyName();
if ((roll._total ?? 0) >= required) { if ((roll._total ?? 0) >= required) {
await this.#applySuccess(c, spent, specialtyName); await this.#applySuccess(c, specialtyName, roll._total ?? 0);
} }
// Les Dés d'Expérience sont perdus que le jet soit réussi ou raté.
await actor.update({ "system.experience.dice": Math.max(0, (actor.system.experience.dice || 0) - spent) });
this.close(); this.close();
} }
async #applySuccess(c, spent, specialtyName) { async #applySuccess(c, specialtyName, successes = 0) {
const actor = this.#actor; const actor = this.#actor;
const updates = { "system.experience.dice": (actor.system.experience.dice || 0) - spent }; const updates = {};
if (c.type === "skill") { if (c.type === "skill") {
const current = actor.system.skills[c.key].value || 0; const current = actor.system.skills[c.key].value || 0;
updates["system.skills." + c.key + ".value"] = Math.min(5, current + 1); updates["system.skills." + c.key + ".value"] = Math.min(5, current + 1);
@@ -316,9 +517,23 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
const bonus = actor.system.experience.reserveBonus?.[c.key] || 0; const bonus = actor.system.experience.reserveBonus?.[c.key] || 0;
updates["system.experience.reserveBonus." + c.key] = bonus + 1; updates["system.experience.reserveBonus." + c.key] = bonus + 1;
updates["system.experience.learned.reserve"] = true; updates["system.experience.learned.reserve"] = true;
} else { } else if (c.type === "evolution") {
updates["system.experience.evolution"] = (actor.system.experience.evolution || 0) + 1; updates["system.experience.evolution"] = (actor.system.experience.evolution || 0) + 1;
updates["system.experience.learned.evolution"] = true; updates["system.experience.learned.evolution"] = true;
} else if (c.type === "totem") {
updates["system.experience.learned.ability"] = true;
await actor.updateEmbeddedDocuments("Item", [{ _id: c.key, "system.learned": true }]);
} else if (c.type === "background") {
updates["system.experience.learned.background"] = true;
await actor.updateEmbeddedDocuments("Item", [{ _id: c.key, "system.learned": true }]);
} else if (c.type === "reputation") {
const current = actor.system?.attributes?.reputation?.value ?? 0;
if (c.key === "increase") {
updates["system.attributes.reputation.value"] = Math.min(10, current + successes);
} else {
updates["system.attributes.reputation.value"] = Math.max(0, current - 1);
}
updates["system.experience.learned.reputation"] = true;
} }
updates["system.experience.learned.phase"] = actor.system.experience.phase; updates["system.experience.learned.phase"] = actor.system.experience.phase;
await actor.update(updates); await actor.update(updates);
+214
View File
@@ -0,0 +1,214 @@
import { VermineUtils } from "../roll.mjs"
import { VermineExchange } from "../exchange.mjs"
const { HandlebarsApplicationMixin } = foundry.applications.api
/**
* Dialogue de jet simplifié pour les PNJ.
*
* Un PNJ n'a ni caractéristiques ni compétences : toutes ses actions sont
* résolues par une valeur unique dérivée de son Profil (Menace / Expérience /
* Rôle), exposée dans system.computed. Ce dialogue fixe le pool selon le type
* de jet (Action, Spécialité, Attaque ou Réaction) et n'offre que la difficulté,
* le handicap et les bonus simples.
*
* Utilisé par :
* - la fiche PNJ (boutons de jet rapide Action / Spécialité / Réaction) ;
* - la défense des PNJ dans les échanges de coups (mode verrouillé).
*/
export default class NpcRollDialog extends HandlebarsApplicationMixin(foundry.applications.api.ApplicationV2) {
#actor
get title() {
return game.i18n.localize("VERMINE.roll")
}
static DEFAULT_OPTIONS = {
classes: ["vermine-roll"],
tag: "form",
window: {
icon: "fas fa-dice-d10",
resizable: false
},
position: {
width: 520,
height: 600
},
actions: {
roll: NpcRollDialog.#onRoll,
cancel: NpcRollDialog.#onCancel
}
}
static PARTS = {
main: { template: "systems/vermine2047/templates/dialogs/npc-roll-dialog.hbs" }
}
static async create(data = {}) {
const actor = data.actor instanceof Actor ? data.actor : await game.actors.get(data.actorId)
if (!actor) {
ui.notifications.warn(game.i18n.localize("VERMINE.error_no_actor_selected"))
return null
}
return new NpcRollDialog({
actor,
rolltype: data.rolltype ?? "action",
locks: data.locks ?? null,
defense: data.defense ?? null
})
}
constructor(options = {}) {
super(options)
this.#actor = options.actor
this.rolltype = options.rolltype ?? "action"
this.locks = options.locks ?? null
this.defense = options.defense ?? null
}
// ── Getters ──────────────────────────────────────────────────────────
get computed() { return this.#actor.system?.computed || {} }
/** Pool de dés selon le type de jet. */
get pool() {
const c = this.computed
switch (this.rolltype) {
case "specialty": return c.specialties || 0
case "attack": return c.attack || 0
case "reaction": return c.reaction || 0
default: return c.action || 3
}
}
/** Réussites automatiques (bonus de Réaction du rôle). */
get freeSuccesses() {
return this.rolltype === "reaction" ? (this.computed.reactionBonus || 0) : 0
}
/** Relances : utilisables uniquement sur les attaques ou les Spécialités. */
get rerolls() {
if (this.rolltype !== "specialty" && this.rolltype !== "attack") return 0
return this.computed.rerolls || 0
}
get label() {
const keys = {
action: "ADVERSITY.action",
specialty: "ADVERSITY.specialties",
attack: "ADVERSITY.attack",
reaction: "ADVERSITY.reaction"
}
return game.i18n.localize(keys[this.rolltype] || "VERMINE.roll")
}
// ── Rendu ────────────────────────────────────────────────────────────
async _prepareContext() {
const difficultyOptions = []
for (let d = 3; d <= 10; d++) {
difficultyOptions.push({ difficulty: d, label: String(d) })
}
let lockedDifficulty = null
let defaultDifficulty = 7
if (this.locks?.difficulty !== undefined && this.locks?.difficulty !== null) {
lockedDifficulty = parseInt(this.locks.difficulty, 10)
defaultDifficulty = lockedDifficulty
}
return {
actor: this.#actor,
system: this.#actor.system,
config: CONFIG.VERMINE,
rollLabel: this.label,
rolltype: this.rolltype,
pool: this.pool,
freeSuccesses: this.freeSuccesses,
rerolls: this.rerolls,
isDefense: Boolean(this.defense),
lockedDifficulty,
difficultyOptions,
defaultDifficulty,
speakerId: this.#actor.id,
availableItems: this.#actor.items.filter(i => i.type === "item")
}
}
async _onRender(context, options) {
this.element.dataset.actorId = this.#actor.id
for (const inp of this.element.querySelectorAll("[data-roll]")) {
inp.addEventListener("change", this.#onInputChange.bind(this))
}
this.element.querySelector("#handicap")?.addEventListener("change", () => this.#updateUI())
this.#updateUI()
}
get #el() { return this.element }
#getHandicap() {
const sel = this.#el.querySelector("#handicap")
return Math.max(0, (parseInt(sel?.value, 10) || 1) - 1)
}
#getDifficulty() {
const sel = this.#el.querySelector("#difficulty")
return parseInt(sel?.value, 10) || 7
}
#getHelped() { return this.#el.querySelector("#helped")?.checked ? 1 : 0 }
#getGroup() { return parseInt(this.#el.querySelector("#group")?.value, 10) || 0 }
getPoolBreakdown() {
const help = this.#getHelped()
const group = this.#getGroup()
const malus = VermineExchange.woundMalus(this.#actor)
return { pool: this.pool, help, group, malus, total: Math.max(0, this.pool + help + group - malus) }
}
#updateUI() {
const b = this.getPoolBreakdown()
const totalEl = this.#el.querySelector("#dice-pool-total")
if (totalEl) totalEl.textContent = `${b.total}D`
const bonusEl = this.#el.querySelector("#total-bonus")
if (bonusEl) bonusEl.textContent = b.help + b.group - b.malus
const freeEl = this.#el.querySelector("#free-successes")
if (freeEl) freeEl.textContent = String(this.freeSuccesses)
}
#onInputChange() {
this.#updateUI()
}
// ── Actions ──────────────────────────────────────────────────────────
static async #onCancel(event, target) {
this.close()
}
static async #onRoll(event, target) {
const label = this.defense
? (this.defense.type === "parade"
? game.i18n.localize("VERMINE.defense_parade")
: game.i18n.localize("VERMINE.defense_esquive"))
: this.label
const rollParams = {
actor: this.#actor,
NoD: this.getPoolBreakdown().total,
Reroll: this.rerolls,
difficulty: this.#getDifficulty(),
handicap: this.#getHandicap(),
rollLabel: label,
bonusSuccesses: this.freeSuccesses,
poolBreakdown: this.getPoolBreakdown(),
messageFlags: this.defense ? { "vermine-defense": this.defense } : null
}
// Fermeture immédiate du dialogue : le jet (et l'animation 3D) continue
// en arrière-plan sans bloquer la fermeture.
this.close()
await VermineUtils.roll(rollParams)
}
}
+11 -5
View File
@@ -1,4 +1,5 @@
import { VermineUtils } from "../roll.mjs"; import { VermineUtils } from "../roll.mjs";
import { VermineExchange } from "../exchange.mjs";
const { HandlebarsApplicationMixin } = foundry.applications.api; const { HandlebarsApplicationMixin } = foundry.applications.api;
@@ -164,8 +165,9 @@ export default class RollDialog extends HandlebarsApplicationMixin(foundry.appli
const specialty = specChecked ? 1 : 0; const specialty = specChecked ? 1 : 0;
const help = helped ? 1 : 0; const help = helped ? 1 : 0;
const tooling = tools ? 1 : 0; const tooling = tools ? 1 : 0;
const total = ability + skillPool + selfControl + specialty + help + tooling + group + experience; const malus = VermineExchange.woundMalus(this.#actor);
return { ability, skill: skillPool, selfControl, specialty, help, tooling, group, experience, total }; const total = Math.max(0, ability + skillPool + selfControl + specialty + help + tooling + group + experience - malus);
return { ability, skill: skillPool, selfControl, specialty, help, tooling, group, experience, malus, total };
} }
getDicePool() { getDicePool() {
@@ -293,7 +295,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(foundry.appli
#calculateBonusCount() { #calculateBonusCount() {
const b = this.getPoolBreakdown(); const b = this.getPoolBreakdown();
return b.specialty + b.help + b.tooling + b.group + b.selfControl + b.experience; return b.specialty + b.help + b.tooling + b.group + b.selfControl + b.experience - b.malus;
} }
#refreshExperienceUI() { #refreshExperienceUI() {
@@ -448,7 +450,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(foundry.appli
await group.update({ "system.experience.dice": groupPool - pulled }); await group.update({ "system.experience.dice": groupPool - pulled });
} }
await VermineUtils.roll({ const rollParams = {
actor: this.#actor, actor: this.#actor,
NoD: this.getDicePool(), NoD: this.getDicePool(),
Reroll: this.getReroll(), Reroll: this.getReroll(),
@@ -467,9 +469,13 @@ export default class RollDialog extends HandlebarsApplicationMixin(foundry.appli
weapon: this.weapon?.toObject() ?? null, weapon: this.weapon?.toObject() ?? null,
targets: [...game.user.targets].map(t => t.name), targets: [...game.user.targets].map(t => t.name),
messageFlags: this.defense ? { "vermine-defense": this.defense } : null messageFlags: this.defense ? { "vermine-defense": this.defense } : null
}); };
// Fermeture immédiate du dialogue : le jet (et l'animation 3D) continue
// en arrière-plan sans bloquer la fermeture.
this.close(); this.close();
await VermineUtils.roll(rollParams);
} }
} }
+72 -18
View File
@@ -204,20 +204,52 @@ export class VermineExchange {
return Boolean(actor) && (game.user.isGM || actor.isOwner) return Boolean(actor) && (game.user.isGM || actor.isOwner)
} }
/** Ordre de gravité des blessures (croissant). */
static WOUND_ORDER = ['minorWound', 'majorWound', 'deadlyWound']
/** Malus de la pire blessure (règles p.42) : -1D Légère, -2D Grave, -3D Mortelle. */
static WOUND_MALUS = { minorWound: 1, majorWound: 2, deadlyWound: 3 }
/** /**
* Applique +1 dans la catégorie de blessure indiquée (plafonné au max). * Malus de blessure applicable à toutes les actions : seul le Malus de la
* Blessure la plus grave compte (non cumulatif).
* @param {Actor} actor
* @returns {number} 0, 1, 2 ou 3
*/
static woundMalus(actor) {
if (!actor?.system) return 0
if (actor.system.deadlyWound?.value > 0) return 3
if (actor.system.majorWound?.value > 0) return 2
if (actor.system.minorWound?.value > 0) return 1
return 0
}
/**
* Applique +1 cercle de blessure, en montant en gravité si les cercles du
* niveau correspondant sont déjà pleins (règles p.42 : si tous les cercles
* d'une Blessure sont cochés, la suivante est de gravité supérieure).
* @param {Actor} actor * @param {Actor} actor
* @param {string} woundCategory 'minorWound' | 'majorWound' | 'deadlyWound' * @param {string} woundCategory 'minorWound' | 'majorWound' | 'deadlyWound'
* @returns {Promise<number|boolean>} nouvelle valeur, ou false si refusé * @returns {Promise<Object|boolean|null>}
* - false si refusé (permissions) ou null si invalide
* - { category, value, escalated, death } si appliqué (death si tout est plein)
*/ */
static async applyWound(actor, woundCategory) { static async applyWound(actor, woundCategory) {
if (!actor || !woundCategory) return null if (!actor || !woundCategory) return null
if (!this.canApplyWound(actor)) return false if (!this.canApplyWound(actor)) return false
const current = actor.system?.[woundCategory]?.value || 0 const startIdx = this.WOUND_ORDER.indexOf(woundCategory)
const max = actor.system?.[woundCategory]?.max ?? Number.POSITIVE_INFINITY if (startIdx === -1) return null
const next = Math.min(current + 1, max) for (let i = startIdx; i < this.WOUND_ORDER.length; i++) {
await actor.update({ [`system.${woundCategory}.value`]: next }) const cat = this.WOUND_ORDER[i]
return next const sys = actor.system?.[cat]
const max = sys?.max ?? Number.POSITIVE_INFINITY
const value = sys?.value || 0
if (value < max) {
await actor.update({ [`system.${cat}.value`]: value + 1 })
return { category: cat, value: value + 1, escalated: cat !== woundCategory, death: false }
}
}
return { category: woundCategory, value: null, escalated: false, death: true }
} }
// ── Résolution ────────────────────────────────────────────────────── // ── Résolution ──────────────────────────────────────────────────────
@@ -359,14 +391,7 @@ export class VermineExchange {
ui.notifications.warn(game.i18n.localize('VERMINE.error_creature_no_defense')) ui.notifications.warn(game.i18n.localize('VERMINE.error_creature_no_defense'))
return return
} }
const { default: RollDialog } = await import('./dialogs/rollDialog.mjs') const defense = {
const locks = this.#defenseLocks(actor, exchange, type)
const dialog = await RollDialog.create({
actor,
rolltype: 'skill',
label: locks.skill,
locks,
defense: {
exchangeId: exchange.id, exchangeId: exchange.id,
type, type,
actorId: actor.id, actorId: actor.id,
@@ -375,6 +400,27 @@ export class VermineExchange {
difficulty: exchange.difficulty, difficulty: exchange.difficulty,
parryLevel: type === 'parade' ? this.#parryLevel(actor) : null parryLevel: type === 'parade' ? this.#parryLevel(actor) : null
} }
// PNJ : pas de caractéristiques/compétences — la défense utilise la valeur
// d'Action (Expérience), difficulté verrouillée.
if (actor.type === 'npc') {
const { default: NpcRollDialog } = await import('./dialogs/npcRollDialog.mjs')
const dialog = await NpcRollDialog.create({
actor,
rolltype: 'action',
locks: { difficulty: exchange.difficulty },
defense
})
if (dialog) dialog.render(true)
return
}
const { default: RollDialog } = await import('./dialogs/rollDialog.mjs')
const locks = this.#defenseLocks(actor, exchange, type)
const dialog = await RollDialog.create({
actor,
rolltype: 'skill',
label: locks.skill,
locks,
defense
}) })
if (dialog) dialog.render(true) if (dialog) dialog.render(true)
} }
@@ -443,10 +489,18 @@ export class VermineExchange {
ui.notifications.warn(game.i18n.localize('VERMINE.error_no_permission')) ui.notifications.warn(game.i18n.localize('VERMINE.error_no_permission'))
return return
} }
const next = await this.applyWound(defender, wound) const result = await this.applyWound(defender, wound)
if (next === false) return if (result === false) return
btn.disabled = true btn.disabled = true
btn.innerHTML = `<i class="fas fa-check"></i> ${game.i18n.localize('VERMINE.wound_applied')}` let label = game.i18n.localize('VERMINE.wound_applied')
if (result?.death) {
label = game.i18n.localize('VERMINE.wound_death')
} else if (result?.escalated) {
const from = game.i18n.localize(this.woundLabelKey(wound))
const to = game.i18n.localize(this.woundLabelKey(result.category))
label = game.i18n.format('VERMINE.wound_escalated', { from, to })
}
btn.innerHTML = `<i class="fas fa-check"></i> ${label}`
// Persiste l'état du bouton seulement si l'utilisateur peut modifier le message // Persiste l'état du bouton seulement si l'utilisateur peut modifier le message
if (game.user.isGM || attackMessage.isOwner) { if (game.user.isGM || attackMessage.isOwner) {
const msgEl = btn.closest('.vermine-roll-message') const msgEl = btn.closest('.vermine-roll-message')
+70
View File
@@ -123,6 +123,76 @@ export class VermineExperience {
return 0 return 0
} }
/**
* Un de Totem Adapté peut remplacer un d'Expérience (compte ×2)
* uniquement si le personnage possède strictement plus de Dés Adaptés
* que Humains.
* @param {number} human human totem dice
* @param {number} adapted adapted totem dice
* @returns {boolean}
*/
static canUseAdaptedDie(human, adapted) {
return adapted > human
}
/**
* Difficulté d'un jet d'apprentissage de Capacité de Totem : valeur
* d'Apprentissage de la capacité.
* @param {object} abilityData system data de l'item ability
* @returns {number}
*/
static totemAbilityLearningDifficulty(abilityData) {
return parseInt(abilityData?.learn?.threshold, 10) || 5
}
/**
* Handicap d'un jet d'apprentissage de Capacité de Totem : valeur
* d'Apprentissage de la capacité.
* @param {object} abilityData system data de l'item ability
* @returns {number}
*/
static totemAbilityLearningHandicap(abilityData) {
return parseInt(abilityData?.learn?.hindrance, 10) || 0
}
/**
* Difficulté d'un jet d'obtention d'un Élément d'Historique.
* @returns {number}
*/
static backgroundLearningDifficulty() {
return 9
}
/**
* Handicap d'un jet d'obtention d'un Élément d'Historique : (I) si
* l'Historique est listé avec un astérisque.
* @param {boolean} asterisk
* @returns {number}
*/
static backgroundLearningHandicap(asterisk) {
return asterisk ? 1 : 0
}
/**
* Difficulté d'un jet visant à réduire le score de Réputation (devenir
* plus célèbre) : 10 moins le score que le personnage souhaite atteindre.
* @param {number} current réputation actuelle
* @returns {number}
*/
static reputationReduceDifficulty(current) {
return 10 - Math.max(0, (parseInt(current, 10) || 0) - 1)
}
/**
* Difficulté d'un jet visant à augmenter le score de Réputation (se faire
* oublier) : égale à la Réputation actuelle.
* @param {number} current réputation actuelle
* @returns {number}
*/
static reputationIncreaseDifficulty(current) {
return parseInt(current, 10) || 0
}
/** /**
* Clé i18n du libellé du niveau de compétence visé. * Clé i18n du libellé du niveau de compétence visé.
* @param {number} level * @param {number} level
+14 -2
View File
@@ -98,8 +98,9 @@ export const registerHandlebarsHelpers = function () {
}); });
// search translation with variables // search translation with variables
Handlebars.registerHelper('smarttl', function (arrayLabel, objectLabel, options) { Handlebars.registerHelper('smarttl', function (arrayLabel, objectLabel, key) {
return game.i18n.localize(arrayLabel + "." + objectLabel + ".name"); if (!key || typeof key !== 'string') key = 'name';
return game.i18n.localize(arrayLabel + "." + objectLabel + "." + key);
}); });
// Return an object of skills ordered alphabetically by their localized // Return an object of skills ordered alphabetically by their localized
@@ -367,6 +368,17 @@ export const registerHandlebarsHelpers = function () {
Handlebars.registerHelper('ifStartsWith', function (arg1, arg2, options) { Handlebars.registerHelper('ifStartsWith', function (arg1, arg2, options) {
return (typeof arg1 === 'string' && arg1.startsWith(arg2)) ? options.fn(this) : options.inverse(this); return (typeof arg1 === 'string' && arg1.startsWith(arg2)) ? options.fn(this) : options.inverse(this);
}); });
// Chiffre → chiffre romain (handicaps, entraves). 0 → chaîne vide.
Handlebars.registerHelper('roman', function (n) {
const table = [[10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I']]
let num = Number(n)
if (!Number.isFinite(num) || num <= 0) return ''
let out = ''
for (const [v, s] of table) {
while (num >= v) { out += s; num -= v }
}
return out
});
//math operations //math operations
+11 -4
View File
@@ -30,6 +30,7 @@ export class VermineUtils {
skillLevel = null, skillLevel = null,
hasSpecialty = false, hasSpecialty = false,
handicap = 0, handicap = 0,
bonusSuccesses = 0,
poolBreakdown = null, poolBreakdown = null,
specialtyName = null, specialtyName = null,
weapon = null, weapon = null,
@@ -147,9 +148,9 @@ export class VermineUtils {
totemBonuses: { ...totemBonus }, totemBonuses: { ...totemBonus },
baseNoD: NoD, baseNoD: NoD,
rerolls: Reroll, rerolls: Reroll,
selfControl: self_control selfControl: self_control,
bonusSuccesses: bonusSuccesses
}; };
// Evaluate the roll // Evaluate the roll
await roll.evaluate(); await roll.evaluate();
@@ -171,6 +172,7 @@ export class VermineUtils {
skillLevel, skillLevel,
hasSpecialty, hasSpecialty,
handicap, handicap,
bonusSuccesses,
poolBreakdown, poolBreakdown,
specialtyName, specialtyName,
weapon, weapon,
@@ -321,6 +323,9 @@ export class VermineUtils {
const isTotem = die.classList.contains('human') || die.classList.contains('adapted'); const isTotem = die.classList.contains('human') || die.classList.contains('adapted');
total += isTotem ? 2 : 1; total += isTotem ? 2 : 1;
}); });
// Réussites automatiques (ex. bonus de Réaction) : ajoutées au total.
const freeSuccesses = parseInt(rollMessage.dataset?.freeSuccesses ?? '0', 10) || 0;
total += freeSuccesses;
totalEl.innerText = total; totalEl.innerText = total;
} }
@@ -488,11 +493,13 @@ export class VermineUtils {
} }
// Verdict // Verdict
const required = 1 + (param.handicap ?? 0); const required = 1 + (param.handicap ?? 0);
const total = roll._total ?? 0; const bonusSuccesses = param.bonusSuccesses ?? 0;
const total = (roll._total ?? 0) + bonusSuccesses;
const verdict = { const verdict = {
success: total >= required, success: total >= required,
total, total,
required required,
bonusSuccesses
}; };
const content = await foundry.applications.handlebars.renderTemplate("systems/vermine2047/templates/roll-message.hbs", { roll, param, diceStats, verdict }); const content = await foundry.applications.handlebars.renderTemplate("systems/vermine2047/templates/roll-message.hbs", { roll, param, diceStats, verdict });
+16 -10
View File
@@ -38,19 +38,22 @@ Hooks.once('init', async function () {
CONFIG.Actor.dataModels.group = models.VermineGroupData; CONFIG.Actor.dataModels.group = models.VermineGroupData;
CONFIG.Actor.dataModels.creature = models.VermineCreatureData; CONFIG.Actor.dataModels.creature = models.VermineCreatureData;
CONFIG.Actor.dataModels.community = models.VermineCommunityData; CONFIG.Actor.dataModels.community = models.VermineCommunityData;
CONFIG.Actor.dataModels.vehicle = models.VermineVehicleData;
// Libellé du type "community" (TYPES.Actor.* du core ne le connaît pas) // Libellés des types "community"/"vehicle" (TYPES.Actor.* du core ne les connaît pas)
CONFIG.Actor.typeLabels ??= {}; CONFIG.Actor.typeLabels ??= {};
CONFIG.Actor.typeLabels.community = "VERMINE.type_community"; CONFIG.Actor.typeLabels.community = "VERMINE.type_community";
CONFIG.Actor.typeLabels.vehicle = "VERMINE.type_vehicle";
Hooks.once('i18nInit', () => { Hooks.once('i18nInit', () => {
CONFIG.Actor.typeLabels.community = "VERMINE.type_community"; CONFIG.Actor.typeLabels.community = "VERMINE.type_community";
CONFIG.Actor.typeLabels.vehicle = "VERMINE.type_vehicle";
}); });
CONFIG.Item.dataModels.item = models.VermineItemData; CONFIG.Item.dataModels.item = models.VermineItemData;
CONFIG.Item.dataModels.weapon = models.VermineWeaponData; CONFIG.Item.dataModels.weapon = models.VermineWeaponData;
CONFIG.Item.dataModels.defense = models.VermineDefenseData; CONFIG.Item.dataModels.defense = models.VermineDefenseData;
CONFIG.Item.dataModels.vehicle = models.VermineVehicleData;
CONFIG.Item.dataModels.ability = models.VermineAbilityData; CONFIG.Item.dataModels.ability = models.VermineAbilityData;
CONFIG.Item.dataModels.creaturecapacity = models.VermineCreatureCapacityData;
CONFIG.Item.dataModels.specialty = models.VermineSpecialtyData; CONFIG.Item.dataModels.specialty = models.VermineSpecialtyData;
CONFIG.Item.dataModels.background = models.VermineBackgroundData; CONFIG.Item.dataModels.background = models.VermineBackgroundData;
CONFIG.Item.dataModels.trauma = models.VermineTraumaData; CONFIG.Item.dataModels.trauma = models.VermineTraumaData;
@@ -80,7 +83,7 @@ Hooks.once('init', async function () {
// Register sheet application classes (ApplicationV2) // Register sheet application classes (ApplicationV2)
// Unregister core sheets // Unregister core sheets
foundry.documents.collections.Actors.unregisterSheet("core", foundry.applications.sheets.ActorSheetV2, { foundry.documents.collections.Actors.unregisterSheet("core", foundry.applications.sheets.ActorSheetV2, {
types: ["character", "npc", "group", "creature"] types: ["character", "npc", "group", "creature", "community", "vehicle"]
}) })
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1?.sheets?.ItemSheet) foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1?.sheets?.ItemSheet)
@@ -100,6 +103,9 @@ Hooks.once('init', async function () {
foundry.documents.collections.Actors.registerSheet("vermine2047", sheets.VermineCommunitySheetV2, { foundry.documents.collections.Actors.registerSheet("vermine2047", sheets.VermineCommunitySheetV2, {
types: ["community"], makeDefault: true, label: "VERMINE.Sheet.community" types: ["community"], makeDefault: true, label: "VERMINE.Sheet.community"
}) })
foundry.documents.collections.Actors.registerSheet("vermine2047", sheets.VermineVehicleSheetV2, {
types: ["vehicle"], makeDefault: true, label: "VERMINE.Sheet.vehicle"
})
// Item sheets — un par type // Item sheets — un par type
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineItemSheetV2, { foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineItemSheetV2, {
@@ -111,12 +117,12 @@ Hooks.once('init', async function () {
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineDefenseSheetV2, { foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineDefenseSheetV2, {
types: ["defense"], makeDefault: true types: ["defense"], makeDefault: true
}) })
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineVehicleSheetV2, {
types: ["vehicle"], makeDefault: true
})
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineAbilitySheetV2, { foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineAbilitySheetV2, {
types: ["ability"], makeDefault: true types: ["ability"], makeDefault: true
}) })
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineCreatureCapacitySheetV2, {
types: ["creaturecapacity"], makeDefault: true
})
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineSpecialtySheetV2, { foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineSpecialtySheetV2, {
types: ["specialty"], makeDefault: true types: ["specialty"], makeDefault: true
}) })
@@ -189,10 +195,8 @@ Hooks.once('init', async function () {
"systems/vermine2047/templates/actor/appv2/character-stories.hbs", "systems/vermine2047/templates/actor/appv2/character-stories.hbs",
"systems/vermine2047/templates/actor/appv2/character-combat.hbs", "systems/vermine2047/templates/actor/appv2/character-combat.hbs",
"systems/vermine2047/templates/actor/appv2/npc-main.hbs", "systems/vermine2047/templates/actor/appv2/npc-main.hbs",
"systems/vermine2047/templates/actor/appv2/npc-characteristics.hbs", "systems/vermine2047/templates/actor/appv2/npc-traits.hbs",
"systems/vermine2047/templates/actor/appv2/npc-skills.hbs",
"systems/vermine2047/templates/actor/appv2/npc-equipment.hbs", "systems/vermine2047/templates/actor/appv2/npc-equipment.hbs",
"systems/vermine2047/templates/actor/appv2/npc-threat.hbs",
"systems/vermine2047/templates/actor/appv2/npc-combat.hbs", "systems/vermine2047/templates/actor/appv2/npc-combat.hbs",
"systems/vermine2047/templates/actor/appv2/npc-notes.hbs", "systems/vermine2047/templates/actor/appv2/npc-notes.hbs",
"systems/vermine2047/templates/actor/appv2/group-main.hbs", "systems/vermine2047/templates/actor/appv2/group-main.hbs",
@@ -208,7 +212,9 @@ Hooks.once('init', async function () {
"systems/vermine2047/templates/actor/appv2/community-main.hbs", "systems/vermine2047/templates/actor/appv2/community-main.hbs",
"systems/vermine2047/templates/actor/appv2/community-identity.hbs", "systems/vermine2047/templates/actor/appv2/community-identity.hbs",
"systems/vermine2047/templates/actor/appv2/community-domains.hbs", "systems/vermine2047/templates/actor/appv2/community-domains.hbs",
"systems/vermine2047/templates/actor/appv2/community-resources.hbs" "systems/vermine2047/templates/actor/appv2/community-resources.hbs",
"systems/vermine2047/templates/actor/appv2/vehicle-main.hbs",
"systems/vermine2047/templates/actor/appv2/vehicle-specs.hbs"
]); ]);
}); });
Binary file not shown.

Before

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 329 KiB

+4 -3
View File
@@ -33,16 +33,17 @@
"Actor": { "Actor": {
"character": { "htmlFields": ["equipment.description", "identity.biography", "identity.relations"] }, "character": { "htmlFields": ["equipment.description", "identity.biography", "identity.relations"] },
"npc": { "htmlFields": ["equipment.description", "identity.notes"] }, "npc": { "htmlFields": ["equipment.description", "identity.notes"] },
"group": { "htmlFields": ["equipment.description", "identity.notes"] }, "group": { "htmlFields": ["equipment.description", "identity.notes", "roadNotes"] },
"creature": { "htmlFields": ["equipment.description", "identity.notes", "identity.biography"] }, "creature": { "htmlFields": ["equipment.description", "identity.notes", "identity.biography"] },
"community": { "htmlFields": ["identity.notes", "location.landmarks", "location.territory", "history.events", "history.relations"] } "community": { "htmlFields": ["identity.notes", "location.landmarks", "location.territory", "history.events", "history.relations"] },
"vehicle": { "htmlFields": ["identity.notes"] }
}, },
"Item": { "Item": {
"item": { "htmlFields": ["description"] }, "item": { "htmlFields": ["description"] },
"weapon": { "htmlFields": ["description"] }, "weapon": { "htmlFields": ["description"] },
"defense": { "htmlFields": ["description"] }, "defense": { "htmlFields": ["description"] },
"vehicle": { "htmlFields": ["description"] },
"ability": { "htmlFields": ["description"] }, "ability": { "htmlFields": ["description"] },
"creaturecapacity": { "htmlFields": ["description"] },
"specialty": {}, "specialty": {},
"background": { "htmlFields": ["description"] }, "background": { "htmlFields": ["description"] },
"trauma": { "htmlFields": ["description"] }, "trauma": { "htmlFields": ["description"] },
+3 -3
View File
@@ -127,7 +127,7 @@
name="system.minorWound.value" name="system.minorWound.value"
value="{{@minorwoundmax}}" value="{{@minorwoundmax}}"
data-wound="minorWound" data-wound="minorWound"
data-action="clickRadio" data-action="clickWound"
class=" class="
{{#iflteq @minorwoundmax @root.system.minorWound.value }} {{#iflteq @minorwoundmax @root.system.minorWound.value }}
checked checked
@@ -156,7 +156,7 @@
name="system.majorWound.value" name="system.majorWound.value"
value="{{@majourwoundmax}}" value="{{@majourwoundmax}}"
data-wound="majorWound" data-wound="majorWound"
data-action="clickRadio" data-action="clickWound"
class=" class="
{{#iflteq @majourwoundmax @root.system.majorWound.value }} {{#iflteq @majourwoundmax @root.system.majorWound.value }}
checked checked
@@ -188,7 +188,7 @@
name="system.deadlyWound.value" name="system.deadlyWound.value"
value="{{@deadlywoundmax}}" value="{{@deadlywoundmax}}"
data-wound="deadlyWound" data-wound="deadlyWound"
data-action="clickRadio" data-action="clickWound"
class=" {{#iflteq @deadlywoundmax @root.system.deadlyWound.value }} class=" {{#iflteq @deadlywoundmax @root.system.deadlyWound.value }}
checked checked
{{/iflteq}} {{/iflteq}}
@@ -86,6 +86,47 @@
{{/each}} {{/each}}
</ol> </ol>
</div> </div>
<div class="vehicles">
<h4>{{localize 'ITEMS.vehicles'}}</h4>
<ol class="items-list">
<li class="item flexrow items-header">
<div class="item-name" style="flex:4;">{{ localize 'IDENTITY.name'}}</div>
<div class="item-mobility">{{ localize 'VERMINE.mobility'}}</div>
<div class="item-rarity">{{ localize 'VERMINE.rarity'}}</div>
<div class="item-reliability">{{ localize 'VERMINE.reliability'}}</div>
<div class="item-controls">
{{#if isEditMode}}
<a class="item-control item-create" data-action="createVehicle" title="Create vehicle"><i class="fas fa-plus"></i></a>
{{/if}}
</div>
</li>
{{#each vehicles as |vehicle|}}
<li class="item flexrow flex-group-center" data-vehicle-id="{{vehicle.id}}">
<div class="item-name" style="flex:4;">
<div class="item-image">
<a class="item-control item-edit" data-action="editVehicle" data-vehicle-id="{{vehicle.id}}"><img
src="{{vehicle.img}}" title="{{vehicle.name}}" width="24"
height="24" /></a>
</div>
<a class="item-control item-edit" data-action="editVehicle" data-vehicle-id="{{vehicle.id}}" title="Edit Vehicle">{{vehicle.name}}</a>
</div>
<div><a class="item-control item-edit" data-action="editVehicle" data-vehicle-id="{{vehicle.id}}" title="Edit Vehicle">{{vehicle.system.mobility}}</a></div>
<div><a class="item-control item-edit" data-action="editVehicle" data-vehicle-id="{{vehicle.id}}" title="Edit Vehicle">{{vehicle.system.rarity.value}}</a></div>
<div><a class="item-control item-edit" data-action="editVehicle" data-vehicle-id="{{vehicle.id}}" title="Edit Vehicle">{{vehicle.system.reliability}}</a></div>
<div class="item-controls">
{{#if @root.isEditMode}}
<a class="item-control item-delete" data-action="deleteVehicle" data-vehicle-id="{{vehicle.id}}" title="Delete Vehicle"><i
class="fas fa-trash"></i></a>
{{/if}}
</div>
</li>
{{else}}
<li class="item flexrow flex-group-center">
<span class="empty">{{ localize 'VERMINE.no_vehicles' }}</span>
</li>
{{/each}}
</ol>
</div>
<div class="equipment-notes"> <div class="equipment-notes">
<h4>{{ localize 'IDENTITY.notes'}}</h4> <h4>{{ localize 'IDENTITY.notes'}}</h4>
{{formInput systemFields.equipment.fields.description enriched=enrichedEquipmentNotes value=system.equipment.description name="system.equipment.description" toggled=true}} {{formInput systemFields.equipment.fields.description enriched=enrichedEquipmentNotes value=system.equipment.description name="system.equipment.description" toggled=true}}
@@ -59,6 +59,9 @@
<span class="{{#if system.experience.learned.reserve}}learned{{/if}}">{{ localize 'VERMINE.learned_reserve' }}</span> <span class="{{#if system.experience.learned.reserve}}learned{{/if}}">{{ localize 'VERMINE.learned_reserve' }}</span>
<span class="{{#if system.experience.learned.characteristic}}learned{{/if}}">{{ localize 'VERMINE.learned_characteristic' }}</span> <span class="{{#if system.experience.learned.characteristic}}learned{{/if}}">{{ localize 'VERMINE.learned_characteristic' }}</span>
<span class="{{#if system.experience.learned.evolution}}learned{{/if}}">{{ localize 'VERMINE.learned_evolution' }}</span> <span class="{{#if system.experience.learned.evolution}}learned{{/if}}">{{ localize 'VERMINE.learned_evolution' }}</span>
<span class="{{#if system.experience.learned.ability}}learned{{/if}}">{{ localize 'VERMINE.learned_ability' }}</span>
<span class="{{#if system.experience.learned.background}}learned{{/if}}">{{ localize 'VERMINE.learned_background' }}</span>
<span class="{{#if system.experience.learned.reputation}}learned{{/if}}">{{ localize 'VERMINE.learned_reputation' }}</span>
</div> </div>
</section> </section>
@@ -47,14 +47,6 @@
{{else}} {{else}}
<span>{{system.attributes.reputation.value}}</span> <span>{{system.attributes.reputation.value}}</span>
{{/if}} {{/if}}
<label>{{ localize 'VERMINE.experience' }}</label>
{{#if isEditMode}}
<input name="system.attributes.xp.value" data-type="Number" type="number"
value="{{system.attributes.xp.value}}" />
{{else}}
<span>{{system.attributes.xp.value}}</span>
{{/if}}
</div> </div>
</section> </section>
</header> </header>
+46 -3
View File
@@ -13,10 +13,10 @@
<p style="font-size:80%;">{{smarttl "TOTEMS" system.identity.totem "description"}}</p> <p style="font-size:80%;">{{smarttl "TOTEMS" system.identity.totem "description"}}</p>
<div class="grid grid-2col"> <div class="grid grid-2col">
<div class> <div class>
<div class="item-name"><strong>Principes</strong><br />{{smarttl "TOTEMS" system.identity.totem "instincts"}}</div> <div class="item-name"><strong>{{ localize 'IDENTITY.instincts' }}</strong><br />{{smarttl "TOTEMS" system.identity.totem "instincts"}}</div>
</div> </div>
<div class> <div class>
<div class="item-name"><strong>Interdits</strong><br />{{smarttl "TOTEMS" system.identity.totem "bans"}}</div> <div class="item-name"><strong>{{ localize 'IDENTITY.prohibits' }}</strong><br />{{smarttl "TOTEMS" system.identity.totem "bans"}}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -28,6 +28,11 @@
{{/if}} {{/if}}
<div class="chaman-toggle flexrow flex-group-center">
<label for="identity-chaman">{{ localize 'VERMINE.chaman' }}</label>
<input type="checkbox" id="identity-chaman" name="system.identity.chaman" data-dtype="Boolean" {{#if system.identity.chaman}}checked{{/if}} />
</div>
{{!-- Abstract Items --}} {{!-- Abstract Items --}}
<div class="grid grid-2col"> <div class="grid grid-2col">
<div> <div>
@@ -54,7 +59,7 @@
data-tooltip="niveau">{{item.system.level.value}}</span> data-tooltip="niveau">{{item.system.level.value}}</span>
<span <span
data-tooltip="apprentissage"> data-tooltip="apprentissage">
{{item.system.learn.threshold}}/{{item.system.learn.hindrance}} {{item.system.learn.threshold}}{{#ifgt item.system.learn.hindrance 0}}/{{ roman item.system.learn.hindrance }}{{/ifgt}}
</span> </span>
</div> </div>
<div class="item-controls flexrow"> <div class="item-controls flexrow">
@@ -217,5 +222,43 @@
{{/each}} {{/each}}
</ol> </ol>
</div> </div>
{{#if system.identity.chaman}}
<div>
<h4>
<span>{{localize "ITEMS.rites"}}</span>
{{#if isEditMode}}
<a
class="item-control item-create"
title="Create item"
data-action="create"
data-type="rite"><i class="fas fa-plus"></i></a>
{{/if}}
</h4>
<ol class="list-item">
{{#each rites as |item id|}}
<li class="item flexrow" data-item-id="{{item._id}}">
<div class="item-name" style="flex:4;">
<a
class="item-control item-edit"
data-action="edit"
data-item-id="{{item._id}}"
title="Edit Item">{{item.name}}</a>
</div>
<div class="item-controls flexrow">
{{#if item.system.description}}
<i
class="fas fa-circle-question"
data-tooltip="{{item.system.description}}"></i>
{{/if}}
{{#if @root.isEditMode}}
<a class="item-control item-delete" data-action="delete" data-item-id="{{item._id}}" title="Delete Item"><i
class="fas fa-trash"></i></a>
{{/if}}
</div>
</li>
{{/each}}
</ol>
</div>
{{/if}}
</div> </div>
</div> </div>
@@ -2,14 +2,6 @@
<section class="community-section"> <section class="community-section">
<h3>{{ localize 'COMMUNITY.identity' }}</h3> <h3>{{ localize 'COMMUNITY.identity' }}</h3>
<div class="grid grid-2col"> <div class="grid grid-2col">
<div class="form-group">
<label for="system.identity.origin">{{ localize 'COMMUNITY.origin' }}</label>
{{#if isEditMode}}
<input type="text" name="system.identity.origin" value="{{system.identity.origin}}" data-dtype="String" />
{{else}}
<span>{{system.identity.origin}}</span>
{{/if}}
</div>
<div class="form-group"> <div class="form-group">
<label for="system.identity.theme">{{ localize 'COMMUNITY.theme' }}</label> <label for="system.identity.theme">{{ localize 'COMMUNITY.theme' }}</label>
{{#if isEditMode}} {{#if isEditMode}}
-8
View File
@@ -59,14 +59,6 @@
{{/if}} {{/if}}
<span>/ {{system.level.max}}</span> <span>/ {{system.level.max}}</span>
</div> </div>
<div class="population flexrow flex-group-center">
<label>{{ localize 'COMMUNITY.population' }}</label>
{{#if isEditMode}}
<input name="system.population" type="number" value="{{system.population}}" data-dtype="Number" min="0" />
{{else}}
<span>{{system.population}}</span>
{{/if}}
</div>
</section> </section>
</header> </header>
+24 -12
View File
@@ -39,31 +39,43 @@
<ul class="unstyled"> <ul class="unstyled">
<li class="row mdb"> <li class="row mdb">
{{ localize 'VERMINE.wounds.light' }} ({{ system.minorWound.threshold }}D) {{ localize 'VERMINE.wounds.light' }} ({{ system.minorWound.threshold }}D)
{{#repeat system.minorWound.max }} <div class="flexrow align-center">
{{#repeat system.minorWound.max 1 "minorwoundmax"}}
<div class="hexa ability{{#iflteq @minorwoundmax @root.system.minorWound.value }} checked{{/iflteq}}">
<input type="radio" data-dtype="Number" name="system.minorWound.value" <input type="radio" data-dtype="Number" name="system.minorWound.value"
value="{{this}}" data-action="clickRadio" data-wound="minorWound" value="{{@minorwoundmax}}" data-action="clickWound" data-wound="minorWound"
{{#ife @root.system.minorWound.value this }}checked="checked"{{/ife}} class="{{#iflteq @minorwoundmax @root.system.minorWound.value }}checked{{/iflteq}}"
class="{{#iflteq this @root.system.minorWound.value }}checked{{/iflteq}}" {{#ife @minorwoundmax @root.system.minorWound.value }}checked="checked"{{/ife}}
/> />
</div>
{{/repeat}} {{/repeat}}
</div>
</li> </li>
<li class="row mdb"> <li class="row mdb">
{{ localize 'VERMINE.wounds.heavy' }} ({{ system.majorWound.threshold }}D) {{ localize 'VERMINE.wounds.heavy' }} ({{ system.majorWound.threshold }}D)
{{#repeat system.majorWound.max }} <div class="flexrow align-center">
<input type="radio" name="system.majorWound.value" value="{{this}}" data-action="clickRadio" data-wound="majorWound" {{#repeat system.majorWound.max 1 "majorwoundmax"}}
{{#ife @root.system.majorWound.value this }}checked="checked"{{/ife}} <div class="hexa ability{{#iflteq @majorwoundmax @root.system.majorWound.value }} checked{{/iflteq}}">
class="{{#iflteq this @root.system.majorWound.value }}checked{{/iflteq}}" <input type="radio" name="system.majorWound.value" value="{{@majorwoundmax}}" data-action="clickWound" data-wound="majorWound"
class="{{#iflteq @majorwoundmax @root.system.majorWound.value }}checked{{/iflteq}}"
{{#ife @majorwoundmax @root.system.majorWound.value }}checked="checked"{{/ife}}
/> />
</div>
{{/repeat}} {{/repeat}}
</div>
</li> </li>
<li class="row mdb"> <li class="row mdb">
{{ localize 'VERMINE.wounds.deadly' }} ({{ system.deadlyWound.threshold }}D) {{ localize 'VERMINE.wounds.deadly' }} ({{ system.deadlyWound.threshold }}D)
{{#repeat system.deadlyWound.max }} <div class="flexrow align-center">
<input type="radio" name="system.deadlyWound.value" value="{{this}}" data-action="clickRadio" data-wound="deadlyWound" {{#repeat system.deadlyWound.max 1 "deadlywoundmax"}}
{{#ife @root.system.deadlyWound.value this }}checked="checked"{{/ife}} <div class="hexa ability{{#iflteq @deadlywoundmax @root.system.deadlyWound.value }} checked{{/iflteq}}">
class="{{#iflteq this @root.system.deadlyWound.value }}checked{{/iflteq}}" <input type="radio" name="system.deadlyWound.value" value="{{@deadlywoundmax}}" data-action="clickWound" data-wound="deadlyWound"
class="{{#iflteq @deadlywoundmax @root.system.deadlyWound.value }}checked{{/iflteq}}"
{{#ife @deadlywoundmax @root.system.deadlyWound.value }}checked="checked"{{/ife}}
/> />
</div>
{{/repeat}} {{/repeat}}
</div>
</li> </li>
</ul> </ul>
</div> </div>
+21
View File
@@ -38,6 +38,27 @@
</div> </div>
</div> </div>
{{!-- Capacités de créature --}}
<div class="grid-span-2">
<h4>{{ localize 'VERMINE.creature_capacities' }}{{#if isEditMode}}<a class="item-control item-create" data-action="create" data-type="creaturecapacity" title="{{ localize 'ITEMS.new_creaturecapacity' }}"><i class="fas fa-plus"></i></a>{{/if}}</h4>
<ol class="creature-list">
{{#each capacities as |item id|}}
<li class="item" data-item-id="{{item._id}}">
<div class="item-name">
<a class="item-control item-roll" data-action="roll" data-item-id="{{item._id}}" title="{{ localize 'VERMINE.roll' }}">{{item.name}}</a>
<span class="capacity-type">{{ smarttl "CREATURE_CAPACITY_TYPES" item.system.type }}</span>
</div>
<div class="item-controls">
{{#if @root.isEditMode}}
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_edit' }}"><i class="fas fa-edit"></i></a>
<a class="item-control item-delete" data-action="delete" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_delete' }}"><i class="fas fa-trash"></i></a>
{{/if}}
</div>
</li>
{{/each}}
</ol>
</div>
<div class="grid-span-2"> <div class="grid-span-2">
<fieldset> <fieldset>
<legend><h4>{{ localize 'IDENTITY.notes' }}</h4></legend> <legend><h4>{{ localize 'IDENTITY.notes' }}</h4></legend>
+6 -6
View File
@@ -61,12 +61,12 @@
<select name="system.size.value" id="system.size.value" data-dtype="Number"> <select name="system.size.value" id="system.size.value" data-dtype="Number">
{{#each sizeOptions}} {{#each sizeOptions}}
<option value="{{@key}}" {{#ife @key @root.system.size.value}}selected{{/ife}}> <option value="{{@key}}" {{#ife @key @root.system.size.value}}selected{{/ife}}>
{{@key}} {{@key}} ({{localize label}})
</option> </option>
{{/each}} {{/each}}
</select> </select>
{{else}} {{else}}
<span>{{system.size.value}}</span> <span>{{#if (ne system.size.value "0")}}{{system.size.value}} ({{localize sizeLabel}}){{else}}{{localize 'VERMINE.none'}}{{/if}}</span>
{{/if}} {{/if}}
</div> </div>
</div> </div>
@@ -75,17 +75,17 @@
<div class="resource-content"> <div class="resource-content">
{{#if isEditMode}} {{#if isEditMode}}
<select name="system.pack.value" id="system.pack.value" data-dtype="Number"> <select name="system.pack.value" id="system.pack.value" data-dtype="Number">
<option value="0">{{localize 'VERMINE.none'}}</option> <option value="0" {{#ife "0" @root.system.pack.value}}selected{{/ife}}>{{ localize 'VERMINE.none' }}</option>
{{#each packOptions}} {{#each packOptions}}
{{#if @key}} {{#if (ne @key "0")}}
<option value="{{@key}}" {{#ife @key @root.system.pack.value}}selected{{/ife}}> <option value="{{@key}}" {{#ife @key @root.system.pack.value}}selected{{/ife}}>
{{@key}} {{@key}} ({{localize label}})
</option> </option>
{{/if}} {{/if}}
{{/each}} {{/each}}
</select> </select>
{{else}} {{else}}
<span>{{system.pack.value}}</span> <span>{{#if system.pack.value}}{{system.pack.value}} ({{localize packLabel}}){{else}}{{ localize 'VERMINE.none' }}{{/if}}</span>
{{/if}} {{/if}}
</div> </div>
</div> </div>
+6
View File
@@ -7,6 +7,12 @@
<a class="rollable" data-type="creature-attack" data-label="attack" title="{{ localize 'VERMINE.roll' }}">{{ system.computed.attack }}<i class="fas fa-dice-d10"></i></a> <a class="rollable" data-type="creature-attack" data-label="attack" title="{{ localize 'VERMINE.roll' }}">{{ system.computed.attack }}<i class="fas fa-dice-d10"></i></a>
</div> </div>
{{!-- Réaction (lancable) --}}
<div class="mdb align-center">
<h4>{{ localize 'ADVERSITY.reaction' }}</h4>
<a class="rollable" data-type="creature-reaction" data-label="reaction" title="{{ localize 'VERMINE.roll' }}">{{ system.computed.reaction }}{{#if system.computed.reactionBonus}}+{{system.computed.reactionBonus}}{{/if}}<i class="fas fa-dice-d10"></i></a>
</div>
<section class="grid grid-3col gap-md"> <section class="grid grid-3col gap-md">
<div class="mdb"> <div class="mdb">
<h4 class="align-center">{{ localize 'ADVERSITY.pattern' }}: {{ patternLabel }}</h4> <h4 class="align-center">{{ localize 'ADVERSITY.pattern' }}: {{ patternLabel }}</h4>
+1 -1
View File
@@ -33,7 +33,7 @@
</div> </div>
<div> <div>
<h4>{{ localize 'IDENTITY.notes'}}</h4> <h4>{{ localize 'IDENTITY.notes'}}</h4>
{{editor system.equipment.description target="system.equipment.description" button=true owner=owner editable=editable}} {{formInput systemFields.equipment.fields.description enriched=enrichedEquipmentNotes value=system.equipment.description name="system.equipment.description" toggled=true}}
</div> </div>
</div> </div>
<h4>{{localize 'ITEMS.weapons'}}</h4> <h4>{{localize 'ITEMS.weapons'}}</h4>
+113 -130
View File
@@ -1,13 +1,11 @@
<div class="tab info sheet-part {{#if @root.tabs.info.active}}active{{/if}}" data-group="sheet" data-tab="info"> <div class="tab info sheet-part {{#if @root.tabs.info.active}}active{{/if}}" data-group="sheet" data-tab="info">
<div class="grid grid-2col"> <div class="grid grid-2col">
{{!-- Group Identity --}} {{!-- Origine / Concept --}}
<div> <div>
<h4>{{ localize 'IDENTITY.origin' }}</h4> <h4>{{ localize 'IDENTITY.origin' }}</h4>
<div class="flexrow flex-group-center"> <div class="group-field">
{{#if isEditMode}} {{#if isEditMode}}
<input type="text" name="system.identity.origin" value="{{system.identity.origin}}" <input type="text" name="system.identity.origin" value="{{system.identity.origin}}" placeholder="Origine..." />
placeholder="Origine..." style="flex: 1;" />
{{else}} {{else}}
<span>{{system.identity.origin}}</span> <span>{{system.identity.origin}}</span>
{{/if}} {{/if}}
@@ -16,40 +14,126 @@
<div> <div>
<h4>{{ localize 'IDENTITY.theme' }}</h4> <h4>{{ localize 'IDENTITY.theme' }}</h4>
<div class="flexrow flex-group-center"> <div class="group-field">
{{#if isEditMode}} {{#if isEditMode}}
<input type="text" name="system.identity.theme" value="{{system.identity.theme}}" <input type="text" name="system.identity.theme" value="{{system.identity.theme}}" placeholder="Concept..." />
placeholder="Concept..." style="flex: 1;" />
{{else}} {{else}}
<span>{{system.identity.theme}}</span> <span>{{system.identity.theme}}</span>
{{/if}} {{/if}}
</div> </div>
</div> </div>
{{!-- Group Notes --}} {{!-- Notes --}}
<div class="grid-span-2"> <div class="grid-span-2">
<fieldset> <h4>{{ localize 'IDENTITY.notes' }}</h4>
<legend><h4>{{ localize 'IDENTITY.notes' }}</h4></legend>
{{formInput systemFields.identity.fields.notes enriched=enrichedNotes value=system.identity.notes name="system.identity.notes" toggled=true}} {{formInput systemFields.identity.fields.notes enriched=enrichedNotes value=system.identity.notes name="system.identity.notes" toggled=true}}
</fieldset>
</div> </div>
{{!-- Group Abilities (from items) --}} {{!-- Objectifs --}}
<div class="grid-span-2"> <div class="grid-span-2">
<h4> <h4>{{ localize 'VERMINE.objectives' }}</h4>
<span>{{ localize 'VERMINE.abilities' }}</span>
{{#if isEditMode}} <h5>{{ localize 'VERMINE.major_objectives' }}</h5>
<a class="item-control item-create" data-action="create" data-type="ability" title="{{ localize 'ITEMS.new_ability' }}"><i class="fas fa-plus"></i></a> <ol class="group-list">
{{#each system.objectives.major as |objective id|}}
<li class="item" data-index="{{id}}">
<div class="item-name">
{{#if @root.isEditMode}}
<input type="text" name="system.objectives.major.{{id}}" value="{{objective}}"
placeholder="{{ localize 'VERMINE.major_objective_placeholder' }}" />
{{else}}
<span>{{objective}}</span>
{{/if}} {{/if}}
</h4> </div>
<ol class="list-item"> <div class="item-controls">
{{#if @root.isEditMode}}
<a class="item-control objective-delete" data-action="deleteObjective" title="{{ localize 'UI.effect_delete' }}"
data-type="major" data-index="{{id}}"><i class="fas fa-trash"></i></a>
{{/if}}
</div>
</li>
{{/each}}
</ol>
{{#if isEditMode}}
<a class="item-control item-create" data-action="addObjective" title="{{ localize 'UI.add' }}"
data-type="major_objective"><i class="fas fa-plus"></i> {{ localize 'ITEMS.new_objective' }}</a>
{{/if}}
<h5>{{ localize 'VERMINE.minor_objectives' }}</h5>
<ol class="group-list">
{{#each system.objectives.minor as |objective id|}}
<li class="item" data-index="{{id}}">
<div class="item-name">
{{#if @root.isEditMode}}
<input type="text" name="system.objectives.minor.{{id}}" value="{{objective}}"
placeholder="{{ localize 'VERMINE.minor_objective_placeholder' }}" />
{{else}}
<span>{{objective}}</span>
{{/if}}
</div>
<div class="item-controls">
{{#if @root.isEditMode}}
<a class="item-control objective-delete" data-action="deleteObjective" title="{{ localize 'UI.effect_delete' }}"
data-type="minor" data-index="{{id}}"><i class="fas fa-trash"></i></a>
{{/if}}
</div>
</li>
{{/each}}
</ol>
{{#if isEditMode}}
<a class="item-control item-create" data-action="addObjective" title="{{ localize 'UI.add' }}"
data-type="minor_objective"><i class="fas fa-plus"></i> {{ localize 'ITEMS.new_objective' }}</a>
{{/if}}
</div>
{{!-- Totem --}}
<div class="grid-span-2">
<h4>{{ localize 'IDENTITY.totem' }}</h4>
<div class="group-field">
{{#if (eq system.identity.totem "")}}
<a data-action="chooseTotem" class="chooseTotem">{{ localize 'VERMINE.totem_picker' }}</a>
{{else}}
<div class="totem-display flexrow">
<span>{{ smarttl "TOTEMS" system.identity.totem }}</span>
<a data-action="chooseTotem" class="chooseTotem">{{ localize 'UI.effect_edit' }}</a>
</div>
<div class="totem-base grid grid-2col">
<div class="item-name"><strong>{{ localize 'IDENTITY.instincts' }}</strong><br />{{ smarttl "TOTEMS" system.identity.totem "instincts" }}</div>
<div class="item-name"><strong>{{ localize 'IDENTITY.prohibits' }}</strong><br />{{ smarttl "TOTEMS" system.identity.totem "bans" }}</div>
</div>
{{/if}}
</div>
<div class="group-field">
<label>{{ localize 'IDENTITY.instincts' }}:</label>
{{#if isEditMode}}
<input type="text" name="system.identity.instincts" value="{{system.identity.instincts}}"
placeholder="{{ localize 'VERMINE.instincts_placeholder' }}" />
{{else}}
<span>{{system.identity.instincts}}</span>
{{/if}}
</div>
<div class="group-field">
<label>{{ localize 'IDENTITY.prohibits' }}:</label>
{{#if isEditMode}}
<input type="text" name="system.identity.prohibits" value="{{system.identity.prohibits}}"
placeholder="{{ localize 'VERMINE.prohibits_placeholder' }}" />
{{else}}
<span>{{system.identity.prohibits}}</span>
{{/if}}
</div>
</div>
{{!-- Capacités de groupe --}}
<div class="grid-span-2">
<h4>{{ localize 'VERMINE.group_abilities' }}{{#if isEditMode}}<a class="item-control item-create" data-action="create" data-type="ability" title="{{ localize 'ITEMS.new_ability' }}"><i class="fas fa-plus"></i></a>{{/if}}</h4>
<ol class="group-list">
{{#each abilities as |item id|}} {{#each abilities as |item id|}}
{{#if (ne item.system.type 'totem')}} {{#if (ne item.system.type 'totem')}}
<li class="item flexrow" data-item-id="{{item._id}}"> <li class="item" data-item-id="{{item._id}}">
<div class="item-name" style="flex:4;"> <div class="item-name">
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_edit' }}">{{item.name}}</a> <a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_edit' }}">{{item.name}}</a>
</div> </div>
<div class="item-controls flexrow"> <div class="item-controls">
{{#if @root.isEditMode}} {{#if @root.isEditMode}}
<a class="item-control item-delete" data-action="delete" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_delete' }}"><i class="fas fa-trash"></i></a> <a class="item-control item-delete" data-action="delete" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_delete' }}"><i class="fas fa-trash"></i></a>
{{/if}} {{/if}}
@@ -60,119 +144,18 @@
</ol> </ol>
</div> </div>
{{!-- Group Totem Abilities --}} {{!-- Capacités de totem --}}
<div class="grid-span-2"> <div class="grid-span-2">
<h4> <h4>{{ localize 'VERMINE.totem_abilities' }}{{#if isEditMode}}<a class="item-control item-create" data-action="createTotemAbility" title="{{ localize 'ITEMS.new_ability' }}"><i class="fas fa-plus"></i></a>{{/if}}</h4>
<span>{{ localize 'VERMINE.totem_abilities' }}</span> <ol class="group-list">
{{#if isEditMode}}
<a class="item-control item-create" data-action="createTotemAbility" title="{{ localize 'ITEMS.new_ability' }}"><i class="fas fa-plus"></i></a>
{{/if}}
</h4>
<ol class="list-item">
{{#each totem_abilities as |item id|}} {{#each totem_abilities as |item id|}}
<li class="item flexrow" data-item-id="{{item._id}}"> <li class="item" data-item-id="{{item._id}}">
<div class="item-name" style="flex:4;"> <div class="item-name">
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_edit' }}">{{item.name}}</a> <a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_edit' }}">{{item.name}}</a>
<span class="hexa" title="{{ localize 'VERMINE.level' }}">{{item.system.level.value}}</span> <span class="hexa" title="{{ localize 'VERMINE.level' }}">{{item.system.level.value}}</span>
<span title="{{ localize 'VERMINE.learning' }}">{{item.system.learn.threshold}}/{{item.system.learn.hindrance}}</span> <span title="{{ localize 'VERMINE.learning' }}">{{item.system.learn.threshold}}{{#ifgt item.system.learn.hindrance 0}}/{{ roman item.system.learn.hindrance }}{{/ifgt}}</span>
</div> </div>
<div class="item-controls flexrow"> <div class="item-controls">
{{#if @root.isEditMode}}
<a class="item-control item-delete" data-action="delete" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_delete' }}"><i class="fas fa-trash"></i></a>
{{/if}}
</div>
</li>
{{/each}}
</ol>
</div>
{{!-- Specialties --}}
<div>
<h4>
<span>{{ localize 'ITEMS.specialties' }}</span>
{{#if isEditMode}}
<a class="item-control item-create" data-action="create" data-type="specialty" title="{{ localize 'ITEMS.new_specialty' }}"><i class="fas fa-plus"></i></a>
{{/if}}
</h4>
<ol class="list-item">
{{#each specialties as |item id|}}
<li class="item flexrow" data-item-id="{{item._id}}">
<div class="item-name" style="flex:4;">
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_edit' }}">{{item.name}}</a>
</div>
<div class="item-controls flexrow">
{{#if @root.isEditMode}}
<a class="item-control item-delete" data-action="delete" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_delete' }}"><i class="fas fa-trash"></i></a>
{{/if}}
</div>
</li>
{{/each}}
</ol>
</div>
{{!-- Backgrounds --}}
<div>
<h4>
<span>{{ localize 'ITEMS.backgrounds' }}</span>
{{#if isEditMode}}
<a class="item-control item-create" data-action="create" data-type="background" title="{{ localize 'ITEMS.new_background' }}"><i class="fas fa-plus"></i></a>
{{/if}}
</h4>
<ol class="list-item">
{{#each backgrounds as |item id|}}
<li class="item flexrow" data-item-id="{{item._id}}">
<div class="item-name" style="flex:4;">
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_edit' }}">{{item.name}}</a>
</div>
<div class="item-controls flexrow">
{{#if @root.isEditMode}}
<a class="item-control item-delete" data-action="delete" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_delete' }}"><i class="fas fa-trash"></i></a>
{{/if}}
</div>
</li>
{{/each}}
</ol>
</div>
{{!-- Traumas --}}
<div>
<h4>
<span>{{ localize 'ITEMS.traumas' }}</span>
{{#if isEditMode}}
<a class="item-control item-create" data-action="create" data-type="trauma" title="{{ localize 'ITEMS.new_trauma' }}"><i class="fas fa-plus"></i></a>
{{/if}}
</h4>
<ol class="list-item">
{{#each traumas as |item id|}}
<li class="item flexrow" data-item-id="{{item._id}}">
<div class="item-name" style="flex:4;">
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_edit' }}">{{item.name}}</a>
</div>
<div class="item-controls flexrow">
{{#if @root.isEditMode}}
<a class="item-control item-delete" data-action="delete" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_delete' }}"><i class="fas fa-trash"></i></a>
{{/if}}
</div>
</li>
{{/each}}
</ol>
</div>
{{!-- Evolutions --}}
<div>
<h4>
<span>{{ localize 'ITEMS.evolutions' }}</span>
{{#if isEditMode}}
<a class="item-control item-create" data-action="create" data-type="evolution" title="{{ localize 'ITEMS.new_evolution' }}"><i class="fas fa-plus"></i></a>
{{/if}}
</h4>
<ol class="list-item">
{{#each evolutions as |item id|}}
<li class="item flexrow" data-item-id="{{item._id}}">
<div class="item-name" style="flex:4;">
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_edit' }}">{{item.name}}</a>
</div>
<div class="item-controls flexrow">
{{#if @root.isEditMode}} {{#if @root.isEditMode}}
<a class="item-control item-delete" data-action="delete" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_delete' }}"><i class="fas fa-trash"></i></a> <a class="item-control item-delete" data-action="delete" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_delete' }}"><i class="fas fa-trash"></i></a>
{{/if}} {{/if}}
+23 -166
View File
@@ -1,8 +1,8 @@
<div class="tab reserve sheet-part {{#if @root.tabs.reserve.active}}active{{/if}}" data-group="sheet" data-tab="reserve"> <div class="tab reserve sheet-part {{#if @root.tabs.reserve.active}}active{{/if}}" data-group="sheet" data-tab="reserve">
<div class="grid grid-2col"> <div class="grid grid-2col">
{{!-- Group Reserve and Morale --}} {{!-- serve et Moral --}}
<div class="grid-span-2"> <div class="grid-span-2">
<h4 class="align-center">{{ localize 'VERMINE.reserve' }}</h4> <h4>{{ localize 'VERMINE.reserve' }}</h4>
<div class="reserve-control flexrow flex-group-center"> <div class="reserve-control flexrow flex-group-center">
<label>{{ localize 'VERMINE.pool' }}:</label> <label>{{ localize 'VERMINE.pool' }}:</label>
{{#if isEditMode}} {{#if isEditMode}}
@@ -33,29 +33,37 @@
{{/if}} {{/if}}
<span>/ {{system.morale.max}}D</span> <span>/ {{system.morale.max}}D</span>
</div> </div>
<div class="experience-control flexrow flex-group-center">
<label>{{ localize 'VERMINE.experience_dice' }}:</label>
{{#if isEditMode}}
<input type="number" name="system.experience.dice" value="{{system.experience.dice}}"
data-dtype="Number" min="0" />
{{else}}
<span>{{system.experience.dice}}</span>
{{/if}}
<span>D</span>
</div>
<div class="group-experience-actions flexrow flex-group-center">
<button type="button" data-action="openLearning"><i class="fas fa-graduation-cap"></i> {{ localize 'VERMINE.open_learning' }}</button>
<button type="button" data-action="openExperiencePhase"><i class="fas fa-star"></i> {{ localize 'VERMINE.experience_phase' }}</button>
</div>
</div> </div>
{{!-- Group Members --}} {{!-- Membres --}}
<div> <div class="grid-span-2">
<h4 class="align-center"> <h4>{{ localize 'VERMINE.group_members' }}{{#if isEditMode}}<a class="item-control chooseActor" data-action="chooseActor" data-list="members" title="{{ localize 'UI.add' }}" data-type="members"><i class="fas fa-plus"></i></a>{{/if}}</h4>
<span>{{ localize 'VERMINE.group_members' }}</span> <ol class="group-list">
{{#if isEditMode}}
<a class="item-control chooseActor" data-action="chooseActor" data-list="members" title="{{ localize 'UI.add' }}"
data-type="members"><i class="fas fa-plus"></i></a>
{{/if}}
</h4>
<ol class="actor-list">
{{#each system.members as |memberId|}} {{#each system.members as |memberId|}}
{{#with (lookup ../resolvedMembers memberId) as |member|}} {{#with (lookup ../resolvedMembers memberId) as |member|}}
<li class="actor flexrow" data-actor-id="{{memberId}}"> <li class="actor" data-actor-id="{{memberId}}">
<div class="actor-name" style="flex:4;"> <div class="actor-name">
{{#if member}} {{#if member}}
<a class="item-control">{{member.name}}</a> <a class="item-control">{{member.name}}</a>
{{else}} {{else}}
<a class="item-control" style="color: red;">ID: {{memberId}}</a> <a class="item-control" style="color: red;">ID: {{memberId}}</a>
{{/if}} {{/if}}
</div> </div>
<div class="item-controls flexrow"> <div class="item-controls">
{{#if @root.isEditMode}} {{#if @root.isEditMode}}
<a class="item-control member-delete" data-action="deleteMember" data-actor-id="{{memberId}}" title="{{ localize 'UI.effect_delete' }}"><i class="fas fa-trash"></i></a> <a class="item-control member-delete" data-action="deleteMember" data-actor-id="{{memberId}}" title="{{ localize 'UI.effect_delete' }}"><i class="fas fa-trash"></i></a>
{{/if}} {{/if}}
@@ -65,156 +73,5 @@
{{/each}} {{/each}}
</ol> </ol>
</div> </div>
{{!-- Group Encounters --}}
<div>
<h4 class="align-center">
<span>{{ localize 'VERMINE.encounters' }}</span>
{{#if isEditMode}}
<a class="item-control chooseActor" data-action="chooseActor" data-list="encounters" title="{{ localize 'UI.add' }}"
data-type="encounters"><i class="fas fa-plus"></i></a>
{{/if}}
</h4>
<ol class="actor-list">
{{#each system.encounters as |encounterId|}}
{{#with (lookup ../resolvedEncounters encounterId) as |encounter|}}
<li class="actor flexrow" data-actor-id="{{encounterId}}">
<div class="item-name" style="flex:4;">
{{#if encounter}}
<a class="item-control">{{encounter.name}}</a>
{{else}}
<a class="item-control" style="color: red;">ID: {{encounterId}}</a>
{{/if}}
</div>
<div class="item-controls flexrow">
{{#if @root.isEditMode}}
<a class="item-control encounter-delete" data-action="deleteEncounter" data-actor-id="{{encounterId}}" title="{{ localize 'UI.effect_delete' }}"><i class="fas fa-trash"></i></a>
{{/if}}
</div>
</li>
{{/with}}
{{/each}}
</ol>
</div>
{{!-- Group Objectives --}}
<div class="grid-span-2">
<h4 class="align-center">{{ localize 'VERMINE.objectives' }}</h4>
<div>
<h5>{{ localize 'VERMINE.major_objectives' }}</h5>
<ol class="list-item">
{{#each system.objectives.major as |objective id|}}
<li class="item flexrow" data-index="{{id}}">
<div class="item-name" style="flex:4;">
{{#if @root.isEditMode}}
<input type="text" name="system.objectives.major.{{id}}" value="{{objective}}"
placeholder="Objectif majeur..." />
{{else}}
<span>{{objective}}</span>
{{/if}}
</div>
<div class="item-controls flexrow">
{{#if @root.isEditMode}}
<a class="item-control objective-delete" data-action="deleteObjective" title="{{ localize 'UI.effect_delete' }}"
data-type="major" data-index="{{id}}"><i class="fas fa-trash"></i></a>
{{/if}}
</div>
</li>
{{/each}}
</ol>
{{#if isEditMode}}
<a class="item-control item-create" data-action="addObjective" title="{{ localize 'UI.add' }}"
data-type="major_objective"><i class="fas fa-plus"></i> {{ localize 'ITEMS.new_objective' }}</a>
{{/if}}
</div>
<div>
<h5>{{ localize 'VERMINE.minor_objectives' }}</h5>
<ol class="list-item">
{{#each system.objectives.minor as |objective id|}}
<li class="item flexrow" data-index="{{id}}">
<div class="item-name" style="flex:4;">
{{#if @root.isEditMode}}
<input type="text" name="system.objectives.minor.{{id}}" value="{{objective}}"
placeholder="Objectif mineur..." />
{{else}}
<span>{{objective}}</span>
{{/if}}
</div>
<div class="item-controls flexrow">
{{#if @root.isEditMode}}
<a class="item-control objective-delete" data-action="deleteObjective" title="{{ localize 'UI.effect_delete' }}"
data-type="minor" data-index="{{id}}"><i class="fas fa-trash"></i></a>
{{/if}}
</div>
</li>
{{/each}}
</ol>
{{#if isEditMode}}
<a class="item-control item-create" data-action="addObjective" title="{{ localize 'UI.add' }}"
data-type="minor_objective"><i class="fas fa-plus"></i> {{ localize 'ITEMS.new_objective' }}</a>
{{/if}}
</div>
</div>
{{!-- Group Abilities --}}
<div class="grid-span-2">
<h4 class="align-center">
<span>{{ localize 'VERMINE.group_abilities' }}</span>
{{#if isEditMode}}
<a class="item-control item-create" data-action="create" data-type="ability" title="{{ localize 'ITEMS.new_ability' }}"><i class="fas fa-plus"></i></a>
{{/if}}
</h4>
<ol class="list-item">
{{#each abilities as |item id|}}
{{#if (ne item.type 'totem')}}
<li class="item flexrow" data-item-id="{{item._id}}">
<div class="item-name" style="flex:4;">
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_edit' }}">{{item.name}}</a>
</div>
<div class="item-controls flexrow">
{{#if @root.isEditMode}}
<a class="item-control item-delete" data-action="delete" data-item-id="{{item._id}}" title="{{ localize 'UI.effect_delete' }}"><i class="fas fa-trash"></i></a>
{{/if}}
</div>
</li>
{{/if}}
{{/each}}
</ol>
</div>
{{!-- Totem Section --}}
<div class="grid-span-2">
<h4 class="align-center">{{ localize 'IDENTITY.totem' }}</h4>
<div class="flexrow flex-group-center">
<label>{{ localize 'IDENTITY.instincts' }}:</label>
{{#if isEditMode}}
<input type="text" name="system.identity.instincts" value="{{system.identity.instincts}}"
placeholder="Ex: Triompher, relever un défi..." />
{{else}}
<span>{{system.identity.instincts}}</span>
{{/if}}
</div>
<div class="flexrow flex-group-center">
<label>{{ localize 'IDENTITY.prohibits' }}:</label>
{{#if isEditMode}}
<input type="text" name="system.identity.prohibits" value="{{system.identity.prohibits}}"
placeholder="Ex: Fuir, abandonner..." />
{{else}}
<span>{{system.identity.prohibits}}</span>
{{/if}}
</div>
<div class="flexrow flex-group-center">
{{#if (eq system.identity.totem "")}}
<a data-action="chooseTotem" class="chooseTotem">{{ localize 'VERMINE.totem_picker' }}</a>
{{else}}
<div class="totem-display flexrow">
<span style="flex: 1;">{{ smarttl "TOTEMS" system.identity.totem }}</span>
<a data-action="chooseTotem" class="chooseTotem">{{ localize 'UI.effect_edit' }}</a>
</div>
{{/if}}
</div>
</div>
</div> </div>
</div> </div>
+40 -28
View File
@@ -1,37 +1,49 @@
<div class="tab road sheet-part {{#if @root.tabs.road.active}}active{{/if}}" data-group="sheet" data-tab="road"> <div class="tab road sheet-part {{#if @root.tabs.road.active}}active{{/if}}" data-group="sheet" data-tab="road">
<ol class="items-list"> <div class="grid grid-1col">
<li class="item flexrow items-header"> {{!-- Rencontres --}}
<div class="item-name" style="flex:4;">{{ localize 'IDENTITY.name'}}</div> <div>
<div class="item-mobility">{{ localize 'VERMINE.mobility'}}</div> <h4>{{ localize 'VERMINE.encounters' }}{{#if isEditMode}}<a class="item-control chooseActor" data-action="chooseActor" data-list="encounters" title="{{ localize 'UI.add' }}" data-type="encounters"><i class="fas fa-plus"></i></a>{{/if}}</h4>
<div class="item-rarity">{{ localize 'VERMINE.rarity'}}</div> <ol class="group-list">
<div class="item-reliability">{{ localize 'VERMINE.reliability'}}</div> <li class="group-list-header">
<div class="gcol-name">{{ localize 'IDENTITY.name'}}</div>
<div class="gcol-profile">{{ localize 'IDENTITY.profile'}}</div>
<div class="gcol-totem">{{ localize 'IDENTITY.totem'}}</div>
<div class="item-controls"></div>
</li>
{{#each system.encounters as |encounterId|}}
{{#with (lookup ../resolvedEncounters encounterId) as |encounter|}}
<li class="actor" data-actor-id="{{encounterId}}">
{{#if encounter}}
<div class="gcol-name">
<a class="item-control">{{encounter.name}}</a>
<span class="encounter-type">({{encounter.type}})</span>
</div>
<div class="gcol-profile">{{encounter.profile}}</div>
<div class="gcol-totem">{{#if encounter.totem}}{{ smarttl "TOTEMS" encounter.totem }}{{/if}}</div>
{{else}}
<div class="gcol-name"><a class="item-control" style="color: red;">ID: {{encounterId}}</a></div>
<div class="gcol-profile"></div>
<div class="gcol-totem"></div>
{{/if}}
<div class="item-controls"> <div class="item-controls">
{{#if isEditMode}} {{#if @root.isEditMode}}
<a class="item-control item-create" data-action="create" title="Create item" <a class="item-control encounter-delete" data-action="deleteEncounter" data-actor-id="{{encounterId}}" title="{{ localize 'UI.effect_delete' }}"><i class="fas fa-trash"></i></a>
data-type="vehicle"><i class="fas fa-plus"></i></a>
{{/if}} {{/if}}
</div> </div>
</li> </li>
{{#each vehicles as |item id|}} {{/with}}
<li class="item flexrow flex-group-center" data-item-id="{{item._id}}"> {{else}}
<div class="item-name" style="flex:4;"> <li class="item">
<div class="item-image"> <span class="item-name">{{ localize 'VERMINE.no_encounters' }}</span>
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" data-roll-type="item"><img
src="{{item.img}}" title="{{item.name}}" width="24"
height="24" /></a>
</div>
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="Edit Item">{{item.name}}</a>
</div>
<div><a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="Edit Item">{{item.system.mobility}}</a></div>
<div><a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="Edit Item">{{item.system.rarity.value}}</a></div>
<div><a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="Edit Item">{{item.system.reliability}}</a></div>
<div class="item-controls">
{{#if @root.isEditMode}}
<a class="item-control item-delete" data-action="delete" data-item-id="{{item._id}}" title="Delete Item"><i
class="fas fa-trash"></i></a>
{{/if}}
</div>
</li> </li>
{{/each}} {{/each}}
</ol> </ol>
</div>
{{!-- Notes de voyage --}}
<div>
<h4>{{ localize 'VERMINE.travel_notes' }}</h4>
{{formInput systemFields.roadNotes enriched=enrichedRoadNotes value=system.roadNotes name="system.roadNotes" toggled=true}}
</div>
</div>
</div> </div>
@@ -1,116 +0,0 @@
<div class="tab characteristics sheet-part {{#if @root.tabs.characteristics.active}}active{{/if}}" data-group="sheet" data-tab="characteristics">
<h3>{{ localize 'VERMINE.abilities' }}</h3>
{{!-- Caracteristiques physiques --}}
<div class="ability-section">
<h4 class="ability-category-header">{{ localize 'VERMINE.ability_category.physical' }}</h4>
<div class="grid grid-2col">
<div class="ability-item">
<label for="system.abilities.vigor.value" class="resource-label rollable" data-type="ability" data-label="vigor">{{ localize 'ABILITIES.vigor.name' }}</label>
<div class="ability-control">
{{#if isEditMode}}
<input type="range" name="system.abilities.vigor.value" value="{{system.abilities.vigor.value}}" min="{{system.abilities.vigor.min}}" max="{{system.abilities.vigor.max}}" data-dtype="Number"/>
{{/if}}
<span class="ability-value">{{system.abilities.vigor.value}}D</span>
</div>
</div>
<div class="ability-item">
<label for="system.abilities.health.value" class="resource-label rollable" data-type="ability" data-label="health">{{ localize 'ABILITIES.health.name' }}</label>
<div class="ability-control">
{{#if isEditMode}}
<input type="range" name="system.abilities.health.value" value="{{system.abilities.health.value}}" min="{{system.abilities.health.min}}" max="{{system.abilities.health.max}}" data-dtype="Number"/>
{{/if}}
<span class="ability-value">{{system.abilities.health.value}}D</span>
</div>
</div>
</div>
</div>
{{!-- Caracteristiques manuelles --}}
<div class="ability-section">
<h4 class="ability-category-header">{{ localize 'VERMINE.ability_category.manual' }}</h4>
<div class="grid grid-2col">
<div class="ability-item">
<label for="system.abilities.precision.value" class="resource-label rollable" data-type="ability" data-label="precision">{{ localize 'ABILITIES.precision.name' }}</label>
<div class="ability-control">
{{#if isEditMode}}
<input type="range" name="system.abilities.precision.value" value="{{system.abilities.precision.value}}" min="{{system.abilities.precision.min}}" max="{{system.abilities.precision.max}}" data-dtype="Number"/>
{{/if}}
<span class="ability-value">{{system.abilities.precision.value}}D</span>
</div>
</div>
<div class="ability-item">
<label for="system.abilities.reflexes.value" class="resource-label rollable" data-type="ability" data-label="reflexes">{{ localize 'ABILITIES.reflexes.name' }}</label>
<div class="ability-control">
{{#if isEditMode}}
<input type="range" name="system.abilities.reflexes.value" value="{{system.abilities.reflexes.value}}" min="{{system.abilities.reflexes.min}}" max="{{system.abilities.reflexes.max}}" data-dtype="Number"/>
{{/if}}
<span class="ability-value">{{system.abilities.reflexes.value}}D</span>
</div>
</div>
</div>
</div>
{{!-- Caracteristiques mentales --}}
<div class="ability-section">
<h4 class="ability-category-header">{{ localize 'VERMINE.ability_category.mental' }}</h4>
<div class="grid grid-2col">
<div class="ability-item">
<label for="system.abilities.knowledge.value" class="resource-label rollable" data-type="ability" data-label="knowledge">{{ localize 'ABILITIES.knowledge.name' }}</label>
<div class="ability-control">
{{#if isEditMode}}
<input type="range" name="system.abilities.knowledge.value" value="{{system.abilities.knowledge.value}}" min="{{system.abilities.knowledge.min}}" max="{{system.abilities.knowledge.max}}" data-dtype="Number"/>
{{/if}}
<span class="ability-value">{{system.abilities.knowledge.value}}D</span>
</div>
</div>
<div class="ability-item">
<label for="system.abilities.perception.value" class="resource-label rollable" data-type="ability" data-label="perception">{{ localize 'ABILITIES.perception.name' }}</label>
<div class="ability-control">
{{#if isEditMode}}
<input type="range" name="system.abilities.perception.value" value="{{system.abilities.perception.value}}" min="{{system.abilities.perception.min}}" max="{{system.abilities.perception.max}}" data-dtype="Number"/>
{{/if}}
<span class="ability-value">{{system.abilities.perception.value}}D</span>
</div>
</div>
</div>
</div>
{{!-- Caracteristiques sociales --}}
<div class="ability-section">
<h4 class="ability-category-header">{{ localize 'VERMINE.ability_category.social' }}</h4>
<div class="grid grid-2col">
<div class="ability-item">
<label for="system.abilities.will.value" class="resource-label rollable" data-type="ability" data-label="will">{{ localize 'ABILITIES.will.name' }}</label>
<div class="ability-control">
{{#if isEditMode}}
<input type="range" name="system.abilities.will.value" value="{{system.abilities.will.value}}" min="{{system.abilities.will.min}}" max="{{system.abilities.will.max}}" data-dtype="Number"/>
{{/if}}
<span class="ability-value">{{system.abilities.will.value}}D</span>
</div>
</div>
<div class="ability-item">
<label for="system.abilities.empathy.value" class="resource-label rollable" data-type="ability" data-label="empathy">{{ localize 'ABILITIES.empathy.name' }}</label>
<div class="ability-control">
{{#if isEditMode}}
<input type="range" name="system.abilities.empathy.value" value="{{system.abilities.empathy.value}}" min="{{system.abilities.empathy.min}}" max="{{system.abilities.empathy.max}}" data-dtype="Number"/>
{{/if}}
<span class="ability-value">{{system.abilities.empathy.value}}D</span>
</div>
</div>
</div>
</div>
{{!-- Reserves --}}
<div class="ability-section">
<h4 class="ability-category-header">{{ localize 'VERMINE.reserves' }}</h4>
<div class="grid grid-2col">
<div class="ability-item">
<span>{{ localize 'VERMINE.effort' }} : {{system.attributes.effort.value}}/{{system.attributes.effort.max}}D</span>
</div>
<div class="ability-item">
<span>{{ localize 'VERMINE.self_control' }} : {{system.attributes.self_control.value}}/{{system.attributes.self_control.max}}D</span>
</div>
</div>
</div>
</div>
+37 -50
View File
@@ -1,58 +1,45 @@
<div class="tab combat sheet-part {{#if @root.tabs.combat.active}}active{{/if}}" data-group="sheet" data-tab="combat"> <div class="tab combat sheet-part {{#if @root.tabs.combat.active}}active{{/if}}" data-group="sheet" data-tab="combat">
<div class="grid grid-2col">
<div>
<h4 class="align-center">{{ localize "VERMINE.self_control"}}</h4>
<p class="align-center">
{{#if isEditMode}}
<input type="number" name="system.attributes.self_control.value"
value="{{ system.attributes.self_control.value }}" data-dtype="Number"
min="{{ system.attributes.self_control.min }}"
max="{{ system.attributes.self_control.max }}" />
{{/if}}
{{ system.attributes.self_control.value }} / {{
system.attributes.self_control.max }}
</p>
</div>
<div>
<h4 class="align-center">{{ localize "VERMINE.effort"}}</h4>
<p class="align-center">
{{#if isEditMode}}
<input type="number" name="system.attributes.effort.value"
value="{{ system.attributes.effort.value }}" data-dtype="Number"
min="{{ system.attributes.effort.min }}"
max="{{ system.attributes.effort.max }}" />
{{/if}}
{{ system.attributes.effort.value }} / {{
system.attributes.effort.max }}
</p>
</div>
</div>
{{> "systems/vermine2047/templates/actor/parts/actor-equipped-gear.hbs"}} {{> "systems/vermine2047/templates/actor/parts/actor-equipped-gear.hbs"}}
<h4 class="item-name effect-name flexrow">{{ localize <h4 class="item-name effect-name flexrow">{{ localize "VERMINE.wounds.name"}}</h4>
"VERMINE.wounds.name"}}</h4> <div class="npc-wounds flexrow">
<ul class="unstyled"> <span class="wound-cat">
<li class="row mdb">{{ localize 'VERMINE.wounds.light'}} ({{ <span class="wound-label">{{ localize 'VERMINE.wounds.light'}} ({{ system.minorWound.threshold }})</span>
system.minorWound.threshold }}) <span class="wound-boxes">
{{#repeat system.minorWound.max }} {{#repeat system.minorWound.max 1 "minorwoundmax"}}
<div class="hexa ability{{#iflteq @minorwoundmax @root.system.minorWound.value }} checked{{/iflteq}}">
<input type="radio" data-dtype="Number" name="system.minorWound.value" <input type="radio" data-dtype="Number" name="system.minorWound.value"
value="{{this}}" data-action="clickRadio" data-wound="minorWound" value="{{@minorwoundmax}}" data-action="clickWound" data-wound="minorWound"
{{#ife @root.system.minorWound.value this }}checked="checked"{{/ife}} /> class="{{#iflteq @minorwoundmax @root.system.minorWound.value }}checked{{/iflteq}}"
{{/repeat}}</li> {{#ife @minorwoundmax @root.system.minorWound.value }}checked="checked"{{/ife}} />
<li class="row mdb">{{ localize 'VERMINE.wounds.heavy'}} ({{ </div>
system.majorWound.threshold }})
{{#repeat system.majorWound.max }}
<input type="radio" name="system.majorWound.value" value="{{this}}" data-action="clickRadio" data-wound="majorWound"
{{#ife @root.system.majorWound.value this }}checked="checked"{{/ife}} />
{{/repeat}} {{/repeat}}
</li> </span>
<li class="row mdb">{{ localize 'VERMINE.wounds.deadly'}} ({{ </span>
system.deadlyWound.threshold }}) <span class="wound-cat">
{{#repeat system.deadlyWound.max }} <span class="wound-label">{{ localize 'VERMINE.wounds.heavy'}} ({{ system.majorWound.threshold }})</span>
<input type="radio" name="system.deadlyWound.value" value="{{this}}" data-action="clickRadio" data-wound="deadlyWound" <span class="wound-boxes">
{{#ife @root.system.deadlyWound.value this }}checked="checked"{{/ife}} /> {{#repeat system.majorWound.max 1 "majorwoundmax"}}
<div class="hexa ability{{#iflteq @majorwoundmax @root.system.majorWound.value }} checked{{/iflteq}}">
<input type="radio" name="system.majorWound.value" value="{{@majorwoundmax}}" data-action="clickWound" data-wound="majorWound"
class="{{#iflteq @majorwoundmax @root.system.majorWound.value }}checked{{/iflteq}}"
{{#ife @majorwoundmax @root.system.majorWound.value }}checked="checked"{{/ife}} />
</div>
{{/repeat}} {{/repeat}}
</li> </span>
</ul> </span>
<span class="wound-cat">
<span class="wound-label">{{ localize 'VERMINE.wounds.deadly'}} ({{ system.deadlyWound.threshold }})</span>
<span class="wound-boxes">
{{#repeat system.deadlyWound.max 1 "deadlywoundmax"}}
<div class="hexa ability{{#iflteq @deadlywoundmax @root.system.deadlyWound.value }} checked{{/iflteq}}">
<input type="radio" name="system.deadlyWound.value" value="{{@deadlywoundmax}}" data-action="clickWound" data-wound="deadlyWound"
class="{{#iflteq @deadlywoundmax @root.system.deadlyWound.value }}checked{{/iflteq}}"
{{#ife @deadlywoundmax @root.system.deadlyWound.value }}checked="checked"{{/ife}} />
</div>
{{/repeat}}
</span>
</span>
</div>
<h4 class="item-name effect-name flexrow">{{ localize "UI.effects.name"}}</h4> <h4 class="item-name effect-name flexrow">{{ localize "UI.effects.name"}}</h4>
<ol class="items-list effects-list"> <ol class="items-list effects-list">
{{#each effects as |section sid|}} {{#each effects as |section sid|}}
+29 -9
View File
@@ -20,7 +20,7 @@
{{/if}} {{/if}}
</h1> </h1>
{{!-- Niveaux de Menace, Experience, Role --}} {{!-- Critères : Menace, Expérience, Rôle --}}
<div class="resources grid grid-3col"> <div class="resources grid grid-3col">
<div class="resource flex-group-center"> <div class="resource flex-group-center">
<label for="system.threat.value" class="resource-label">{{ localize 'ADVERSITY.threat' }}</label> <label for="system.threat.value" class="resource-label">{{ localize 'ADVERSITY.threat' }}</label>
@@ -29,12 +29,12 @@
<select name="system.threat.value" id="system.threat.value" data-dtype="Number"> <select name="system.threat.value" id="system.threat.value" data-dtype="Number">
{{#each npcThreatOptions}} {{#each npcThreatOptions}}
<option value="{{@key}}" {{#ife @key @root.system.threat.value}}selected{{/ife}}> <option value="{{@key}}" {{#ife @key @root.system.threat.value}}selected{{/ife}}>
{{localize label}} ({{@key}}) {{localize label}}
</option> </option>
{{/each}} {{/each}}
</select> </select>
{{else}} {{else}}
<span>{{system.threat.value}}</span> <span>{{ system.threat.value }}</span>
{{/if}} {{/if}}
</div> </div>
</div> </div>
@@ -46,12 +46,12 @@
<select name="system.experience.value" id="system.experience.value" data-dtype="Number"> <select name="system.experience.value" id="system.experience.value" data-dtype="Number">
{{#each npcExperienceOptions}} {{#each npcExperienceOptions}}
<option value="{{@key}}" {{#ife @key @root.system.experience.value}}selected{{/ife}}> <option value="{{@key}}" {{#ife @key @root.system.experience.value}}selected{{/ife}}>
{{localize label}} ({{@key}}) {{localize label}}
</option> </option>
{{/each}} {{/each}}
</select> </select>
{{else}} {{else}}
<span>{{system.experience.value}}</span> <span>{{ system.experience.value }}</span>
{{/if}} {{/if}}
</div> </div>
</div> </div>
@@ -63,19 +63,39 @@
<select name="system.role.value" id="system.role.value" data-dtype="Number"> <select name="system.role.value" id="system.role.value" data-dtype="Number">
{{#each npcRoleOptions}} {{#each npcRoleOptions}}
<option value="{{@key}}" {{#ife @key @root.system.role.value}}selected{{/ife}}> <option value="{{@key}}" {{#ife @key @root.system.role.value}}selected{{/ife}}>
{{localize label}} ({{@key}}) {{localize label}}
</option> </option>
{{/each}} {{/each}}
</select> </select>
{{else}} {{else}}
<span>{{system.role.value}}</span> <span>{{ system.role.value }}</span>
{{/if}} {{/if}}
</div> </div>
</div> </div>
</div> </div>
{{!-- Totem et Origine --}} {{!-- Profil : âge, activité, totem, origine --}}
<div class="resources grid grid-2col"> <div class="resources grid grid-4col npc-profile-grid">
<div class="resource flex-group-center">
<label for="system.identity.age" class="resource-label">{{ localize 'IDENTITY.age' }}</label>
<div class="resource-content">
{{#if isEditMode}}
<input type="text" name="system.identity.age" id="system.identity.age" value="{{system.identity.age}}" data-dtype="String"/>
{{else}}
<span>{{system.identity.age}}</span>
{{/if}}
</div>
</div>
<div class="resource flex-group-center">
<label for="system.identity.profile" class="resource-label">{{ localize 'IDENTITY.profile' }}</label>
<div class="resource-content">
{{#if isEditMode}}
<input type="text" name="system.identity.profile" id="system.identity.profile" value="{{system.identity.profile}}" data-dtype="String" placeholder="{{ localize 'PROFILE' }}"/>
{{else}}
<span>{{system.identity.profile}}</span>
{{/if}}
</div>
</div>
<div class="resource flex-group-center"> <div class="resource flex-group-center">
<label for="system.identity.totem" class="resource-label">{{ localize 'TOTEM' }}</label> <label for="system.identity.totem" class="resource-label">{{ localize 'TOTEM' }}</label>
<div class="resource-content"> <div class="resource-content">
-8
View File
@@ -1,14 +1,6 @@
<div class="tab notes sheet-part {{#if @root.tabs.notes.active}}active{{/if}}" data-group="sheet" data-tab="notes"> <div class="tab notes sheet-part {{#if @root.tabs.notes.active}}active{{/if}}" data-group="sheet" data-tab="notes">
<h3>{{ localize 'IDENTITY.notes' }}</h3> <h3>{{ localize 'IDENTITY.notes' }}</h3>
<div class="grid grid-1col"> <div class="grid grid-1col">
<div class="form-group">
<label for="system.identity.profile">{{ localize 'IDENTITY.profile' }}</label>
{{#if isEditMode}}
<input type="text" name="system.identity.profile" value="{{system.identity.profile}}" data-dtype="String" placeholder="{{ localize 'PROFILE' }}"/>
{{else}}
<span>{{system.identity.profile}}</span>
{{/if}}
</div>
<div class="form-group"> <div class="form-group">
<label for="system.identity.theme">{{ localize 'IDENTITY.theme' }}</label> <label for="system.identity.theme">{{ localize 'IDENTITY.theme' }}</label>
{{#if isEditMode}} {{#if isEditMode}}
-45
View File
@@ -1,45 +0,0 @@
<div class="tab skills sheet-part {{#if @root.tabs.skills.active}}active{{/if}}" data-group="sheet" data-tab="skills">
<h3>{{ localize 'VERMINE.skills' }}</h3>
{{!-- Category Selector --}}
<div class="skill-category-selector">
<label>{{ localize 'VERMINE.preferred_category' }}:</label>
{{#if isEditMode}}
<select name="system.skill_categories.preferred" data-dtype="String">
<option value="">{{ localize 'NONE' }}</option>
{{#each system.skill_categories as |category key|}}
{{#ife key "preferred"}}{{else}}
<option value="{{key}}" {{#ife key ../system.skill_categories.preferred}}selected{{/ife}}>
{{localize category.label}}
</option>
{{/ife}}
{{/each}}
</select>
{{else}}
<span>{{system.skill_categories.preferred}}</span>
{{/if}}
</div>
{{!-- Skills Grid by Category --}}
<div class="skills-container">
{{#each config.skillCategories as |category key|}}
{{#ife key "preferred"}}{{else}}
{{> "systems/vermine2047/templates/actor/parts/npc-skill-category.hbs"
categoryKey=key
categoryLabel=(concat "VERMINE.skill_category." key)
skills=@root.system.skills
}}
{{/ife}}
{{/each}}
</div>
{{!-- Free Skills text field --}}
<div class="form-group">
<label for="system.freeSkills">{{ localize 'ADVERSITY.skills' }}</label>
{{#if isEditMode}}
<input type="text" name="system.freeSkills" value="{{system.freeSkills}}" data-dtype="String" placeholder="{{ localize 'ADVERSITY.skills_placeholder' }}"/>
{{else}}
<span>{{system.freeSkills}}</span>
{{/if}}
</div>
</div>
-75
View File
@@ -1,75 +0,0 @@
<div class="tab threat sheet-part {{#if @root.tabs.threat.active}}active{{/if}}" data-group="sheet" data-tab="threat">
<h3>{{ localize 'ADVERSITY.threat_details' }}</h3>
<div class="grid grid-3col">
{{!-- Menace --}}
<div class="card npc-card">
<h4 class="align-center"><i class="fas fa-exclamation-triangle"></i> {{ localize 'ADVERSITY.threat' }}</h4>
<ul class="unstyled">
<li>
<strong>{{ localize 'ADVERSITY.attack' }}:</strong>
{{ npcThreatLevel "attack" system.threat.value }}
</li>
<li>
<strong>{{ localize 'ADVERSITY.vigor' }}:</strong>
{{ npcThreatLevel "vigor" system.threat.value }}
</li>
<li>
<strong>{{ localize 'ADVERSITY.wounds' }}:</strong>
{{ npcThreatLevel "minorWound" system.threat.value }} /
{{ npcThreatLevel "majorWound" system.threat.value }} /
{{ npcThreatLevel "deadlyWound" system.threat.value }}
</li>
</ul>
</div>
{{!-- Experience --}}
<div class="card npc-card">
<h4 class="align-center"><i class="fas fa-star"></i> {{ localize 'ADVERSITY.experience' }}</h4>
<ul class="unstyled">
<li>
<strong>{{ localize 'ADVERSITY.action' }}:</strong>
{{ npcExperienceLevel "action" system.experience.value }}D
</li>
<li>
<strong>{{ localize 'ADVERSITY.specialties' }}:</strong>
{{ npcExperienceLevel "specialties" system.experience.value }}
</li>
<li>
<strong>{{ localize 'ADVERSITY.rerolls' }}:</strong>
{{ npcExperienceLevel "rerolls" system.experience.value }}D
</li>
<li>
<strong>{{ localize 'ADVERSITY.contact' }}:</strong>
{{ npcExperienceLevel "contact" system.experience.value }}
</li>
</ul>
</div>
{{!-- Role --}}
<div class="card npc-card">
<h4 class="align-center"><i class="fas fa-users"></i> {{ localize 'ADVERSITY.role' }}</h4>
<ul class="unstyled">
<li>
<strong>{{ localize 'ADVERSITY.reaction' }}:</strong>
{{ npcRoleLevel "reaction" system.role.value }} + {{ npcRoleLevel "reaction_bonus" system.role.value }}
</li>
<li>
<strong>{{ localize 'ADVERSITY.pools' }}:</strong>
{{ npcRoleLevel "pools" system.role.value }}D
</li>
<li>
<strong>{{ localize 'ADVERSITY.gear' }}:</strong>
{{ npcRoleLevel "gear" system.role.value }}
</li>
<li>
<strong>{{ localize 'ADVERSITY.gear_hindrance' }}:</strong>
{{ npcRoleLevel "gear_hindrance" system.role.value }}
</li>
<li>
<strong>{{ localize 'ADVERSITY.protection' }}:</strong>
{{ npcRoleLevel "protection" system.role.value }}
</li>
</ul>
</div>
</div>
</div>
+86
View File
@@ -0,0 +1,86 @@
<div class="tab traits sheet-part {{#if @root.tabs.traits.active}}active{{/if}}" data-group="sheet" data-tab="traits">
<h3>{{ localize 'VERMINE.traits' }}</h3>
<div class="grid grid-3col">
{{!-- Menace --}}
<div class="card npc-card">
<h4 class="align-center"><i class="fas fa-exclamation-triangle"></i> {{ localize 'ADVERSITY.threat' }}</h4>
<ul class="unstyled npc-trait-list">
<li>
<strong>{{ localize 'ADVERSITY.attack' }}:</strong>
<span class="trait-value">{{ system.computed.attack }}D</span>
<a class="rollable npc-roll-btn" data-type="attack" title="{{ localize 'VERMINE.roll' }}"><i class="fas fa-dice-d10"></i></a>
</li>
<li>
<strong>{{ localize 'ADVERSITY.vigor' }}:</strong>
<span class="trait-value">{{ system.computed.vigor }}D</span>
</li>
<li class="npc-wounds">
<strong>{{ localize 'ADVERSITY.wounds' }}:</strong>
<span class="wound-cat"><span class="wound-boxes">{{#repeat system.minorWound.max 1 "woundindex"}}<i class="fas fa-circle"></i>{{/repeat}}</span> {{ system.minorWound.threshold }}</span>
<span class="wound-cat"><span class="wound-boxes">{{#repeat system.majorWound.max 1 "woundindex"}}<i class="fas fa-circle"></i>{{/repeat}}</span> {{ system.majorWound.threshold }}</span>
<span class="wound-cat"><span class="wound-boxes">{{#repeat system.deadlyWound.max 1 "woundindex"}}<i class="fas fa-circle"></i>{{/repeat}}</span> {{ system.deadlyWound.threshold }}</span>
</li>
</ul>
</div>
{{!-- Expérience --}}
<div class="card npc-card">
<h4 class="align-center"><i class="fas fa-star"></i> {{ localize 'ADVERSITY.experience' }}</h4>
<ul class="unstyled npc-trait-list">
<li>
<strong>{{ localize 'ADVERSITY.action' }}:</strong>
<span class="trait-value">{{ system.computed.action }}D</span>
<a class="rollable npc-roll-btn" data-type="action" title="{{ localize 'VERMINE.roll' }}"><i class="fas fa-dice-d10"></i></a>
</li>
<li>
<strong>{{ localize 'ADVERSITY.specialties' }}:</strong>
<span class="trait-value">{{ system.computed.specialties }}D</span>
<a class="rollable npc-roll-btn" data-type="specialty" title="{{ localize 'VERMINE.roll' }}"><i class="fas fa-dice-d10"></i></a>
</li>
<li>
<strong>{{ localize 'ADVERSITY.rerolls' }}:</strong>
<span class="trait-value">{{ system.computed.rerolls }}D</span>
</li>
<li>
<strong>{{ localize 'ADVERSITY.contact' }}:</strong>
<span class="trait-value">{{ system.computed.contact }}</span>
</li>
</ul>
</div>
{{!-- Rôle --}}
<div class="card npc-card">
<h4 class="align-center"><i class="fas fa-users"></i> {{ localize 'ADVERSITY.role' }}</h4>
<ul class="unstyled npc-trait-list">
<li>
<strong>{{ localize 'ADVERSITY.reaction' }}:</strong>
<span class="trait-value">{{ system.computed.reaction }}D{{#if system.computed.reactionBonus}} +{{ system.computed.reactionBonus }}{{/if}}</span>
<a class="rollable npc-roll-btn" data-type="reaction" title="{{ localize 'VERMINE.roll' }}"><i class="fas fa-dice-d10"></i></a>
</li>
<li>
<strong>{{ localize 'ADVERSITY.pools' }}:</strong>
<span class="trait-value">{{ system.computed.pools }}D</span>
</li>
<li>
<strong>{{ localize 'ADVERSITY.gear' }}:</strong>
<span class="trait-value">{{ system.computed.gear }}{{#if system.computed.gearHindrance}} ({{ system.computed.gearHindrance }}){{/if}}</span>
</li>
<li>
<strong>{{ localize 'ADVERSITY.protection' }}:</strong>
<span class="trait-value">{{ system.computed.protection }}</span>
</li>
</ul>
</div>
</div>
{{!-- Spécialités (description libre) --}}
<div class="form-group npc-free-skills">
<label for="system.freeSkills">{{ localize 'ADVERSITY.skills' }}</label>
{{#if isEditMode}}
<input type="text" name="system.freeSkills" id="system.freeSkills" value="{{system.freeSkills}}" data-dtype="String" placeholder="{{ localize 'ADVERSITY.skills_placeholder' }}"/>
{{else}}
<span>{{system.freeSkills}}</span>
{{/if}}
</div>
</div>
+94
View File
@@ -0,0 +1,94 @@
<div class="tab main sheet-part" data-group="sheet" data-tab="main">
{{!-- Toggle Edit/Play --}}
<div class="sheet-header-toggle">
{{#if isEditMode}}
<button type="button" data-action="toggleSheet">{{localize "VERMINE.playMode"}}</button>
{{else}}
<button type="button" data-action="toggleSheet">{{localize "VERMINE.editMode"}}</button>
{{/if}}
</div>
{{!-- HEADER --}}
<header class="sheet-header">
<img class="profile-img" src="{{actor.img}}" data-action="editImage" data-edit="img" title="{{actor.name}}"
height="100" width="100" />
<div class="header-fields">
<div class="resources grid grid-4col">
<h1 class="charname grid-span-2">
{{#if isEditMode}}
<input name="name" type="text" value="{{actor.name}}" placeholder="{{ localize 'VERMINE.vehicle' }}" />
{{else}}
<span>{{actor.name}}</span>
{{/if}}
</h1>
<div class="resource flex-group-center grid-span-1">
<label for="system.kind" class="resource-label">{{ localize 'VERMINE.kind' }}</label>
<div class="resource-content">
{{#if isEditMode}}
<select name="system.kind" id="system.kind" data-dtype="String">
<option value="vehicle" {{#ife system.kind "vehicle"}}selected{{/ife}}>{{ localize "VERMINE.kind_vehicle" }}</option>
<option value="mount" {{#ife system.kind "mount"}}selected{{/ife}}>{{ localize "VERMINE.kind_mount" }}</option>
</select>
{{else}}
<span>{{#ife system.kind "mount"}}{{ localize "VERMINE.kind_mount" }}{{else}}{{ localize "VERMINE.kind_vehicle" }}{{/ife}}</span>
{{/if}}
</div>
</div>
<div class="resource flex-group-center grid-span-1">
<label for="system.reliability" class="resource-label">{{ localize 'VERMINE.reliability' }}</label>
<div class="resource-content">
{{#if isEditMode}}
<input type="number" name="system.reliability" id="system.reliability" value="{{system.reliability}}"
data-dtype="Number" min="1" max="10" />
{{else}}
<span>{{system.reliability}}</span>
{{/if}}
</div>
</div>
</div>
<div class="resources grid grid-4col">
<div class="resource flex-group-center grid-span-1">
<label for="system.rarity.value" class="resource-label">{{ localize 'VERMINE.rarity' }}</label>
<div class="resource-content">
{{#if isEditMode}}
<input type="number" name="system.rarity.value" id="system.rarity.value" value="{{system.rarity.value}}"
data-dtype="Number" min="1" max="10" />
{{else}}
<span>{{system.rarity.value}}</span>
{{/if}}
</div>
</div>
<div class="resource flex-group-center grid-span-1">
<label for="system.mobility" class="resource-label">{{ localize 'VERMINE.mobility' }}</label>
<div class="resource-content">
{{#if isEditMode}}
<input type="number" name="system.mobility" id="system.mobility" value="{{system.mobility}}"
data-dtype="Number" min="0" />
{{else}}
<span>{{system.mobility}}</span>
{{/if}}
</div>
</div>
<div class="resource flex-group-center grid-span-2">
<label for="system.distance" class="resource-label">{{ localize 'VERMINE.distance' }}</label>
<div class="resource-content">
{{#if isEditMode}}
<input type="number" name="system.distance" id="system.distance" value="{{system.distance}}"
data-dtype="Number" min="0" />
{{else}}
<span>{{system.distance}}</span>
{{/if}}
<span> km</span>
</div>
</div>
</div>
</div>
</header>
{{#if resolvedOwner}}
<div class="vehicle-owner">
<span class="label">{{ localize 'VERMINE.owner' }}:</span>
<span class="value">{{ resolvedOwner.name }}</span>
</div>
{{/if}}
</div>
+83
View File
@@ -0,0 +1,83 @@
<div class="tab specs sheet-part {{#if @root.tabs.specs.active}}active{{/if}}" data-group="sheet" data-tab="specs">
<section class="vehicle-section">
<h3>{{ localize 'VERMINE.vehicle_specs' }}</h3>
<div class="grid grid-2col">
<div class="resource align-center flexcol">
<label class="resource-label">{{ localize 'VERMINE.distance' }}</label>
<div class="flexrow align-center">
<div class="hexa"><input type="number" name="system.distance" value="{{system.distance}}" data-dtype="Number" /></div>
<span class="unit">km</span>
</div>
</div>
<div class="resource align-center flexcol">
<label class="resource-label">{{ localize 'VERMINE.speed' }}</label>
<div class="flexrow align-center">
<div class="hexa"><input type="number" name="system.speed.cruise" value="{{system.speed.cruise}}" data-dtype="Number" /></div>
<span>/</span>
<div class="hexa"><input type="number" name="system.speed.top" value="{{system.speed.top}}" data-dtype="Number" /></div>
</div>
<input type="text" name="system.speed.topDuration" value="{{system.speed.topDuration}}"
placeholder="{{ localize 'VERMINE.speed_duration_ph' }}" />
</div>
</div>
<div class="grid grid-2col">
<div class="resource align-center flexcol">
<label class="resource-label">{{ localize 'VERMINE.effort_reserve' }}</label>
<div class="flexrow align-center">
<div class="hexa"><input type="number" name="system.effortReserve.value" value="{{system.effortReserve.value}}" data-dtype="Number" /></div>
<span>/</span>
<div class="hexa"><input type="number" name="system.effortReserve.max" value="{{system.effortReserve.max}}" data-dtype="Number" /></div>
</div>
</div>
<div class="resource align-center flexcol">
<label class="resource-label">{{ localize 'VERMINE.mobility' }}</label>
<div class="hexa"><input type="number" name="system.mobility" value="{{system.mobility}}" data-dtype="Number" /></div>
</div>
</div>
<div class="maintenance">
<label class="resource-label">{{ localize 'VERMINE.maintenance' }}</label>
<div class="flexrow flex-wrap">
{{#each maintenanceSkills}}
<label class="flexrow align-center">
<input type="checkbox" name="system.maintenance" value="{{ this.key }}" {{#if this.checked}}checked{{/if}} />
<span>{{ this.label }}</span>
</label>
{{/each}}
</div>
</div>
</section>
<section class="vehicle-section">
<h3>{{ localize 'IDENTITY.profile' }}</h3>
<div class="grid grid-2col">
<div class="form-group">
<label for="system.identity.profile">{{ localize 'IDENTITY.profile' }}</label>
{{#if isEditMode}}
<input type="text" name="system.identity.profile" value="{{system.identity.profile}}" data-dtype="String" />
{{else}}
<span>{{system.identity.profile}}</span>
{{/if}}
</div>
<div class="form-group">
<label for="system.identity.origin">{{ localize 'IDENTITY.origin' }}</label>
{{#if isEditMode}}
<input type="text" name="system.identity.origin" value="{{system.identity.origin}}" data-dtype="String" />
{{else}}
<span>{{system.identity.origin}}</span>
{{/if}}
</div>
<div class="form-group">
<label for="system.identity.theme">{{ localize 'IDENTITY.theme' }}</label>
{{#if isEditMode}}
<input type="text" name="system.identity.theme" value="{{system.identity.theme}}" data-dtype="String" />
{{else}}
<span>{{system.identity.theme}}</span>
{{/if}}
</div>
</div>
<div class="form-group">
<label for="system.identity.notes">{{ localize 'IDENTITY.notes' }}</label>
{{formInput systemFields.identity.fields.notes enriched=enrichedNotes value=system.identity.notes name="system.identity.notes" toggled=true}}
</div>
</section>
</div>
@@ -35,10 +35,6 @@
<input name="system.attributes.reputation.value" type="text" <input name="system.attributes.reputation.value" type="text"
value="{{system.attributes.reputation.value }}" value="{{system.attributes.reputation.value }}"
data-dtype="Number" /> data-dtype="Number" />
<label>{{ localize 'VERMINE.experience' }}</label>
<input name="system.attributes.xp.value" type="text"
value="{{system.attributes.xp.value}}" data-dtype="Number" />
</div> </div>
</section> </section>
</header> </header>
@@ -12,10 +12,10 @@
<p style="font-size:80%;">{{smarttl "TOTEMS" system.identity.totem "description"}}</p> <p style="font-size:80%;">{{smarttl "TOTEMS" system.identity.totem "description"}}</p>
<div class="grid grid-2col"> <div class="grid grid-2col">
<div class> <div class>
<div class="item-name"><strong>Principes</strong><br />{{smarttl "TOTEMS" system.identity.totem "instincts"}}</div> <div class="item-name"><strong>{{ localize 'IDENTITY.instincts' }}</strong><br />{{smarttl "TOTEMS" system.identity.totem "instincts"}}</div>
</div> </div>
<div class> <div class>
<div class="item-name"><strong>Interdits</strong><br />{{smarttl "TOTEMS" system.identity.totem "bans"}}</div> <div class="item-name"><strong>{{ localize 'IDENTITY.prohibits' }}</strong><br />{{smarttl "TOTEMS" system.identity.totem "bans"}}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -48,7 +48,7 @@
data-tooltip="niveau">{{item.system.level.value}}</span> data-tooltip="niveau">{{item.system.level.value}}</span>
<span <span
data-tooltip="apprentissage"> data-tooltip="apprentissage">
{{item.system.learn.threshold}}/{{item.system.learn.hindrance}} {{item.system.learn.threshold}}{{#ifgt item.system.learn.hindrance 0}}/{{ roman item.system.learn.hindrance }}{{/ifgt}}
</span> </span>
</div> </div>
<div class="item-controls flexrow"> <div class="item-controls flexrow">
+18 -11
View File
@@ -5,27 +5,34 @@
<div class="item-rarity">{{ localize 'VERMINE.rarity'}}</div> <div class="item-rarity">{{ localize 'VERMINE.rarity'}}</div>
<div class="item-reliability">{{ localize 'VERMINE.reliability'}}</div> <div class="item-reliability">{{ localize 'VERMINE.reliability'}}</div>
<div class="item-controls"> <div class="item-controls">
<a class="item-control item-create" title="Create item" {{#if @root.isEditMode}}
data-type="vehicle" data-action="create"><i class="fas fa-plus"></i></a> <a class="item-control item-create" data-action="createVehicle" title="Create vehicle"><i class="fas fa-plus"></i></a>
{{/if}}
</div> </div>
</li> </li>
{{#each vehicles as |item id|}} {{#each vehicles as |vehicle|}}
<li class="item flexrow flex-group-center" data-item-id="{{item._id}}"> <li class="item flexrow flex-group-center" data-vehicle-id="{{vehicle.id}}">
<div class="item-name" style="flex:4;"> <div class="item-name" style="flex:4;">
<div class="item-image"> <div class="item-image">
<a data-action="edit"><img <a class="item-control item-edit" data-action="editVehicle" data-vehicle-id="{{vehicle.id}}"><img
src="{{item.img}}" title="{{item.name}}" width="24" src="{{vehicle.img}}" title="{{vehicle.name}}" width="24"
height="24" /></a> height="24" /></a>
</div> </div>
<a data-action="edit" title="Edit Item">{{item.name}}</a> <a class="item-control item-edit" data-action="editVehicle" data-vehicle-id="{{vehicle.id}}" title="Edit Vehicle">{{vehicle.name}}</a>
</div> </div>
<div><a data-action="edit" title="Edit Item">{{item.system.mobility}}</a></div> <div><a class="item-control item-edit" data-action="editVehicle" data-vehicle-id="{{vehicle.id}}" title="Edit Vehicle">{{vehicle.system.mobility}}</a></div>
<div><a data-action="edit" title="Edit Item">{{item.system.rarity.value}}</a></div> <div><a class="item-control item-edit" data-action="editVehicle" data-vehicle-id="{{vehicle.id}}" title="Edit Vehicle">{{vehicle.system.rarity.value}}</a></div>
<div><a data-action="edit" title="Edit Item">{{item.system.reliability}}</a></div> <div><a class="item-control item-edit" data-action="editVehicle" data-vehicle-id="{{vehicle.id}}" title="Edit Vehicle">{{vehicle.system.reliability}}</a></div>
<div class="item-controls"> <div class="item-controls">
<a data-action="delete" title="Delete Item"><i {{#if @root.isEditMode}}
<a class="item-control item-delete" data-action="deleteVehicle" data-vehicle-id="{{vehicle.id}}" title="Delete Vehicle"><i
class="fas fa-trash"></i></a> class="fas fa-trash"></i></a>
{{/if}}
</div> </div>
</li> </li>
{{else}}
<li class="item flexrow flex-group-center">
<span class="empty">{{ localize 'VERMINE.no_vehicles' }}</span>
</li>
{{/each}} {{/each}}
</ol> </ol>
@@ -27,6 +27,11 @@
({{item.system.damage.type}}) ({{item.system.damage.type}})
</a></div> </a></div>
<div style="flex:2;">{{#if item.system.skill}}{{ smarttlk 'SKILLS' item.system.skill 'name' }}{{/if}}</div> <div style="flex:2;">{{#if item.system.skill}}{{ smarttlk 'SKILLS' item.system.skill 'name' }}{{/if}}</div>
<div class="item-controls">
<a data-action="toggleEquip" data-item-id="{{item._id}}"
title="{{#if item.system.equipped}}{{ localize 'VERMINE.unequip'}}{{else}}{{ localize 'VERMINE.equip'}}{{/if}}"><i
class="fas {{#if item.system.equipped}}fa-circle-check{{else}}fa-circle{{/if}}"></i></a>
</div>
</li> </li>
{{/each}} {{/each}}
</ol> </ol>
-2
View File
@@ -47,8 +47,6 @@
class="fas fa-edit"></i></a> class="fas fa-edit"></i></a>
<a data-action="attack" title="{{ localize 'VERMINE.attack' }}"><i <a data-action="attack" title="{{ localize 'VERMINE.attack' }}"><i
class="fas fa-crosshairs"></i></a> class="fas fa-crosshairs"></i></a>
<a data-action="roll" title="Roll Item"><i
class="fas fa-dice"></i></a>
</div> </div>
</li> </li>
{{/each}} {{/each}}
@@ -47,7 +47,7 @@
<div class="exp-phase-characters"> <div class="exp-phase-characters">
{{#each characters}} {{#each characters}}
<details class="difficulty-section" data-character-id="{{ this.id }}" open> <details class="difficulty-section" data-character-id="{{ this.id }}" data-group-id="{{ this.groupId }}" open>
<summary class="flexrow"> <summary class="flexrow">
<span class="label">{{ this.name }}</span> <span class="label">{{ this.name }}</span>
<span class="current-values">{{ localize 'VERMINE.experience_dice' }}: {{ this.dice }}D</span> <span class="current-values">{{ localize 'VERMINE.experience_dice' }}: {{ this.dice }}D</span>
@@ -0,0 +1,72 @@
<div class="roll-dialog-content">
<h3 class="combat-mode-label">{{ localize 'VERMINE.open_learning' }}</h3>
<div class="learning-summary">
<span class="label">{{ localize 'VERMINE.experience_dice' }}:</span>
<span class="exp-value">{{ dice }}D</span>
</div>
<div class="learning-type">
<label><input type="radio" data-roll="true" name="learning-type" value="level" checked /><span>{{ localize 'VERMINE.learn_level' }}</span></label>
<label><input type="radio" data-roll="true" name="learning-type" value="reserve" /><span>{{ localize 'VERMINE.learn_group_reserve' }}</span></label>
<label><input type="radio" data-roll="true" name="learning-type" value="ability" /><span>{{ localize 'VERMINE.learn_ability' }}</span></label>
</div>
<div id="learning-level-section" class="learning-targets">
<p class="learning-hint">{{ localize 'VERMINE.group_level_hint' }}</p>
</div>
<div id="learning-reserve-section" class="learning-targets" style="display:none;">
<p class="learning-hint">{{ localize 'VERMINE.group_reserve_hint' }}</p>
</div>
<div id="learning-ability-section" class="learning-targets" style="display:none;">
<label class="label" for="learning-ability">{{ localize 'VERMINE.learn_ability' }}</label>
<select class="info-value" data-roll="true" id="learning-ability" name="learning-ability">
{{#each abilities}}
<option value="{{ this.id }}" data-threshold="{{ this.threshold }}" data-level="{{ this.level }}">
{{ this.name }} ({{ localize 'VERMINE.learn' }} {{ this.threshold }} / {{ localize 'VERMINE.level' }} {{ this.level }})
</option>
{{/each}}
</select>
{{#unless abilities.length}}
<p class="learning-hint">{{ localize 'VERMINE.no_group_ability' }}</p>
{{/unless}}
<p class="learning-hint">{{ localize 'VERMINE.group_ability_hint' }}</p>
</div>
<div class="flexrow bonus-item full-width">
<label class="label" for="learning-dice">{{ localize 'VERMINE.dice_spent' }}</label>
<input type="number" class="numeric-entry" style="text-align:center;" data-roll="true"
id="learning-dice" name="learning-dice" min="1" max="{{ dice }}" value="1" />
<span>D</span>
</div>
<div class="learning-result">
<div class="context-item">
<span class="context-label">{{ localize 'VERMINE.difficulty' }}</span>
<span class="context-value" id="diff-value">5</span>
</div>
<div class="context-item">
<span class="context-label">{{ localize 'VERMINE.handicap' }}</span>
<span class="context-value" id="handicap-value">0</span>
</div>
<div class="context-item">
<span class="context-label">{{ localize 'VERMINE.success_required' }}</span>
<span class="context-value" id="required-value">1</span>
</div>
<div class="context-item">
<span class="context-label">{{ localize 'VERMINE.pool_total' }}</span>
<span class="context-value" id="pool-value">1D</span>
</div>
</div>
<footer class="sheet-footer flexrow">
<button type="button" data-action="cancel">
<i class="fas fa-times"></i> {{localize "VERMINE.cancel"}}
</button>
<button type="button" data-action="roll">
<i class="fas fa-graduation-cap"></i> {{localize "VERMINE.roll"}}
</button>
</footer>
</div>
+51
View File
@@ -12,6 +12,9 @@
<label><input type="radio" data-roll="true" name="learning-type" value="characteristic" /><span>{{ localize 'VERMINE.learn_characteristic' }}</span></label> <label><input type="radio" data-roll="true" name="learning-type" value="characteristic" /><span>{{ localize 'VERMINE.learn_characteristic' }}</span></label>
<label><input type="radio" data-roll="true" name="learning-type" value="reserve" /><span>{{ localize 'VERMINE.learn_reserve' }}</span></label> <label><input type="radio" data-roll="true" name="learning-type" value="reserve" /><span>{{ localize 'VERMINE.learn_reserve' }}</span></label>
<label><input type="radio" data-roll="true" name="learning-type" value="evolution" /><span>{{ localize 'VERMINE.learn_evolution' }}</span></label> <label><input type="radio" data-roll="true" name="learning-type" value="evolution" /><span>{{ localize 'VERMINE.learn_evolution' }}</span></label>
<label><input type="radio" data-roll="true" name="learning-type" value="totem" /><span>{{ localize 'VERMINE.learn_totem_ability' }}</span></label>
<label><input type="radio" data-roll="true" name="learning-type" value="background" /><span>{{ localize 'VERMINE.learn_background' }}</span></label>
<label><input type="radio" data-roll="true" name="learning-type" value="reputation" /><span>{{ localize 'VERMINE.learn_reputation' }}</span></label>
</div> </div>
<div id="learning-skill-section" class="learning-targets"> <div id="learning-skill-section" class="learning-targets">
@@ -55,6 +58,54 @@
<div id="learning-evolution-section" class="learning-targets" style="display:none;"> <div id="learning-evolution-section" class="learning-targets" style="display:none;">
<p class="learning-hint">{{ localize 'VERMINE.evolution_hint' }}</p> <p class="learning-hint">{{ localize 'VERMINE.evolution_hint' }}</p>
{{#if canUseAdaptedDie}}
<div class="flexrow bonus-item" id="learning-adapted-die-row" style="display:none;">
<label class="label" for="learning-adapted-die">{{ localize 'VERMINE.learn_adapted_die' }}</label>
<input type="checkbox" data-roll="true" id="learning-adapted-die" name="learning-adapted-die" />
</div>
{{/if}}
</div>
<div id="learning-totem-section" class="learning-targets" style="display:none;">
{{#if totemAbilities.length}}
<label class="label" for="learning-totem">{{ localize 'VERMINE.learn_totem_ability' }}</label>
<select class="info-value" data-roll="true" id="learning-totem" name="learning-totem">
{{#each totemAbilities}}
<option value="{{ this.id }}" data-level="{{ this.level }}" data-threshold="{{ this.threshold }}" data-hindrance="{{ this.hindrance }}">
{{ this.name }} (N{{ this.level }}{{ this.threshold }}{{#ifgt this.hindrance 0}}/{{ roman this.hindrance }}{{/ifgt}})
</option>
{{/each}}
</select>
{{else}}
<p class="learning-hint">{{ localize 'VERMINE.no_totem_ability' }}</p>
{{/if}}
<p class="learning-hint">{{ localize 'VERMINE.totem_ability_hint' }}</p>
</div>
<div id="learning-background-section" class="learning-targets" style="display:none;">
{{#if backgrounds.length}}
<label class="label" for="learning-background">{{ localize 'VERMINE.learn_background' }}</label>
<select class="info-value" data-roll="true" id="learning-background" name="learning-background">
{{#each backgrounds}}
<option value="{{ this.id }}" data-asterisk="{{ this.asterisk }}">
{{ this.name }}{{#if this.asterisk}} *{{/if}}
</option>
{{/each}}
</select>
{{else}}
<p class="learning-hint">{{ localize 'VERMINE.no_background' }}</p>
{{/if}}
<p class="learning-hint">{{ localize 'VERMINE.background_hint' }}</p>
</div>
<div id="learning-reputation-section" class="learning-targets" style="display:none;">
<label class="label" for="learning-reputation-direction">{{ localize 'VERMINE.learn_reputation' }}</label>
<select class="info-value" data-roll="true" id="learning-reputation-direction" name="learning-reputation-direction">
<option value="reduce">{{ localize 'VERMINE.reputation_reduce' }}</option>
<option value="increase">{{ localize 'VERMINE.reputation_increase' }}</option>
</select>
<p class="learning-hint">{{ localize 'VERMINE.reputation_hint' }}</p>
<p class="learning-hint"><strong>{{ localize 'VERMINE.reputation_current' }}: <span id="reputation-target">{{ reputation }}</span></strong></p>
</div> </div>
<div class="flexrow bonus-item full-width"> <div class="flexrow bonus-item full-width">
+105
View File
@@ -0,0 +1,105 @@
<div class="roll-dialog-content">
<!-- HIDDEN DATA -->
<input type="hidden" name="speakerId" value="{{ speakerId }}" />
<h4 class="npc-roll-label">{{ rollLabel }}</h4>
<div class="npc-roll-pool">
<span class="label">{{localize 'VERMINE.dice_pool'}}:</span>
<span class="pool-value">{{ pool }}D</span>
{{#if freeSuccesses}}
<span class="free-successes-badge"><i class="fas fa-star"></i> +{{ freeSuccesses }} {{localize 'VERMINE.auto_successes'}}</span>
{{/if}}
{{#if rerolls}}
<span class="rerolls-badge">{{localize 'ADVERSITY.rerolls'}}: {{ rerolls }}D</span>
{{/if}}
</div>
<div class="dice-pool">
<!-- DIFFICULTY -->
<div class="flexcol difficulty-section">
<label class="label" for="difficulty">{{localize 'VERMINE.difficulty'}}</label>
<select
class="info-value"
data-roll="true"
data-dtype="String"
type="number"
name="difficulty"
id="difficulty"
min="3"
max="10"
{{#if lockedDifficulty}}disabled{{/if}}
>
{{#each difficultyOptions}}
<option value="{{ this.difficulty }}" {{#ife @root.defaultDifficulty this.difficulty}}selected{{/ife}}>
{{ this.label }} ({{ this.difficulty }})
</option>
{{/each}}
</select>
</div>
<!-- HANDICAP -->
<div class="flexcol handicap-section">
<label class="label" for="handicap">{{localize 'VERMINE.handicap'}}</label>
<select
class="info-value"
data-roll="true"
data-dtype="String"
type="number"
name="handicap"
id="handicap"
min="0"
max="2"
>
<option value="1" selected>{{localize 'VERMINE.none'}}</option>
<option value="2">(I)</option>
<option value="3">(II)</option>
</select>
</div>
<!-- BONUSES -->
<details class="bonuses-section">
<summary class="flexrow">
<span class="label">{{localize 'VERMINE.bonuses'}}</span>
<span class="bonus-count">+<span id="total-bonus">0</span>D</span>
</summary>
<div class="grid grid-2col bonus-grid">
<div class="flexrow bonus-item">
<input type="checkbox" data-roll="true" name="helped" id="helped" value="1" />
<label class="label" for="helped">{{localize 'VERMINE.help'}} (+1D)</label>
</div>
<div class="flexrow bonus-item">
<label class="label" for="group">{{localize 'VERMINE.group'}}</label>
<input type="number" data-roll="true" class="numeric-entry" style="text-align: center;"
name="group" id="group" min="0" max="5" value="0" />
<span>D</span>
</div>
</div>
</details>
<!-- TOTAL -->
<div class="flexrow total-section">
<label class="label">{{localize 'VERMINE.total'}}:</label>
<span id="dice-pool-total" class="total-value">0D</span>
{{#if freeSuccesses}}
<span class="totem-selector">
(+<span id="free-successes">{{ freeSuccesses }}</span> {{localize 'VERMINE.auto_successes'}})
</span>
{{/if}}
</div>
</div>
<footer class="sheet-footer flexrow">
<button type="button" data-action="cancel">
<i class="fas fa-times"></i> {{localize "VERMINE.cancel"}}
</button>
<button type="button" data-action="roll">
<i class="fas fa-dice-d10"></i> {{localize "VERMINE.roll"}}
</button>
</footer>
</div>
+1 -1
View File
@@ -30,7 +30,7 @@
<span> <span>
{{item.system.learn.threshold}} {{item.system.learn.threshold}}
{{#ifgt item.system.learn.hindrance 0}} {{#ifgt item.system.learn.hindrance 0}}
/ {{item.system.learn.hindrance}} / {{ roman item.system.learn.hindrance }}
{{/ifgt}} {{/ifgt}}
</span> </span>
</div> </div>
@@ -0,0 +1,13 @@
<form class="{{cssClass}}" autocomplete="off">
{{> "systems/vermine2047/templates/item/chatCards/parts/base.hbs"}}
{{!-- Capacité de créature specific content --}}
<div class="grid grid-2col">
{{#if item.system.type}}
<div class="resource flexrow">
<span class="resource-label">{{ localize 'VERMINE.type' }}:</span>
<span>{{ smarttl "CREATURE_CAPACITY_TYPES" item.system.type }}</span>
</div>
{{/if}}
</div>
</form>
+1
View File
@@ -1,5 +1,6 @@
<header class="flexrow align-center"> <header class="flexrow align-center">
<img <img
class="chat-card-img"
src="{{item.img}}" src="{{item.img}}"
alt="image de {{item.name}}" alt="image de {{item.name}}"
> >
-83
View File
@@ -1,83 +0,0 @@
<form class="{{cssClass}}" autocomplete="off">
{{> "systems/vermine2047/templates/item/chatCards/parts/base.hbs"}}
{{!-- Vehicle specific content --}}
<div class="grid grid-2col">
{{#if item.system.mobility}}
<div class="resource flexrow">
<span class="resource-label">{{ localize 'VERMINE.mobility' }}:</span>
<span>{{item.system.mobility}}</span>
</div>
{{/if}}
{{#if item.system.quantity}}
<div class="resource flexrow">
<span class="resource-label">{{ localize 'VERMINE.qty' }}:</span>
<span>{{item.system.quantity}}</span>
</div>
{{/if}}
{{#if item.system.weight}}
<div class="resource flexrow">
<span class="resource-label">{{ localize 'VERMINE.weight' }}:</span>
<span>{{item.system.weight}}</span>
</div>
{{/if}}
{{#if item.system.rarity.value}}
<div class="resource flexrow">
<span class="resource-label">{{ localize 'VERMINE.rarity' }}:</span>
<span>
{{#repeat item.system.rarity.value 1 "i"}}
{{romanNumber i}}
{{/repeat}}
{{#ifgt item.system.rarity.handicap 0}}
({{romanNumber item.system.rarity.handicap}})
{{/ifgt}}
</span>
</div>
{{/if}}
{{#if item.system.damages.value}}
<div class="resource flexrow">
<span class="resource-label">{{ localize 'VERMINE.damages' }}:</span>
<span class="damage-indicator">
{{getDamagesData item.system.damages "state"}}
{{#ifgt item.system.damages.value 1}}
({{item.system.damages.value}})
{{/ifgt}}
</span>
</div>
{{/if}}
{{#if item.system.reliability}}
<div class="resource flexrow">
<span class="resource-label">{{ localize 'VERMINE.reliability' }}:</span>
<span>{{item.system.reliability}}</span>
</div>
{{/if}}
{{#if item.system.traits}}
<div class="resource flexcol">
<span class="resource-label">{{ localize 'VERMINE.traits' }}:</span>
<ul class="unstyled">
{{#each item.system.traits as |trait key|}}
{{#if trait}}
<li>
<strong>{{localize (lookup (lookup ../config.traits key) "name")}}</strong>:
{{localize (lookup (lookup ../config.traits key) "description")}}
</li>
{{/if}}
{{/each}}
</ul>
</div>
{{/if}}
{{#if item.system.description}}
<div class="resource flexcol full-width">
<p>{{{item.system.description}}}</p>
</div>
{{/if}}
</div>
</form>
+7 -2
View File
@@ -31,10 +31,15 @@
<h4 class="resource flexrow"> <h4 class="resource flexrow">
<label class="resource-label">{{ localize "ITEMS.learning"}}</label> <label class="resource-label">{{ localize "ITEMS.learning"}}</label>
<span class=" hexa"> <span class=" hexa">
<input type="number" name="system.learning.threshold" value="{{system.learning.threshold }}" data-dtype="Number" min="3" max="10"/> <input type="number" name="system.learn.threshold" value="{{system.learn.threshold }}" data-dtype="Number" min="3" max="10"/>
</span>/ </span>/
<span class="hexa"> <span class="hexa">
<input type="number" name="system.learning.hindrance" value="{{system.learning.hindrance }}" data-dtype="Number" min="0" max="3"/> <select name="system.learn.hindrance" data-dtype="Number">
<option value="0" {{#if (eq system.learn.hindrance 0)}}selected{{/if}}>&mdash;</option>
<option value="1" {{#if (eq system.learn.hindrance 1)}}selected{{/if}}>I</option>
<option value="2" {{#if (eq system.learn.hindrance 2)}}selected{{/if}}>II</option>
<option value="3" {{#if (eq system.learn.hindrance 3)}}selected{{/if}}>III</option>
</select>
</span> </span>
</h4> </h4>
<h4>description</h4> <h4>description</h4>
@@ -0,0 +1,24 @@
<div class="{{cssClass}}">
<header class="sheet-header">
<img class="profile-img" src="{{item.img}}" data-edit="img" title="{{item.name}}"/>
<div class="header-fields">
<h1 class="charname"><input name="name" type="text" value="{{item.name}}" placeholder="Name"/></h1>
</div>
</header>
<section class="sheet-body">
<h4 class="resource flexrow">
<label class="resource-label">{{ localize "VERMINE.type"}}</label>
<select name="system.type" data-dtype="String">
{{#each config.creatureCapacityTypes as |cc cck|}}
<option value="{{cck}}" {{#if (eq cck @root.system.type)}} selected {{/if}}>{{ smarttl "CREATURE_CAPACITY_TYPES" cck }}</option>
{{/each}}
</select>
</h4>
<h4>{{ localize "VERMINE.sheet.description"}}</h4>
<fieldset>
<legend>{{localize "VERMINE.sheet.description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
</section>
</div>
-64
View File
@@ -1,64 +0,0 @@
<div class="{{cssClass}}">
{{> "systems/vermine2047/templates/item/partials/header.hbs"}}
<section class="sheet-body">
{{> "systems/vermine2047/templates/item/partials/traits.hbs"}}
<section class="vehicle-fields">
<h3>{{ localize "VERMINE.vehicle_specs" }}</h3>
<div class="flexrow">
<div class="resource align-center flexcol">
<label class="resource-label">{{ localize "VERMINE.kind" }}</label>
<select name="system.kind" data-dtype="String">
<option value="vehicle" {{#ife system.kind "vehicle"}}selected{{/ife}}>{{ localize "VERMINE.kind_vehicle" }}</option>
<option value="mount" {{#ife system.kind "mount"}}selected{{/ife}}>{{ localize "VERMINE.kind_mount" }}</option>
</select>
</div>
<div class="resource align-center flexcol">
<label class="resource-label">{{ localize "VERMINE.distance" }}</label>
<div class="hexa"><input type="number" name="system.distance" value="{{system.distance}}" data-dtype="Number" /></div>
<span class="unit">km</span>
</div>
<div class="resource align-center flexcol">
<label class="resource-label">{{ localize "VERMINE.speed" }}</label>
<div class="flexrow align-center">
<input type="number" name="system.speed.cruise" value="{{system.speed.cruise}}" data-dtype="Number" />
<span>/</span>
<input type="number" name="system.speed.top" value="{{system.speed.top}}" data-dtype="Number" />
</div>
<input type="text" name="system.speed.topDuration" value="{{system.speed.topDuration}}" placeholder="{{ localize 'VERMINE.speed_duration_ph' }}" />
</div>
</div>
<div class="flexrow">
<div class="resource align-center flexcol">
<label class="resource-label">{{ localize "VERMINE.effort_reserve" }}</label>
<div class="flexrow align-center">
<div class="hexa"><input type="number" name="system.effortReserve.value" value="{{system.effortReserve.value}}" data-dtype="Number" /></div>
<span>/</span>
<div class="hexa"><input type="number" name="system.effortReserve.max" value="{{system.effortReserve.max}}" data-dtype="Number" /></div>
</div>
</div>
<div class="resource align-center flexcol">
<label class="resource-label">{{ localize "VERMINE.mobility" }}</label>
<div class="hexa"><input type="number" name="system.mobility" value="{{system.mobility}}" data-dtype="Number" /></div>
</div>
</div>
<div class="maintenance">
<label class="resource-label">{{ localize "VERMINE.maintenance" }}</label>
<div class="flexrow flex-wrap">
{{#each maintenanceSkills}}
<label class="flexrow align-center">
<input type="checkbox" name="system.maintenance" value="{{ this.key }}" {{#if this.checked}}checked{{/if}} />
<span>{{ this.label }}</span>
</label>
{{/each}}
</div>
</div>
</section>
{{> "systems/vermine2047/templates/item/partials/physicalItems.hbs"}}
</section>
</div>
+11 -2
View File
@@ -1,4 +1,4 @@
<div class="vermine-roll-message"> <div class="vermine-roll-message" data-free-successes="{{verdict.bonusSuccesses}}">
{{!-- Verdict --}} {{!-- Verdict --}}
<div class="roll-verdict {{#if verdict.success}}success{{else}}failure{{/if}}"> <div class="roll-verdict {{#if verdict.success}}success{{else}}failure{{/if}}">
{{#if verdict.success}} {{#if verdict.success}}
@@ -75,6 +75,12 @@
<span class="context-value">+{{param.handicap}}</span> <span class="context-value">+{{param.handicap}}</span>
</div> </div>
{{/if}} {{/if}}
{{#if verdict.bonusSuccesses}}
<div class="context-item">
<span class="context-label">{{localize "VERMINE.auto_successes"}}</span>
<span class="context-value">+{{verdict.bonusSuccesses}}</span>
</div>
{{/if}}
{{#if param.specialtyName}} {{#if param.specialtyName}}
<div class="context-item"> <div class="context-item">
<span class="context-label">{{localize "VERMINE.specialty"}}</span> <span class="context-label">{{localize "VERMINE.specialty"}}</span>
@@ -142,6 +148,9 @@
{{#if param.poolBreakdown.domainBonus}} {{#if param.poolBreakdown.domainBonus}}
<span class="pool-item">{{#ifgt param.poolBreakdown.domainBonus 0}}{{localize "VERMINE.domain_bonus"}}{{else}}{{localize "VERMINE.domain_penalty"}}{{/ifgt}} {{param.poolBreakdown.domainBonus}}D</span> <span class="pool-item">{{#ifgt param.poolBreakdown.domainBonus 0}}{{localize "VERMINE.domain_bonus"}}{{else}}{{localize "VERMINE.domain_penalty"}}{{/ifgt}} {{param.poolBreakdown.domainBonus}}D</span>
{{/if}} {{/if}}
{{#if param.poolBreakdown.malus}}
<span class="pool-item pool-malus">{{localize "VERMINE.pool_malus"}} -{{param.poolBreakdown.malus}}D</span>
{{/if}}
<span class="pool-item pool-total">{{localize "VERMINE.pool_total"}} {{param.poolBreakdown.total}}D</span> <span class="pool-item pool-total">{{localize "VERMINE.pool_total"}} {{param.poolBreakdown.total}}D</span>
</div> </div>
</div> </div>
@@ -208,7 +217,7 @@
<div class="roll-total flexrow"> <div class="roll-total flexrow">
<div class="flexcol"> <div class="flexcol">
<h4>{{localize "VERMINE.success_count"}}:</h4> <h4>{{localize "VERMINE.success_count"}}:</h4>
<span id="total">{{roll._total}}</span> <span id="total">{{verdict.total}}</span>
</div> </div>
<div class="flexcol"> <div class="flexcol">
<h4>{{localize "VERMINE.success_required"}}:</h4> <h4>{{localize "VERMINE.success_required"}}:</h4>