diff --git a/assets/icons/actors/vehicle.webp b/assets/icons/actors/vehicle.webp new file mode 100644 index 0000000..84a296a Binary files /dev/null and b/assets/icons/actors/vehicle.webp differ diff --git a/lang/en.json b/lang/en.json index e7b2eb4..410568a 100644 --- a/lang/en.json +++ b/lang/en.json @@ -16,7 +16,7 @@ "TYPES.Item.item": "Item", "TYPES.Item.weapon": "Weapon", "TYPES.Item.defense": "Defense", - "TYPES.Item.vehicle": "Vehicle", + "TYPES.Actor.vehicle": "Vehicle", "TYPES.Item.ability": "Ability", "TYPES.Item.specialty": "Specialty", "TYPES.Item.background": "Background", @@ -145,6 +145,8 @@ }, "pool": "Reserve", "pools": "Reserves", + "dice_pool": "Dice pool", + "auto_successes": "automatic successes", "self_control": "Self-control", "effort": "Effort", "equip": "Equip", @@ -174,6 +176,7 @@ "range": "Range", "ranges": "Ranges", "mobility": "Mobility", + "vehicle": "Vehicle", "vehicle_specs": "Vehicle specifications", "kind": "Kind", "kind_vehicle": "Vehicle", @@ -183,6 +186,8 @@ "speed_duration_ph": "top speed duration (e.g. 10 min)", "effort_reserve": "Effort reserve", "maintenance": "Maintenance", + "owner": "Owner", + "no_vehicles": "No vehicles", "rarity": "Rarity", "rarity_sm": "Rar.", "reliability": "Reliability", @@ -214,6 +219,7 @@ "type_npc": "NPC", "type_creature": "Creature", "type_community": "Community", + "type_vehicle": "Vehicle", "type": "Type", "group_members": "Members", "encounters": "Encounters", @@ -394,7 +400,8 @@ "npc": "NPC", "group": "Group", "creature": "Creature", - "community": "Community" + "community": "Community", + "vehicle": "Vehicle" }, "base_values": "Base Values", "computed_values": "Computed Values", diff --git a/lang/fr.json b/lang/fr.json index 99c17b9..07e2fb6 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -8,7 +8,7 @@ "TYPES.Item.item": "Objet", "TYPES.Item.weapon": "Arme", "TYPES.Item.defense": "Protection", - "TYPES.Item.vehicle": "Véhicule", + "TYPES.Actor.vehicle": "Véhicule", "TYPES.Item.ability": "Capacité", "TYPES.Item.specialty": "Spécialité", "TYPES.Item.background": "Historique", @@ -152,6 +152,8 @@ "pool": "Réserve", "pools": "Réserves", "reserves": "Réserves", + "dice_pool": "Pool de dés", + "auto_successes": "réussites automatiques", "self_control": "Sang-Froid", "effort": "Effort", "equip": "Équiper", @@ -255,6 +257,7 @@ "range": "Portée", "ranges": "Portées", "mobility": "Mobilité", + "vehicle": "Véhicule", "vehicle_specs": "Caractéristiques du véhicule", "kind": "Type", "kind_vehicle": "Véhicule", @@ -264,6 +267,8 @@ "speed_duration_ph": "durée de pointe (ex: 10 min)", "effort_reserve": "Réserve d'Effort", "maintenance": "Entretien", + "owner": "Propriétaire", + "no_vehicles": "Aucun véhicule", "rarity": "Rareté", "rarity_sm": "Rar.", "reliability": "Fiabilité", @@ -405,6 +410,7 @@ "type_npc": "PNJ", "type_creature": "Créature", "type_community": "Communauté", + "type_vehicle": "Véhicule", "type": "Type", "group_members": "Membres", "encounters": "Rencontres", @@ -451,7 +457,8 @@ "npc": "PNJ", "group": "Groupe", "creature": "Créature", - "community": "Communauté" + "community": "Communauté", + "vehicle": "Véhicule" } }, "UI": { diff --git a/less/actor/npc.less b/less/actor/npc.less index f3d61cc..f56fcf7 100644 --- a/less/actor/npc.less +++ b/less/actor/npc.less @@ -30,77 +30,70 @@ } } - .skills-container { - display: flex; - flex-direction: column; - gap: 15px; - } - - .skill-category { - .card-style(); - padding: 10px; - - h4 { - font-family: "DistressBlack", sans-serif; - font-size: @font-size-14; - text-transform: uppercase; - margin-top: 0; - margin-bottom: 8px; - background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp"); - } - } - - .skill-item { - display: flex; - align-items: center; - gap: 8px; - padding: 5px; - border-bottom: 1px solid @color-border-dark-3; - - &:last-child { border-bottom: none; } - - label { - flex: 1; + .npc-trait-list { + li { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 0; + border-bottom: 1px solid @color-border-dark-3; font-size: @font-size-12; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + + &:last-child { border-bottom: none; } + + strong { flex: 1; } } + .trait-value { + min-width: 30px; + text-align: center; + font-weight: bold; + white-space: nowrap; + } + } + + .npc-roll-btn { + color: @color-text-light-primary; + opacity: 0.7; + transition: opacity 0.15s ease; + &:hover { - box-shadow: 0 0 10px @theme-color-highlight inset; + opacity: 1; + color: @color-text-light-1; + text-shadow: 0 0 6px @theme-color-highlight; } } - .skill-control { - display: flex; - align-items: center; - gap: 5px; + .npc-wounds { + flex-wrap: wrap; + + .wound-cat { + display: inline-flex; + align-items: center; + gap: 4px; + font-weight: bold; + } + + .wound-boxes { + display: inline-flex; + gap: 2px; + + i { + font-size: 8px; + color: @color-border-dark-2; + } + } } - .skill-value { - min-width: 25px; - text-align: center; - font-weight: bold; - } + .npc-free-skills { + margin-top: 15px; + .form-group-style(); - .rarity-badge { - display: inline-block; - padding: 1px 4px; - border-radius: 3px; - font-size: 10px; - font-weight: bold; - margin-left: 5px; - - &.rarity-0 { .rarity-badge(@rarity-0-bg, @rarity-0-text); } - &.rarity-1 { .rarity-badge(@rarity-1-bg, @rarity-1-text); } - &.rarity-2 { .rarity-badge(@rarity-2-bg, @rarity-2-text); } - &.rarity-3 { .rarity-badge(@rarity-3-bg, @rarity-3-text); } - } - - .skill-category-selector { - margin-bottom: 15px; - .card-style(); + label { + font-size: @font-size-12; + font-weight: bold; + white-space: nowrap; + } } .wounds-card { @@ -133,39 +126,6 @@ } } - .ability-section { - .card-style(); - } - - .ability-category-header { - font-family: "DistressBlack", sans-serif; - font-size: @font-size-14; - text-transform: uppercase; - margin-top: 0; - margin-bottom: 8px; - border-bottom: 1px solid @color-border-dark-3; - padding-bottom: 5px; - } - - .ability-item { - display: flex; - align-items: center; - gap: 8px; - padding: 5px 0; - - label { - flex: 1; - font-size: @font-size-12; - white-space: nowrap; - } - } - - .ability-control { - display: flex; - align-items: center; - gap: 8px; - } - .tab { padding: 10px; overflow-y: auto; @@ -213,6 +173,17 @@ padding: 3px 5px; min-width: 100px; } + + input[type="text"] { + font-size: @font-size-12; + padding: 3px 5px; + min-width: 0; + width: 100%; + } + } + + .npc-profile-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); } .editor { diff --git a/less/actor/vehicle.less b/less/actor/vehicle.less new file mode 100644 index 0000000..265c8ad --- /dev/null +++ b/less/actor/vehicle.less @@ -0,0 +1,172 @@ +@import "../variables"; +@import "../utilities"; + +.system-vermine2047 .vermine2047.actor.vehicle { + display: grid !important; + grid-template-columns: 1fr !important; + grid-template-rows: auto 1fr auto !important; + + > header.window-header { + grid-column: 1; + grid-row: 1; + } + + > section.window-content { + grid-column: 1; + grid-row: 2; + } + + > div.window-resize-handle { + grid-column: 1; + grid-row: 3; + } + + .window-content { + display: grid; + grid-template-rows: auto auto 1fr; + grid-template-columns: 1fr; + overflow: hidden; + + > .tab.main { + grid-column: 1; + grid-row: 1; + display: flex !important; + flex-direction: column; + overflow-y: auto; + padding: 8px; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + } + + > nav.tabs { + grid-column: 1; + grid-row: 2; + } + + > .tab:not(.main) { + grid-column: 1; + grid-row: 3; + display: none; + overflow-y: auto; + padding: 8px; + + &.active { + display: flex; + flex-direction: column; + } + } + } + + .sheet-header { + .background-image(url("@{ui-path}/barre_haut.webp"), no-repeat, 100% 100%, right top); + padding: 10px; + max-height: 130px; + } + + .header-fields { flex: 1; } + + .resource { + .card-style(); + } + + .resource-label { + font-weight: bold; + margin-right: 8px; + min-width: 60px; + font-size: @font-size-12; + } + + .resource-content { + display: flex; + align-items: center; + + select { + margin-right: 8px; + min-width: 100px; + } + + input[type="number"] { + width: 60px; + text-align: center; + } + } + + .charname { + margin: 0; + + input { + width: 100%; + font-size: @font-size-18; + font-weight: bold; + text-align: center; + } + } + + .vehicle-owner { + display: flex; + gap: 6px; + margin-bottom: 8px; + padding: 6px 8px; + border: 1px dashed @color-hemolymph; + background: fade(@color-hemolymph, 8%); + + .label { + font-family: "DistressBlack", sans-serif; + text-transform: uppercase; + font-size: @font-size-11; + color: @color-amber; + } + } + + .vehicle-section { + padding: 8px; + border: 1px solid @color-border-dark-4; + background: fade(@color-chitin-dark, 20%); + margin-bottom: 8px; + + h3 { + font-family: "DistressBlack", sans-serif; + text-transform: uppercase; + font-size: @font-size-13; + color: @theme-color-dark; + margin: 0 0 8px; + text-align: left; + border-bottom: none; + } + + .resource-label { + display: block; + margin: 0 0 4px; + min-width: 0; + } + + .unit { + font-size: @font-size-11; + color: @color-text-light-0; + opacity: 0.8; + } + + .maintenance { + margin-top: 8px; + + .flexrow label { + gap: 4px; + margin-right: 12px; + font-size: @font-size-12; + } + } + + .form-group { + label { + font-family: "DistressBlack", sans-serif; + text-transform: uppercase; + font-size: @font-size-11; + color: @color-amber; + } + } + } + + input[type="text"] { + width: 100%; + padding: 5px; + } +} diff --git a/less/vermine2047.less b/less/vermine2047.less index af9cea7..355e4b7 100644 --- a/less/vermine2047.less +++ b/less/vermine2047.less @@ -23,6 +23,7 @@ @import "actor/group"; @import "actor/creature"; @import "actor/community"; +@import "actor/vehicle"; // 6. Styles des items @import "items"; diff --git a/module/applications/sheets/_module.mjs b/module/applications/sheets/_module.mjs index cfe806c..ad39a94 100644 --- a/module/applications/sheets/_module.mjs +++ b/module/applications/sheets/_module.mjs @@ -5,11 +5,11 @@ export { default as VermineNpcSheetV2 } from "./npc-sheet.mjs" export { default as VermineGroupSheetV2 } from "./group-sheet.mjs" export { default as VermineCreatureSheetV2 } from "./creature-sheet.mjs" export { default as VermineCommunitySheetV2 } from "./community-sheet.mjs" +export { default as VermineVehicleSheetV2 } from "./vehicle-sheet.mjs" export { VermineItemSheetV2, VermineWeaponSheetV2, VermineDefenseSheetV2, - VermineVehicleSheetV2, VermineAbilitySheetV2, VermineSpecialtySheetV2, VermineBackgroundSheetV2, diff --git a/module/applications/sheets/base-actor-sheet.mjs b/module/applications/sheets/base-actor-sheet.mjs index a18bd4d..6a0109f 100644 --- a/module/applications/sheets/base-actor-sheet.mjs +++ b/module/applications/sheets/base-actor-sheet.mjs @@ -32,6 +32,9 @@ export default class VermineBaseActorSheet extends HandlebarsApplicationMixin(fo delete: VermineBaseActorSheet.#onItemDelete, toggleEquip: VermineBaseActorSheet.#onToggleEquip, create: VermineBaseActorSheet.#onItemCreate, + createVehicle: VermineBaseActorSheet.#onCreateVehicle, + editVehicle: VermineBaseActorSheet.#onEditVehicle, + deleteVehicle: VermineBaseActorSheet.#onDeleteVehicle, roll: VermineBaseActorSheet.#onRollItem, attack: VermineBaseActorSheet.#onAttack, clickRadio: VermineBaseActorSheet.#onClickRadioHexa, @@ -229,11 +232,46 @@ export default class VermineBaseActorSheet extends HandlebarsApplicationMixin(fo await this.document.createEmbeddedDocuments("Item", [{ name, type }]) } + // ── Véhicules (acteurs) ────────────────────────────────────────────── + + /** Crée un acteur "vehicle" rattaché à cet acteur (groupe ou personnage). */ + static async #onCreateVehicle(event, target) { + const name = game.i18n.localize("ITEMS.new_vehicle") + const vehicle = await Actor.create({ + name, + type: "vehicle", + system: { ownerId: this.document.id }, + // Le véhicule est un bien partagé : reprendre la propriété du propriétaire + ownership: foundry.utils.deepClone(this.document.ownership) + }) + vehicle?.sheet.render(true) + } + + static async #onEditVehicle(event, target) { + const id = target.closest("[data-vehicle-id]")?.dataset?.vehicleId + const vehicle = game.actors.get(id) + vehicle?.sheet.render(true) + } + + static async #onDeleteVehicle(event, target) { + const id = target.closest("[data-vehicle-id]")?.dataset?.vehicleId + const vehicle = game.actors.get(id) + await vehicle?.deleteDialog() + } + static async #onRollItem(event, target) { const id = target.closest("[data-item-id]")?.dataset?.itemId if (!id) return const item = this.document.items.get(id) if (!item) return + // Armes : PNJ → dialogue de combat (valeur d'Attaque) ; personnage → + // dialogue de compétence. Les PNJ n'ont ni caractéristiques ni compétences. + if (item.type === "weapon" && this.document.type === 'npc') { + const { default: CombatDialog } = await import("../../system/dialogs/combatDialog.mjs") + const dialog = await CombatDialog.create({ actorId: this.document.id, weapon: item }) + if (dialog) dialog.render(true) + return + } if (item.type === "weapon" && this.document.system.abilities && this.document.system.skills) { const { default: RollDialog } = await import("../../system/dialogs/rollDialog.mjs") const dialog = await RollDialog.create({ diff --git a/module/applications/sheets/character-sheet.mjs b/module/applications/sheets/character-sheet.mjs index 58e394d..106ec5f 100644 --- a/module/applications/sheets/character-sheet.mjs +++ b/module/applications/sheets/character-sheet.mjs @@ -71,7 +71,7 @@ export default class VermineCharacterSheetV2 extends VermineBaseActorSheet { context.gear = doc.itemTypes.item context.weapons = doc.itemTypes.weapon context.defenses = doc.itemTypes.defense - context.vehicles = doc.itemTypes.vehicle + context.vehicles = game.actors.filter(a => a.type === "vehicle" && a.system.ownerId === doc.id) break case "stories": context.tab = context.tabs.stories diff --git a/module/applications/sheets/group-sheet.mjs b/module/applications/sheets/group-sheet.mjs index f9a0b20..ef5e55e 100644 --- a/module/applications/sheets/group-sheet.mjs +++ b/module/applications/sheets/group-sheet.mjs @@ -80,10 +80,11 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet { context.gear = doc.itemTypes.item context.weapons = doc.itemTypes.weapon context.defenses = doc.itemTypes.defense + context.vehicles = this.#getOwnedVehicles() break case "road": context.tab = context.tabs.road - context.vehicles = doc.itemTypes.vehicle + context.vehicles = this.#getOwnedVehicles() break case "reserve": context.tab = context.tabs.reserve @@ -92,6 +93,11 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet { return context } + /** Véhicules (acteurs) appartenant à ce groupe via system.ownerId. */ + #getOwnedVehicles() { + return game.actors.filter(a => a.type === "vehicle" && a.system.ownerId === this.document.id) + } + // Actions : délégation aux applications AppV1 existantes pour TotemPicker/ActorPicker static async #onChooseTotem(event, target) { const { TotemPicker } = await import("../../system/applications.mjs") diff --git a/module/applications/sheets/item-sheets.mjs b/module/applications/sheets/item-sheets.mjs index 951ab61..17730a1 100644 --- a/module/applications/sheets/item-sheets.mjs +++ b/module/applications/sheets/item-sheets.mjs @@ -18,34 +18,6 @@ export class VermineDefenseSheetV2 extends VermineBaseItemSheet { static PARTS = { main: { template: "systems/vermine2047/templates/item/item-defense-sheet.hbs", scrollable: [""] } } } -// ── Véhicule ────────────────────────────────────────────────────────── -export class VermineVehicleSheetV2 extends VermineBaseItemSheet { - static DEFAULT_OPTIONS = { classes: ["vehicle"], position: { width: 520 } } - static PARTS = { main: { template: "systems/vermine2047/templates/item/item-vehicle-sheet.hbs", scrollable: [""] } } - - async _prepareContext() { - const context = await super._prepareContext() - // Compétences d'Entretien possibles (mécanique, technologie, artisanat, animalisme…) - const maintenanceKeys = ["mecanical", "technology", "crafting", "animalism", "environment", "wildlife", "flora", "repulsion", "toxics"] - const current = this.document.system.maintenance || [] - context.maintenanceSkills = maintenanceKeys.map(key => ({ - key, - label: game.i18n.localize("SKILLS." + key + ".name"), - checked: current.includes(key) - })) - return context - } - - /** @override - nettoie le tableau d'Entretien (retire les cases non cochées). */ - _prepareSubmitData(event, form, formData, updateData) { - const result = super._prepareSubmitData(event, form, formData, updateData) - if (Array.isArray(result?.system?.maintenance)) { - result.system.maintenance = result.system.maintenance.filter(v => typeof v === "string" && v.trim() !== "") - } - return result - } -} - // ── Capacité ────────────────────────────────────────────────────────── export class VermineAbilitySheetV2 extends VermineBaseItemSheet { static DEFAULT_OPTIONS = { classes: ["ability"], position: { width: 560 } } diff --git a/module/applications/sheets/npc-sheet.mjs b/module/applications/sheets/npc-sheet.mjs index baeb1f9..bc5b78f 100644 --- a/module/applications/sheets/npc-sheet.mjs +++ b/module/applications/sheets/npc-sheet.mjs @@ -2,8 +2,9 @@ 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). + // La fiche PNJ s'ouvre en mode édition : profil et niveaux (Menace, + // Expérience, Rôle) se règlent dès l'ouverture (le mode jeu reste accessible + // via le toggle). _sheetMode = this.constructor.SHEET_MODES.EDIT static DEFAULT_OPTIONS = { @@ -15,22 +16,18 @@ export default class VermineNpcSheetV2 extends VermineBaseActorSheet { static PARTS = { main: { template: "systems/vermine2047/templates/actor/appv2/npc-main.hbs", scrollable: [""] }, tabs: { template: "templates/generic/tab-navigation.hbs" }, - characteristics: { template: "systems/vermine2047/templates/actor/appv2/npc-characteristics.hbs", scrollable: [""] }, - skills: { template: "systems/vermine2047/templates/actor/appv2/npc-skills.hbs", scrollable: [""] }, + traits: { template: "systems/vermine2047/templates/actor/appv2/npc-traits.hbs", scrollable: [""] }, equipment: { template: "systems/vermine2047/templates/actor/appv2/npc-equipment.hbs", scrollable: [""] }, - threat: { template: "systems/vermine2047/templates/actor/appv2/npc-threat.hbs", scrollable: [""] }, combat: { template: "systems/vermine2047/templates/actor/appv2/npc-combat.hbs", scrollable: [""] }, notes: { template: "systems/vermine2047/templates/actor/appv2/npc-notes.hbs", scrollable: [""] } } - tabGroups = { sheet: "characteristics" } + tabGroups = { sheet: "traits" } #getTabs() { const tabs = { - characteristics: { id: "characteristics", group: "sheet", icon: "fas fa-dice", label: "VERMINE.abilities" }, - skills: { id: "skills", group: "sheet", icon: "fas fa-brain", label: "VERMINE.skills" }, + traits: { id: "traits", group: "sheet", icon: "fas fa-dice", label: "VERMINE.traits" }, equipment: { id: "equipment", group: "sheet", icon: "fas fa-hammer", label: "VERMINE.tabs.equipment" }, - threat: { id: "threat", group: "sheet", icon: "fas fa-exclamation-triangle", label: "ADVERSITY.threat" }, combat: { id: "combat", group: "sheet", icon: "fas fa-sword", label: "VERMINE.combat" }, notes: { id: "notes", group: "sheet", icon: "fas fa-sticky-note", label: "IDENTITY.notes" } } @@ -57,20 +54,14 @@ export default class VermineNpcSheetV2 extends VermineBaseActorSheet { context.totemOptions = CONFIG.VERMINE.totems context.originOptions = CONFIG.VERMINE.origins break - case "characteristics": - context.tab = context.tabs.characteristics - break - case "skills": - context.tab = context.tabs.skills + case "traits": + context.tab = context.tabs.traits break case "equipment": context.tab = context.tabs.equipment context.weapons = doc.itemTypes.weapon context.defenses = doc.itemTypes.defense break - case "threat": - context.tab = context.tabs.threat - break case "combat": context.tab = context.tabs.combat context.equippedWeapons = doc.itemTypes.weapon.filter(i => i.system.equipped) @@ -84,4 +75,27 @@ export default class VermineNpcSheetV2 extends VermineBaseActorSheet { } return context } + + /** @override - Le PNJ n'a ni caractéristiques ni compétences : les jets + * rapides utilisent la valeur d'Attaque (dialogue de combat) ou les valeurs + * dérivées Action / Spécialité / Réaction (dialogue PNJ simplifié). */ + async _onRoll(event) { + event.preventDefault() + const el = event.currentTarget + const type = el.dataset.type + if (!type) return + if (type === "attack") { + const { default: CombatDialog } = await import("../../system/dialogs/combatDialog.mjs") + const dialog = await CombatDialog.create({ actorId: this.document.id }) + if (dialog) dialog.render(true) + return + } + if (["action", "specialty", "reaction"].includes(type)) { + const { default: NpcRollDialog } = await import("../../system/dialogs/npcRollDialog.mjs") + const dialog = await NpcRollDialog.create({ actorId: this.document.id, rolltype: type }) + if (dialog) dialog.render(true) + return + } + return super._onRoll(event) + } } diff --git a/module/applications/sheets/vehicle-sheet.mjs b/module/applications/sheets/vehicle-sheet.mjs new file mode 100644 index 0000000..d51f39c --- /dev/null +++ b/module/applications/sheets/vehicle-sheet.mjs @@ -0,0 +1,76 @@ +import VermineBaseActorSheet from "./base-actor-sheet.mjs" + +export default class VermineVehicleSheetV2 extends VermineBaseActorSheet { + + static DEFAULT_OPTIONS = { + classes: ["vehicle"], + position: { width: 640, height: 580 }, + window: { contentClasses: ["vehicle-content"] } + } + + static PARTS = { + main: { template: "systems/vermine2047/templates/actor/appv2/vehicle-main.hbs", scrollable: [""] }, + tabs: { template: "templates/generic/tab-navigation.hbs" }, + specs: { template: "systems/vermine2047/templates/actor/appv2/vehicle-specs.hbs", scrollable: [""] } + } + + tabGroups = { sheet: "specs" } + + #getTabs() { + const tabs = { + specs: { id: "specs", group: "sheet", icon: "fas fa-car-side", label: "VERMINE.vehicle_specs" } + } + for (const v of Object.values(tabs)) { + v.active = this.tabGroups[v.group] === v.id + v.cssClass = v.active ? "active" : "" + } + return tabs + } + + async _prepareContext() { + const context = await super._prepareContext() + context.tabs = this.#getTabs() + // Compétences d'Entretien possibles (mécanique, technologie, artisanat, animalisme…) + const maintenanceKeys = ["mecanical", "technology", "crafting", "animalism", "environment", "wildlife", "flora", "repulsion", "toxics"] + const current = this.document.system.maintenance || [] + context.maintenanceSkills = maintenanceKeys.map(key => ({ + key, + label: game.i18n.localize("SKILLS." + key + ".name"), + checked: current.includes(key) + })) + // Propriétaire (groupe ou personnage) résolu + context.resolvedOwner = null + if (this.document.system.ownerId) { + const owner = game.actors.get(this.document.system.ownerId) + if (owner) context.resolvedOwner = { id: owner.id, name: owner.name } + } + return context + } + + async _preparePartContext(partId, context) { + switch (partId) { + case "main": break + case "specs": + context.tab = context.tabs.specs + break + } + return context + } + + changeTab(tab, group, options = {}) { + super.changeTab(tab, group, options) + if (group === "sheet") { + const main = this.element?.querySelector('[data-group="sheet"][data-tab="main"]') + if (main) main.classList.add("active") + } + } + + /** @override - nettoie le tableau d'Entretien (retire les cases non cochées). */ + _prepareSubmitData(event, form, formData, updateData) { + const result = super._prepareSubmitData(event, form, formData, updateData) + if (Array.isArray(result?.system?.maintenance)) { + result.system.maintenance = result.system.maintenance.filter(v => typeof v === "string" && v.trim() !== "") + } + return result + } +} diff --git a/module/models/npc.mjs b/module/models/npc.mjs index 86a2bc6..6958ac8 100644 --- a/module/models/npc.mjs +++ b/module/models/npc.mjs @@ -2,17 +2,22 @@ * DataModel pour les acteurs de type "npc" (PNJ). * Étend foundry.abstract.TypeDataModel. * - * Note : le champ libre de compétences (texte descriptif) est nommé "freeSkills" - * pour éviter le conflit avec le SchemaField "skills" qui contient les 30 compétences. + * Conforme aux règles des PNJ (regles/regles_pnj.txt) : un PNJ n'a ni + * caractéristiques chiffrées, ni réserves détaillées, ni liste de compétences. + * Il est défini uniquement par son Profil (identité) et trois critères : + * Menace, Expérience et Rôle. Toutes les valeurs utiles en jeu sont dérivées + * des tableaux de référence (CONFIG.VERMINE.npc*Levels) et exposées dans + * "system.computed". + * + * Note : le champ libre de description des Spécialités est nommé "freeSkills" + * pour éviter le conflit avec le champ "skills" que le template.json historique + * définissait comme texte libre pour les PNJ. */ import { woundSchema, combatStatusSchema, equipmentSchema, - attributeSchema, - abilitiesSchema, - skillCategoriesSchema, - skillsSchema + attributeSchema } from "./_shared.mjs" export default class VermineNpcData extends foundry.abstract.TypeDataModel { @@ -23,8 +28,7 @@ export default class VermineNpcData extends foundry.abstract.TypeDataModel { /** * Migration des données avant traitement par le schéma. * Avant DataModel, template.json définissait "skills" comme un champ texte libre - * pour les PNJ. Le DataModel réserve "skills" pour les 30 compétences individuelles - * (SchemaField) et utilise "freeSkills" pour le texte libre. + * pour les PNJ. Le DataModel utilise "freeSkills" pour cette description. * @param {Object} source Données brutes avant validation du schéma * @returns {Object} Données migrées */ @@ -48,9 +52,10 @@ export default class VermineNpcData extends foundry.abstract.TypeDataModel { // Statut de combat (base, difficulté par défaut 9 pour PNJ) combatStatus: combatStatusSchema("9"), - // Identité + // Identité (Profil du PNJ) identity: new fields.SchemaField({ name: new fields.StringField({ required: true, nullable: false, initial: "" }), + age: new fields.StringField({ required: true, nullable: false, initial: "" }), profile: new fields.StringField({ required: true, nullable: false, initial: "" }), origin: new fields.StringField({ required: true, nullable: false, initial: "" }), totem: new fields.StringField({ required: true, nullable: false, initial: "" }), @@ -58,30 +63,29 @@ export default class VermineNpcData extends foundry.abstract.TypeDataModel { notes: new fields.HTMLField({ required: true, initial: "" }) }), - // Attributs (XP, réputation, sang-froid, effort) - attributes: new fields.SchemaField({ - xp: attributeSchema(0, 0, 10), - reputation: attributeSchema(0, 0, 10), - self_control: attributeSchema(0, 0, 5), - effort: attributeSchema(0, 0, 5) - }), - - // Niveaux PNJ (menace, expérience, rôle) + // Critères de définition du PNJ (Menace, Expérience, Rôle) threat: attributeSchema(1, 1, 4), experience: attributeSchema(1, 1, 4), role: attributeSchema(1, 1, 4), - // Compétences (les 30 compétences individuelles) - skills: skillsSchema(), - - // Description libre des compétences (champ texte PNJ) + // Description libre des Spécialités (champ texte PNJ) freeSkills: new fields.StringField({ required: true, nullable: false, initial: "" }), - // Catégories de compétences - skill_categories: skillCategoriesSchema(), - - // Caractéristiques (8) - abilities: abilitiesSchema(), + // Valeurs calculées (dérivées des niveaux de menace / expérience / rôle) + computed: new fields.SchemaField({ + attack: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }), + vigor: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }), + action: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 3 }), + specialties: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 4 }), + rerolls: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }), + contact: new fields.StringField({ required: true, nullable: false, initial: "7" }), + reaction: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 3 }), + reactionBonus: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }), + pools: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }), + gear: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 9 }), + gearHindrance: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }), + protection: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1 }) + }), // Équipement equipment: equipmentSchema() @@ -92,58 +96,67 @@ export default class VermineNpcData extends foundry.abstract.TypeDataModel { prepareDerivedData() { super.prepareDerivedData() - // 1. Calculer les seuils de blessures selon le niveau de menace + // 1. Calculer les valeurs dérivées selon les niveaux de menace/expérience/rôle + this._setNpcComputedValues() + + // 2. Calculer les seuils de blessures selon le niveau de menace this._setNpcWoundThresholds() - // 2. Calculer les réserves selon le niveau de rôle - this._setNpcAttributes() - - // 3. Définir les libellés des caractéristiques - this._setAbilityLabels() - - // 4. Mettre à jour le statut de combat + // 3. Mettre à jour le statut de combat this._updateCombatStatus() } /** - * Calcule les seuils de blessures à partir du niveau de menace. - * Utilise CONFIG.VERMINE.npcThreatLevels. + * Calcule les valeurs dérivées à partir des niveaux de Menace, Expérience + * et Rôle. Utilise CONFIG.VERMINE.npcThreatLevels, .npcExperienceLevels + * et .npcRoleLevels. + */ + _setNpcComputedValues() { + const threatLevel = this.threat?.value || 1 + const expLevel = this.experience?.value || 1 + const roleLevel = this.role?.value || 1 + + const threatConfig = CONFIG.VERMINE.npcThreatLevels[threatLevel] || {} + const expConfig = CONFIG.VERMINE.npcExperienceLevels[expLevel] || {} + const roleConfig = CONFIG.VERMINE.npcRoleLevels[roleLevel] || {} + + // Menace + this.computed.attack = threatConfig.attack || 0 + this.computed.vigor = threatConfig.vigor || 0 + + // Expérience + this.computed.action = expConfig.action || 3 + this.computed.specialties = expConfig.specialties || 4 + this.computed.rerolls = expConfig.rerolls || 0 + this.computed.contact = expConfig.contact || "7" + + // Rôle + this.computed.reaction = roleConfig.reaction || 0 + this.computed.reactionBonus = roleConfig.reaction_bonus || 0 + this.computed.pools = roleConfig.pools || 0 + this.computed.gear = roleConfig.gear || 9 + this.computed.gearHindrance = roleConfig.gear_hindrance || 0 + this.computed.protection = roleConfig.protection || 1 + } + + /** + * Calcule les seuils et le nombre de boîtes de blessures à partir du + * niveau de menace (tableau "Niveau de Menace"). Utilise + * CONFIG.VERMINE.npcThreatLevels. */ _setNpcWoundThresholds() { - const health = this.abilities?.health?.value || 1 const threatLevel = this.threat?.value || 1 const threatConfig = CONFIG.VERMINE.npcThreatLevels[threatLevel] || {} - this.minorWound.threshold = health - this.majorWound.threshold = health + 3 - this.deadlyWound.threshold = Math.min(health + 7, 10) + this.minorWound.threshold = threatConfig.minorThreshold ?? 1 + this.majorWound.threshold = threatConfig.majorThreshold ?? 4 + this.deadlyWound.threshold = threatConfig.deadlyThreshold ?? 6 this.minorWound.max = threatConfig.minorWound || 1 this.majorWound.max = threatConfig.majorWound || 1 this.deadlyWound.max = threatConfig.deadlyWound || 1 } - /** - * Définit les attributs dérivés (effort, sang-froid) selon le niveau de rôle. - * Utilise CONFIG.VERMINE.npcRoleLevels. - */ - _setNpcAttributes() { - const roleLevel = this.role?.value || 1 - const roleConfig = CONFIG.VERMINE.npcRoleLevels[roleLevel] || {} - - this.attributes.effort.max = roleConfig.pools || 0 - this.attributes.self_control.max = roleConfig.reaction_bonus || 0 - } - - /** - * Définit les libellés localisés des caractéristiques. - */ - _setAbilityLabels() { - for (const [k, v] of Object.entries(this.abilities)) { - v.label = game.i18n.localize(CONFIG.VERMINE.abilities[k]) ?? k - } - } - /** * Met à jour le label du statut de combat en fonction de la difficulté. */ diff --git a/module/models/vehicle.mjs b/module/models/vehicle.mjs index 78ae47f..9cdd93c 100644 --- a/module/models/vehicle.mjs +++ b/module/models/vehicle.mjs @@ -1,43 +1,67 @@ -import { baseItemSchema } from "./_shared.mjs" - /** - * DataModel pour les items de type "vehicle" (véhicules et montures). - * Caractéristiques : Rareté, Fiabilité, Distance, Vitesse (croisière/pointe), - * Entretien (compétences) et Réserve d'Effort (montures). - * @augments {foundry.abstract.TypeDataModel} + * DataModel pour les acteurs de type "vehicle" (véhicules et montures). + * Un véhicule est désormais un acteur (et non un objet) : il possède sa propre + * fiche, peut être posé sur la scène comme un jeton et appartient à un groupe + * ou un personnage via `ownerId`. + * + * Caractéristiques (regles_vehicules.txt) : Rareté, Fiabilité (véhicules), + * Réserve d'Effort (montures), Distance, Vitesse (croisière/pointe) et Entretien. + * Pas de statistiques de combat (les règles ne définissent pas de points de vie). */ +import { raritySchema } from "./_shared.mjs" + export default class VermineVehicleData extends foundry.abstract.TypeDataModel { + /** @override */ - static LOCALIZATION_PREFIXES = ["VERMINE.item.vehicle"] + static LOCALIZATION_PREFIXES = ["VERMINE.vehicle"] /** @override */ static defineSchema() { const fields = foundry.data.fields const reqInt = { required: true, nullable: false, integer: true } return { - ...baseItemSchema(), + // Identité + identity: new fields.SchemaField({ + profile: new fields.StringField({ required: true, nullable: false, initial: "" }), + origin: new fields.StringField({ required: true, nullable: false, initial: "" }), + theme: new fields.StringField({ required: true, nullable: false, initial: "" }), + notes: new fields.HTMLField({ required: true, initial: "", textSearch: true }) + }), + + // Rareté (valeur 1-10 + handicap) + rarity: raritySchema(), + // Fiabilité étendue (1-10) pour les véhicules reliability: new fields.NumberField({ ...reqInt, initial: 5, min: 1, max: 10 }), + // Type : "vehicle" (véhicule) ou "mount" (monture) kind: new fields.StringField({ required: true, nullable: false, initial: "vehicle" }), + // Distance : km parcourus avec un "plein" distance: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), + // Vitesse : croisière / pointe (durée de la pointe) speed: new fields.SchemaField({ cruise: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), top: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), topDuration: new fields.StringField({ required: true, nullable: false, initial: "10 min" }) }), + // Entretien : compétences nécessaires (clés de CONFIG.VERMINE.skills) maintenance: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })), + // Réserve d'Effort (montures) effortReserve: new fields.SchemaField({ value: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), max: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }) }), - // Mobilité (héritée) - mobility: new fields.NumberField({ ...reqInt, initial: 3, min: 0 }) + + // Mobilité + mobility: new fields.NumberField({ ...reqInt, initial: 3, min: 0 }), + + // Propriétaire (id d'acteur : groupe ou personnage) + ownerId: new fields.StringField({ required: true, nullable: false, initial: "" }) } } } diff --git a/module/system/config.mjs b/module/system/config.mjs index 757d81d..e56b1cf 100644 --- a/module/system/config.mjs +++ b/module/system/config.mjs @@ -99,10 +99,10 @@ VERMINE.totemDomains = { * NPC Threat Levels configuration */ VERMINE.npcThreatLevels = { - 1: { "label": "THREAT_LEVELS.minor", "attack": 3, "vigor": 1, "minorWound": 1, "majorWound": 1, "deadlyWound": 1 }, - 2: { "label": "THREAT_LEVELS.serious", "attack": 4, "vigor": 2, "minorWound": 2, "majorWound": 1, "deadlyWound": 1 }, - 3: { "label": "THREAT_LEVELS.major", "attack": 5, "vigor": 3, "minorWound": 2, "majorWound": 1, "deadlyWound": 1 }, - 4: { "label": "THREAT_LEVELS.deadly", "attack": 6, "vigor": 4, "minorWound": 2, "majorWound": 2, "deadlyWound": 2 } + 1: { "label": "THREAT_LEVELS.minor", "attack": 3, "vigor": 1, "minorThreshold": 1, "majorThreshold": 4, "deadlyThreshold": 6, "minorWound": 1, "majorWound": 1, "deadlyWound": 1 }, + 2: { "label": "THREAT_LEVELS.serious", "attack": 4, "vigor": 2, "minorThreshold": 2, "majorThreshold": 5, "deadlyThreshold": 8, "minorWound": 2, "majorWound": 1, "deadlyWound": 1 }, + 3: { "label": "THREAT_LEVELS.major", "attack": 5, "vigor": 3, "minorThreshold": 2, "majorThreshold": 5, "deadlyThreshold": 9, "minorWound": 2, "majorWound": 2, "deadlyWound": 1 }, + 4: { "label": "THREAT_LEVELS.deadly", "attack": 6, "vigor": 4, "minorThreshold": 3, "majorThreshold": 6, "deadlyThreshold": 9, "minorWound": 2, "majorWound": 2, "deadlyWound": 2 } } /** diff --git a/module/system/dialogs/npcRollDialog.mjs b/module/system/dialogs/npcRollDialog.mjs new file mode 100644 index 0000000..7d7826c --- /dev/null +++ b/module/system/dialogs/npcRollDialog.mjs @@ -0,0 +1,208 @@ +import { VermineUtils } from "../roll.mjs" + +const { HandlebarsApplicationMixin } = foundry.applications.api + +/** + * Dialogue de jet simplifié pour les PNJ. + * + * Un PNJ n'a ni caractéristiques ni compétences : toutes ses actions sont + * résolues par une valeur unique dérivée de son Profil (Menace / Expérience / + * Rôle), exposée dans system.computed. Ce dialogue fixe le pool selon le type + * de jet (Action, Spécialité, Attaque ou Réaction) et n'offre que la difficulté, + * le handicap et les bonus simples. + * + * Utilisé par : + * - la fiche PNJ (boutons de jet rapide Action / Spécialité / Réaction) ; + * - la défense des PNJ dans les échanges de coups (mode verrouillé). + */ +export default class NpcRollDialog extends HandlebarsApplicationMixin(foundry.applications.api.ApplicationV2) { + + #actor + + get title() { + return game.i18n.localize("VERMINE.roll") + } + + static DEFAULT_OPTIONS = { + classes: ["vermine-roll"], + tag: "form", + window: { + icon: "fas fa-dice-d10", + resizable: false + }, + position: { + width: 520, + height: 600 + }, + actions: { + roll: NpcRollDialog.#onRoll, + cancel: NpcRollDialog.#onCancel + } + } + + static PARTS = { + main: { template: "systems/vermine2047/templates/dialogs/npc-roll-dialog.hbs" } + } + + static async create(data = {}) { + const actor = data.actor instanceof Actor ? data.actor : await game.actors.get(data.actorId) + if (!actor) { + ui.notifications.warn(game.i18n.localize("VERMINE.error_no_actor_selected")) + return null + } + return new NpcRollDialog({ + actor, + rolltype: data.rolltype ?? "action", + locks: data.locks ?? null, + defense: data.defense ?? null + }) + } + + constructor(options = {}) { + super(options) + this.#actor = options.actor + this.rolltype = options.rolltype ?? "action" + this.locks = options.locks ?? null + this.defense = options.defense ?? null + } + + // ── Getters ────────────────────────────────────────────────────────── + + get computed() { return this.#actor.system?.computed || {} } + + /** Pool de dés selon le type de jet. */ + get pool() { + const c = this.computed + switch (this.rolltype) { + case "specialty": return c.specialties || 0 + case "attack": return c.attack || 0 + case "reaction": return c.reaction || 0 + default: return c.action || 3 + } + } + + /** Réussites automatiques (bonus de Réaction du rôle). */ + get freeSuccesses() { + return this.rolltype === "reaction" ? (this.computed.reactionBonus || 0) : 0 + } + + /** Relances : utilisables uniquement sur les attaques ou les Spécialités. */ + get rerolls() { + if (this.rolltype !== "specialty" && this.rolltype !== "attack") return 0 + return this.computed.rerolls || 0 + } + + get label() { + const keys = { + action: "ADVERSITY.action", + specialty: "ADVERSITY.specialties", + attack: "ADVERSITY.attack", + reaction: "ADVERSITY.reaction" + } + return game.i18n.localize(keys[this.rolltype] || "VERMINE.roll") + } + + // ── Rendu ──────────────────────────────────────────────────────────── + + async _prepareContext() { + const difficultyOptions = [] + for (let d = 3; d <= 10; d++) { + difficultyOptions.push({ difficulty: d, label: String(d) }) + } + let lockedDifficulty = null + let defaultDifficulty = 7 + if (this.locks?.difficulty !== undefined && this.locks?.difficulty !== null) { + lockedDifficulty = parseInt(this.locks.difficulty, 10) + defaultDifficulty = lockedDifficulty + } + return { + actor: this.#actor, + system: this.#actor.system, + config: CONFIG.VERMINE, + rollLabel: this.label, + rolltype: this.rolltype, + pool: this.pool, + freeSuccesses: this.freeSuccesses, + rerolls: this.rerolls, + isDefense: Boolean(this.defense), + lockedDifficulty, + difficultyOptions, + defaultDifficulty, + speakerId: this.#actor.id, + availableItems: this.#actor.items.filter(i => i.type === "item") + } + } + + async _onRender(context, options) { + this.element.dataset.actorId = this.#actor.id + for (const inp of this.element.querySelectorAll("[data-roll]")) { + inp.addEventListener("change", this.#onInputChange.bind(this)) + } + this.element.querySelector("#handicap")?.addEventListener("change", () => this.#updateUI()) + this.#updateUI() + } + + get #el() { return this.element } + + #getHandicap() { + const sel = this.#el.querySelector("#handicap") + return Math.max(0, (parseInt(sel?.value, 10) || 1) - 1) + } + + #getDifficulty() { + const sel = this.#el.querySelector("#difficulty") + return parseInt(sel?.value, 10) || 7 + } + + #getHelped() { return this.#el.querySelector("#helped")?.checked ? 1 : 0 } + + #getGroup() { return parseInt(this.#el.querySelector("#group")?.value, 10) || 0 } + + getPoolBreakdown() { + const help = this.#getHelped() + const group = this.#getGroup() + return { pool: this.pool, help, group, total: this.pool + help + group } + } + + #updateUI() { + const b = this.getPoolBreakdown() + const totalEl = this.#el.querySelector("#dice-pool-total") + if (totalEl) totalEl.textContent = `${b.total}D` + const bonusEl = this.#el.querySelector("#total-bonus") + if (bonusEl) bonusEl.textContent = b.help + b.group + const freeEl = this.#el.querySelector("#free-successes") + if (freeEl) freeEl.textContent = String(this.freeSuccesses) + } + + #onInputChange() { + this.#updateUI() + } + + // ── Actions ────────────────────────────────────────────────────────── + + static async #onCancel(event, target) { + this.close() + } + + static async #onRoll(event, target) { + const label = this.defense + ? (this.defense.type === "parade" + ? game.i18n.localize("VERMINE.defense_parade") + : game.i18n.localize("VERMINE.defense_esquive")) + : this.label + + await VermineUtils.roll({ + actor: this.#actor, + NoD: this.getPoolBreakdown().total, + Reroll: this.rerolls, + difficulty: this.#getDifficulty(), + handicap: this.#getHandicap(), + rollLabel: label, + bonusSuccesses: this.freeSuccesses, + poolBreakdown: this.getPoolBreakdown(), + messageFlags: this.defense ? { "vermine-defense": this.defense } : null + }) + + this.close() + } +} diff --git a/module/system/exchange.mjs b/module/system/exchange.mjs index 17b94c8..00f514c 100644 --- a/module/system/exchange.mjs +++ b/module/system/exchange.mjs @@ -359,6 +359,28 @@ export class VermineExchange { ui.notifications.warn(game.i18n.localize('VERMINE.error_creature_no_defense')) return } + const defense = { + exchangeId: exchange.id, + type, + actorId: actor.id, + actorUuid: actor.uuid, + actorName: actor.name, + difficulty: exchange.difficulty, + parryLevel: type === 'parade' ? this.#parryLevel(actor) : null + } + // PNJ : pas de caractéristiques/compétences — la défense utilise la valeur + // d'Action (Expérience), difficulté verrouillée. + if (actor.type === 'npc') { + const { default: NpcRollDialog } = await import('./dialogs/npcRollDialog.mjs') + const dialog = await NpcRollDialog.create({ + actor, + rolltype: 'action', + locks: { difficulty: exchange.difficulty }, + defense + }) + if (dialog) dialog.render(true) + return + } const { default: RollDialog } = await import('./dialogs/rollDialog.mjs') const locks = this.#defenseLocks(actor, exchange, type) const dialog = await RollDialog.create({ @@ -366,15 +388,7 @@ export class VermineExchange { rolltype: 'skill', label: locks.skill, locks, - defense: { - exchangeId: exchange.id, - type, - actorId: actor.id, - actorUuid: actor.uuid, - actorName: actor.name, - difficulty: exchange.difficulty, - parryLevel: type === 'parade' ? this.#parryLevel(actor) : null - } + defense }) if (dialog) dialog.render(true) } diff --git a/module/system/roll.mjs b/module/system/roll.mjs index 4c1532b..95a51b8 100644 --- a/module/system/roll.mjs +++ b/module/system/roll.mjs @@ -30,6 +30,7 @@ export class VermineUtils { skillLevel = null, hasSpecialty = false, handicap = 0, + bonusSuccesses = 0, poolBreakdown = null, specialtyName = null, weapon = null, @@ -147,9 +148,9 @@ export class VermineUtils { totemBonuses: { ...totemBonus }, baseNoD: NoD, rerolls: Reroll, - selfControl: self_control + selfControl: self_control, + bonusSuccesses: bonusSuccesses }; - // Evaluate the roll await roll.evaluate(); @@ -171,6 +172,7 @@ export class VermineUtils { skillLevel, hasSpecialty, handicap, + bonusSuccesses, poolBreakdown, specialtyName, weapon, @@ -321,6 +323,9 @@ export class VermineUtils { const isTotem = die.classList.contains('human') || die.classList.contains('adapted'); total += isTotem ? 2 : 1; }); + // Réussites automatiques (ex. bonus de Réaction) : ajoutées au total. + const freeSuccesses = parseInt(rollMessage.dataset?.freeSuccesses ?? '0', 10) || 0; + total += freeSuccesses; totalEl.innerText = total; } @@ -488,11 +493,13 @@ export class VermineUtils { } // Verdict const required = 1 + (param.handicap ?? 0); - const total = roll._total ?? 0; + const bonusSuccesses = param.bonusSuccesses ?? 0; + const total = (roll._total ?? 0) + bonusSuccesses; const verdict = { success: total >= required, total, - required + required, + bonusSuccesses }; const content = await foundry.applications.handlebars.renderTemplate("systems/vermine2047/templates/roll-message.hbs", { roll, param, diceStats, verdict }); diff --git a/module/vermine2047.mjs b/module/vermine2047.mjs index 09d24e9..66f764c 100644 --- a/module/vermine2047.mjs +++ b/module/vermine2047.mjs @@ -38,18 +38,20 @@ Hooks.once('init', async function () { CONFIG.Actor.dataModels.group = models.VermineGroupData; CONFIG.Actor.dataModels.creature = models.VermineCreatureData; CONFIG.Actor.dataModels.community = models.VermineCommunityData; + CONFIG.Actor.dataModels.vehicle = models.VermineVehicleData; - // Libellé du type "community" (TYPES.Actor.* du core ne le connaît pas) + // Libellés des types "community"/"vehicle" (TYPES.Actor.* du core ne les connaît pas) CONFIG.Actor.typeLabels ??= {}; CONFIG.Actor.typeLabels.community = "VERMINE.type_community"; + CONFIG.Actor.typeLabels.vehicle = "VERMINE.type_vehicle"; Hooks.once('i18nInit', () => { CONFIG.Actor.typeLabels.community = "VERMINE.type_community"; + CONFIG.Actor.typeLabels.vehicle = "VERMINE.type_vehicle"; }); CONFIG.Item.dataModels.item = models.VermineItemData; CONFIG.Item.dataModels.weapon = models.VermineWeaponData; CONFIG.Item.dataModels.defense = models.VermineDefenseData; - CONFIG.Item.dataModels.vehicle = models.VermineVehicleData; CONFIG.Item.dataModels.ability = models.VermineAbilityData; CONFIG.Item.dataModels.specialty = models.VermineSpecialtyData; CONFIG.Item.dataModels.background = models.VermineBackgroundData; @@ -80,7 +82,7 @@ Hooks.once('init', async function () { // Register sheet application classes (ApplicationV2) // Unregister core sheets foundry.documents.collections.Actors.unregisterSheet("core", foundry.applications.sheets.ActorSheetV2, { - types: ["character", "npc", "group", "creature"] + types: ["character", "npc", "group", "creature", "community", "vehicle"] }) foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1?.sheets?.ItemSheet) @@ -100,6 +102,9 @@ Hooks.once('init', async function () { foundry.documents.collections.Actors.registerSheet("vermine2047", sheets.VermineCommunitySheetV2, { types: ["community"], makeDefault: true, label: "VERMINE.Sheet.community" }) + foundry.documents.collections.Actors.registerSheet("vermine2047", sheets.VermineVehicleSheetV2, { + types: ["vehicle"], makeDefault: true, label: "VERMINE.Sheet.vehicle" + }) // Item sheets — un par type foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineItemSheetV2, { @@ -111,9 +116,6 @@ Hooks.once('init', async function () { foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineDefenseSheetV2, { types: ["defense"], makeDefault: true }) - foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineVehicleSheetV2, { - types: ["vehicle"], makeDefault: true - }) foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineAbilitySheetV2, { types: ["ability"], makeDefault: true }) @@ -189,10 +191,8 @@ Hooks.once('init', async function () { "systems/vermine2047/templates/actor/appv2/character-stories.hbs", "systems/vermine2047/templates/actor/appv2/character-combat.hbs", "systems/vermine2047/templates/actor/appv2/npc-main.hbs", - "systems/vermine2047/templates/actor/appv2/npc-characteristics.hbs", - "systems/vermine2047/templates/actor/appv2/npc-skills.hbs", + "systems/vermine2047/templates/actor/appv2/npc-traits.hbs", "systems/vermine2047/templates/actor/appv2/npc-equipment.hbs", - "systems/vermine2047/templates/actor/appv2/npc-threat.hbs", "systems/vermine2047/templates/actor/appv2/npc-combat.hbs", "systems/vermine2047/templates/actor/appv2/npc-notes.hbs", "systems/vermine2047/templates/actor/appv2/group-main.hbs", @@ -208,7 +208,9 @@ Hooks.once('init', async function () { "systems/vermine2047/templates/actor/appv2/community-main.hbs", "systems/vermine2047/templates/actor/appv2/community-identity.hbs", "systems/vermine2047/templates/actor/appv2/community-domains.hbs", - "systems/vermine2047/templates/actor/appv2/community-resources.hbs" + "systems/vermine2047/templates/actor/appv2/community-resources.hbs", + "systems/vermine2047/templates/actor/appv2/vehicle-main.hbs", + "systems/vermine2047/templates/actor/appv2/vehicle-specs.hbs" ]); }); diff --git a/system.json b/system.json index 9d2548c..d7333bd 100644 --- a/system.json +++ b/system.json @@ -35,13 +35,13 @@ "npc": { "htmlFields": ["equipment.description", "identity.notes"] }, "group": { "htmlFields": ["equipment.description", "identity.notes"] }, "creature": { "htmlFields": ["equipment.description", "identity.notes", "identity.biography"] }, - "community": { "htmlFields": ["identity.notes", "location.landmarks", "location.territory", "history.events", "history.relations"] } + "community": { "htmlFields": ["identity.notes", "location.landmarks", "location.territory", "history.events", "history.relations"] }, + "vehicle": { "htmlFields": ["identity.notes"] } }, "Item": { "item": { "htmlFields": ["description"] }, "weapon": { "htmlFields": ["description"] }, "defense": { "htmlFields": ["description"] }, - "vehicle": { "htmlFields": ["description"] }, "ability": { "htmlFields": ["description"] }, "specialty": {}, "background": { "htmlFields": ["description"] }, diff --git a/templates/actor/appv2/character-equipment.hbs b/templates/actor/appv2/character-equipment.hbs index 30ed020..33335ad 100644 --- a/templates/actor/appv2/character-equipment.hbs +++ b/templates/actor/appv2/character-equipment.hbs @@ -86,6 +86,47 @@ {{/each}} +
+

{{localize 'ITEMS.vehicles'}}

+
    +
  1. +
    {{ localize 'IDENTITY.name'}}
    +
    {{ localize 'VERMINE.mobility'}}
    +
    {{ localize 'VERMINE.rarity'}}
    +
    {{ localize 'VERMINE.reliability'}}
    +
    + {{#if isEditMode}} + + {{/if}} +
    +
  2. + {{#each vehicles as |vehicle|}} +
  3. +
    +
    + +
    + {{vehicle.name}} +
    +
    {{vehicle.system.mobility}}
    +
    {{vehicle.system.rarity.value}}
    +
    {{vehicle.system.reliability}}
    +
    + {{#if @root.isEditMode}} + + {{/if}} +
    +
  4. + {{else}} +
  5. + {{ localize 'VERMINE.no_vehicles' }} +
  6. + {{/each}} +
+

{{ localize 'IDENTITY.notes'}}

{{formInput systemFields.equipment.fields.description enriched=enrichedEquipmentNotes value=system.equipment.description name="system.equipment.description" toggled=true}} diff --git a/templates/actor/appv2/group-road.hbs b/templates/actor/appv2/group-road.hbs index 068f394..5cf24f5 100644 --- a/templates/actor/appv2/group-road.hbs +++ b/templates/actor/appv2/group-road.hbs @@ -7,31 +7,34 @@
{{ localize 'VERMINE.reliability'}}
{{#if isEditMode}} - + {{/if}}
- {{#each vehicles as |item id|}} -
  • + {{#each vehicles as |vehicle|}} +
  • -
    {{item.system.mobility}}
    -
    {{item.system.rarity.value}}
    -
    {{item.system.reliability}}
    +
    {{vehicle.system.mobility}}
    +
    {{vehicle.system.rarity.value}}
    +
    {{vehicle.system.reliability}}
    {{#if @root.isEditMode}} - {{/if}}
  • + {{else}} +
  • + {{ localize 'VERMINE.no_vehicles' }} +
  • {{/each}}
    diff --git a/templates/actor/appv2/npc-characteristics.hbs b/templates/actor/appv2/npc-characteristics.hbs deleted file mode 100644 index cb3caa3..0000000 --- a/templates/actor/appv2/npc-characteristics.hbs +++ /dev/null @@ -1,116 +0,0 @@ -
    -

    {{ localize 'VERMINE.abilities' }}

    - - {{!-- Caracteristiques physiques --}} -
    -

    {{ localize 'VERMINE.ability_category.physical' }}

    -
    -
    - -
    - {{#if isEditMode}} - - {{/if}} - {{system.abilities.vigor.value}}D -
    -
    -
    - -
    - {{#if isEditMode}} - - {{/if}} - {{system.abilities.health.value}}D -
    -
    -
    -
    - - {{!-- Caracteristiques manuelles --}} -
    -

    {{ localize 'VERMINE.ability_category.manual' }}

    -
    -
    - -
    - {{#if isEditMode}} - - {{/if}} - {{system.abilities.precision.value}}D -
    -
    -
    - -
    - {{#if isEditMode}} - - {{/if}} - {{system.abilities.reflexes.value}}D -
    -
    -
    -
    - - {{!-- Caracteristiques mentales --}} -
    -

    {{ localize 'VERMINE.ability_category.mental' }}

    -
    -
    - -
    - {{#if isEditMode}} - - {{/if}} - {{system.abilities.knowledge.value}}D -
    -
    -
    - -
    - {{#if isEditMode}} - - {{/if}} - {{system.abilities.perception.value}}D -
    -
    -
    -
    - - {{!-- Caracteristiques sociales --}} -
    -

    {{ localize 'VERMINE.ability_category.social' }}

    -
    -
    - -
    - {{#if isEditMode}} - - {{/if}} - {{system.abilities.will.value}}D -
    -
    -
    - -
    - {{#if isEditMode}} - - {{/if}} - {{system.abilities.empathy.value}}D -
    -
    -
    -
    - - {{!-- Reserves --}} -
    -

    {{ localize 'VERMINE.reserves' }}

    -
    -
    - {{ localize 'VERMINE.effort' }} : {{system.attributes.effort.value}}/{{system.attributes.effort.max}}D -
    -
    - {{ localize 'VERMINE.self_control' }} : {{system.attributes.self_control.value}}/{{system.attributes.self_control.max}}D -
    -
    -
    -
    diff --git a/templates/actor/appv2/npc-combat.hbs b/templates/actor/appv2/npc-combat.hbs index 1240d82..68f57a5 100644 --- a/templates/actor/appv2/npc-combat.hbs +++ b/templates/actor/appv2/npc-combat.hbs @@ -1,56 +1,43 @@
    -
    -
    -

    {{ localize "VERMINE.self_control"}}

    -

    - {{#if isEditMode}} - - {{/if}} - {{ system.attributes.self_control.value }} / {{ - system.attributes.self_control.max }} -

    -
    -
    -

    {{ localize "VERMINE.effort"}}

    -

    - {{#if isEditMode}} - - {{/if}} - {{ system.attributes.effort.value }} / {{ - system.attributes.effort.max }} -

    -
    -
    {{> "systems/vermine2047/templates/actor/parts/actor-equipped-gear.hbs"}} -

    {{ localize - "VERMINE.wounds.name"}}

    +

    {{ localize "VERMINE.wounds.name"}}

    {{ localize "UI.effects.name"}}

    diff --git a/templates/actor/appv2/npc-main.hbs b/templates/actor/appv2/npc-main.hbs index 0f7ecc3..7216dee 100644 --- a/templates/actor/appv2/npc-main.hbs +++ b/templates/actor/appv2/npc-main.hbs @@ -20,7 +20,7 @@ {{/if}} - {{!-- Niveaux de Menace, Experience, Role --}} + {{!-- Critères : Menace, Expérience, Rôle --}}
    @@ -29,12 +29,12 @@ {{else}} - {{system.threat.value}} + {{ system.threat.value }} {{/if}}
    @@ -46,12 +46,12 @@ {{else}} - {{system.experience.value}} + {{ system.experience.value }} {{/if}}
    @@ -63,19 +63,39 @@ {{else}} - {{system.role.value}} + {{ system.role.value }} {{/if}} - {{!-- Totem et Origine --}} -
    + {{!-- Profil : âge, activité, totem, origine --}} +
    +
    + +
    + {{#if isEditMode}} + + {{else}} + {{system.identity.age}} + {{/if}} +
    +
    +
    + +
    + {{#if isEditMode}} + + {{else}} + {{system.identity.profile}} + {{/if}} +
    +
    diff --git a/templates/actor/appv2/npc-skills.hbs b/templates/actor/appv2/npc-skills.hbs deleted file mode 100644 index bc57b65..0000000 --- a/templates/actor/appv2/npc-skills.hbs +++ /dev/null @@ -1,45 +0,0 @@ -
    -

    {{ localize 'VERMINE.skills' }}

    - - {{!-- Category Selector --}} -
    - - {{#if isEditMode}} - - {{else}} - {{system.skill_categories.preferred}} - {{/if}} -
    - - {{!-- Skills Grid by Category --}} -
    - {{#each config.skillCategories as |category key|}} - {{#ife key "preferred"}}{{else}} - {{> "systems/vermine2047/templates/actor/parts/npc-skill-category.hbs" - categoryKey=key - categoryLabel=(concat "VERMINE.skill_category." key) - skills=@root.system.skills - }} - {{/ife}} - {{/each}} -
    - - {{!-- Free Skills text field --}} -
    - - {{#if isEditMode}} - - {{else}} - {{system.freeSkills}} - {{/if}} -
    -
    diff --git a/templates/actor/appv2/npc-threat.hbs b/templates/actor/appv2/npc-threat.hbs deleted file mode 100644 index 0925240..0000000 --- a/templates/actor/appv2/npc-threat.hbs +++ /dev/null @@ -1,75 +0,0 @@ -
    -

    {{ localize 'ADVERSITY.threat_details' }}

    -
    - {{!-- Menace --}} -
    -

    {{ localize 'ADVERSITY.threat' }}

    -
      -
    • - {{ localize 'ADVERSITY.attack' }}: - {{ npcThreatLevel "attack" system.threat.value }} -
    • -
    • - {{ localize 'ADVERSITY.vigor' }}: - {{ npcThreatLevel "vigor" system.threat.value }} -
    • -
    • - {{ localize 'ADVERSITY.wounds' }}: - {{ npcThreatLevel "minorWound" system.threat.value }} / - {{ npcThreatLevel "majorWound" system.threat.value }} / - {{ npcThreatLevel "deadlyWound" system.threat.value }} -
    • -
    -
    - - {{!-- Experience --}} -
    -

    {{ localize 'ADVERSITY.experience' }}

    -
      -
    • - {{ localize 'ADVERSITY.action' }}: - {{ npcExperienceLevel "action" system.experience.value }}D -
    • -
    • - {{ localize 'ADVERSITY.specialties' }}: - {{ npcExperienceLevel "specialties" system.experience.value }} -
    • -
    • - {{ localize 'ADVERSITY.rerolls' }}: - {{ npcExperienceLevel "rerolls" system.experience.value }}D -
    • -
    • - {{ localize 'ADVERSITY.contact' }}: - {{ npcExperienceLevel "contact" system.experience.value }} -
    • -
    -
    - - {{!-- Role --}} -
    -

    {{ localize 'ADVERSITY.role' }}

    -
      -
    • - {{ localize 'ADVERSITY.reaction' }}: - {{ npcRoleLevel "reaction" system.role.value }} + {{ npcRoleLevel "reaction_bonus" system.role.value }} -
    • -
    • - {{ localize 'ADVERSITY.pools' }}: - {{ npcRoleLevel "pools" system.role.value }}D -
    • -
    • - {{ localize 'ADVERSITY.gear' }}: - {{ npcRoleLevel "gear" system.role.value }} -
    • -
    • - {{ localize 'ADVERSITY.gear_hindrance' }}: - {{ npcRoleLevel "gear_hindrance" system.role.value }} -
    • -
    • - {{ localize 'ADVERSITY.protection' }}: - {{ npcRoleLevel "protection" system.role.value }} -
    • -
    -
    -
    -
    diff --git a/templates/actor/appv2/npc-traits.hbs b/templates/actor/appv2/npc-traits.hbs new file mode 100644 index 0000000..f850fe1 --- /dev/null +++ b/templates/actor/appv2/npc-traits.hbs @@ -0,0 +1,86 @@ +
    +

    {{ localize 'VERMINE.traits' }}

    +
    + + {{!-- Menace --}} +
    +

    {{ localize 'ADVERSITY.threat' }}

    +
      +
    • + {{ localize 'ADVERSITY.attack' }}: + {{ system.computed.attack }}D + +
    • +
    • + {{ localize 'ADVERSITY.vigor' }}: + {{ system.computed.vigor }}D +
    • +
    • + {{ localize 'ADVERSITY.wounds' }}: + {{#repeat system.minorWound.max}}{{/repeat}} {{ system.minorWound.threshold }} + {{#repeat system.majorWound.max}}{{/repeat}} {{ system.majorWound.threshold }} + {{#repeat system.deadlyWound.max}}{{/repeat}} {{ system.deadlyWound.threshold }} +
    • +
    +
    + + {{!-- Expérience --}} +
    +

    {{ localize 'ADVERSITY.experience' }}

    +
      +
    • + {{ localize 'ADVERSITY.action' }}: + {{ system.computed.action }}D + +
    • +
    • + {{ localize 'ADVERSITY.specialties' }}: + {{ system.computed.specialties }}D + +
    • +
    • + {{ localize 'ADVERSITY.rerolls' }}: + {{ system.computed.rerolls }}D +
    • +
    • + {{ localize 'ADVERSITY.contact' }}: + {{ system.computed.contact }} +
    • +
    +
    + + {{!-- Rôle --}} +
    +

    {{ localize 'ADVERSITY.role' }}

    +
      +
    • + {{ localize 'ADVERSITY.reaction' }}: + {{ system.computed.reaction }}D{{#if system.computed.reactionBonus}} +{{ system.computed.reactionBonus }}{{/if}} + +
    • +
    • + {{ localize 'ADVERSITY.pools' }}: + {{ system.computed.pools }}D +
    • +
    • + {{ localize 'ADVERSITY.gear' }}: + {{ system.computed.gear }}{{#if system.computed.gearHindrance}} ({{ system.computed.gearHindrance }}){{/if}} +
    • +
    • + {{ localize 'ADVERSITY.protection' }}: + {{ system.computed.protection }} +
    • +
    +
    +
    + + {{!-- Spécialités (description libre) --}} +
    + + {{#if isEditMode}} + + {{else}} + {{system.freeSkills}} + {{/if}} +
    +
    diff --git a/templates/actor/appv2/vehicle-main.hbs b/templates/actor/appv2/vehicle-main.hbs new file mode 100644 index 0000000..ea95aa0 --- /dev/null +++ b/templates/actor/appv2/vehicle-main.hbs @@ -0,0 +1,94 @@ +
    + {{!-- Toggle Edit/Play --}} +
    + {{#if isEditMode}} + + {{else}} + + {{/if}} +
    + + {{!-- HEADER --}} +
    + +
    +
    +

    + {{#if isEditMode}} + + {{else}} + {{actor.name}} + {{/if}} +

    +
    + +
    + {{#if isEditMode}} + + {{else}} + {{#ife system.kind "mount"}}{{ localize "VERMINE.kind_mount" }}{{else}}{{ localize "VERMINE.kind_vehicle" }}{{/ife}} + {{/if}} +
    +
    +
    + +
    + {{#if isEditMode}} + + {{else}} + {{system.reliability}} + {{/if}} +
    +
    +
    +
    +
    + +
    + {{#if isEditMode}} + + {{else}} + {{system.rarity.value}} + {{/if}} +
    +
    +
    + +
    + {{#if isEditMode}} + + {{else}} + {{system.mobility}} + {{/if}} +
    +
    +
    + +
    + {{#if isEditMode}} + + {{else}} + {{system.distance}} + {{/if}} + km +
    +
    +
    +
    +
    + + {{#if resolvedOwner}} +
    + {{ localize 'VERMINE.owner' }}: + {{ resolvedOwner.name }} +
    + {{/if}} +
    diff --git a/templates/actor/appv2/vehicle-specs.hbs b/templates/actor/appv2/vehicle-specs.hbs new file mode 100644 index 0000000..aee9653 --- /dev/null +++ b/templates/actor/appv2/vehicle-specs.hbs @@ -0,0 +1,83 @@ +
    +
    +

    {{ localize 'VERMINE.vehicle_specs' }}

    +
    +
    + +
    +
    + km +
    +
    +
    + +
    +
    + / +
    +
    + +
    +
    +
    +
    + +
    +
    + / +
    +
    +
    +
    + +
    +
    +
    +
    + +
    + {{#each maintenanceSkills}} + + {{/each}} +
    +
    +
    + +
    +

    {{ localize 'IDENTITY.profile' }}

    +
    +
    + + {{#if isEditMode}} + + {{else}} + {{system.identity.profile}} + {{/if}} +
    +
    + + {{#if isEditMode}} + + {{else}} + {{system.identity.origin}} + {{/if}} +
    +
    + + {{#if isEditMode}} + + {{else}} + {{system.identity.theme}} + {{/if}} +
    +
    +
    + + {{formInput systemFields.identity.fields.notes enriched=enrichedNotes value=system.identity.notes name="system.identity.notes" toggled=true}} +
    +
    +
    diff --git a/templates/actor/group/group-vehicles.hbs b/templates/actor/group/group-vehicles.hbs index e3db0e6..180ad2f 100644 --- a/templates/actor/group/group-vehicles.hbs +++ b/templates/actor/group/group-vehicles.hbs @@ -5,27 +5,34 @@
    {{ localize 'VERMINE.rarity'}}
    {{ localize 'VERMINE.reliability'}}
    - + {{#if @root.isEditMode}} + + {{/if}}
    - {{#each vehicles as |item id|}} -
  • + {{#each vehicles as |vehicle|}} +
  • - - - + + +
    - + {{/if}}
  • + {{else}} +
  • + {{ localize 'VERMINE.no_vehicles' }} +
  • {{/each}} - \ No newline at end of file + diff --git a/templates/dialogs/npc-roll-dialog.hbs b/templates/dialogs/npc-roll-dialog.hbs new file mode 100644 index 0000000..8475475 --- /dev/null +++ b/templates/dialogs/npc-roll-dialog.hbs @@ -0,0 +1,105 @@ +
    + + + +

    {{ rollLabel }}

    + +
    + {{localize 'VERMINE.dice_pool'}}: + {{ pool }}D + {{#if freeSuccesses}} + +{{ freeSuccesses }} {{localize 'VERMINE.auto_successes'}} + {{/if}} + {{#if rerolls}} + {{localize 'ADVERSITY.rerolls'}}: {{ rerolls }}D + {{/if}} +
    + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + {{localize 'VERMINE.bonuses'}} + +0D + +
    + +
    + + +
    + +
    + + + D +
    + +
    +
    + + +
    + + 0D + {{#if freeSuccesses}} + + (+{{ freeSuccesses }} {{localize 'VERMINE.auto_successes'}}) + + {{/if}} +
    + +
    + +
    + + +
    +
    diff --git a/templates/item/chatCards/vehicle.hbs b/templates/item/chatCards/vehicle.hbs deleted file mode 100644 index 9e98df0..0000000 --- a/templates/item/chatCards/vehicle.hbs +++ /dev/null @@ -1,83 +0,0 @@ -
    - {{> "systems/vermine2047/templates/item/chatCards/parts/base.hbs"}} - - {{!-- Vehicle specific content --}} -
    - {{#if item.system.mobility}} -
    - {{ localize 'VERMINE.mobility' }}: - {{item.system.mobility}} -
    - {{/if}} - - {{#if item.system.quantity}} -
    - {{ localize 'VERMINE.qty' }}: - {{item.system.quantity}} -
    - {{/if}} - - {{#if item.system.weight}} -
    - {{ localize 'VERMINE.weight' }}: - {{item.system.weight}} -
    - {{/if}} - - {{#if item.system.rarity.value}} -
    - {{ localize 'VERMINE.rarity' }}: - - {{#repeat item.system.rarity.value 1 "i"}} - {{romanNumber i}} - {{/repeat}} - {{#ifgt item.system.rarity.handicap 0}} - ({{romanNumber item.system.rarity.handicap}}) - {{/ifgt}} - -
    - {{/if}} - - {{#if item.system.damages.value}} -
    - {{ localize 'VERMINE.damages' }}: - - {{getDamagesData item.system.damages "state"}} - {{#ifgt item.system.damages.value 1}} - ({{item.system.damages.value}}) - {{/ifgt}} - -
    - {{/if}} - - {{#if item.system.reliability}} -
    - {{ localize 'VERMINE.reliability' }}: - {{item.system.reliability}} -
    - {{/if}} - - {{#if item.system.traits}} -
    - {{ localize 'VERMINE.traits' }}: -
      - {{#each item.system.traits as |trait key|}} - {{#if trait}} -
    • - {{localize (lookup (lookup ../config.traits key) "name")}}: - {{localize (lookup (lookup ../config.traits key) "description")}} -
    • - {{/if}} - {{/each}} -
    -
    - {{/if}} - - {{#if item.system.description}} -
    -

    {{{item.system.description}}}

    -
    - {{/if}} -
    - -
    diff --git a/templates/item/item-vehicle-sheet.hbs b/templates/item/item-vehicle-sheet.hbs deleted file mode 100644 index e28fa38..0000000 --- a/templates/item/item-vehicle-sheet.hbs +++ /dev/null @@ -1,64 +0,0 @@ -
    - {{> "systems/vermine2047/templates/item/partials/header.hbs"}} - -
    - {{> "systems/vermine2047/templates/item/partials/traits.hbs"}} - -
    -

    {{ localize "VERMINE.vehicle_specs" }}

    - -
    -
    - - -
    -
    - -
    - km -
    -
    - -
    - - / - -
    - -
    -
    - -
    -
    - -
    -
    - / -
    -
    -
    -
    - -
    -
    -
    - -
    - -
    - {{#each maintenanceSkills}} - - {{/each}} -
    -
    -
    - - {{> "systems/vermine2047/templates/item/partials/physicalItems.hbs"}} -
    -
    diff --git a/templates/roll-message.hbs b/templates/roll-message.hbs index b0d0913..353fb03 100644 --- a/templates/roll-message.hbs +++ b/templates/roll-message.hbs @@ -1,4 +1,4 @@ -
    +
    {{!-- Verdict --}}
    {{#if verdict.success}} @@ -75,6 +75,12 @@ +{{param.handicap}}
    {{/if}} + {{#if verdict.bonusSuccesses}} +
    + {{localize "VERMINE.auto_successes"}} + +{{verdict.bonusSuccesses}} +
    + {{/if}} {{#if param.specialtyName}}
    {{localize "VERMINE.specialty"}} @@ -208,7 +214,7 @@

    {{localize "VERMINE.success_count"}}:

    - {{roll._total}} + {{verdict.total}}

    {{localize "VERMINE.success_required"}}: