1 Commits
Author SHA1 Message Date
uberwald 31f2f4530c feat: améliorer les fiches (tests PNJ/créature, message de jet, compétences, créatures)
Release Creation / build (release) Failing after 1m23s
2026-08-04 00:49:37 +02:00
28 changed files with 736 additions and 224 deletions
+25 -2
View File
@@ -241,6 +241,28 @@
"success_count": "Success Count",
"success_required": "Success Required",
"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",
"totem_dice": "Totem Dice",
"totem_hint": "Totem Hint",
@@ -574,7 +596,7 @@
"name": "Repulsion"
},
"tracks": {
"name": "Tracks"
"name": "Tracking"
},
"crafting": {
"name": "Crafting"
@@ -604,7 +626,7 @@
"name": "Melee"
},
"archery": {
"name": "Ranged"
"name": "Ranged Weapons"
},
"throwing": {
"name": "Throwing"
@@ -656,6 +678,7 @@
"Round": "Round"
},
"SIZE_LEVELS": {
"tiny": "Insect",
"small": "Small",
"medium": "Medium",
"large": "Large"
+26 -3
View File
@@ -93,7 +93,7 @@
"AGE_TYPES": {
"young": "Jeune",
"adult": "Adulte",
"old": "Vieux"
"old": "Ancien"
},
"IDENTITY": {
"biography": "Biographie",
@@ -188,6 +188,28 @@
"grant_reroll": "accorder des relances",
"success_count": "nombre de succès",
"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_cannot_reroll": "Vous ne pouvez pas relancer un dés sur ce jet",
"error_no_rerolls_left": "Plus de relances possibles",
@@ -433,6 +455,7 @@
"major": "Majeur"
},
"SIZE_LEVELS": {
"tiny": "Insecte",
"small": "Petit",
"medium": "Moyen",
"large": "Grand"
@@ -596,7 +619,7 @@
"name": "Répulsion"
},
"tracks": {
"name": "Pistage"
"name": "Traces"
},
"crafting": {
"name": "Artisanat"
@@ -617,7 +640,7 @@
"name": "Armes à feu"
},
"archery": {
"name": "Tir à l'arc"
"name": "Armes de Tir"
},
"armory": {
"name": "Armurerie"
+158 -1
View File
@@ -44,6 +44,24 @@ body.system-vermine2047.theme-dark {
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 {
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 {
&.smb { margin-bottom: 0.25rem; }
&.mdb { margin-bottom: 0.5rem; }
@@ -113,6 +146,29 @@ ul.unstyled {
.rollable {
.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 {
@@ -420,7 +476,7 @@ body.system-vermine2047 img#logo {
}
.item-name {
padding-left: 5px;
padding: 0 2px 0 5px;
text-align: left;
}
}
@@ -674,6 +730,107 @@ ol.chat-log {
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 {
@@ -51,6 +51,7 @@ export default class VermineCharacterSheetV2 extends VermineBaseActorSheet {
case "main": break
case "abilities":
context.tab = context.tabs.abilities
context.specialties = doc.itemTypes.specialty
break
case "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 {
// 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 = {
classes: ["creature"],
position: { width: 700, height: 650 },
@@ -61,10 +65,22 @@ export default class VermineCreatureSheetV2 extends VermineBaseActorSheet {
break
case "stats":
context.tab = context.tabs.stats
context.patternLabel = doc.system.pattern?.value ? CONFIG.VERMINE.creaturePatternLevels[doc.system.pattern.value]?.label : ""
context.sizeLabel = doc.system.size?.value || ""
context.roleLabel = doc.system.role?.value ? CONFIG.VERMINE.creatureRoleLevels[doc.system.role.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.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")
// 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
case "combat":
context.tab = context.tabs.combat
@@ -77,4 +93,25 @@ export default class VermineCreatureSheetV2 extends VermineBaseActorSheet {
}
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 {
// 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 = {
classes: ["npc"],
position: { width: 750, height: 680 },
+1 -1
View File
@@ -219,7 +219,7 @@ const reqInt = { required: true, nullable: false, integer: true }
export function raritySchema() {
const fields = foundry.data.fields
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 })
})
}
+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)
pattern: attributeSchema(1, 1, 4),
size: attributeSchema(1, 1, 3),
size: attributeSchema(0, 0, 3),
role: attributeSchema(1, 1, 4),
pack: attributeSchema(0, 0, 3),
@@ -97,28 +97,38 @@ export default class VermineCreatureData extends foundry.abstract.TypeDataModel
*/
_calculateCreatureComputedValues() {
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 packLevel = this.pack?.value || 0
const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {}
const sizeConfig = CONFIG.VERMINE.creatureSizeLevels[sizeLevel] || {}
const roleConfig = CONFIG.VERMINE.creatureRoleLevels[roleLevel] || {}
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
this.computed.attack = (patternConfig.attack || 0)
+ (sizeConfig.attack || 0)
+ sizeAttack
+ (packConfig.attack || 0)
+ (roleConfig.reaction || 0)
// Dégâts : patron + vigueur de taille + meute
this.computed.damage = (patternConfig.damage || 0)
+ (sizeConfig.vigor || 0)
+ sizeVigor
+ (packConfig.damage || 0)
// Vigueur : taille + meute
this.computed.vigor = (sizeConfig.vigor || 0) + (packConfig.damage || 0)
this.computed.vigor = sizeVigor + (packConfig.damage || 0)
// Réaction : rôle
this.computed.reaction = (roleConfig.reaction || 0) + (roleConfig.reaction_bonus || 0)
@@ -141,21 +151,32 @@ export default class VermineCreatureData extends foundry.abstract.TypeDataModel
*/
_calculateCreatureWoundThresholds() {
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 patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {}
const sizeConfig = CONFIG.VERMINE.creatureSizeLevels[sizeLevel] || {}
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)
+ (sizeConfig.minorWound || 0)
+ sizeMinor
+ (packConfig.minorWound || 0)
this.majorWound.threshold = (patternConfig.majorWound || 0)
+ (sizeConfig.majorWound || 0)
+ sizeMajor
+ (packConfig.majorWound || 0)
this.deadlyWound.threshold = (patternConfig.deadlyWound || 0)
+ (sizeConfig.deadlyWound || 0)
+ sizeDeadly
+ (packConfig.deadlyWound || 0)
// Max de blessures
+3 -1
View File
@@ -10,9 +10,11 @@ export default class VermineSpecialtyData extends foundry.abstract.TypeDataModel
/** @override */
static defineSchema() {
const fields = foundry.data.fields
const reqInt = { required: true, nullable: false, integer: true }
return {
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
*/
VERMINE.creatureSizeLevels = {
1: { "label": "SIZE_LEVELS.small", "attack": 2, "vigor": 1, "minorWound": 0, "majorWound": 0, "deadlyWound": 1 },
2: { "label": "SIZE_LEVELS.medium", "attack": 3, "vigor": 2, "minorWound": 0, "majorWound": 1, "deadlyWound": 1 },
3: { "label": "SIZE_LEVELS.large", "attack": 4, "vigor": 3, "minorWound": 1, "majorWound": 1, "deadlyWound": 1 }
0: { "label": "SIZE_LEVELS.tiny", "attack": 0, "vigor": 0, "minorWound": 0, "majorWound": 0, "deadlyWound": 0 },
1: { "label": "SIZE_LEVELS.small", "attack": 1, "vigor": 1, "minorWound": 0, "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"); }
#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 abilVal = parseInt(abil?.options[abil?.selectedIndex]?.value, 10) || 0;
const ability = parseInt(abil?.options[abil?.selectedIndex]?.value, 10) || 0;
const skill = this.#getSkill();
const skillPool = parseInt(skill?.options[skill?.selectedIndex]?.dataset?.pool, 10) || 0;
const sc = parseInt(this.#getSelfCtrl()?.value, 10) || 0;
const specChecked = this.#el.querySelector("#usingSpecialization")?.checked;
const selfControl = parseInt(this.#getSelfCtrl()?.value, 10) || 0;
const specChecked = this.hasSpecialtySelected();
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 bonuses = (specChecked ? 1 : 0) + (helped ? 1 : 0) + (tools ? 1 : 0) + group;
return (abilVal + sc + skillPool + bonuses) || 0;
const specialty = specChecked ? 1 : 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() {
@@ -207,14 +221,8 @@ export default class RollDialog extends HandlebarsApplicationMixin(foundry.appli
}
#calculateBonusCount() {
let b = 0;
if (this.#el.querySelector("#helped")?.checked) b += 1;
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;
const b = this.getPoolBreakdown();
return b.specialty + b.help + b.tooling + b.group + b.selfControl;
}
#updateUI() {
@@ -315,7 +323,9 @@ export default class RollDialog extends HandlebarsApplicationMixin(foundry.appli
keepTotem: this.getKeepTotem(),
skillCategory: this.getSkillCategory(),
skillLevel: this.getSkillLevel(),
hasSpecialty: this.hasSpecialtySelected()
hasSpecialty: this.hasSpecialtySelected(),
poolBreakdown: this.getPoolBreakdown(),
specialtyName: this.getSpecialtyName()
});
this.close();
+19
View File
@@ -32,6 +32,7 @@ export const preloadHandlebarsTemplates = async function () {
// npc partials
"systems/vermine2047/templates/actor/npc/npc-combat.hbs",
"systems/vermine2047/templates/actor/parts/npc-skill-category.hbs",
"systems/vermine2047/templates/actor/parts/npc-skill-item.hbs",
// creature partials
"systems/vermine2047/templates/actor/creature/creature-combat.hbs",
@@ -100,6 +101,21 @@ export const registerHandlebarsHelpers = function () {
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) {
return game.i18n.localize(arrayLabel + "." + objectLabel + "." + key);
});
@@ -347,6 +363,9 @@ export const registerHandlebarsHelpers = function () {
Handlebars.registerHelper('ifincludes', function (arg1, arg2, options) {
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
+114 -28
View File
@@ -29,7 +29,9 @@ export class VermineUtils {
keepTotem = null,
skillLevel = null,
hasSpecialty = false,
handicap = 0
handicap = 0,
poolBreakdown = null,
specialtyName = null
}) {
// Validate inputs
if (!actor) {
@@ -105,6 +107,27 @@ export class VermineUtils {
// Build final formula
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
const roll = new Roll(formula, actor.getRollData());
@@ -143,7 +166,9 @@ export class VermineUtils {
keepTotem,
skillLevel,
hasSpecialty,
handicap
handicap,
poolBreakdown,
specialtyName
});
return roll;
@@ -199,10 +224,10 @@ export class VermineUtils {
/**
* Handles reroll events on dice in chat messages.
* @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
*/
static async onReroll(message, ev) {
static async onReroll(message, target) {
// Verify user permissions
const msgUserId = message.user?.id ?? message.user;
if (msgUserId !== game.user?.id && !game.user?.isGM) {
@@ -211,7 +236,7 @@ export class VermineUtils {
}
// Get reroll count
const rollMessage = ev.currentTarget.closest('div.vermine-roll-message');
const rollMessage = target.closest('div.vermine-roll-message');
if (!rollMessage) {
return false;
}
@@ -221,22 +246,22 @@ export class VermineUtils {
// Check if rerolls are available
if (!rerollCount || parseInt(rerollCount, 10) < 1) {
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) {
rerollables.forEach(el => el.classList.remove('rerollable'));
}
return false;
}
ev.currentTarget.classList.add('rerolled');
target.classList.add('rerolled');
// Set reroll flag
await message.setFlag("world", "reroll", true);
// Get difficulty and dice type
const ulElement = ev.currentTarget.closest('ul');
const ulElement = target.closest('ul');
const difficulty = ulElement?.dataset.difficulty ?? 7;
let diceType = ev.currentTarget.dataset.diceType;
let diceType = target.dataset.diceType;
// Sanitize user name
const safeUserName = (game.user?.name ?? "user").replace(/[^a-zA-Z0-9_]/g, '_');
@@ -265,30 +290,68 @@ export class VermineUtils {
// Update die display
const result = reroll.dice[0]?.results[0]?.result ?? 0;
const dieSpan = ev.currentTarget.querySelector('span');
const dieSpan = target.querySelector('span');
if (dieSpan) {
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;
if (success) {
ev.currentTarget.classList.add('success');
const totalElement = rollMessage.querySelector('#total');
if (totalElement) {
const currentTotal = parseInt(totalElement.innerText, 10) || 0;
totalElement.innerText = currentTotal + reroll.total;
}
target.classList.toggle('success', !!success);
target.classList.remove("rerollable");
// Recompute the total from the live dice in the DOM. This avoids the
// cumulative-increment drift of the old logic (which never removed a
// 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
ev.currentTarget.classList.remove("rerollable");
const messageContent = ev.currentTarget.closest('div.message-content');
const messageContent = target.closest('div.message-content');
if (messageContent) {
const newRerollCount = parseInt(rerollCount, 10) - 1;
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({
content: messageContent.outerHTML
});
@@ -314,10 +377,11 @@ export class VermineUtils {
html.querySelectorAll('.rerollable').forEach(el => {
el.addEventListener('click', async (ev) => {
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) {
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
html.querySelectorAll("button.grant-reroll").forEach(el => {
el.addEventListener('click', async (ev) => {
const btn = ev.currentTarget;
const grantedRerollElement = html.querySelector('#granted-reroll');
const allowedRerollElement = html.querySelector("#allowed_reroll");
@@ -343,13 +408,13 @@ export class VermineUtils {
allowedRerollElement.innerText = grantedRerollElement.innerText;
}
const mesEl = ev.currentTarget.closest('[data-message-id]');
const mesEl = btn.closest('[data-message-id]');
const messageId = mesEl?.dataset?.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) {
const content = rollMessage.outerHTML;
const message = await game.messages.get(messageId);
@@ -401,7 +466,28 @@ export class VermineUtils {
* @returns {Promise<ChatMessage>} The created chat message
*/
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 = {
user: game.user?._id,
speaker: ChatMessage.getSpeaker(),
+1
View File
@@ -221,6 +221,7 @@
{{> "systems/vermine2047/templates/actor/parts/npc-skill-category.hbs"
categoryKey=key
categoryLabel=(concat "VERMINE.skill_category." key)
skills=@root.system.skills
}}
{{/if}}
{{/each}}
@@ -55,7 +55,7 @@
>
{{/if}}
</h4>
{{#each @root.system.skills as |skill skey|}}
{{#each (sortedSkills @root.system.skills) as |skill skey|}}
{{#if (eq skill.category sckey) }}
<div class="ability flexrow ">
@@ -79,12 +79,10 @@
data-action="addSpecialty"
></i>
{{/ifgt}}
{{#each skill.specialties as |spe ind|}}
{{#each @root.specialties as |spe|}}
{{#ife spe.system.skill skey}}
<i
class="specialty"
data-tooltip="spécialité utilisable"
>{{spe.name}}</i>
<span class="specialty" data-tooltip="spécialité utilisable"
>{{spe.name}}<sup>{{spe.system.level}}</sup></span>
{{/ife}}
{{/each}}
</div>
+83 -86
View File
@@ -1,90 +1,4 @@
<div class="tab equipment sheet-part" data-group="sheet" data-tab="equipment">
<div class="grid grid-2col">
<div>
<ol class="items-list">
<li class="item flexrow items-header">
<div
class="item-name"
style="flex:4;"
>{{ localize
'IDENTITY.name'}}</div>
<div class="item-quantity">{{ localize 'VERMINE.qty'}}</div>
<div class="item-weight">{{ localize 'VERMINE.weight'}}</div>
<div class="item-controls">
{{#if isEditMode}}
<a
class="item-control item-create"
title="Create item"
data-action="create"
data-type="item"
><i class="fas fa-plus"></i></a>
{{/if}}
</div>
</li>
{{#each gear as |item id|}}
<li
class="item flexrow flex-group-center"
data-item-id="{{item._id}}"
>
<div
class="item-name"
style="flex:4;"
>
<div class="item-image">
<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}}"
data-tooltip="Edit Item"
>{{item.name}} </a>
{{#if item.system.damages.value}}
/
<span data-tooltip="{{item.damagedLabel}}">{{{item.damagedIcon}}}</span>
{{/if}}
</div>
<div>
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="Edit Item">
{{item.system.quantity}}
</a>
</div>
<div>
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="Edit Item">
{{item.system.weight}}
</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>
{{/each}}
</ol>
</div>
<div>
<h4>{{ localize 'IDENTITY.notes'}}</h4>
{{formInput systemFields.equipment.fields.description enriched=enrichedEquipmentNotes value=system.equipment.description name="system.equipment.description" toggled=true}}
</div>
</div>
<div class="weapons">
<h4>{{localize 'ITEMS.weapons'}}</h4>
{{> "systems/vermine2047/templates/actor/parts/actor-weapons.hbs"}}
@@ -93,4 +7,87 @@
<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">
<li class="item flexrow items-header">
<div
class="item-name"
style="flex:4;"
>{{ localize
'IDENTITY.name'}}</div>
<div class="item-quantity">{{ localize 'VERMINE.qty'}}</div>
<div class="item-weight">{{ localize 'VERMINE.weight'}}</div>
<div class="item-controls">
<a
class="item-control item-create"
title="Create item"
data-action="create"
data-type="item"
><i class="fas fa-plus"></i></a>
</div>
</li>
{{#each gear as |item id|}}
<li
class="item flexrow flex-group-center"
data-item-id="{{item._id}}"
>
<div
class="item-name"
style="flex:4;"
>
<div class="item-image">
<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}}"
data-tooltip="Edit Item"
>{{item.name}} </a>
{{#if item.system.damages.value}}
/
<span data-tooltip="{{item.damagedLabel}}">{{{item.damagedIcon}}}</span>
{{/if}}
</div>
<div>
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="Edit Item">
{{item.system.quantity}}
</a>
</div>
<div>
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="Edit Item">
{{item.system.weight}}
</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>
{{/each}}
</ol>
</div>
<div class="equipment-notes">
<h4>{{ localize 'IDENTITY.notes'}}</h4>
{{formInput systemFields.equipment.fields.description enriched=enrichedEquipmentNotes value=system.equipment.description name="system.equipment.description" toggled=true}}
</div>
</div>
+12 -5
View File
@@ -1,5 +1,12 @@
<div class="tab stats sheet-part" data-group="sheet" data-tab="stats">
<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">
<div class="mdb">
<h4 class="align-center">{{ localize 'ADVERSITY.pattern' }}: {{ patternLabel }}</h4>
@@ -16,12 +23,12 @@
<div class="mdb">
<h4 class="align-center">{{ localize 'ADVERSITY.size' }}: {{ sizeLabel }}</h4>
<ul class="unstyled">
<li>{{ localize 'ADVERSITY.attack' }}: {{ creatureSizeLevel "attack" system.size.value }}</li>
<li>{{ localize 'ADVERSITY.vigor' }}: {{ creatureSizeLevel "vigor" system.size.value }}</li>
<li>{{ localize 'ADVERSITY.attack' }}: {{ sizeCumulative.attack }}</li>
<li>{{ localize 'ADVERSITY.vigor' }}: {{ sizeCumulative.vigor }}</li>
<li>{{ localize 'ADVERSITY.wounds' }}:
{{ creatureSizeLevel "minorWound" system.size.value }}/
{{ creatureSizeLevel "majorWound" system.size.value }}/
{{ creatureSizeLevel "deadlyWound" system.size.value }}
{{ sizeCumulative.minorWound }}/
{{ sizeCumulative.majorWound }}/
{{ sizeCumulative.deadlyWound }}
</li>
</ul>
</div>
@@ -6,7 +6,7 @@
<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">{{ 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">
{{#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"/>
@@ -15,7 +15,7 @@
</div>
</div>
<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">
{{#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"/>
@@ -31,7 +31,7 @@
<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">{{ 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">
{{#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"/>
@@ -40,7 +40,7 @@
</div>
</div>
<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">
{{#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"/>
@@ -56,7 +56,7 @@
<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">{{ 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">
{{#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"/>
@@ -65,7 +65,7 @@
</div>
</div>
<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">
{{#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"/>
@@ -81,7 +81,7 @@
<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">{{ 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">
{{#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"/>
@@ -90,7 +90,7 @@
</div>
</div>
<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">
{{#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"/>
+1
View File
@@ -27,6 +27,7 @@
{{> "systems/vermine2047/templates/actor/parts/npc-skill-category.hbs"
categoryKey=key
categoryLabel=(concat "VERMINE.skill_category." key)
skills=@root.system.skills
}}
{{/ife}}
{{/each}}
@@ -48,7 +48,7 @@
{{/ife}}
>
</h4>
{{#each @root.system.skills as |skill skey|}}
{{#each (sortedSkills @root.system.skills) as |skill skey|}}
{{#if (eq skill.category sckey) }}
<div class="ability flexrow ">
@@ -70,14 +70,16 @@
data-tooltip="ajout de spécialité"
></i>
{{/ifgt}}
{{#each skill.specialties as |spe ind|}}
{{#if @root.specialties}}
{{#each @root.specialties as |spe|}}
{{#ife spe.system.skill skey}}
<i
<span
class="specialty"
data-tooltip="spécialité utilisable"
>{{spe.name}}</i>
>{{spe.name}}<sup>{{spe.system.level}}</sup></span>
{{/ife}}
{{/each}}
{{/if}}
</div>
<select
name="system.skills.{{skey}}.value"
+1
View File
@@ -15,6 +15,7 @@
data-type="defense"
data-action="create"
><i class="fas fa-plus"></i></a>
<a style="visibility:hidden;" aria-hidden="true"><i class="fas fa-trash"></i></a>
</div>
</li>
{{#each defenses as |item id|}}
+2
View File
@@ -9,6 +9,8 @@
<div class="item-controls">
<a class="item-control item-create" title="Create item"
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>
</li>
{{#each weapons as |item id|}}
+2 -2
View File
@@ -2,8 +2,8 @@
<div class="skill-category">
<h4>{{ localize categoryLabel }}</h4>
<div class="grid grid-2col">
{{#each system.skills as |skill key|}}
{{#ife skill.category categoryKey}}
{{#each (sortedSkills skills) as |skill key|}}
{{#ife skill.category ../categoryKey}}
{{> "systems/vermine2047/templates/actor/parts/npc-skill-item.hbs" skill=skill key=key}}
{{/ife}}
{{/each}}
+2 -2
View File
@@ -1,6 +1,6 @@
<div class="skill-item">
<label for="system.skills.{{key}}.value" title="{{ localize (concat 'SKILLS.' key) }} - {{ localize (concat 'VERMINE.rarity_' skill.rarity) }}">
{{ localize (concat 'SKILLS.' key) }}
<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) }}">
{{ smarttlk 'SKILLS' key 'name' }}
{{#if skill.rarity}}
<span class="rarity-badge rarity-{{skill.rarity}}" title="{{ localize (concat 'VERMINE.rarity_' skill.rarity) }}">
{{skill.rarity}}
+4 -4
View File
@@ -7,18 +7,18 @@
{{#if (ne tk "adapted")}}
<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 }}"
title="{{ smarttlk "TOTEMS"
tk "description"}}">
<img
src="/systems/vermine2047/assets/images/ui/totems/{{ tk }}.webp"
alt="{{ localize totem }}" width="80" height="80" />
<h2>{{ smarttlk "TOTEMS"
alt="{{ localize totem }}" width="60" height="60" />
<h2 style="font-size:1rem;">{{ smarttlk "TOTEMS"
tk "name"}}</h2>
</a>
</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
"instincts"}}<br>
<strong>Interdits</strong> : {{ smarttlk "TOTEMS" tk
+13 -6
View File
@@ -3,12 +3,19 @@
{{!-- Specialty specific content --}}
<div class="grid grid-2col">
{{#if item.system.skill}}
<div class="resource flexrow">
<span class="resource-label">{{ localize 'VERMINE.skill_title' }}:</span>
<span>{{ smarttlk 'SKILLS' item.system.skill 'name' }}</span>
</div>
{{/if}}
{{#if item.system.skill}}
<div class="resource flexrow">
<span class="resource-label">{{ localize 'VERMINE.skill_title' }}:</span>
<span>{{ smarttlk 'SKILLS' item.system.skill 'name' }}</span>
</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 item.system.description}}
<div class="resource flexcol full-width">
+28 -19
View File
@@ -13,25 +13,34 @@
value="{{item.name}}"
placeholder="Name"
/></h1>
<select
name="system.skill"
class="skill-select"
>
{{#each @root.config.skillCategories as |skillCategory sckey|}}
<optgroup label="{{ smarttlk 'SKILLS_CATEGORIES' sckey 'name' }}">
{{#each @root.config.skills as |skill key|}}
{{#ife skill.category sckey}}
<option
value="{{key}}"
{{#ife key @root.item.system.skill}}
selected
{{/ife}}
>{{ smarttlk 'SKILLS' key 'name' }}</option>
{{/ife}}
{{/each}}
</optgroup>
{{/each}}
</select>
<div class="flexrow">
<select
name="system.skill"
class="skill-select"
>
{{#each @root.config.skillCategories as |skillCategory sckey|}}
<optgroup label="{{ smarttlk 'SKILLS_CATEGORIES' sckey 'name' }}">
{{#each @root.config.skills as |skill key|}}
{{#ife skill.category sckey}}
<option
value="{{key}}"
{{#ife key @root.item.system.skill}}
selected
{{/ife}}
>{{ smarttlk 'SKILLS' key 'name' }}</option>
{{/ife}}
{{/each}}
</optgroup>
{{/each}}
</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>
</header>
+116 -13
View File
@@ -1,11 +1,99 @@
<div class="vermine-roll-message">
<h3>{{param.actor.name}} : {{localize "VERMINE.test_of"}} {{param.rollLabel}}</h3>
<div class="flexrow">
<h4>{{localize "VERMINE.difficulty"}}:</h4>
<span id="difficulty">{{param.difficulty}}</span>
{{!-- 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>
{{!-- Context --}}
<div class="roll-context flexrow flex-wrap">
<div class="context-item">
<span class="context-label">{{localize "VERMINE.difficulty"}}</span>
<span class="context-value" id="difficulty">{{param.difficulty}}</span>
</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">
<h4>{{localize "VERMINE.rerolls_possible"}} : <span id="allowed_reroll">{{param.Reroll}}</span></h4>
<div class="reroll flexrow">
@@ -25,21 +113,22 @@
</div>
</div>
</div>
{{!-- Dice results --}}
<ul class="flexrow roll-results initial-roll" data-difficulty="{{param.difficulty}}">
{{#each roll.dice as |dieType index|}}
{{#each dieType.results as |die index|}}
{{! Determine dice class and type based on flavor }}
{{#ifincludes dieType.options.flavor "human"}}
{{! Determine dice class and type based on flavor (leading token) }}
{{#ifStartsWith dieType.options.flavor "human_"}}
{{set diceClass="human"}}
{{set diceTypeVal="human"}}
{{else ifincludes dieType.options.flavor "adapted"}}
{{else ifStartsWith dieType.options.flavor "adapted_"}}
{{set diceClass="adapted"}}
{{set diceTypeVal="adapted"}}
{{else}}
{{set diceClass="regular"}}
{{set diceTypeVal="regular"}}
{{/ifincludes}}
{{/ifStartsWith}}
<li class="roll die flexcol {{diceClass}} {{#if die.success}}success{{/if}}"
data-dice-type="{{diceTypeVal}}"
data-result="{{die.result}}">
@@ -48,7 +137,21 @@
{{/each}}
{{/each}}
</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="flexcol">
<h4>{{localize "VERMINE.success_count"}}:</h4>
@@ -56,7 +159,7 @@
</div>
<div class="flexcol">
<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>