feat(combat): refonte UI — tracker v14, handicap III, malus d'attaque, checkbox
Release Creation / build (release) Failing after 1m28s

- combat-tracker.hbs + fight.mjs : alignés sur le CSS core v14 (header/tracker/footer, boutons <button>), attitudes/couleurs PJ-PNJ conservées, menu contextuel combatant réparé
- less/combat.less (nouveau) : styles custom tracker + footer sticky des dialogues
- handicap des 3 dialogues de jet étendu jusqu'à III
- dialogue d'attaque : champ « Malus temporaires » (malus sur les jets)
- checkbox/radio : flex: 0 0 auto global (fin des icônes étirées — ex. « Deuxième action » 228px → 24px)
- TEST_COMBAT.md : plan de test combat rejouable (S1-S8, tout PASS)
This commit is contained in:
2026-08-07 18:31:04 +02:00
parent 61b7815381
commit 9f88a66b72
11 changed files with 429 additions and 167 deletions
+10 -3
View File
@@ -51,7 +51,9 @@ export default class CombatDialog extends HandlebarsApplicationMixin(foundry.app
this.weapon = options.weapon ?? null;
this.mode = options.mode ?? "contact";
this.abilityKey = VermineExchange.attackAbility(this.mode);
this.skillKey = this.weapon?.system?.skill || null;
// Une arme sans compétence définie est traitée comme une attaque
// improvisée au contact : Corps-à-corps (Vigueur + pool Corps-à-corps).
this.skillKey = this.weapon?.system?.skill || 'close';
this.skillValue = this.skillKey ? (this.actor.system?.skills?.[this.skillKey]?.value || 0) : 0;
this.skillPool = this.skillValue ? (CONFIG.VERMINE.SkillLevels?.[this.skillValue]?.dicePool || 0) : 0;
this.skillReroll = this.skillValue ? (CONFIG.VERMINE.SkillLevels?.[this.skillValue]?.reroll || 0) : 0;
@@ -218,6 +220,10 @@ export default class CombatDialog extends HandlebarsApplicationMixin(foundry.app
return parseInt(this.#el.querySelector("#pools-spent")?.value, 10) || 0;
}
#getTemporaryMalus() {
return parseInt(this.#el.querySelector("#temporary-malus")?.value, 10) || 0;
}
#getUseAmmo() {
return this.#el.querySelector("#use-ammo")?.checked ?? false;
}
@@ -278,9 +284,10 @@ export default class CombatDialog extends HandlebarsApplicationMixin(foundry.app
const group = parseInt(this.#el.querySelector("#group")?.value, 10) || 0;
const wound = VermineExchange.woundMalus(this.actor);
const lourd = VermineTraits.lourdMalus(this.actor);
const malus = wound + lourd;
const tempMalus = this.#getTemporaryMalus();
const malus = wound + lourd + tempMalus;
const total = Math.max(0, this.basePool + selfControl + pools + specialty + help + tooling + group - malus);
return { ability, skill, selfControl, pools, specialty, help, tooling, group, wound, lourd, malus, total };
return { ability, skill, selfControl, pools, specialty, help, tooling, group, wound, lourd, tempMalus, malus, total };
}
getDicePool() {
+47 -1
View File
@@ -223,6 +223,14 @@ export class VermineCombat extends Combat {
export class VermineCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
/** @override */
static DEFAULT_OPTIONS = {
actions: {
deleteCombatant: VermineCombatTracker.#onDeleteCombatant,
deleteCombat: VermineCombatTracker.#onDeleteCombat
}
};
/** @override */
static PARTS = {
main: {
@@ -248,13 +256,17 @@ export class VermineCombatTracker extends foundry.applications.sidebar.tabs.Comb
Object.assign(context, {
combat,
hasCombat,
combats: combats.map((c, i) => ({ id: c.id, name: c.name, label: i + 1, active: i === currentIdx })),
combatCount: combats.length,
currentIndex: currentIdx + 1,
previousId: combats[currentIdx - 1]?.id,
nextId: combats[currentIdx + 1]?.id,
round: combat?.round,
displayCycle: combats.length > 7,
control: isPlayerTurn && canControl,
linked: combat?.scene !== null,
initiativeIcon: CONFIG.Combat.initiativeIcon,
css: combats.length > 7 ? "cycle" : combats.length ? "tabbed" : "",
cssClass: combats.length > 7 ? "cycle" : combats.length ? "tabbed" : "",
cssId: "combat",
tabName: "combat",
@@ -264,12 +276,21 @@ export class VermineCombatTracker extends foundry.applications.sidebar.tabs.Comb
turns: []
});
// Tracker part : construit les tours + gestion des décimales d'initiative.
let hasDecimals = false;
if ( combat ) {
for ( const [i, combatant] of combat.turns.entries() ) {
if ( !combatant.visible ) continue;
context.turns.push(await this._prepareTurnContext(combat, combatant, i));
const turn = await this._prepareTurnContext(combat, combatant, i);
if ( turn.hasDecimals ) hasDecimals = true;
context.turns.push(turn);
}
}
const precision = CONFIG.Combat.initiative.decimals;
context.turns.forEach(t => {
if ( Number.isFinite(t.initiative) ) t.initiative = t.initiative.toFixed(hasDecimals ? precision : 0);
});
context.hasDecimals = hasDecimals;
return context;
}
@@ -318,6 +339,31 @@ export class VermineCombatTracker extends foundry.applications.sidebar.tabs.Comb
let actor = await game.actors.get(combatant.actorId)
// combatant attitude set
}
/**
* Supprime un combatant du combat en cours.
* @param {PointerEvent} event
* @param {HTMLElement} target
*/
static async #onDeleteCombatant(event, target) {
const combatantId = target.closest("[data-combatant-id]")?.dataset?.combatantId
const combatant = this.viewed?.combatants.get(combatantId)
if (!combatant) return
return combatant.delete()
}
/**
* Supprime le combat affiché (bouton poubelle de l'en-tête).
* @param {PointerEvent} event
* @param {HTMLElement} target
*/
static async #onDeleteCombat() {
const combat = this.viewed
if (!combat) return
const deleted = await combat.delete()
this.viewed = null
return deleted
}
}
export class VermineCombatant extends Combatant {