feat: améliorer les fiches (tests PNJ/créature, message de jet, compétences, créatures)
Release Creation / build (release) Failing after 1m23s

This commit is contained in:
2026-08-04 00:49:37 +02:00
parent 9c7ebc6df9
commit 31f2f4530c
28 changed files with 736 additions and 224 deletions
+25 -2
View File
@@ -241,6 +241,28 @@
"success_count": "Success Count", "success_count": "Success Count",
"success_required": "Success Required", "success_required": "Success Required",
"test_of": "Test of", "test_of": "Test of",
"roll_success": "Success",
"roll_failure": "Failure",
"roll_context": "Roll context",
"roll_pool": "Dice pool",
"roll_dice": "Dice detail",
"pool_ability": "Ability",
"pool_skill": "Skill",
"pool_specialty": "Specialty",
"pool_help": "Help",
"pool_tooling": "Equipment",
"pool_group": "Group",
"pool_selfControl": "Self-Control",
"pool_total": "Total",
"totem_used": "Totem",
"totem_keep": "kept",
"domain_bonus": "Domain bonus",
"domain_penalty": "Domain penalty",
"used": "used",
"die_regular": "dice",
"die_human": "human",
"die_adapted": "adapted",
"die_successes": "successes",
"total": "Total", "total": "Total",
"totem_dice": "Totem Dice", "totem_dice": "Totem Dice",
"totem_hint": "Totem Hint", "totem_hint": "Totem Hint",
@@ -574,7 +596,7 @@
"name": "Repulsion" "name": "Repulsion"
}, },
"tracks": { "tracks": {
"name": "Tracks" "name": "Tracking"
}, },
"crafting": { "crafting": {
"name": "Crafting" "name": "Crafting"
@@ -604,7 +626,7 @@
"name": "Melee" "name": "Melee"
}, },
"archery": { "archery": {
"name": "Ranged" "name": "Ranged Weapons"
}, },
"throwing": { "throwing": {
"name": "Throwing" "name": "Throwing"
@@ -656,6 +678,7 @@
"Round": "Round" "Round": "Round"
}, },
"SIZE_LEVELS": { "SIZE_LEVELS": {
"tiny": "Insect",
"small": "Small", "small": "Small",
"medium": "Medium", "medium": "Medium",
"large": "Large" "large": "Large"
+26 -3
View File
@@ -93,7 +93,7 @@
"AGE_TYPES": { "AGE_TYPES": {
"young": "Jeune", "young": "Jeune",
"adult": "Adulte", "adult": "Adulte",
"old": "Vieux" "old": "Ancien"
}, },
"IDENTITY": { "IDENTITY": {
"biography": "Biographie", "biography": "Biographie",
@@ -188,6 +188,28 @@
"grant_reroll": "accorder des relances", "grant_reroll": "accorder des relances",
"success_count": "nombre de succès", "success_count": "nombre de succès",
"success_required": "succès requis", "success_required": "succès requis",
"roll_success": "Réussite",
"roll_failure": "Échec",
"roll_context": "Contexte du jet",
"roll_pool": "Composition du jet",
"roll_dice": "Détail des dés",
"pool_ability": "Caractéristique",
"pool_skill": "Compétence",
"pool_specialty": "Spécialité",
"pool_help": "Entraide",
"pool_tooling": "Équipement",
"pool_group": "Groupe",
"pool_selfControl": "Sang-Froid",
"pool_total": "Total",
"totem_used": "Totem",
"totem_keep": "gardé",
"domain_bonus": "Bonus de domaine",
"domain_penalty": "Malus de domaine",
"used": "utilisé",
"die_regular": "dés",
"die_human": "humains",
"die_adapted": "adaptés",
"die_successes": "succès",
"error_no_actor_selected": "Vous n'avez pas de personnage attitré ou de token sélectionné", "error_no_actor_selected": "Vous n'avez pas de personnage attitré ou de token sélectionné",
"error_cannot_reroll": "Vous ne pouvez pas relancer un dés sur ce jet", "error_cannot_reroll": "Vous ne pouvez pas relancer un dés sur ce jet",
"error_no_rerolls_left": "Plus de relances possibles", "error_no_rerolls_left": "Plus de relances possibles",
@@ -433,6 +455,7 @@
"major": "Majeur" "major": "Majeur"
}, },
"SIZE_LEVELS": { "SIZE_LEVELS": {
"tiny": "Insecte",
"small": "Petit", "small": "Petit",
"medium": "Moyen", "medium": "Moyen",
"large": "Grand" "large": "Grand"
@@ -596,7 +619,7 @@
"name": "Répulsion" "name": "Répulsion"
}, },
"tracks": { "tracks": {
"name": "Pistage" "name": "Traces"
}, },
"crafting": { "crafting": {
"name": "Artisanat" "name": "Artisanat"
@@ -617,7 +640,7 @@
"name": "Armes à feu" "name": "Armes à feu"
}, },
"archery": { "archery": {
"name": "Tir à l'arc" "name": "Armes de Tir"
}, },
"armory": { "armory": {
"name": "Armurerie" "name": "Armurerie"
+158 -1
View File
@@ -44,6 +44,24 @@ body.system-vermine2047.theme-dark {
color: #000000; color: #000000;
} }
// Core sheets the system doesn't restyle (RollTable, Journal, …) inherit the
// gray text overrides above, which are unreadable on their dark backgrounds.
// Restore Foundry's default light-on-dark text colors for those apps (dark theme only).
body.system-vermine2047.theme-dark {
.roll-table-sheet,
.journal-sheet,
.document-sheet,
.settings-sheet {
--color-text-primary: var(--color-light-2);
--color-text-emphatic: var(--color-light-1);
--color-text-secondary: var(--color-light-3);
--color-text-subtle: var(--color-light-5);
--color-text-light-highlight: #f0f0e0;
--color-text-light-heading: #c9c7b8;
--color-text-light-primary: #b5b3a4;
}
}
body.system-vermine2047.theme-dark .form-group > label { body.system-vermine2047.theme-dark .form-group > label {
color: #777777; color: #777777;
} }
@@ -97,6 +115,21 @@ ul.unstyled {
} }
} }
// Les fenêtres de choix (totem, acteur) peuvent dépasser la hauteur fixe :
// on autorise le défilement pour accéder à la totalité du contenu.
.application#TOTEM_PICKER .window-content,
.application#ACTOR_PICKER .window-content,
.application#TRAIT_SELECTOR .window-content {
overflow-y: auto;
}
// La fiche de groupe contient beaucoup de zones (membres, objectifs, totem…) :
// dès que le contenu dépasse la hauteur de la fenêtre, on autorise le défilement
// pour ne pas perdre l'accès aux zones du bas.
.application.sheet.vermine2047.group .window-content {
overflow-y: auto;
}
.window-content .row { .window-content .row {
&.smb { margin-bottom: 0.25rem; } &.smb { margin-bottom: 0.25rem; }
&.mdb { margin-bottom: 0.5rem; } &.mdb { margin-bottom: 0.5rem; }
@@ -113,6 +146,29 @@ ul.unstyled {
.rollable { .rollable {
.transition(); .transition();
// Affordance légère au repos : la zone est cliquable.
i.fa-dice-d10,
i.fa-dice {
font-size: 0.8em;
color: @color-acid-green;
}
}
// Sur la fiche créature, les valeurs lançables (Attaque…) sont fortement
// matérialisées : pastille de dé + bordure pointillée.
.application.sheet.vermine2047.creature {
.rollable {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 1px 5px;
border-radius: 3px;
color: @theme-color-light;
border: 1px dashed fade(@color-acid-green, 45%);
background: fade(@color-acid-green, 8%);
cursor: pointer;
}
} }
img.profile-img { img.profile-img {
@@ -420,7 +476,7 @@ body.system-vermine2047 img#logo {
} }
.item-name { .item-name {
padding-left: 5px; padding: 0 2px 0 5px;
text-align: left; text-align: left;
} }
} }
@@ -674,6 +730,107 @@ ol.chat-log {
color: @color-amber; color: @color-amber;
} }
} }
// ── Verdict ───────────────────────────────────────────────────────
.roll-verdict {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin: 6px 0;
padding: 6px 12px;
border-radius: 3px;
font-family: "DistressBlack";
text-transform: uppercase;
font-size: @font-size-16;
letter-spacing: 0.5px;
&.success {
color: @color-acid-green;
background: fade(@color-acid-green, 15%);
border: 1px solid @color-acid-green;
}
&.failure {
color: @color-hemolymph;
background: fade(@color-hemolymph, 15%);
border: 1px solid @color-hemolymph;
}
}
// ── Contexte du jet ───────────────────────────────────────────────
div.roll-context {
gap: 6px 12px;
margin: 4px 0 6px;
padding: 6px 8px;
background: rgba(0, 0, 0, 0.06);
.context-item {
display: inline-flex;
align-items: center;
gap: 5px;
.context-label {
font-family: "DistressBlack";
text-transform: uppercase;
font-size: @font-size-11;
opacity: 0.8;
}
.context-value {
font-family: "Roboto", sans-serif;
font-size: @font-size-12;
font-weight: 600;
}
&.totem-human .context-value { color: @totem-human-color; }
&.totem-adapted .context-value { color: @totem-adapted-color; }
}
}
// ── Composition du jet ────────────────────────────────────────────
div.roll-pool {
margin: 4px 0;
h4 {
font-family: "DistressBlack";
text-transform: uppercase;
font-size: @font-size-11;
margin: 4px 0;
border-bottom: none;
}
.pool-breakdown {
gap: 4px 10px;
.pool-item {
font-family: "Roboto", sans-serif;
font-size: @font-size-11;
&.pool-total {
font-weight: 700;
color: @theme-color-light;
}
}
}
}
// ── Résumé des dés ────────────────────────────────────────────────
div.roll-dice-summary {
gap: 6px 12px;
justify-content: center;
margin: 4px 0 2px;
padding: 4px 8px;
background: rgba(0, 0, 0, 0.06);
.die-summary {
font-family: "Roboto", sans-serif;
font-size: @font-size-11;
&.human { color: @totem-human-color; }
&.adapted { color: @totem-adapted-color; }
}
}
} }
div.item-card header img { div.item-card header img {
@@ -51,6 +51,7 @@ export default class VermineCharacterSheetV2 extends VermineBaseActorSheet {
case "main": break case "main": break
case "abilities": case "abilities":
context.tab = context.tabs.abilities context.tab = context.tabs.abilities
context.specialties = doc.itemTypes.specialty
break break
case "totem": case "totem":
context.tab = context.tabs.totem context.tab = context.tabs.totem
+40 -3
View File
@@ -2,6 +2,10 @@ import VermineBaseActorSheet from "./base-actor-sheet.mjs"
export default class VermineCreatureSheetV2 extends VermineBaseActorSheet { export default class VermineCreatureSheetV2 extends VermineBaseActorSheet {
// La fiche créature s'ouvre en mode édition : Gabarit, Taille, Rôle et Meute
// se règlent dès l'ouverture (le mode jeu reste accessible via le toggle).
_sheetMode = this.constructor.SHEET_MODES.EDIT
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ["creature"], classes: ["creature"],
position: { width: 700, height: 650 }, position: { width: 700, height: 650 },
@@ -61,10 +65,22 @@ export default class VermineCreatureSheetV2 extends VermineBaseActorSheet {
break break
case "stats": case "stats":
context.tab = context.tabs.stats context.tab = context.tabs.stats
context.patternLabel = doc.system.pattern?.value ? 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 || "" context.sizeLabel = doc.system.size?.value !== undefined ? game.i18n.localize(CONFIG.VERMINE.creatureSizeLevels[doc.system.size.value]?.label ?? "") : ""
context.roleLabel = doc.system.role?.value ? 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 || game.i18n.localize("VERMINE.none")
// Contribution cumulée de la Taille (les modificateurs se cumulent : Taille N = somme 1..N)
const sizeLevels = CONFIG.VERMINE.creatureSizeLevels || {}
const sizeLevel = doc.system.size?.value ?? 0
context.sizeCumulative = { attack: 0, vigor: 0, minorWound: 0, majorWound: 0, deadlyWound: 0 }
for (let i = 1; i <= sizeLevel; i++) {
const cfg = sizeLevels[i] || {}
context.sizeCumulative.attack += cfg.attack || 0
context.sizeCumulative.vigor += cfg.vigor || 0
context.sizeCumulative.minorWound += cfg.minorWound || 0
context.sizeCumulative.majorWound += cfg.majorWound || 0
context.sizeCumulative.deadlyWound += cfg.deadlyWound || 0
}
break break
case "combat": case "combat":
context.tab = context.tabs.combat context.tab = context.tabs.combat
@@ -77,4 +93,25 @@ export default class VermineCreatureSheetV2 extends VermineBaseActorSheet {
} }
return context return context
} }
/** @override - Les créatures n'ont pas de caractéristiques/compétences : on lance directement le pool d'attaque. */
async _onRoll(event) {
event.preventDefault()
const el = event.currentTarget
const type = el.dataset.type
if (type !== "creature-attack") return super._onRoll(event)
const pool = this.document.system.computed?.attack ?? 0
if (!pool) {
ui.notifications.warn(game.i18n.localize("VERMINE.error_select_ability"))
return
}
const difficulty = this.document.system.combatStatus?.difficulty ?? 7
const { VermineUtils } = await import("../../system/roll.mjs")
await VermineUtils.roll({
actor: this.document,
NoD: pool,
difficulty: parseInt(difficulty, 10),
rollLabel: game.i18n.localize("ADVERSITY.attack")
})
}
} }
+4
View File
@@ -2,6 +2,10 @@ 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
// niveaux se règlent dès l'ouverture (le mode jeu reste accessible via le toggle).
_sheetMode = this.constructor.SHEET_MODES.EDIT
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ["npc"], classes: ["npc"],
position: { width: 750, height: 680 }, position: { width: 750, height: 680 },
+1 -1
View File
@@ -219,7 +219,7 @@ const reqInt = { required: true, nullable: false, integer: true }
export function raritySchema() { export function raritySchema() {
const fields = foundry.data.fields const fields = foundry.data.fields
return new fields.SchemaField({ return new fields.SchemaField({
value: new fields.NumberField({ ...reqInt, initial: 3, min: 1, max: 5 }), value: new fields.NumberField({ ...reqInt, initial: 3, min: 1, max: 10 }),
handicap: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }) handicap: new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
}) })
} }
+32 -11
View File
@@ -48,7 +48,7 @@ export default class VermineCreatureData extends foundry.abstract.TypeDataModel
// Niveaux de créature (patron, taille, rôle, meute) // Niveaux de créature (patron, taille, rôle, meute)
pattern: attributeSchema(1, 1, 4), pattern: attributeSchema(1, 1, 4),
size: attributeSchema(1, 1, 3), size: attributeSchema(0, 0, 3),
role: attributeSchema(1, 1, 4), role: attributeSchema(1, 1, 4),
pack: attributeSchema(0, 0, 3), pack: attributeSchema(0, 0, 3),
@@ -97,28 +97,38 @@ export default class VermineCreatureData extends foundry.abstract.TypeDataModel
*/ */
_calculateCreatureComputedValues() { _calculateCreatureComputedValues() {
const patternLevel = this.pattern?.value || 1 const patternLevel = this.pattern?.value || 1
const sizeLevel = this.size?.value || 1 const sizeLevel = this.size?.value || 0
const roleLevel = this.role?.value || 1 const roleLevel = this.role?.value || 1
const packLevel = this.pack?.value || 0 const packLevel = this.pack?.value || 0
const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {} const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {}
const sizeConfig = CONFIG.VERMINE.creatureSizeLevels[sizeLevel] || {}
const roleConfig = CONFIG.VERMINE.creatureRoleLevels[roleLevel] || {} const roleConfig = CONFIG.VERMINE.creatureRoleLevels[roleLevel] || {}
const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {} const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {}
// Les modificateurs de Taille se cumulent : une créature de Taille N applique
// les modificateurs de chaque Taille de 1 à N (Taille 0 = insecte, aucun).
const sizeLevels = CONFIG.VERMINE.creatureSizeLevels || {}
let sizeAttack = 0
let sizeVigor = 0
for (let i = 1; i <= sizeLevel; i++) {
const cfg = sizeLevels[i] || {}
sizeAttack += cfg.attack || 0
sizeVigor += cfg.vigor || 0
}
// Attaque : patron + taille + meute + réaction du rôle // Attaque : patron + taille + meute + réaction du rôle
this.computed.attack = (patternConfig.attack || 0) this.computed.attack = (patternConfig.attack || 0)
+ (sizeConfig.attack || 0) + sizeAttack
+ (packConfig.attack || 0) + (packConfig.attack || 0)
+ (roleConfig.reaction || 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)
+ (sizeConfig.vigor || 0) + sizeVigor
+ (packConfig.damage || 0) + (packConfig.damage || 0)
// Vigueur : taille + meute // Vigueur : taille + meute
this.computed.vigor = (sizeConfig.vigor || 0) + (packConfig.damage || 0) this.computed.vigor = sizeVigor + (packConfig.damage || 0)
// Réaction : rôle // Réaction : rôle
this.computed.reaction = (roleConfig.reaction || 0) + (roleConfig.reaction_bonus || 0) this.computed.reaction = (roleConfig.reaction || 0) + (roleConfig.reaction_bonus || 0)
@@ -141,21 +151,32 @@ export default class VermineCreatureData extends foundry.abstract.TypeDataModel
*/ */
_calculateCreatureWoundThresholds() { _calculateCreatureWoundThresholds() {
const patternLevel = this.pattern?.value || 1 const patternLevel = this.pattern?.value || 1
const sizeLevel = this.size?.value || 1 const sizeLevel = this.size?.value || 0
const packLevel = this.pack?.value || 0 const packLevel = this.pack?.value || 0
const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {} const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {}
const sizeConfig = CONFIG.VERMINE.creatureSizeLevels[sizeLevel] || {}
const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {} const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {}
// Les modificateurs de Taille se cumulent (Taille N = somme des tailles 1..N).
const sizeLevels = CONFIG.VERMINE.creatureSizeLevels || {}
let sizeMinor = 0
let sizeMajor = 0
let sizeDeadly = 0
for (let i = 1; i <= sizeLevel; i++) {
const cfg = sizeLevels[i] || {}
sizeMinor += cfg.minorWound || 0
sizeMajor += cfg.majorWound || 0
sizeDeadly += cfg.deadlyWound || 0
}
this.minorWound.threshold = (patternConfig.minorWound || 0) this.minorWound.threshold = (patternConfig.minorWound || 0)
+ (sizeConfig.minorWound || 0) + sizeMinor
+ (packConfig.minorWound || 0) + (packConfig.minorWound || 0)
this.majorWound.threshold = (patternConfig.majorWound || 0) this.majorWound.threshold = (patternConfig.majorWound || 0)
+ (sizeConfig.majorWound || 0) + sizeMajor
+ (packConfig.majorWound || 0) + (packConfig.majorWound || 0)
this.deadlyWound.threshold = (patternConfig.deadlyWound || 0) this.deadlyWound.threshold = (patternConfig.deadlyWound || 0)
+ (sizeConfig.deadlyWound || 0) + sizeDeadly
+ (packConfig.deadlyWound || 0) + (packConfig.deadlyWound || 0)
// Max de blessures // Max de blessures
+3 -1
View File
@@ -10,9 +10,11 @@ export default class VermineSpecialtyData extends foundry.abstract.TypeDataModel
/** @override */ /** @override */
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields const fields = foundry.data.fields
const reqInt = { required: true, nullable: false, integer: true }
return { return {
name: new fields.StringField({ required: true, nullable: false, initial: "" }), name: new fields.StringField({ required: true, nullable: false, initial: "" }),
skill: new fields.StringField({ required: true, nullable: false, initial: "" }) skill: new fields.StringField({ required: true, nullable: false, initial: "" }),
level: new fields.NumberField({ ...reqInt, initial: 1, min: 0, max: 5 })
} }
} }
} }
+4 -3
View File
@@ -139,9 +139,10 @@ VERMINE.creaturePatternLevels = {
* Creature Size Levels configuration * Creature Size Levels configuration
*/ */
VERMINE.creatureSizeLevels = { VERMINE.creatureSizeLevels = {
1: { "label": "SIZE_LEVELS.small", "attack": 2, "vigor": 1, "minorWound": 0, "majorWound": 0, "deadlyWound": 1 }, 0: { "label": "SIZE_LEVELS.tiny", "attack": 0, "vigor": 0, "minorWound": 0, "majorWound": 0, "deadlyWound": 0 },
2: { "label": "SIZE_LEVELS.medium", "attack": 3, "vigor": 2, "minorWound": 0, "majorWound": 1, "deadlyWound": 1 }, 1: { "label": "SIZE_LEVELS.small", "attack": 1, "vigor": 1, "minorWound": 0, "majorWound": 1, "deadlyWound": 1 },
3: { "label": "SIZE_LEVELS.large", "attack": 4, "vigor": 3, "minorWound": 1, "majorWound": 1, "deadlyWound": 1 } 2: { "label": "SIZE_LEVELS.medium", "attack": 2, "vigor": 2, "minorWound": 2, "majorWound": 2, "deadlyWound": 2 },
3: { "label": "SIZE_LEVELS.large", "attack": 3, "vigor": 3, "minorWound": 3, "majorWound": 3, "deadlyWound": 3 }
} }
/** /**
+26 -16
View File
@@ -112,18 +112,32 @@ export default class RollDialog extends HandlebarsApplicationMixin(foundry.appli
#getHandicap() { return this.#el.querySelector("#handicap"); } #getHandicap() { return this.#el.querySelector("#handicap"); }
#getSelfCtrl() { return this.#el.querySelector("#self_control"); } #getSelfCtrl() { return this.#el.querySelector("#self_control"); }
getDicePool() { /** Décomposition du pool de dés pour affichage dans le message. */
getPoolBreakdown() {
const abil = this.#getAbility(); const abil = this.#getAbility();
const abilVal = parseInt(abil?.options[abil?.selectedIndex]?.value, 10) || 0; const ability = parseInt(abil?.options[abil?.selectedIndex]?.value, 10) || 0;
const skill = this.#getSkill(); const skill = this.#getSkill();
const skillPool = parseInt(skill?.options[skill?.selectedIndex]?.dataset?.pool, 10) || 0; const skillPool = parseInt(skill?.options[skill?.selectedIndex]?.dataset?.pool, 10) || 0;
const sc = parseInt(this.#getSelfCtrl()?.value, 10) || 0; const selfControl = parseInt(this.#getSelfCtrl()?.value, 10) || 0;
const specChecked = this.#el.querySelector("#usingSpecialization")?.checked; const specChecked = this.hasSpecialtySelected();
const helped = this.#el.querySelector("#helped")?.checked; const helped = this.#el.querySelector("#helped")?.checked;
const tools = this.#el.querySelector("input[name='usingTools']:checked")?.value !== "0"; const toolsChecked = this.#el.querySelector("input[name='usingTools']:checked");
const tools = toolsChecked && toolsChecked.value !== "0";
const group = parseInt(this.#el.querySelector("#group")?.value, 10) || 0; const group = parseInt(this.#el.querySelector("#group")?.value, 10) || 0;
const bonuses = (specChecked ? 1 : 0) + (helped ? 1 : 0) + (tools ? 1 : 0) + group; const specialty = specChecked ? 1 : 0;
return (abilVal + sc + skillPool + bonuses) || 0; const help = helped ? 1 : 0;
const tooling = tools ? 1 : 0;
const total = ability + skillPool + selfControl + specialty + help + tooling + group;
return { ability, skill: skillPool, selfControl, specialty, help, tooling, group, total };
}
getDicePool() {
return this.getPoolBreakdown().total;
}
getSpecialtyName() {
const checked = this.#el.querySelector("input[name='usingSpecialization']:checked");
return checked && checked.value !== "aucune" ? checked.value : null;
} }
getDifficultySelect() { getDifficultySelect() {
@@ -207,14 +221,8 @@ export default class RollDialog extends HandlebarsApplicationMixin(foundry.appli
} }
#calculateBonusCount() { #calculateBonusCount() {
let b = 0; const b = this.getPoolBreakdown();
if (this.#el.querySelector("#helped")?.checked) b += 1; return b.specialty + b.help + b.tooling + b.group + b.selfControl;
b += parseInt(this.#el.querySelector("#group")?.value, 10) || 0;
b += parseInt(this.#getSelfCtrl()?.value, 10) || 0;
const tools = this.#el.querySelector("input[name='usingTools']:checked");
if (tools && tools.value !== "0") b += 1;
if (this.hasSpecialtySelected()) b += 1;
return b;
} }
#updateUI() { #updateUI() {
@@ -315,7 +323,9 @@ export default class RollDialog extends HandlebarsApplicationMixin(foundry.appli
keepTotem: this.getKeepTotem(), keepTotem: this.getKeepTotem(),
skillCategory: this.getSkillCategory(), skillCategory: this.getSkillCategory(),
skillLevel: this.getSkillLevel(), skillLevel: this.getSkillLevel(),
hasSpecialty: this.hasSpecialtySelected() hasSpecialty: this.hasSpecialtySelected(),
poolBreakdown: this.getPoolBreakdown(),
specialtyName: this.getSpecialtyName()
}); });
this.close(); this.close();
+19
View File
@@ -32,6 +32,7 @@ export const preloadHandlebarsTemplates = async function () {
// npc partials // npc partials
"systems/vermine2047/templates/actor/npc/npc-combat.hbs", "systems/vermine2047/templates/actor/npc/npc-combat.hbs",
"systems/vermine2047/templates/actor/parts/npc-skill-category.hbs", "systems/vermine2047/templates/actor/parts/npc-skill-category.hbs",
"systems/vermine2047/templates/actor/parts/npc-skill-item.hbs",
// creature partials // creature partials
"systems/vermine2047/templates/actor/creature/creature-combat.hbs", "systems/vermine2047/templates/actor/creature/creature-combat.hbs",
@@ -100,6 +101,21 @@ export const registerHandlebarsHelpers = function () {
return game.i18n.localize(arrayLabel + "." + objectLabel + ".name"); return game.i18n.localize(arrayLabel + "." + objectLabel + ".name");
}); });
// Return an object of skills ordered alphabetically by their localized
// display name (easier to find a skill quickly). Use with `{{#each}}`.
Handlebars.registerHelper('sortedSkills', function (skills) {
if (!skills || typeof skills !== 'object') return skills;
const sorted = {};
Object.keys(skills)
.sort((a, b) => {
const na = game.i18n.localize(`SKILLS.${a}.name`).toLocaleLowerCase();
const nb = game.i18n.localize(`SKILLS.${b}.name`).toLocaleLowerCase();
return na.localeCompare(nb, game.i18n.lang);
})
.forEach(k => { sorted[k] = skills[k]; });
return sorted;
});
Handlebars.registerHelper('smarttlk', function (arrayLabel, objectLabel, key) { Handlebars.registerHelper('smarttlk', function (arrayLabel, objectLabel, key) {
return game.i18n.localize(arrayLabel + "." + objectLabel + "." + key); return game.i18n.localize(arrayLabel + "." + objectLabel + "." + key);
}); });
@@ -347,6 +363,9 @@ export const registerHandlebarsHelpers = function () {
Handlebars.registerHelper('ifincludes', function (arg1, arg2, options) { Handlebars.registerHelper('ifincludes', function (arg1, arg2, options) {
return (arg1.includes(arg2)) ? options.fn(this) : options.inverse(this); return (arg1.includes(arg2)) ? options.fn(this) : options.inverse(this);
}); });
Handlebars.registerHelper('ifStartsWith', function (arg1, arg2, options) {
return (typeof arg1 === 'string' && arg1.startsWith(arg2)) ? options.fn(this) : options.inverse(this);
});
//math operations //math operations
+113 -27
View File
@@ -29,7 +29,9 @@ export class VermineUtils {
keepTotem = null, keepTotem = null,
skillLevel = null, skillLevel = null,
hasSpecialty = false, hasSpecialty = false,
handicap = 0 handicap = 0,
poolBreakdown = null,
specialtyName = null
}) { }) {
// Validate inputs // Validate inputs
if (!actor) { if (!actor) {
@@ -105,6 +107,27 @@ export class VermineUtils {
// Build final formula // Build final formula
formula = modFormula !== null ? `${baseFormula}+${modFormula}` : baseFormula; formula = modFormula !== null ? `${baseFormula}+${modFormula}` : baseFormula;
// Reconcile the breakdown displayed in chat with the dice actually rolled.
// Domain bonuses/malus are only applied when the corresponding totem die
// is used, and totem dice replace (not add to) base-pool dice.
if (poolBreakdown) {
// Count totem dice present in the final modifier formula.
let totemDice = 0;
if (modFormula) {
if (modFormula.includes("human_")) totemDice += 1;
if (modFormula.includes("adapted_")) totemDice += 1;
}
// Only count the domain bonus of totems actually activated in the roll.
const domainBonus = (totems.human ? totemBonus.human : 0)
+ (totems.adapted ? totemBonus.adapted : 0);
const basePool = poolBreakdown.total;
poolBreakdown.totemDice = totemDice;
poolBreakdown.domainBonus = domainBonus;
// Total = nombre réel de dés lancés (NoD réguliers + dés de totem).
// Équivaut à basePool + domainBonus sauf si le pool régulier clampe à 0.
poolBreakdown.total = NoD + totemDice;
}
// Create the roll // Create the roll
const roll = new Roll(formula, actor.getRollData()); const roll = new Roll(formula, actor.getRollData());
@@ -143,7 +166,9 @@ export class VermineUtils {
keepTotem, keepTotem,
skillLevel, skillLevel,
hasSpecialty, hasSpecialty,
handicap handicap,
poolBreakdown,
specialtyName
}); });
return roll; return roll;
@@ -199,10 +224,10 @@ export class VermineUtils {
/** /**
* Handles reroll events on dice in chat messages. * Handles reroll events on dice in chat messages.
* @param {Object} message - The chat message containing the reroll event * @param {Object} message - The chat message containing the reroll event
* @param {Object} ev - The reroll event * @param {HTMLElement} target - The clicked die element
* @returns {Promise<boolean>} Whether the reroll was successful * @returns {Promise<boolean>} Whether the reroll was successful
*/ */
static async onReroll(message, ev) { static async onReroll(message, target) {
// Verify user permissions // Verify user permissions
const msgUserId = message.user?.id ?? message.user; const msgUserId = message.user?.id ?? message.user;
if (msgUserId !== game.user?.id && !game.user?.isGM) { if (msgUserId !== game.user?.id && !game.user?.isGM) {
@@ -211,7 +236,7 @@ export class VermineUtils {
} }
// Get reroll count // Get reroll count
const rollMessage = ev.currentTarget.closest('div.vermine-roll-message'); const rollMessage = target.closest('div.vermine-roll-message');
if (!rollMessage) { if (!rollMessage) {
return false; return false;
} }
@@ -221,22 +246,22 @@ export class VermineUtils {
// Check if rerolls are available // Check if rerolls are available
if (!rerollCount || parseInt(rerollCount, 10) < 1) { if (!rerollCount || parseInt(rerollCount, 10) < 1) {
ui.notifications.warn(game.i18n.localize('VERMINE.error_no_rerolls_left')); ui.notifications.warn(game.i18n.localize('VERMINE.error_no_rerolls_left'));
const rerollables = ev.currentTarget.closest('ul')?.querySelectorAll('.rerollable'); const rerollables = target.closest('ul')?.querySelectorAll('.rerollable');
if (rerollables) { if (rerollables) {
rerollables.forEach(el => el.classList.remove('rerollable')); rerollables.forEach(el => el.classList.remove('rerollable'));
} }
return false; return false;
} }
ev.currentTarget.classList.add('rerolled'); target.classList.add('rerolled');
// Set reroll flag // Set reroll flag
await message.setFlag("world", "reroll", true); await message.setFlag("world", "reroll", true);
// Get difficulty and dice type // Get difficulty and dice type
const ulElement = ev.currentTarget.closest('ul'); const ulElement = target.closest('ul');
const difficulty = ulElement?.dataset.difficulty ?? 7; const difficulty = ulElement?.dataset.difficulty ?? 7;
let diceType = ev.currentTarget.dataset.diceType; let diceType = target.dataset.diceType;
// Sanitize user name // Sanitize user name
const safeUserName = (game.user?.name ?? "user").replace(/[^a-zA-Z0-9_]/g, '_'); const safeUserName = (game.user?.name ?? "user").replace(/[^a-zA-Z0-9_]/g, '_');
@@ -265,30 +290,68 @@ export class VermineUtils {
// Update die display // Update die display
const result = reroll.dice[0]?.results[0]?.result ?? 0; const result = reroll.dice[0]?.results[0]?.result ?? 0;
const dieSpan = ev.currentTarget.querySelector('span'); const dieSpan = target.querySelector('span');
if (dieSpan) { if (dieSpan) {
dieSpan.innerText = result; dieSpan.innerText = result;
} }
// Update the face used for the CSS background image.
target.dataset.result = result;
// Update total if successful // Set/clear the success class from the rerolled die (a previous success
// must be removed if the new result fails).
const success = reroll.dice[0]?.results[0]?.success; const success = reroll.dice[0]?.results[0]?.success;
if (success) { target.classList.toggle('success', !!success);
ev.currentTarget.classList.add('success'); target.classList.remove("rerollable");
const totalElement = rollMessage.querySelector('#total');
if (totalElement) { // Recompute the total from the live dice in the DOM. This avoids the
const currentTotal = parseInt(totalElement.innerText, 10) || 0; // cumulative-increment drift of the old logic (which never removed a
totalElement.innerText = currentTotal + reroll.total; // removed success from #total). Totem dice (human/adapted) count double.
} const totalEl = rollMessage.querySelector('#total');
if (totalEl) {
let total = 0;
rollMessage.querySelectorAll('li.die.success').forEach(die => {
const isTotem = die.classList.contains('human') || die.classList.contains('adapted');
total += isTotem ? 2 : 1;
});
totalEl.innerText = total;
} }
// Update message content // Update message content
ev.currentTarget.classList.remove("rerollable"); const messageContent = target.closest('div.message-content');
const messageContent = ev.currentTarget.closest('div.message-content');
if (messageContent) { if (messageContent) {
const newRerollCount = parseInt(rerollCount, 10) - 1; const newRerollCount = parseInt(rerollCount, 10) - 1;
rollMessage.querySelector('#allowed_reroll').innerText = newRerollCount; rollMessage.querySelector('#allowed_reroll').innerText = newRerollCount;
// Recompute the verdict banner from the recomputed total.
const verdictEl = rollMessage.querySelector('.roll-verdict');
const requiredEl = rollMessage.querySelector('#required');
if (verdictEl && totalEl && requiredEl) {
const total = parseInt(totalEl.innerText, 10) || 0;
const required = parseInt(requiredEl.innerText, 10) || 1;
const won = total >= required;
verdictEl.classList.toggle('success', won);
verdictEl.classList.toggle('failure', !won);
const label = won
? game.i18n.localize('VERMINE.roll_success')
: game.i18n.localize('VERMINE.roll_failure');
verdictEl.innerHTML = `${won ? '<i class="fas fa-check-circle"></i>' : '<i class="fas fa-times-circle"></i>'}<span>${label}</span>`;
}
// Recompute the dice summary from the current die list.
const summaryEl = rollMessage.querySelector('.roll-dice-summary');
if (summaryEl) {
const stats = { regular: { count: 0, success: 0 }, human: { count: 0, success: 0 }, adapted: { count: 0, success: 0 } };
rollMessage.querySelectorAll('li.die').forEach(die => {
const type = die.classList.contains('human') ? 'human' : die.classList.contains('adapted') ? 'adapted' : 'regular';
stats[type].count += 1;
if (die.classList.contains('success')) stats[type].success += 1;
});
const mk = (key, count, s) => count ? `<span class="die-summary ${key}">${count} ${game.i18n.localize(`VERMINE.die_${key}`)} · ${s} ${game.i18n.localize('VERMINE.die_successes')}</span>` : '';
summaryEl.innerHTML = mk('regular', stats.regular.count, stats.regular.success)
+ mk('human', stats.human.count, stats.human.success)
+ mk('adapted', stats.adapted.count, stats.adapted.success);
}
await message.update({ await message.update({
content: messageContent.outerHTML content: messageContent.outerHTML
}); });
@@ -314,10 +377,11 @@ export class VermineUtils {
html.querySelectorAll('.rerollable').forEach(el => { html.querySelectorAll('.rerollable').forEach(el => {
el.addEventListener('click', async (ev) => { el.addEventListener('click', async (ev) => {
ev.preventDefault(); ev.preventDefault();
const msgId = ev.currentTarget.closest("li.message")?.dataset?.messageId; const dieEl = ev.currentTarget;
const msgId = dieEl.closest("li.message")?.dataset?.messageId;
if (msgId) { if (msgId) {
const message = await game.messages.get(msgId); const message = await game.messages.get(msgId);
await VermineUtils.onReroll(message, ev); await VermineUtils.onReroll(message, dieEl);
} }
}); });
}); });
@@ -336,6 +400,7 @@ export class VermineUtils {
// Add click event for granting rerolls // Add click event for granting rerolls
html.querySelectorAll("button.grant-reroll").forEach(el => { html.querySelectorAll("button.grant-reroll").forEach(el => {
el.addEventListener('click', async (ev) => { el.addEventListener('click', async (ev) => {
const btn = ev.currentTarget;
const grantedRerollElement = html.querySelector('#granted-reroll'); const grantedRerollElement = html.querySelector('#granted-reroll');
const allowedRerollElement = html.querySelector("#allowed_reroll"); const allowedRerollElement = html.querySelector("#allowed_reroll");
@@ -343,13 +408,13 @@ export class VermineUtils {
allowedRerollElement.innerText = grantedRerollElement.innerText; allowedRerollElement.innerText = grantedRerollElement.innerText;
} }
const mesEl = ev.currentTarget.closest('[data-message-id]'); const mesEl = btn.closest('[data-message-id]');
const messageId = mesEl?.dataset?.messageId; const messageId = mesEl?.dataset?.messageId;
if (messageId) { if (messageId) {
ev.currentTarget.closest('.reroll-from-effort').style.display = "none"; btn.closest('.reroll-from-effort').style.display = "none";
const rollMessage = ev.currentTarget.closest(".vermine-roll-message"); const rollMessage = btn.closest(".vermine-roll-message");
if (rollMessage) { if (rollMessage) {
const content = rollMessage.outerHTML; const content = rollMessage.outerHTML;
const message = await game.messages.get(messageId); const message = await game.messages.get(messageId);
@@ -401,7 +466,28 @@ export class VermineUtils {
* @returns {Promise<ChatMessage>} The created chat message * @returns {Promise<ChatMessage>} The created chat message
*/ */
static async diplayChatRoll(roll, param) { static async diplayChatRoll(roll, param) {
const content = await foundry.applications.handlebars.renderTemplate("systems/vermine2047/templates/roll-message.hbs", { roll, param }); // Aggregate dice stats per type (regular / human / adapted)
const diceStats = { regular: { count: 0, success: 0 }, human: { count: 0, success: 0 }, adapted: { count: 0, success: 0 } };
for (const die of roll.dice ?? []) {
// Flavors look like "<type>_<user>"; classify from the leading token.
const flavor = die.options?.flavor ?? "";
const prefix = flavor.split("_")[0];
const type = prefix === "human" ? "human" : prefix === "adapted" ? "adapted" : "regular";
for (const r of die.results ?? []) {
diceStats[type].count += 1;
if (r.success) diceStats[type].success += 1;
}
}
// Verdict
const required = 1 + (param.handicap ?? 0);
const total = roll._total ?? 0;
const verdict = {
success: total >= required,
total,
required
};
const content = await foundry.applications.handlebars.renderTemplate("systems/vermine2047/templates/roll-message.hbs", { roll, param, diceStats, verdict });
const chatData = { const chatData = {
user: game.user?._id, user: game.user?._id,
speaker: ChatMessage.getSpeaker(), speaker: ChatMessage.getSpeaker(),
+1
View File
@@ -221,6 +221,7 @@
{{> "systems/vermine2047/templates/actor/parts/npc-skill-category.hbs" {{> "systems/vermine2047/templates/actor/parts/npc-skill-category.hbs"
categoryKey=key categoryKey=key
categoryLabel=(concat "VERMINE.skill_category." key) categoryLabel=(concat "VERMINE.skill_category." key)
skills=@root.system.skills
}} }}
{{/if}} {{/if}}
{{/each}} {{/each}}
@@ -55,7 +55,7 @@
> >
{{/if}} {{/if}}
</h4> </h4>
{{#each @root.system.skills as |skill skey|}} {{#each (sortedSkills @root.system.skills) as |skill skey|}}
{{#if (eq skill.category sckey) }} {{#if (eq skill.category sckey) }}
<div class="ability flexrow "> <div class="ability flexrow ">
@@ -79,12 +79,10 @@
data-action="addSpecialty" data-action="addSpecialty"
></i> ></i>
{{/ifgt}} {{/ifgt}}
{{#each skill.specialties as |spe ind|}} {{#each @root.specialties as |spe|}}
{{#ife spe.system.skill skey}} {{#ife spe.system.skill skey}}
<i <span class="specialty" data-tooltip="spécialité utilisable"
class="specialty" >{{spe.name}}<sup>{{spe.system.level}}</sup></span>
data-tooltip="spécialité utilisable"
>{{spe.name}}</i>
{{/ife}} {{/ife}}
{{/each}} {{/each}}
</div> </div>
+11 -14
View File
@@ -1,6 +1,14 @@
<div class="tab equipment sheet-part" data-group="sheet" data-tab="equipment"> <div class="tab equipment sheet-part" data-group="sheet" data-tab="equipment">
<div class="grid grid-2col"> <div class="weapons">
<div> <h4>{{localize 'ITEMS.weapons'}}</h4>
{{> "systems/vermine2047/templates/actor/parts/actor-weapons.hbs"}}
</div>
<div class="protections">
<h4>{{localize 'ITEMS.defenses'}}</h4>
{{> "systems/vermine2047/templates/actor/parts/actor-defenses.hbs"}}
</div>
<div class="objects">
<h4>{{localize 'ITEMS.items'}}</h4>
<ol class="items-list"> <ol class="items-list">
<li class="item flexrow items-header"> <li class="item flexrow items-header">
<div <div
@@ -11,14 +19,12 @@
<div class="item-quantity">{{ localize 'VERMINE.qty'}}</div> <div class="item-quantity">{{ localize 'VERMINE.qty'}}</div>
<div class="item-weight">{{ localize 'VERMINE.weight'}}</div> <div class="item-weight">{{ localize 'VERMINE.weight'}}</div>
<div class="item-controls"> <div class="item-controls">
{{#if isEditMode}}
<a <a
class="item-control item-create" class="item-control item-create"
title="Create item" title="Create item"
data-action="create" data-action="create"
data-type="item" data-type="item"
><i class="fas fa-plus"></i></a> ><i class="fas fa-plus"></i></a>
{{/if}}
</div> </div>
</li> </li>
{{#each gear as |item id|}} {{#each gear as |item id|}}
@@ -80,17 +86,8 @@
{{/each}} {{/each}}
</ol> </ol>
</div> </div>
<div> <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}}
</div> </div>
</div> </div>
<div class="weapons">
<h4>{{localize 'ITEMS.weapons'}}</h4>
{{> "systems/vermine2047/templates/actor/parts/actor-weapons.hbs"}}
</div>
<div class="protections">
<h4>{{localize 'ITEMS.defenses'}}</h4>
{{> "systems/vermine2047/templates/actor/parts/actor-defenses.hbs"}}
</div>
</div>
+12 -5
View File
@@ -1,5 +1,12 @@
<div class="tab stats sheet-part" data-group="sheet" data-tab="stats"> <div class="tab stats sheet-part" data-group="sheet" data-tab="stats">
<h3>{{ localize 'VERMINE.base_values' }}</h3> <h3>{{ localize 'VERMINE.base_values' }}</h3>
{{!-- Total attaque (lancable) --}}
<div class="mdb align-center">
<h4>{{ localize 'VERMINE.total_attack' }}</h4>
<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>
<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>
@@ -16,12 +23,12 @@
<div class="mdb"> <div class="mdb">
<h4 class="align-center">{{ localize 'ADVERSITY.size' }}: {{ sizeLabel }}</h4> <h4 class="align-center">{{ localize 'ADVERSITY.size' }}: {{ sizeLabel }}</h4>
<ul class="unstyled"> <ul class="unstyled">
<li>{{ localize 'ADVERSITY.attack' }}: {{ creatureSizeLevel "attack" system.size.value }}</li> <li>{{ localize 'ADVERSITY.attack' }}: {{ sizeCumulative.attack }}</li>
<li>{{ localize 'ADVERSITY.vigor' }}: {{ creatureSizeLevel "vigor" system.size.value }}</li> <li>{{ localize 'ADVERSITY.vigor' }}: {{ sizeCumulative.vigor }}</li>
<li>{{ localize 'ADVERSITY.wounds' }}: <li>{{ localize 'ADVERSITY.wounds' }}:
{{ creatureSizeLevel "minorWound" system.size.value }}/ {{ sizeCumulative.minorWound }}/
{{ creatureSizeLevel "majorWound" system.size.value }}/ {{ sizeCumulative.majorWound }}/
{{ creatureSizeLevel "deadlyWound" system.size.value }} {{ sizeCumulative.deadlyWound }}
</li> </li>
</ul> </ul>
</div> </div>
@@ -6,7 +6,7 @@
<h4 class="ability-category-header">{{ localize 'VERMINE.ability_category.physical' }}</h4> <h4 class="ability-category-header">{{ localize 'VERMINE.ability_category.physical' }}</h4>
<div class="grid grid-2col"> <div class="grid grid-2col">
<div class="ability-item"> <div class="ability-item">
<label for="system.abilities.vigor.value">{{ localize 'ABILITIES.vigor.name' }}</label> <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"> <div class="ability-control">
{{#if isEditMode}} {{#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"/> <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"/>
@@ -15,7 +15,7 @@
</div> </div>
</div> </div>
<div class="ability-item"> <div class="ability-item">
<label for="system.abilities.health.value">{{ localize 'ABILITIES.health.name' }}</label> <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"> <div class="ability-control">
{{#if isEditMode}} {{#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"/> <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"/>
@@ -31,7 +31,7 @@
<h4 class="ability-category-header">{{ localize 'VERMINE.ability_category.manual' }}</h4> <h4 class="ability-category-header">{{ localize 'VERMINE.ability_category.manual' }}</h4>
<div class="grid grid-2col"> <div class="grid grid-2col">
<div class="ability-item"> <div class="ability-item">
<label for="system.abilities.precision.value">{{ localize 'ABILITIES.precision.name' }}</label> <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"> <div class="ability-control">
{{#if isEditMode}} {{#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"/> <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"/>
@@ -40,7 +40,7 @@
</div> </div>
</div> </div>
<div class="ability-item"> <div class="ability-item">
<label for="system.abilities.reflexes.value">{{ localize 'ABILITIES.reflexes.name' }}</label> <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"> <div class="ability-control">
{{#if isEditMode}} {{#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"/> <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"/>
@@ -56,7 +56,7 @@
<h4 class="ability-category-header">{{ localize 'VERMINE.ability_category.mental' }}</h4> <h4 class="ability-category-header">{{ localize 'VERMINE.ability_category.mental' }}</h4>
<div class="grid grid-2col"> <div class="grid grid-2col">
<div class="ability-item"> <div class="ability-item">
<label for="system.abilities.knowledge.value">{{ localize 'ABILITIES.knowledge.name' }}</label> <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"> <div class="ability-control">
{{#if isEditMode}} {{#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"/> <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"/>
@@ -65,7 +65,7 @@
</div> </div>
</div> </div>
<div class="ability-item"> <div class="ability-item">
<label for="system.abilities.perception.value">{{ localize 'ABILITIES.perception.name' }}</label> <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"> <div class="ability-control">
{{#if isEditMode}} {{#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"/> <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"/>
@@ -81,7 +81,7 @@
<h4 class="ability-category-header">{{ localize 'VERMINE.ability_category.social' }}</h4> <h4 class="ability-category-header">{{ localize 'VERMINE.ability_category.social' }}</h4>
<div class="grid grid-2col"> <div class="grid grid-2col">
<div class="ability-item"> <div class="ability-item">
<label for="system.abilities.will.value">{{ localize 'ABILITIES.will.name' }}</label> <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"> <div class="ability-control">
{{#if isEditMode}} {{#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"/> <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"/>
@@ -90,7 +90,7 @@
</div> </div>
</div> </div>
<div class="ability-item"> <div class="ability-item">
<label for="system.abilities.empathy.value">{{ localize 'ABILITIES.empathy.name' }}</label> <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"> <div class="ability-control">
{{#if isEditMode}} {{#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"/> <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"/>
+1
View File
@@ -27,6 +27,7 @@
{{> "systems/vermine2047/templates/actor/parts/npc-skill-category.hbs" {{> "systems/vermine2047/templates/actor/parts/npc-skill-category.hbs"
categoryKey=key categoryKey=key
categoryLabel=(concat "VERMINE.skill_category." key) categoryLabel=(concat "VERMINE.skill_category." key)
skills=@root.system.skills
}} }}
{{/ife}} {{/ife}}
{{/each}} {{/each}}
@@ -48,7 +48,7 @@
{{/ife}} {{/ife}}
> >
</h4> </h4>
{{#each @root.system.skills as |skill skey|}} {{#each (sortedSkills @root.system.skills) as |skill skey|}}
{{#if (eq skill.category sckey) }} {{#if (eq skill.category sckey) }}
<div class="ability flexrow "> <div class="ability flexrow ">
@@ -70,14 +70,16 @@
data-tooltip="ajout de spécialité" data-tooltip="ajout de spécialité"
></i> ></i>
{{/ifgt}} {{/ifgt}}
{{#each skill.specialties as |spe ind|}} {{#if @root.specialties}}
{{#each @root.specialties as |spe|}}
{{#ife spe.system.skill skey}} {{#ife spe.system.skill skey}}
<i <span
class="specialty" class="specialty"
data-tooltip="spécialité utilisable" data-tooltip="spécialité utilisable"
>{{spe.name}}</i> >{{spe.name}}<sup>{{spe.system.level}}</sup></span>
{{/ife}} {{/ife}}
{{/each}} {{/each}}
{{/if}}
</div> </div>
<select <select
name="system.skills.{{skey}}.value" name="system.skills.{{skey}}.value"
+1
View File
@@ -15,6 +15,7 @@
data-type="defense" data-type="defense"
data-action="create" data-action="create"
><i class="fas fa-plus"></i></a> ><i class="fas fa-plus"></i></a>
<a style="visibility:hidden;" aria-hidden="true"><i class="fas fa-trash"></i></a>
</div> </div>
</li> </li>
{{#each defenses as |item id|}} {{#each defenses as |item id|}}
+2
View File
@@ -9,6 +9,8 @@
<div class="item-controls"> <div class="item-controls">
<a class="item-control item-create" title="Create item" <a class="item-control item-create" title="Create item"
data-type="weapon" data-action="create"><i class="fas fa-plus"></i></a> data-type="weapon" data-action="create"><i class="fas fa-plus"></i></a>
<a style="visibility:hidden;" aria-hidden="true"><i class="fas fa-trash"></i></a>
<a style="visibility:hidden;" aria-hidden="true"><i class="fas fa-edit"></i></a>
</div> </div>
</li> </li>
{{#each weapons as |item id|}} {{#each weapons as |item id|}}
+2 -2
View File
@@ -2,8 +2,8 @@
<div class="skill-category"> <div class="skill-category">
<h4>{{ localize categoryLabel }}</h4> <h4>{{ localize categoryLabel }}</h4>
<div class="grid grid-2col"> <div class="grid grid-2col">
{{#each system.skills as |skill key|}} {{#each (sortedSkills skills) as |skill key|}}
{{#ife skill.category categoryKey}} {{#ife skill.category ../categoryKey}}
{{> "systems/vermine2047/templates/actor/parts/npc-skill-item.hbs" skill=skill key=key}} {{> "systems/vermine2047/templates/actor/parts/npc-skill-item.hbs" skill=skill key=key}}
{{/ife}} {{/ife}}
{{/each}} {{/each}}
+2 -2
View File
@@ -1,6 +1,6 @@
<div class="skill-item"> <div class="skill-item">
<label for="system.skills.{{key}}.value" title="{{ localize (concat 'SKILLS.' key) }} - {{ localize (concat 'VERMINE.rarity_' skill.rarity) }}"> <label for="system.skills.{{key}}.value" class="resource-label rollable" data-type="skill" data-label="{{key}}" title="{{ smarttlk 'SKILLS' key 'name' }} - {{ localize (concat 'VERMINE.rarity_' skill.rarity) }}">
{{ localize (concat 'SKILLS.' key) }} {{ smarttlk 'SKILLS' key 'name' }}
{{#if skill.rarity}} {{#if skill.rarity}}
<span class="rarity-badge rarity-{{skill.rarity}}" title="{{ localize (concat 'VERMINE.rarity_' skill.rarity) }}"> <span class="rarity-badge rarity-{{skill.rarity}}" title="{{ localize (concat 'VERMINE.rarity_' skill.rarity) }}">
{{skill.rarity}} {{skill.rarity}}
+4 -4
View File
@@ -7,18 +7,18 @@
{{#if (ne tk "adapted")}} {{#if (ne tk "adapted")}}
<div class="flexrow row mdb flex-group-center"> <div class="flexrow row mdb flex-group-center">
<h4 style="flex:33%;"> <h4 style="flex:30%;text-align:center;">
<a class="totem" data-totem="{{ tk }}" <a class="totem" data-totem="{{ tk }}"
title="{{ smarttlk "TOTEMS" title="{{ smarttlk "TOTEMS"
tk "description"}}"> tk "description"}}">
<img <img
src="/systems/vermine2047/assets/images/ui/totems/{{ tk }}.webp" src="/systems/vermine2047/assets/images/ui/totems/{{ tk }}.webp"
alt="{{ localize totem }}" width="80" height="80" /> alt="{{ localize totem }}" width="60" height="60" />
<h2>{{ smarttlk "TOTEMS" <h2 style="font-size:1rem;">{{ smarttlk "TOTEMS"
tk "name"}}</h2> tk "name"}}</h2>
</a> </a>
</h4> </h4>
<p style="flex:66%;text-align:left;"> <p style="flex:70%;text-align:left;margin:0;font-size:0.8rem;">
<strong>Instincts</strong> : {{ smarttlk "TOTEMS" tk <strong>Instincts</strong> : {{ smarttlk "TOTEMS" tk
"instincts"}}<br> "instincts"}}<br>
<strong>Interdits</strong> : {{ smarttlk "TOTEMS" tk <strong>Interdits</strong> : {{ smarttlk "TOTEMS" tk
+7
View File
@@ -8,6 +8,13 @@
<span class="resource-label">{{ localize 'VERMINE.skill_title' }}:</span> <span class="resource-label">{{ localize 'VERMINE.skill_title' }}:</span>
<span>{{ smarttlk 'SKILLS' item.system.skill 'name' }}</span> <span>{{ smarttlk 'SKILLS' item.system.skill 'name' }}</span>
</div> </div>
{{/if}}
{{#if item.system.level}}
<div class="resource flexrow">
<span class="resource-label">{{ localize 'VERMINE.level' }}:</span>
<span>{{ item.system.level }}</span>
</div>
{{/if}} {{/if}}
{{#if item.system.description}} {{#if item.system.description}}
+9
View File
@@ -13,6 +13,7 @@
value="{{item.name}}" value="{{item.name}}"
placeholder="Name" placeholder="Name"
/></h1> /></h1>
<div class="flexrow">
<select <select
name="system.skill" name="system.skill"
class="skill-select" class="skill-select"
@@ -32,6 +33,14 @@
</optgroup> </optgroup>
{{/each}} {{/each}}
</select> </select>
<select
name="system.level"
class="skill-select"
title="{{ localize 'VERMINE.level' }}"
>
{{selectOptions @root.config.SkillLevels selected=item.system.level localize=true}}
</select>
</div>
</div> </div>
</header> </header>
+111 -8
View File
@@ -1,10 +1,98 @@
<div class="vermine-roll-message"> <div class="vermine-roll-message">
{{!-- Verdict --}}
<div class="roll-verdict {{#if verdict.success}}success{{else}}failure{{/if}}">
{{#if verdict.success}}
<i class="fas fa-check-circle"></i>
<span>{{localize "VERMINE.roll_success"}}</span>
{{else}}
<i class="fas fa-times-circle"></i>
<span>{{localize "VERMINE.roll_failure"}}</span>
{{/if}}
</div>
<h3>{{param.actor.name}} : {{localize "VERMINE.test_of"}} {{param.rollLabel}}</h3> <h3>{{param.actor.name}} : {{localize "VERMINE.test_of"}} {{param.rollLabel}}</h3>
<div class="flexrow"> {{!-- Context --}}
<h4>{{localize "VERMINE.difficulty"}}:</h4> <div class="roll-context flexrow flex-wrap">
<span id="difficulty">{{param.difficulty}}</span> <div class="context-item">
<span class="context-label">{{localize "VERMINE.difficulty"}}</span>
<span class="context-value" id="difficulty">{{param.difficulty}}</span>
</div> </div>
<div class="context-item">
<span class="context-label">{{localize "VERMINE.success_required"}}</span>
<span class="context-value">{{verdict.required}}</span>
</div>
{{#if param.handicap}}
<div class="context-item">
<span class="context-label">{{localize "VERMINE.handicap"}}</span>
<span class="context-value">+{{param.handicap}}</span>
</div>
{{/if}}
{{#if param.specialtyName}}
<div class="context-item">
<span class="context-label">{{localize "VERMINE.specialty"}}</span>
<span class="context-value">+1D {{param.specialtyName}}</span>
</div>
{{/if}}
{{#if param.self_control}}
<div class="context-item">
<span class="context-label">{{localize "VERMINE.self_control"}}</span>
<span class="context-value">+{{param.self_control}}D</span>
</div>
{{/if}}
{{#if param.totems.human}}
<div class="context-item totem-human">
<span class="context-label">{{localize "VERMINE.totem_used"}}</span>
<span class="context-value">{{localize "TOTEMS.human.name"}}{{#ife param.keepTotem "human"}} ({{localize "VERMINE.totem_keep"}}){{/ife}}</span>
</div>
{{/if}}
{{#if param.totems.adapted}}
<div class="context-item totem-adapted">
<span class="context-label">{{localize "VERMINE.totem_used"}}</span>
<span class="context-value">{{localize "TOTEMS.adapted.name"}}{{#ife param.keepTotem "adapted"}} ({{localize "VERMINE.totem_keep"}}){{/ife}}</span>
</div>
{{/if}}
{{#if param.skillLevel}}
<div class="context-item">
<span class="context-label">{{localize "VERMINE.skill_mastery"}}</span>
<span class="context-value">{{skillLevel "label" param.skillLevel}}</span>
</div>
{{/if}}
</div>
{{!-- Dice pool breakdown --}}
{{#if param.poolBreakdown}}
<div class="roll-pool">
<h4>{{localize "VERMINE.roll_pool"}}</h4>
<div class="flexrow flex-wrap pool-breakdown">
{{#if param.poolBreakdown.ability}}
<span class="pool-item">{{localize "VERMINE.pool_ability"}} {{param.poolBreakdown.ability}}D</span>
{{/if}}
{{#if param.poolBreakdown.skill}}
<span class="pool-item">{{localize "VERMINE.pool_skill"}} {{param.poolBreakdown.skill}}D</span>
{{/if}}
{{#if param.poolBreakdown.specialty}}
<span class="pool-item">{{localize "VERMINE.pool_specialty"}} +{{param.poolBreakdown.specialty}}D</span>
{{/if}}
{{#if param.poolBreakdown.help}}
<span class="pool-item">{{localize "VERMINE.pool_help"}} +{{param.poolBreakdown.help}}D</span>
{{/if}}
{{#if param.poolBreakdown.tooling}}
<span class="pool-item">{{localize "VERMINE.pool_tooling"}} +{{param.poolBreakdown.tooling}}D</span>
{{/if}}
{{#if param.poolBreakdown.group}}
<span class="pool-item">{{localize "VERMINE.pool_group"}} +{{param.poolBreakdown.group}}D</span>
{{/if}}
{{#if param.poolBreakdown.selfControl}}
<span class="pool-item">{{localize "VERMINE.pool_selfControl"}} +{{param.poolBreakdown.selfControl}}D</span>
{{/if}}
{{#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>
{{/if}}
<span class="pool-item pool-total">{{localize "VERMINE.pool_total"}} {{param.poolBreakdown.total}}D</span>
</div>
</div>
{{/if}}
<div class="reroll-fromroll"> <div class="reroll-fromroll">
<h4>{{localize "VERMINE.rerolls_possible"}} : <span id="allowed_reroll">{{param.Reroll}}</span></h4> <h4>{{localize "VERMINE.rerolls_possible"}} : <span id="allowed_reroll">{{param.Reroll}}</span></h4>
@@ -26,20 +114,21 @@
</div> </div>
</div> </div>
{{!-- Dice results --}}
<ul class="flexrow roll-results initial-roll" data-difficulty="{{param.difficulty}}"> <ul class="flexrow roll-results initial-roll" data-difficulty="{{param.difficulty}}">
{{#each roll.dice as |dieType index|}} {{#each roll.dice as |dieType index|}}
{{#each dieType.results as |die index|}} {{#each dieType.results as |die index|}}
{{! Determine dice class and type based on flavor }} {{! Determine dice class and type based on flavor (leading token) }}
{{#ifincludes dieType.options.flavor "human"}} {{#ifStartsWith dieType.options.flavor "human_"}}
{{set diceClass="human"}} {{set diceClass="human"}}
{{set diceTypeVal="human"}} {{set diceTypeVal="human"}}
{{else ifincludes dieType.options.flavor "adapted"}} {{else ifStartsWith dieType.options.flavor "adapted_"}}
{{set diceClass="adapted"}} {{set diceClass="adapted"}}
{{set diceTypeVal="adapted"}} {{set diceTypeVal="adapted"}}
{{else}} {{else}}
{{set diceClass="regular"}} {{set diceClass="regular"}}
{{set diceTypeVal="regular"}} {{set diceTypeVal="regular"}}
{{/ifincludes}} {{/ifStartsWith}}
<li class="roll die flexcol {{diceClass}} {{#if die.success}}success{{/if}}" <li class="roll die flexcol {{diceClass}} {{#if die.success}}success{{/if}}"
data-dice-type="{{diceTypeVal}}" data-dice-type="{{diceTypeVal}}"
data-result="{{die.result}}"> data-result="{{die.result}}">
@@ -49,6 +138,20 @@
{{/each}} {{/each}}
</ul> </ul>
{{!-- Dice summary --}}
<div class="roll-dice-summary flexrow">
{{#if diceStats.regular.count}}
<span class="die-summary regular">{{diceStats.regular.count}} {{localize "VERMINE.die_regular"}} · {{diceStats.regular.success}} {{localize "VERMINE.die_successes"}}</span>
{{/if}}
{{#if diceStats.human.count}}
<span class="die-summary human">{{diceStats.human.count}} {{localize "VERMINE.die_human"}} · {{diceStats.human.success}} {{localize "VERMINE.die_successes"}}</span>
{{/if}}
{{#if diceStats.adapted.count}}
<span class="die-summary adapted">{{diceStats.adapted.count}} {{localize "VERMINE.die_adapted"}} · {{diceStats.adapted.success}} {{localize "VERMINE.die_successes"}}</span>
{{/if}}
</div>
{{!-- Total --}}
<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>
@@ -56,7 +159,7 @@
</div> </div>
<div class="flexcol"> <div class="flexcol">
<h4>{{localize "VERMINE.success_required"}}:</h4> <h4>{{localize "VERMINE.success_required"}}:</h4>
<span id="required">{{math_add 1 param.handicap}}</span> <span id="required">{{verdict.required}}</span>
</div> </div>
</div> </div>
</div> </div>