Refonte complète du système Anomalies
- DataModel : renommage value→level (1-4), ajout usesRemaining (0-4), suppression scores/notes - Config : ajout ANOMALY_DEFINITIONS avec compétences applicables par type (8 anomalies) - Fiche item anomalie : header avec level/uses visuels (dots), barre de compétences applicables, 2 onglets Description + Technique/Narratif (suppression onglet Scores) - Fiche PJ onglet Domaines : bloc anomalie proéminent unique avec: - Nom + sous-type + icône - Dots niveau (●●○○) - Dots usages + bouton Utiliser + bouton Réinitialiser - Chips des domaines applicables - Actions : useAnomaly (décrémente usesRemaining), resetAnomalyUses (reset au niveau) - Contrainte : max 1 anomalie par personnage (drop + createAnomaly) - Helpers HBS : lte, gte, lt ajoutés - i18n : nouvelles clés Anomaly.* (level, usesRemaining, use, resetUses, etc.) - CSS : .anomaly-block sur fiche PJ, dots animés, .anomaly-uses-row sur fiche item Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -108,6 +108,10 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
|
||||
}
|
||||
|
||||
async _onDropItem(item) {
|
||||
if (item.type === "anomaly" && this.document.itemTypes.anomaly.length > 0) {
|
||||
ui.notifications.warn(game.i18n.localize("CELESTOPOL.Anomaly.maxAnomaly"))
|
||||
return
|
||||
}
|
||||
await this.document.createEmbeddedDocuments("Item", [item.toObject()], { renderSheet: false })
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,12 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
||||
position: { width: 920, height: 660 },
|
||||
window: { contentClasses: ["character-content"] },
|
||||
actions: {
|
||||
createAnomaly: CelestopolCharacterSheet.#onCreateAnomaly,
|
||||
createAspect: CelestopolCharacterSheet.#onCreateAspect,
|
||||
createAttribute: CelestopolCharacterSheet.#onCreateAttribute,
|
||||
createEquipment: CelestopolCharacterSheet.#onCreateEquipment,
|
||||
createAnomaly: CelestopolCharacterSheet.#onCreateAnomaly,
|
||||
createAspect: CelestopolCharacterSheet.#onCreateAspect,
|
||||
createAttribute: CelestopolCharacterSheet.#onCreateAttribute,
|
||||
createEquipment: CelestopolCharacterSheet.#onCreateEquipment,
|
||||
useAnomaly: CelestopolCharacterSheet.#onUseAnomaly,
|
||||
resetAnomalyUses: CelestopolCharacterSheet.#onResetAnomalyUses,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -64,9 +66,21 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
||||
|
||||
case "competences":
|
||||
context.tab = context.tabs.competences
|
||||
context.anomalies = doc.itemTypes.anomaly
|
||||
context.anomaly = doc.itemTypes.anomaly[0] ?? null
|
||||
context.aspects = doc.itemTypes.aspect
|
||||
context.attributes = doc.itemTypes.attribute
|
||||
if (context.anomaly) {
|
||||
const def = SYSTEM.ANOMALY_DEFINITIONS[context.anomaly.system.subtype] ?? SYSTEM.ANOMALY_DEFINITIONS.none
|
||||
context.anomalySkillLabels = def.technicalSkills.map(key => {
|
||||
if (key === "lune") return game.i18n.localize("CELESTOPOL.Anomaly.moonDie")
|
||||
for (const skills of Object.values(SYSTEM.SKILLS)) {
|
||||
if (skills[key]) return game.i18n.localize(skills[key].label)
|
||||
}
|
||||
return key
|
||||
})
|
||||
} else {
|
||||
context.anomalySkillLabels = []
|
||||
}
|
||||
break
|
||||
|
||||
case "blessures":
|
||||
@@ -91,6 +105,10 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
||||
}
|
||||
|
||||
static #onCreateAnomaly() {
|
||||
if (this.document.itemTypes.anomaly.length > 0) {
|
||||
ui.notifications.warn(game.i18n.localize("CELESTOPOL.Anomaly.maxAnomaly"))
|
||||
return
|
||||
}
|
||||
this.document.createEmbeddedDocuments("Item", [{
|
||||
name: game.i18n.localize("CELESTOPOL.Item.newAnomaly"), type: "anomaly",
|
||||
}])
|
||||
@@ -113,4 +131,23 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
||||
name: game.i18n.localize("CELESTOPOL.Item.newEquipment"), type: "equipment",
|
||||
}])
|
||||
}
|
||||
|
||||
static async #onUseAnomaly(event, target) {
|
||||
const itemId = target.dataset.itemId
|
||||
const anomaly = this.document.items.get(itemId)
|
||||
if (!anomaly) return
|
||||
const current = anomaly.system.usesRemaining
|
||||
if (current <= 0) {
|
||||
ui.notifications.warn(game.i18n.localize("CELESTOPOL.Anomaly.noUsesLeft"))
|
||||
return
|
||||
}
|
||||
await anomaly.update({ "system.usesRemaining": current - 1 })
|
||||
}
|
||||
|
||||
static async #onResetAnomalyUses(event, target) {
|
||||
const itemId = target.dataset.itemId
|
||||
const anomaly = this.document.items.get(itemId)
|
||||
if (!anomaly) return
|
||||
await anomaly.update({ "system.usesRemaining": anomaly.system.level })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { SYSTEM } from "../../config/system.mjs"
|
||||
export class CelestopolAnomalySheet extends CelestopolItemSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["anomaly"],
|
||||
position: { width: 620, height: 560 },
|
||||
position: { width: 560, height: 460 },
|
||||
}
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-celestopol/templates/anomaly.hbs" },
|
||||
@@ -12,7 +12,16 @@ export class CelestopolAnomalySheet extends CelestopolItemSheet {
|
||||
async _prepareContext() {
|
||||
const ctx = await super._prepareContext()
|
||||
ctx.anomalyTypes = SYSTEM.ANOMALY_TYPES
|
||||
ctx.skills = SYSTEM.SKILLS
|
||||
|
||||
const def = SYSTEM.ANOMALY_DEFINITIONS[ctx.system.subtype] ?? SYSTEM.ANOMALY_DEFINITIONS.none
|
||||
ctx.applicableSkillLabels = def.technicalSkills.map(key => {
|
||||
if (key === "lune") return game.i18n.localize("CELESTOPOL.Anomaly.moonDie")
|
||||
for (const skills of Object.values(SYSTEM.SKILLS)) {
|
||||
if (skills[key]) return game.i18n.localize(skills[key].label)
|
||||
}
|
||||
return key
|
||||
})
|
||||
|
||||
ctx.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||
this.document.system.description, { async: true })
|
||||
ctx.enrichedTechnique = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||
|
||||
@@ -59,6 +59,23 @@ export const ANOMALY_TYPES = {
|
||||
voyageastral: { id: "voyageastral", label: "CELESTOPOL.Anomaly.voyageastral" },
|
||||
}
|
||||
|
||||
/**
|
||||
* Définitions des anomalies : compétences applicables pour l'usage Technique.
|
||||
* "lune" est une clé spéciale désignant le dé de lune (Entropie).
|
||||
* Les autres clés correspondent aux identifiants de domaine dans SKILLS.
|
||||
*/
|
||||
export const ANOMALY_DEFINITIONS = {
|
||||
none: { technicalSkills: [] },
|
||||
entropie: { technicalSkills: ["lune"] },
|
||||
communicationaveclesmorts:{ technicalSkills: ["instruction", "mtechnologique", "raisonnement", "traitement"] },
|
||||
telekinesie: { technicalSkills: ["echauffouree", "effacement", "mobilite", "prouesse"] },
|
||||
telepathie: { technicalSkills: ["appreciation", "attraction", "echauffouree", "faveur"] },
|
||||
tarotdivinatoire: { technicalSkills: ["appreciation", "arts", "inspiration", "traque"] },
|
||||
illusion: { technicalSkills: ["coercition", "echauffouree", "effacement", "traque"] },
|
||||
suggestion: { technicalSkills: ["artifice", "attraction", "coercition", "faveur"] },
|
||||
voyageastral: { technicalSkills: ["appreciation", "mtechnologique", "traitement", "traque"] },
|
||||
}
|
||||
|
||||
/** Factions du monde de Célestopol. */
|
||||
export const FACTIONS = {
|
||||
pinkerton: { id: "pinkerton", label: "CELESTOPOL.Faction.pinkerton" },
|
||||
@@ -122,6 +139,7 @@ export const SYSTEM = {
|
||||
SKILLS,
|
||||
ALL_SKILLS,
|
||||
ANOMALY_TYPES,
|
||||
ANOMALY_DEFINITIONS,
|
||||
FACTIONS,
|
||||
WOUND_LEVELS,
|
||||
DIFFICULTY_CHOICES,
|
||||
|
||||
@@ -31,15 +31,14 @@ export class CelestopolAnomaly extends foundry.abstract.TypeDataModel {
|
||||
const fields = foundry.data.fields
|
||||
const reqInt = { required: true, nullable: false, integer: true }
|
||||
return {
|
||||
subtype: new fields.StringField({ required: true, nullable: false, initial: "none",
|
||||
choices: Object.keys(SYSTEM.ANOMALY_TYPES) }),
|
||||
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
||||
reference: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||
scores: skillScoresSchema(),
|
||||
description: new fields.HTMLField({ required: true, textSearch: true }),
|
||||
technique: new fields.HTMLField({ required: true, textSearch: true }),
|
||||
narratif: new fields.HTMLField({ required: true, textSearch: true }),
|
||||
notes: new fields.HTMLField({ required: true, textSearch: true }),
|
||||
subtype: new fields.StringField({ required: true, nullable: false, initial: "none",
|
||||
choices: Object.keys(SYSTEM.ANOMALY_TYPES) }),
|
||||
level: new fields.NumberField({ ...reqInt, initial: 2, min: 1, max: 4 }),
|
||||
usesRemaining: new fields.NumberField({ ...reqInt, initial: 2, min: 0, max: 4 }),
|
||||
reference: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||
description: new fields.HTMLField({ required: true, textSearch: true }),
|
||||
technique: new fields.HTMLField({ required: true, textSearch: true }),
|
||||
narratif: new fields.HTMLField({ required: true, textSearch: true }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user