fix: retours review - validation de phase, drop de véhicule et dés de Réserve

- resetPhaseLimits ne touche plus au compteur de phase (seul le
  dialogue d'attribution du MJ l'avance) : évite le double-incrément
  quand joueur et MJ valident la même phase.
- _onDropActor : import d'une copie monde pour un véhicule déposé
  depuis un compendium, confirmation avant de transférer un véhicule
  déjà rattaché à un autre Groupe, try/catch avec retour utilisateur.
- #onClickRadioHexa : garde isEditable (pas d'erreur de permission
  pour un utilisateur non éditeur).
This commit is contained in:
2026-08-05 21:24:33 +02:00
parent 282fb2794a
commit c96e8d69d7
6 changed files with 56 additions and 14 deletions
+5
View File
@@ -378,6 +378,11 @@
"open_learning": "Learning", "open_learning": "Learning",
"validate_phase": "Validate phase", "validate_phase": "Validate phase",
"validate_phase_done": "Experience phase validated: learning limits reset.", "validate_phase_done": "Experience phase validated: learning limits reset.",
"error_not_editable": "You do not have permission to modify this element.",
"vehicle_assign": "Attach vehicle",
"vehicle_assign_confirm": "This vehicle already belongs to \"{group}\". Attach it to this group?",
"vehicle_assign_failed": "Could not attach this vehicle to the group.",
"vehicle_import_failed": "Could not import this vehicle from the compendium.",
"learning_limits": "Learning limits (per phase)", "learning_limits": "Learning limits (per phase)",
"learned_skill": "Skill", "learned_skill": "Skill",
"learned_specialty": "Specialty", "learned_specialty": "Specialty",
+5
View File
@@ -361,6 +361,11 @@
"open_learning": "Apprentissage", "open_learning": "Apprentissage",
"validate_phase": "Valider la phase", "validate_phase": "Valider la phase",
"validate_phase_done": "Phase d'Expérience validée : limites d'apprentissage réinitialisées.", "validate_phase_done": "Phase d'Expérience validée : limites d'apprentissage réinitialisées.",
"error_not_editable": "Vous n'avez pas la permission de modifier cet élément.",
"vehicle_assign": "Attacher le véhicule",
"vehicle_assign_confirm": "Ce véhicule appartient déjà à « {group} ». L'attacher à ce Groupe ?",
"vehicle_assign_failed": "Impossible d'attacher ce véhicule au Groupe.",
"vehicle_import_failed": "Impossible d'importer ce véhicule depuis le compendium.",
"learning_limits": "Limites d'apprentissage (par phase)", "learning_limits": "Limites d'apprentissage (par phase)",
"learned_skill": "Compétence", "learned_skill": "Compétence",
"learned_specialty": "Spécialité", "learned_specialty": "Spécialité",
@@ -311,6 +311,11 @@ export default class VermineBaseActorSheet extends HandlebarsApplicationMixin(fo
static #onClickRadioHexa(event, target) { static #onClickRadioHexa(event, target) {
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
// Évite une erreur de permission pour un utilisateur non éditeur.
if (!this.isEditable) {
ui.notifications.warn(game.i18n.localize("VERMINE.error_not_editable"))
return
}
const input = target const input = target
const update = {} const update = {}
let current = this.document let current = this.document
@@ -130,7 +130,7 @@ export default class VermineCharacterSheetV2 extends VermineBaseActorSheet {
/** Valide la phase d'Expérience courante et réinitialise les limites. */ /** Valide la phase d'Expérience courante et réinitialise les limites. */
static async #onValidatePhase() { static async #onValidatePhase() {
const { VermineExperience } = await import("../../system/experience.mjs") const { VermineExperience } = await import("../../system/experience.mjs")
await VermineExperience.completePhase(this.document) await VermineExperience.resetPhaseLimits(this.document)
ui.notifications.info(game.i18n.localize("VERMINE.validate_phase_done")) ui.notifications.info(game.i18n.localize("VERMINE.validate_phase_done"))
} }
} }
+35 -6
View File
@@ -113,16 +113,45 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
/** /**
* Dépôt d'un acteur Véhicule sur la fiche : lie le véhicule au Groupe * Dépôt d'un acteur Véhicule sur la fiche : lie le véhicule au Groupe
* (system.ownerId) au lieu de le dupliquer, puis affiche la liste. * (system.ownerId) au lieu de le dupliquer, puis affiche la liste.
* Gère les dépôts depuis un compendium (copie importée dans le monde)
* et demande confirmation si le véhicule appartient déjà à un autre Groupe.
* @override * @override
*/ */
async _onDropActor(event, data) { async _onDropActor(event, data) {
const actor = data instanceof foundry.abstract.Document ? data : await fromUuid(data.uuid) let actor = data instanceof foundry.abstract.Document ? data : await fromUuid(data.uuid)
if (actor?.type === "vehicle") { if (actor?.type !== "vehicle") {
await actor.update({ "system.ownerId": this.document.id }) return super._onDropActor?.(event, data)
this.render() }
// Véhicule issu d'un compendium : importer une copie dans le monde.
if (actor.compendium) {
try {
const copy = await game.actors.importFromCompendium(actor.compendium, actor._id, { renderSheet: false })
if (!copy) return
actor = copy
} catch {
ui.notifications.warn(game.i18n.localize("VERMINE.vehicle_import_failed"))
return return
} }
return super._onDropActor?.(event, data) }
// Véhicule déjà rattaché à un autre Groupe : confirmation avant transfert.
const currentOwner = actor.system?.ownerId
if (currentOwner && currentOwner !== this.document.id) {
const other = game.actors.get(currentOwner)
const content = game.i18n.localize("VERMINE.vehicle_assign_confirm").replace("{group}", other?.name ?? currentOwner)
const confirmed = await Dialog.confirm({
title: game.i18n.localize("VERMINE.vehicle_assign"),
content,
defaultYes: false
})
if (!confirmed) return
}
try {
await actor.update({ "system.ownerId": this.document.id })
} catch {
ui.notifications.warn(game.i18n.localize("VERMINE.vehicle_assign_failed"))
return
}
this.render()
} }
// Actions : délégation aux applications AppV1 existantes pour TotemPicker/ActorPicker // Actions : délégation aux applications AppV1 existantes pour TotemPicker/ActorPicker
@@ -192,7 +221,7 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
/** Valide la phase d'Expérience courante du Groupe et réinitialise les limites. */ /** Valide la phase d'Expérience courante du Groupe et réinitialise les limites. */
static async #onValidatePhase() { static async #onValidatePhase() {
const { VermineExperience } = await import("../../system/experience.mjs") const { VermineExperience } = await import("../../system/experience.mjs")
await VermineExperience.completePhase(this.document) await VermineExperience.resetPhaseLimits(this.document)
ui.notifications.info(game.i18n.localize("VERMINE.validate_phase_done")) ui.notifications.info(game.i18n.localize("VERMINE.validate_phase_done"))
} }
} }
+6 -8
View File
@@ -215,24 +215,22 @@ export class VermineExperience {
} }
/** /**
* Valide la phase d'Expérience courante : passe à la phase suivante et * Réinitialise les limites d'apprentissage (learned.*) de la phase
* réinitialise toutes les limites d'apprentissage (learned.*), pour un * courante, pour un personnage ou un Groupe, sans toucher au compteur
* personnage ou un Groupe. * de phase (avancé uniquement par le dialogue d'attribution du MJ).
* @param {foundry.abstract.Document} actor personnage ou Groupe * @param {foundry.abstract.Document} actor personnage ou Groupe
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
static async completePhase(actor) { static async resetPhaseLimits(actor) {
const ex = actor.system?.experience const ex = actor.system?.experience
if (!ex) return if (!ex) return
const phase = (ex.phase || 0) + 1 const update = {}
const update = { "system.experience.phase": phase }
if (ex.learned && typeof ex.learned === "object") { if (ex.learned && typeof ex.learned === "object") {
update["system.experience.learned.phase"] = phase
for (const key of Object.keys(ex.learned)) { for (const key of Object.keys(ex.learned)) {
if (key === "phase") continue if (key === "phase") continue
update[`system.experience.learned.${key}`] = false update[`system.experience.learned.${key}`] = false
} }
} }
await actor.update(update) if (Object.keys(update).length) await actor.update(update)
} }
} }